Skip to content

Introduce $state.from rune #12956

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

Open
mimbrown opened this issue Aug 21, 2024 · 8 comments
Open

Introduce $state.from rune #12956

mimbrown opened this issue Aug 21, 2024 · 8 comments

Comments

@mimbrown
Copy link
Contributor

Describe the problem

People want to be able to define higher-order states. This could be an escape hatch to allow them to do it.

Describe the proposed solution

Introduce a $state.from rune that takes an object with get and set methods. Compiler then simply appends the .get() and .set(value) methods where appropriate when the variables is read from/written to (or does something trickier to optimise).

This would enable:

  • Native signals to be used ergonomically in svelte (when they become available 🤞)
  • The oft-requested $derived.writable use case
  • Library authors to write higher-order states

Examples

// Native signals
let myVariable = $state.from(someFunctionWhichCreatesNativeSignal());

// $derived.writable-type functionality
let firstName = $state('Rich');
let lastName = $state('Harris');

let fullName = $state.from({
  get: () => firstName + ' ' + lastName,
  set: (value) {
    [firstName, lastName] = value.split(' ');
  },
);

// Library authors
function createStorageSynced(/* snip */) {
  let value = $state(/* snip */);
  // syncs to localStorage
  $effect(() => {
    // snip
  });
  return { get: () => value, set: v => value = v };
}

let myCounter = $state.from(createStorageSynced());

Importance

would make my life easier

@david-plugge
Copy link

Could this also be used for mapping a store to state?

@dummdidumm
Copy link
Member

dummdidumm commented Aug 22, 2024

Could this also be used for mapping a store to state?

You can already do that using fromStore and toStore: https://svelte-5-preview.vercel.app/docs/imports#svelte-store

@david-plugge
Copy link

You can already do that using fromStore and toStore: https://svelte-5-preview.vercel.app/docs/imports#svelte-store

No idea how i missed that, thanks!

@7nik
Copy link
Contributor

7nik commented Aug 22, 2024

It strongly reminds me of #9237, with a bit of another shape. Or $derived.writable idea that was mentioned a few times in various places.

Library authors to write higher-order states

see #11014


Also, I find it very strange that with the proposed solution, you can create a variable with a primitive value but with attached logic.

let array = $state([1, 2, 3]);

let length = $state.from({
  get: () => array.length,
  set: (value) { array.length = value  },
);

length = 100;

It doesn't really make sense to me. If a value has a logic, use an object or class.
One more tricky thing is how to differ between re-assigning and setting the value?

@mimbrown
Copy link
Contributor Author

see #11014

This proposal is specifically to allow external things to be "sveltified" without going down the rabbit hole of allowing users to modify the compiler.

Also, I find it very strange that with the proposed solution, you can create a variable with a primitive value but with attached logic.

Isn't that exactly what a variable declared with $state is (if it has a primitive value)?

@7nik
Copy link
Contributor

7nik commented Aug 22, 2024

see #11014

This proposal is specifically to allow external things to be "sveltified" without going down the rabbit hole of allowing users to modify the compiler.

Not really. It is about a more convenient boilerplateless way of using external stateful things. But it's strongly related to the API design and use cases, so it's a tricky topic.

Also, I find it very strange that with the proposed solution, you can create a variable with a primitive value but with attached logic.

Isn't that exactly what a variable declared with $state is (if it has a primitive value)?

Changing a variable defined with $state does nothing by itself, but there can be effects and derives that will react to this change.
In the case of $statefrom (I think $derived.writable is a better name), some logic will always be executed by its nature.

I've used writable computed, so I won't say it's unneed. But using it for libs and utils isn't good because the result isn't extendable - you cannot add a new prop to a primitive value. So, it makes sense to return an object anyway.

And as for your abstract example with localStorage, here is my implementation, which I then use as simply storage.someProp.

@mimbrown
Copy link
Contributor Author

Changing a variable defined with $state does nothing by itself, but there can be effects and derives that will react to this change.

But all the logic that tracks/triggers those things does run, it's just that logic is written by the authors of Svelte so we trust it more.

I think $derived.writable is a better name

The writeable derived is just one use case, which is why I didn't call it that. I think it's better to keep $derived conceptually something that can't be written to, because it's... derived. Others disagree.

But using it for libs and utils isn't good because the result isn't extendable - you cannot add a new prop to a primitive value.

That depends on what your library is providing. If you have something more complex, then yes, a class makes more sense. If not, then .value is annoying, which is the whole point of the $state rune.

And as for your abstract example with localStorage, here is my implementation, which I then use as simply storage.someProp.

The thing I like about this example with $state.from is the refactor story, if you have a simple state, then later realize you want it to be synced with storage, you update the declaration site only and you're done. In my experience, that's really common when working with clients that don't know what they want until they see it.

@ryanatkn
Copy link
Contributor

ryanatkn commented Aug 23, 2024

This makes sense to me, it looks like it boils down the minimal $state contract with a good API. Syntax sugar for thunks.

I put a lot of weight on the compatibility with the proposed standard, if the API doesn't take a hit - I like this fit.

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

5 participants