Reputation: 161
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>
);
}
Upvotes: 2
Views: 216
Reputation: 156
Maybe not the nicer way of doing this, but I add an random ID key to new object in array, so when the array change and call a rerender, <Item key={obj.ID}...> block the rerender of existing Components.
Upvotes: 0
Reputation: 2967
The library is meant to be a core set of primitives, and therefore doesn't address utilities that can be added from userland. Such things are quite easy to build out yourself. Here's a helpful utility that Jason/developit wrote a while back:
const Item = ({ v, k, f }) => f(v, k);
/**
* Like signal.value.map(fn), but doesn't re-render.
*/
export function For({ each, children: f, fallback }) {
let c = useMemo(() => new Map(), []);
return (
each.value?.map(
(v, k, x) => c.get(v) || (c.set(v, (x = <Item {...{ key: v, v, k, f }} />)), x)
) ?? fallback
);
}
Now for your example, you can use it like so:
function ArrayConsumer({ mySignal }: { mySignal: MySignal }) {
const arr = useComputed(() => mySignal.value.someArr);
return (
<>
<For
each={arr}
children={(item) => (
<ArrayItem key={item.id} item={item} />
)}
/>
</>
);
}
Upvotes: 1