//used so it doesn't log a bunch on first render
let firstRender = true;
//toggles one item or sets value if defined
const toggleItem = (items, [row, col, value]) =>
items.map((r, rowIndex) =>
rowIndex !== row
? r
: r.map((c, colIndex) =>
colIndex !== col
? c
: {
...c,
checked:
value === undefined ? !c.checked : value,
}
)
);
//toggles or sets multiple items (used with setting a row)
const setMultiple = (items, rowsCols) =>
rowsCols.reduce(toggleItem, items);
function App() {
//setting initial state
const [state, setState] = React.useState([
[
{ checked: false },
{ checked: false },
{ checked: false },
],
[
{ checked: false },
{ checked: false },
{ checked: false },
],
]);
//when an item changes, useCallback so we don't re create
// a new reference every time (optimize for pure component)
const itemChange = React.useCallback((row, col) => {
setState(state => toggleItem(state, [row, col]));
}, []);
//change a whole row, also optimized with useCallback
const rowChange = React.useCallback((rowIndex, value) => {
setState(state =>
setMultiple(
state,
state[rowIndex].map((_, colIndex) => [
rowIndex,
colIndex,
value,
])
)
);
}, []);
//just to prevent a bunch of logs at first render
Promise.resolve().then(() => (firstRender = false));
return (
<table>
<tbody>
{state.map((row, rowIndex) => (
<Row
key={rowIndex}
row={row}
rowIndex={rowIndex}
itemChange={itemChange}
rowChange={rowChange}
/>
))}
</tbody>
</table>
);
}
//use React.memo to create a pure component
const Row = React.memo(function Row({
row,
rowIndex,
itemChange,
rowChange,
}) {
//will only log for changed components
if (!firstRender)
console.log('in Row render, index:', rowIndex);
return (
<tr>
<td style={{ backgroundColor: 'gray' }}>
<input
type="checkbox"
onChange={e =>
rowChange(rowIndex, e.target.checked)
}
/>
</td>
{row.map(({ checked }, index) => (
<Item
key={index}
checked={checked}
rowIndex={rowIndex}
colIndex={index}
// with parent having state you now have to do prop drilling
itemChange={itemChange}
/>
))}
</tr>
);
});
//also a pure component using React.memo
const Item = React.memo(function Item({
checked,
rowIndex,
colIndex,
itemChange,
}) {
//also only logs for changed components
if (!firstRender)
console.log(' in Item render, index:', colIndex);
return (
<td>
<input
type="checkbox"
checked={checked}
onChange={e => itemChange(rowIndex, colIndex)}
/>
</td>
);
});
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>