fix: allow $store to be used with changing values including nullish values#7947
Conversation
|
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 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. |
|
it's okay. recently the tests are a bit flaky. |
baseballyama
left a comment
There was a problem hiding this comment.
Almost LGTM but I added 1 comment.
| export function subscribe(store, ...callbacks) { | ||
| if (store == null) { | ||
| for (const callback of callbacks) { | ||
| callback(store); |
There was a problem hiding this comment.
1st argument of callback is a value of store, so in this case, the value is undefined right?
value of null/undefined is undefined.
| callback(store); | |
| callback(undefined); |
There was a problem hiding this comment.
But null !== undefined. This is returning the same value passed, and preserving runtime correctness as checked by TypeScript.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
|
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: With this PR it prints only The reason this happens is that Question is, if passing a falsy value to
I slightly lean towards b or c. Either way, we should have a test that codifies this behavior. |
IMO is c. |
|
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". |
|
@dummdidumm Part of your sample code above, though, was 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. |
|
Mhm true... we need to find out what happens if you derive from multiple stores and one of them gets undefined. |
|
Added some tests, which was good to see that 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 Which now begs the question, if this derived(undefined, () => {
// call this or not call this?
});
--> 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? |
|
@dummdidumm is attempting to deploy a commit to the Svelte Team on Vercel. A member of the Team first needs to authorize it. |
|
We decided to error for falsy derived inputs |
This aims to fix #7946 and fix #7555 by having
utils.js/subscribecall the callbacks even if the store is nullish.Related PR: #7693
Before submitting the PR, please make sure you do the following
[feat],[fix],[chore], or[docs].Tests
npm testand lint the project withnpm run lint