I'm trying to use Signals (@preact/signals-react) to reduce re-rendering with large data objects. In my case, I'm getting object from a network request, and tends to change frequently with live updates.
for direct properties, it works well, reducing the number of rerenders:
export function Root(){
const mySignal = useSignal({ sub: { val: 1 }, arr: [{id: 1, name: "bob"}] });
return <div>
<Counter signal={mySignal} />
<Array signal={mySignal} />
</div>
}
function Counter({ signal }) {
const counter = useComputed(() => signal.value.counter);
return (
<div>
<p>{counter}</p>
<button
onClick={() => {
signal.value = { ...signal.value, counter: signal.value.counter + 1 };
}}
>
Increment
</button>
</div>
);
}
In this case, only Counter rerenders when incrementing 👌.
This is true also when we have useSignals().
However, with arrays the situation is different:
function ArrayConsumer({ mySignal }: { mySignal: MySignal }) {
const arr = useComputed(() => mySignal.value.someArr);
return (
<>
{arr.value.map((item) => (
// renders and edits {item.name}
<ArrayItem key={item.id} item={item} />
))}
</>
);
}
Because I use arr.value directly, any time the array changes, the whole list gets rerendered.
I expected some sort of signalMap() method, but the docs only suggest using the signal.value there.
One solution is to wrap the individual list items in a Signal, but that seems like a bad practice? I really thought signals give similar performance and devexp to Mobx, but without lists support it's much less useful.
// ideal solution
function ArrayItem(item) {
// assuming item is a signal too
// although I'm missing a way to do that
const name = useComputed(() => item.value.name);
return (
<div
// pseudo code
contentEditable
onChange={(e) => (item.value = { ...item.value, name: e.target.value })}
>
{name}
</div>
);
}
ArrayConsumeris the one that passesitemintoArrayItem. If it does not rerender, react can't pass the new prop when you update specific item. Or when you add/delete item, it needs to rerender in order to execute themapmethod again. I don't think there is something that can be done to change this, this is how react works.ArrayConsumerhave to rerender, but if you don't want to rerender all ArrayItems, you can try usingReact.memoForis a component that rerenders every time the array changes. In the implementation it usesMapwhere it only appends, never removes, this will lead to memory leaks. The item is passes as key, I don't see how this gonna work, if the item is an object, it can't be used as key, if it is number there will be dublicate keys