Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: allow $store to be used with changing values including nullish values #7947

Merged
merged 6 commits into from
May 4, 2023

Conversation

ramonsnir
Copy link
Contributor

@ramonsnir ramonsnir commented Oct 14, 2022

This aims to fix #7946 and fix #7555 by having utils.js/subscribe call the callbacks even if the store is nullish.

Related PR: #7693

Before submitting the PR, please make sure you do the following

  • It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs
  • Prefix your PR title with [feat], [fix], [chore], or [docs].
  • This message body should clearly illustrate what problems it solves.
  • Ideally, include a test that fails without this PR but passes with it.

Tests

  • Run the tests with npm test and lint the project with npm run lint

@ramonsnir
Copy link
Contributor Author

Thanks @Conduitry for identifying as a duplicate. I searched in GitHub but must have entered a wrong keyword combination or simply missed it (there are many results for words like "store", "undefined", etc.). 🙂

tanhauhau
tanhauhau previously approved these changes Oct 16, 2022
@ramonsnir
Copy link
Contributor Author

@tanhauhau Do you know why the tests suddenly failed? It seems like a random failure of an unrelated test, only on macOS. I don't have permissions to re-run.

@tanhauhau
Copy link
Member

it's okay. recently the tests are a bit flaky.

Copy link
Member

@baseballyama baseballyama left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Almost LGTM but I added 1 comment.

@@ -66,6 +66,9 @@ export function validate_store(store, name) {

export function subscribe(store, ...callbacks) {
if (store == null) {
for (const callback of callbacks) {
callback(store);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1st argument of callback is a value of store, so in this case, the value is undefined right?
value of null/undefined is undefined.

Suggested change
callback(store);
callback(undefined);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But null !== undefined. This is returning the same value passed, and preserving runtime correctness as checked by TypeScript.

Copy link
Member

@baseballyama baseballyama Dec 4, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding store.value,
if store is null, store.value = null.value. value is undefined.
if store is undefined, store.value = undefined.value. value is undefined.

First argument of callback is value.

https://svelte.dev/docs#component-format-script-4-prefix-stores-with-$-to-access-their-values-store-contract

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see an implicit conversion of null into undefined in the linked section, but my reading skills this morning aside - it seems to match the runtime behavior that exists today (minus this bug) 🙂 REPL confirming null -> undefined

If this is the desired behavior, the types for TypeScript need to be fixed too. This code right now fails to compile:

    let nameStore = null;

    let name: undefined;
    $: name = $nameStore;

Type 'null' is not assignable to type 'undefined'.

That's why I wrote the PR the way I did.

@benmccann benmccann changed the title [fix] allow $store to be used with changing values including nullish values fix: allow $store to be used with changing values including nullish values Jan 12, 2023
@dummdidumm
Copy link
Member

dummdidumm commented Mar 16, 2023

I'm observing a difference in behavior with this change. This code:

<script>
  import { writable, derived } from "svelte/store";

  let name = null;
  $: d = derived(name, (e) => {
    console.log(e);
  });
  setTimeout(() => (name = writable("hi")), 1000);
  setTimeout(() => (name = null), 2000);
</script>

{$name}
{$d}

will print the following in the current version:

undefined
"hi"
undefined

With this PR it prints only

"hi"

The reason this happens is that pending inside the derived store turns truthy because the derived store is called while not being started yet.
I'm not sure yet if this is desired behavior and counts as a bugfix or if this should be seen as a breaking change (although, who really has code like that? 😅 ). Edit: should count as breaking.

Question is, if passing a falsy value to derived

  • a) should be seen as an error
  • b) should result in the behavior this PR has
  • c) should function as before (in which case this isn't a breaking change, but would need a different change)

I slightly lean towards b or c.

Either way, we should have a test that codifies this behavior.

@dummdidumm dummdidumm added this to the 4.x milestone Mar 20, 2023
@benmccann benmccann changed the base branch from master to version-4 April 11, 2023 21:06
@baseballyama
Copy link
Member

c) should function as before (in which case this isn't a breaking change, but would need a different change)

IMO is c.
Because somehow derived store needs to know that the parent store is gone.

@dummdidumm
Copy link
Member

dummdidumm commented Apr 24, 2023

That's a very good point, we can't do b) . I think I vote for a) then. The types already says "should be defined".

@Conduitry
Copy link
Member

@dummdidumm Part of your sample code above, though, was $: d = derived(name, (e) => { ... });. This is how the derived would find out that name changed - the store would get re-created when name (not its contained value) changes. Am I missing something?

That's not to say I'm convinced that b) is definitely a better option than a). I remember feeling very conflicted about this when you brought it up last month. But I don't think we should rule out b) as technically infeasible, as far as I can tell.

@dummdidumm
Copy link
Member

Mhm true... we need to find out what happens if you derive from multiple stores and one of them gets undefined.
If that works as expected you have me going back to favoring Option b) again 😅

@dummdidumm
Copy link
Member

dummdidumm commented Apr 27, 2023

Added some tests, which was good to see that derived will with this change never finish:

const a = writable(1);
const b = derived([a, null, undefined], ([n, un1, un2]) => {
	// I will never end up in here, due to the inner `pending` variable of `derived` never being false
	return n * 2;
});

The fix is to either fill up any falsy stores with readable() (i.e. a store that contains a single undefined value) or to throw an error.

Which now begs the question, if this derived callback should run with [2, undefined, undefined], what should happen in the following situation:

derived(undefined, () => {
	// call this or not call this?
});
  • If we should call it then we can also have a fallback readable(), which reintroduces the previous behavior, basically option c) from above.
  • If we should not call it then it feels a bit inconsistent with the first example where derived is called, which makes me think any falsy value should be a runtime error, which would be option a)

--> option b) to me feels inconsistent now.

From a typings perspective having a falsy value is invalid already (TS will yell at you). From a backwards-compatible and code-golf perspective (you need less code for the fallback than for the error message) I still lean towards c)

Thoughts?

@vercel
Copy link

vercel bot commented Apr 28, 2023

@dummdidumm is attempting to deploy a commit to the Svelte Team on Vercel.

A member of the Team first needs to authorize it.

@dummdidumm
Copy link
Member

We decided to error for falsy derived inputs

@dummdidumm dummdidumm merged commit ea73930 into sveltejs:version-4 May 4, 2023
@gtm-nayan gtm-nayan mentioned this pull request Jun 17, 2023
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

When a store is replaced with undefined/null, state does not update
5 participants