-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
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
Don't directly reference local state #5943
base: alpha
Are you sure you want to change the base?
Conversation
In svelte 5, $state referenced locally is not reactive, because of how evaluation occurs. Svelte will warn about this at runtime (and does so here) To make it reactive locally, the docs recommend using a closure, which is what we do here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @dberlin - thank you very much for submitting a PR, and for the thoughtful technical explanation of the changes!
I don't see an issue attached to this, so I just want to confirm: Is the existing implementation causing an error of some sort? Eg: Loss of functionality, or a runtime/build error to be omitted?
Additionally, would using the mergeObjects
utility function work here in lieu of wrapping state in a closure? I think it may have been done this way in the past. I've submitted this as a suggestion comment.
Thank you again!
const statefulOptions: TableOptions<TFeatures, TData> = mergeObjects( | ||
tableOptions, | ||
{ | ||
_features, | ||
state: { ...state, ...tableOptions.state }, | ||
state: { ...getState(), ...tableOptions.state }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
state: { ...getState(), ...tableOptions.state }, | |
state: mergeObjects(state, tableOptions.state || {}), |
Hey,
So this was discovered because I use newer versions of svelte/vite-plugin-svelte packages than y'all currently test against in the examples. Since y'all distribute as svelte componenets (which is the right thing for sure), you get the user's version, not your version. Once you upgrade to the latest vite-plugin-svelte and svelte for the examples it will issue this warning for every single example. It pops up immediately (here's an example of running pnpm dev in examples/svelte/basic after updating the various packages in the repo):
I didn't bother to file an issue, but i can if it's useful - i'm positive I can break it using updateOptions. Unfortunately, your suggestion won't work, it still references state directly. and by "won't work" i mean "won't reference the correct version of state. I don't know enough about the surrounding options/etc to know if it matters. So let me stick with that definition of "won't work" for the rest of this response, and you are free to tell me "cool, but doesn't matter for reasons x,y, and z". Also, your fix would have worked in svelte4, but doesn't in svelte5. I also apologize for the length of this, and if you know all this. I tend to think sometimes it's just best to walk these things through and see whether we end up with the same thoughts or not :) So let me paste the post-svelte-compiled code for let state = $.state($.proxy(getInitialTableState(_features, tableOptions.initialState)));
const statefulOptions = mergeObjects(tableOptions, {
state: {
...$.get(state)
}
});
function updateOptions() {
return mergeObjects(prev, tableOptions, {
state: mergeObjects($.get(state) /**/),
onStateChange: (updater) => {
$.set(state, $.proxy(mergeObjects($.get(state), updater), null, state));
}
});
} So first thing we do is set Now, we set up We then spread out all the results of the proxy, which copies the contents of the proxy. Your But it will still not work in svelte 5. Obviously the copied proxy pieces will not update. But let's imagine we didn't do a spread, and just pass Note that once Next up, updateOptions. You can actually see what mergeObjects would do on its own here. In this case, But you can then see what happens. The Let's call At this point, the state property in Now you can also see (hopefully) why my fix is still buggy, despite making the warning go away :). While it will lazily evlauate, it will not do it at the right time. We have to use a property getter instead. Then the code will be const statefulOptions = mergeObjects(tableOptions, {
get state(): { return ...$.get(state) }
}); As you can see, now every time the property is accessed on A similar thing needs to be done for the state property in I'll update the fix. Assuming, again, there isn't a reason none of this matters :) |
@dberlin I love the thorough write-up. What you're describing makes sense - learning how runes/signals work was a big challenge for me in setting this adapter up, so it's not a surprise that it would continue to be. :) Would you go ahead and apply the updates that you mentioned above? |
In svelte 5, $state (here, the variable named 'state') referenced locally is not reactive, because of how evaluation occurs[1].
This means in the listed code, ...state will only ever refer to the original value of state, even if it is later updated or re-evaluated (by updateOptions, for example)
To make it reactive locally, the docs recommend using a closure (you can use getter functions and properties too if you want), which is what we do here.
This makes it so ...state always refers to the current value of state, even after updateOptions changes it..
This in turn, fixes the browser warning issued by svelte on all usage of createTable.
See https://svelte.dev/docs/svelte/compiler-warnings#state_referenced_locally for more details.
Svelte (currently) only warns if you do this and also reassign the variable, under the assumption that doing this means you want the local reference to see the new value. A cursory read of the code suggests we do want to see the new value post-updateOptions call, which means we need to lazily evaluate (or use a data class)
[1] The short, mostly correct version: The code is evaluated once, and $state/etc get transformed into function calls and proxies so that it dynamically re-evaluated when dependencies change. As a result, referencing state directly in the local scope (like this code does) only ever refers to the original value, even when you reassign it (as updateOptions does)
This is one of the more confusing foibles of svelte5, and has led to lots of complaining :)