I'm developing one of my projects with React, and I need to create some list where each item represents a table row, and is dependent on the previous line (and partially on the next line) - similar to how Excel or Google Sheet works.
Say each item has three fields, a
, b
and c
. Each item updates its own a
field depending on the a
field of the previous item, and updates its b
field depending on the c
field of the next one.
My current design is to hold each item as a separate component (rendering the table row), and to hold 2 lists in the parent component - one for the states of each item, and one for the items themselves.
Looks something like this:
interface ItemState {
a: number,
b: number,
c: number,
}
function ItemComponent({prevState, nextState, state, setState}:
{prevState: ItemState; nextState: ItemState; state: ItemState; setState: (state: ItemState) => void;}) {
React.useEffect(() => {
setState({...state, a: prevState.a+1});
}, [prevState.a]);
React.useEffect(() => {
setState({...state, b: nextState.c-1});
}, [nextState.c]);
return (<...>); // table row
}
function ParentComponent() {
const [stateList, setStateList] = React.useState<ItemState[]>([item1, item2, item3]);
const mapState = React.useCallback(
(prev: ItemState | null, curr: ItemState, next: ItemState | null) => {
return (
<ItemComponent
key="someUniqueId"
prevState={prev}
nextState={next}
state={curr}
setState="some function which sets a specific item in the list based on id"
/>
);
},
[]
);
const itemList = React.useMemo(
() =>
stateList.map((state, i) => {
const prev = i === 0 ? null : stateList[i - 1];
const next = i === stateList.length - 1 ? null : stateList[i + 1];
return mapState(prev, stintState, next);
}),
[stateList, mapState]
);
return (<...>); // rendered table containing the itemList
}
(Obviously this is a simplified version, in reality I perform more complex calculations over more fields than just a
, b
and c
.)
This solution works. However, its performance is terrible, as I normally have much more than just 3 items in the list, causing it to take a significant amount of time to update all of the decedents when one item is updated.
Moreover, when the list contains over ~14 items I start getting "Maximum update depth exceeded" errors, caused by the large amount of setState
s inside useEffect
s.
I'm sure there's a much more elegant and performative solution to this problem, I just couldn't figure it out myself - and I'd love to get some help on that.
Edit:
The main goal here is to display a table where I can modify some values in each row. Modifications to row i
should trigger the following flows:
- Re-calculate specific values of row
i-1
and re-render it, but do not trigger any further calculations/renders as a result of that. - Re-calculate specific values different than those in step 1. for row
i+1
, and trigger the same process on rowi+1
(steps 1. and 2.).
So, once row i
has been modified, row i-1
should be re-rendered, and any preceding row should be re-rendered.
The calculations I'm doing are rather complicated - hence the row dependency (otherwise I would've directly calculated the data for each row on every update, without having to wait for the previous one).
const a=createSignal<number>(1)
... then you can doconst b=()=>a()*2
andb()
will be double the value ofa()
. Its nice to use like that and dependencies are tracked implicitly, although you can state them explicitly. If you prefer something closer to react there's preact that has signals too which may be useful. – Inject