For the follow code:
function Cell({row, column}) { const state = useAppState() const cell = state.grid[row][column] const dispatch = useAppDispatch() const handleClick = () => dispatch({type: 'UPDATE_GRID_CELL', row, column}) return ( <button className="cell" onClick={handleClick} style={{ color: cell > 50 ? 'white' : 'black', backgroundColor: `rgba(0, 0, 0, ${cell / 100})`, }} > {Math.floor(cell)} </button> ) } Cell = React.memo(Cell)
'useAppState()' is using context.
function useAppState() { const context = React.useContext(AppStateContext) if (!context) { throw new Error('useAppState must be used within the AppProvider') } return context }
The way that context works is that whenever the provided value changes from one render to another, it triggers a re-render of all the consuming components (which will re-render whether or not they’re memoized).
The way to improve the performance is that:
Split the code which cause re-render (context changes) with the code actually do the rendering
Hoc can help with that:
function withStateSlice(Comp, slice) { const MemoComp = React.memo(Comp) function Wrapper(props, ref) { const state = useAppState() const cell = slice(state, props) return <MemoComp ref={ref} state={cell} {...props} /> } Wrapper.displayName = `withStateSlice(${Comp.displayName || Comp.name})` return React.memo(React.forwardRef(Wrapper)) } function Cell({state: cell, row, column}) { const dispatch = useAppDispatch() const handleClick = () => dispatch({type: 'UPDATE_GRID_CELL', row, column}) return ( <button className="cell" onClick={handleClick} style={{ color: cell > 50 ? 'white' : 'black', backgroundColor: `rgba(0, 0, 0, ${cell / 100})`, }} > {Math.floor(cell)} </button> ) } Cell = withStateSlice(Cell, (state, {row, column}) => state.grid[row][column])