Ideally I'd like to have a state management setup which allows me to:
- have an arbitrarily nested state. For example, this could be a valid entry in the state
users[0].vehicle.engine.throttle
- have an arbitrarily nested component tree. For example, there could be a slider which corresponds to the above throttle value deep down in some menu.
- components must only rerender when their part of the state changes
I've read through the release blog post and docs for preact signals. The concept seems very exciting and I appreciate the clean code.
But it's not clear to me how to represent nested state with preact signals.
As far as I understand, when your component looks at signal.value
it will rerender whenever the value changes.
Let's say I have this user
{
name: "John",
age: 34
}
I'd like to consume the name and age in two different components, let's call them NameDisplay
and AgeDisplay
.
How can I prevent AgeDisplay
from re-rendering when I change the name from John
to James
?
As far as I understand, preventing this re-render is only possible by keeping name
and age
in different signals. I'd have to flatten the state and extract every entry into an individual signal.
But what if I'm dealing with a list of users?
{
users:[
{
name: "John",
age: 34
},
...
]
}
I don't see how this could be flattened. Now I'd have a users
signal, and whenever any of the users is changed, all user components re-render.
The wishlist defined at the beginning of the question is definitely doable though. Here's a small hacky prototype with zustand + immer. This code will display two users and allow you to update them independently from each other. Editing the name of one of them doesn't cause the other to rerender (I've checked it with the react devtools and highlighting component re-renders).
store.js
(contains nested state with a list of users, and uses immer to update one field deep in the state)
import { create } from "zustand";
import { immer } from "zustand/middleware/immer";
export const useUsers = create()(
immer((set) => ({
users: [
{
name: "John",
age: 42,
id: "1"
},
{
name: "Jane",
age: 21,
id: "2"
},
],
renameUser: (id, name) =>
set((state) => {
state.users.find((user) => user.id === id).name = name;
}),
}))
);
User.js (subscribes to one specific user, listens only to updates to this user)
import React from "react";
import { useUsers } from "./store";
import { useShallow } from "zustand/react/shallow";
export default function User(props) {
const { id } = props;
const user = useUsers(
useShallow((state) => state.users.find((user) => user.id === id))
);
const rename = useUsers((state) => state.renameUser);
return (
<div>
<h1>{user.name}</h1>
<h2>{user.age}</h2>
<input
type="text"
value={user.name}
onChange={(e) => rename(id, e.target.value)}
/>
</div>
);
}
App.js
(small wrapper to display a list of users)
import React from 'react';
import { useUsers } from './store'
import { useShallow } from "zustand/react/shallow";
import User from './User'
function App() {
const users_ids = useUsers(useShallow((state) => state.users.map((user) => user.id)))
return (
<div className="App">
{users_ids.map((id) => <User key={id} id={id} />)}
</div>
);
}
export default App;
Would it be possible to set up something like this with preact signals?