-
-
Notifications
You must be signed in to change notification settings - Fork 4.7k
Description
Describe the problem
I just wanted to port a simple to-do sort of demo to runes but it was more difficult than expected and the end result is more verbose than a "Svelte" component should be. Of course I might be doing something wrong.
First naive approach was just one state:
<script>
let todos = $state([
{ text: 'Item 1', done: false },
{ text: 'Item 2', done: false },
]);
const remaining = $derived(todos.filter(x => x.done == false));
</script>
{#each todos as todo}
<div>
<input type="checkbox" bind:checked={todo.done} />
<input bind:value={todo.text} />
</div>
{/each}
...
Remaining: {remaining.length}The bindings in the #each block do nothing.
Next step was trying to use a state for each item:
const todo = text => {
let item = $state({
text,
done: false,
});
return item;
}
let todos = $state([
todo('item 1'),
todo('item 2'),
]);Which does not work because the item gets evaluated in the compiled output before it is returned.
So finally, every changing property is stateified:
const todo = initialText => {
let text = $state(initialText);
let done = $state(false);
return {
get text() { return text }, set text(v) { text = v },
get done() { return done }, set done(v) { done = v },
};
}This finally works but is ...not great, even if we could use arrow functions (as already bemoaned by Rich Harris).
Is this just buggy or will it really be necessary to use $state for every single property?
I imagine this being an issue with JSON returned from a server.
Describe the proposed solution
If it is necessary to declare that many properties, that could probably be done by the compiler, e.g. with an additional rune.
const todo = initialText => {
let text = $state(initialText);
let done = $state(false);
return $access({ text, done });
}Should then be spreadable, too, e.g.
const todo = (id, initialText) => {
let text = $state(initialText);
let done = $state(false);
return { id, ...$access({ text, done }) };
}$access could possibly be split into something like $readable/$writable to control whether setters are generated.
Alternatives considered
- Use one
$stateand bind to that indexed:let todos = $state([ { text: 'Item 1', done: false }, { text: 'Item 2', done: false }, ]);
{#each todos as _, i} <div> <input type="checkbox" bind:checked={todos[i].done} /> <input bind:value={todos[i].text} /> </div> {/each} - Have one state per item, but wrap it to preserve reactivity (pretty ugly).
Importance
nice to have