Any recommendations on how to structure complex data? #318
-
I'm trying to wrap my head around using signals for our app. Should I use nested signals for complex data? For example, the todos example. What if I don't want to update all todos when a single todo changes? Should I do the following? const todos = signal([
signal({ text, done }),
signal({ text, done })
]) |
Beta Was this translation helpful? Give feedback.
Replies: 4 comments 1 reply
-
Depends on how granular you want to react to changes to todo items. There is no right or wrong approach here. With signals I notice that my own thinking shifted from "when/how do I update things" to "when do I need to sync with the UI". In your example you wrapped each todo item with a signal: const todos = signal([
signal({ text, done }),
signal({ text, done })
]) That works too, and there is nothing wrong with that approach. Only minor annoyance with that is, that whenever you update a todo's <ul>
{todos.value.map(todo => {
const { text, done } = todo.value;
return <li>{text}, completed: {done ? "yes" : "no"}</li>
})}
</ul> In essence the whole component would re-render everytime we update any of the signals, because all of them are accessed inside the render function. So there isn't really anything we gain by wrapping each item into a signal here. Back to your original question: How granular reactive should my state be? Determine granularityLet's continue with the todo list example: The raw example in our docs looks somewhat like this, were only the whole thing is one big signal. We did that because we didn't want to overload folks new to signals with everything at once. For such a simple app like a todo app, this is perfectly fine too. const todos = signal([
{ text },
{ text }
]) The reason this is perfectly fine is that once you push a new item into the array, you want all code that depend on the array to re-render anyway. The text of each item isn't editable after a todo item was added, so there is no point in making more stuff reactive. But let's imagine we would add a const todos = signal([
{ text, done: signal(false) },
{ text, done: signal(true) }
]) In our UI we can then bind the checkbox directly to this signal and bypass components that depend on the whole todo items array (=the outer signal). Let's imagine we want to add a little blurb under the list of todo items which says something like
For that we could add a const todos = signal([
{ text, done: signal(false) },
{ text, done: signal(true) }
])
const completed = computed(() => {
return todos.value.filter(todo => todo.done.value).length;
}); Because the <p>Congratulations, you completed {completed} todos</p> SummaryIn summary, it's always a question of "when do I need to sync with the UI" and how granular you want to react to state updates. |
Beta Was this translation helpful? Give feedback.
-
Got it. This was very well explained. I'll continue exploring |
Beta Was this translation helpful? Give feedback.
-
@astoilkov sure, feel free to post any further questions here. I quite enjoy these kind of discussion and issues here are perfect for that. |
Beta Was this translation helpful? Give feedback.
-
Thanks, this discussion is extremely helpful and could well be promoted to being an official part of the docs. I'm happy to see the signals approach (and especially together with preact's rendering optimisations) gaining ground over opaque approaches such as hooks, but based on this discussion something doesn't seem quite right to me. What should be a UI-level decision appears to have affected our structuring of the underlying data, and even the paths we use to address it - whether we consume todo.done or todo.done.value is a "breaking change". I'm aware that we are close to the limits of what can be done here ergonomically, and I'm wondering whether this is the kind of issue referred to such article https://dev.to/this-is-learning/the-evolution-of-signals-in-javascript-8ob speculating that "JavaScript might not be the best language for signals". One possible route here is the use of Proxies but these have their own performance and usability issues. Could anyone comment here on how they feel a language could do better at transparently accommodating reactivity, without forcing people to prematurely bake update granularity decisions into the data model? Or experience of other languages that do this. |
Beta Was this translation helpful? Give feedback.
Depends on how granular you want to react to changes to todo items. There is no right or wrong approach here. With signals I notice that my own thinking shifted from "when/how do I update things" to "when do I need to sync with the UI".
In your example you wrapped each todo item with a signal:
That works too, and there is nothing wrong with that approach. Only minor annoyance with that is, that whenever you update a todo's
.done
property, the code that renders the text always needs to be updated too. What's more is that you likely mapped over the list and rendered the items in the same component, which would subs…