Skip to content

Svelte 5: Bring back $state.link #13452

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

Closed
HighFunctioningSociopathSH opened this issue Sep 30, 2024 · 12 comments
Closed

Svelte 5: Bring back $state.link #13452

HighFunctioningSociopathSH opened this issue Sep 30, 2024 · 12 comments

Comments

@HighFunctioningSociopathSH
Copy link

HighFunctioningSociopathSH commented Sep 30, 2024

Describe the problem

Knowing that $state.link existed once, I can't stop thinking how many times I could use it in my project. It's really useful and removes the need of having to write an extra $effect that does nothing other than assigning a value to a variable. Also the work around that uses an $effect isn't really good either because the assignment inside $effect is not fine-grained.

Describe the proposed solution

I think it would be nice to have it back and makes migrating from svelte 4 easier since in svelte 4 you could assign to the reactive variable and it would update again if its dependency changed.
For example, you could do the following in svelte 4 when you didn't want to change the original data passed by the user and still update your finalData if it got updated

export let data;
$: finalData = data;
function handleOnClick() {
  finalData = "something";
}

So here finalData was updated internally but it would have been updated again if the user decided to update the data prop.
This could translate to the following in Svelte 5 which is neat:

let { data } = $props();
let finalData = $state.link(data);
function handleOnClick() {
  finalData = "something"
}

Importance

nice to have

@7nik
Copy link
Contributor

7nik commented Sep 30, 2024

For reference: #12938 (comment) here is the decision to remove $state.link.

The problem was that, after correcting the behaviour, it used the $effect internally anyway. Also, there were open questions about behaviour in SSR and the inability to apply $state.link to an object's prop. A workaround can have whatever behaviour you need.

Examples: sync $state.link and $state.link with async merging.
If you want, you can wrap the logic into the Box pattern and have something like

let box = link(() => parentValue, onMerge);
console.log(box.value);

@Ocean-OS
Copy link
Contributor

Ocean-OS commented Oct 2, 2024

Couldn't you just use a $derived, like this?

let a = $state(0);
let b = $derived(a);

@7nik
Copy link
Contributor

7nik commented Oct 2, 2024

Couldn't you just use a $derived, like this?

let a = $state(0);
let b = $derived(a);

No, derives are read-only (though mutable). So you cannot do b++ or b = 42.

@Ocean-OS
Copy link
Contributor

Ocean-OS commented Oct 2, 2024

No, derives are read-only (though mutable). So you cannot do b++ or b = 42.

Ah, I see.
Then wouldn't something like this work?

let a = $state(0);
let b = $state($state.snapshot(a));
$effect(()=>{
b = $state.snapshot(a);
});

It shouldn't be too much of a problem that $effect is being used here since that was what $state.link used internally.

@trueadm
Copy link
Contributor

trueadm commented Oct 7, 2024

The problem was that, after correcting the behaviour, it used the $effect internally anyway.

It didn't. It used a derived. We wanted to not ship something that we regretted and given how close we are to 5.0 release, we felt like doing this work after release was a better use of time.

@7nik
Copy link
Contributor

7nik commented Oct 7, 2024

The problem was that, after correcting the behaviour, it used the $effect internally anyway.

It didn't. It used a derived.

Initial implementation — yes, but it was buggy, and the PR I've linked was fixing it by using effect instead of derived.

@trueadm
Copy link
Contributor

trueadm commented Oct 7, 2024

@7nik The effect wasn't the fix, it was just a bug in the initial implementation.

@ryanatkn
Copy link
Contributor

ryanatkn commented Oct 7, 2024

Here's a workaround that avoids effects but has manual resetting, the verboseness has some clarity:

value; // maybe from a prop, `type T`

let local_value: T | undefined = $state(value); // or initialize to `undefined` or something else

const final_value = $derived(local_value === undefined ? value : local_value); // `type T`

value = something; // update the source of truth
local_value = something; // update just the final linked binding
local_value = undefined; // revert the final linked binding to the source of truth

This means your handlers may sometime need a 2-liner to both revert the linked binding and update the source of truth, a pattern that's been talked about in these discussions - you can have these updates in a single-code-path helper to mitigate the error-proneness.

This could be wrapped in a helper API with explicit methods to improve usage.

@Siuhnexus
Copy link

Regardless of what implementation is used or what issues have to be addressed, people will need this mechanism. There are too many cases where a single source of truth (for example the server) provides data which has to be mutable for editing. In one of my projects I implemented reordering elements of a list which was saved in a database using exactly this kind of functionality (a seperate variable assigned by an effect). Otherwise, I wouldn't have been able to animate the drag and drop.

Since people need this feature and they shouldn't stumble into the problems of SSR and nested assignments, a seperate rune with thorough documentation would be really helpful. But maybe naming it something else should be considered, for the state is not really linked, but it rather expresses a possible future state of the data. Some funny ideas I had were $potential() or $imagine(), but more technical terms like $grounded() or $dependent() would do the trick too.

I was thinking that it would also be useful to provide a mechanism for resetting the "dependent" state to the source of truth without repeating oneself. Maybe a $ground() or a $resync() rune would be possible.

@AlbertMarashi
Copy link

I've come back to say:

I find myself often using something like $derived to get shorter variable identifiers in my code and templates

let arr = $derived(this.deeply.nested.array_thing) // Not reactive for setting

arr = array.filter(...) // wouldn't work

So yes, having something like:

let arr = $state.link(this.deeply.nested.array_thing)

Would be really nice

@Ocean-OS
Copy link
Contributor

Ocean-OS commented Jan 17, 2025

Theoretically, if #12956 were to be made, wouldn't that provide an easy way to recreate $state.link?

let arr = $state.from({
get: ()=>this.deeply.nested.array_thing,
set: v=>this.deeply.nested.array_thing=v
});

@benmccann
Copy link
Member

Addressed in #15570

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants