React Experiments
Referential Equality
const [items, setItems] = useState(["item", "item"]) return ( <> <div> {items.map((item, i) => <div key={i}>{item}</div> )} </div> <button onClick={() => { const newItems = items newItems.push("item") setItems(newItems) console.log(newItems) }}>Add Item</button> </> )
No re-render (object)
Creates a shallow copy pointing to the same memory location. No re-render since React considers it unchanged (same address).
const [items, setItems] = useState(["item", "item"]) return ( <> <div> {items.map((item, i) => <div key={i}>{item}</div> )} </div> <button onClick={() => { const newItems = [...items] newItems.push("item") setItems(newItems) }}>Add Item</button> </> )
Successful re-render
Using the spread operator creates a deep copy of the object. Points to a new memory location.
Reducer State Management
Item
State actions stored in a reducer
Reducers combine state change logic (different event handlers, etc.) into a single function, called by dispatches.
Kanban Board Re-Renders
const Board = () => { const [items, setItems] = useState([[1, 2, 3], [4, 5], [6]]) const moveLeft = ({ item, col }: {...}) => {...} const moveRight = ({ item, col }: {...}) => {...} return ( <DndContext sensors={sensors} collisionDetection={closestCenter} onDragStart={handleDragStart} onDragEnd={handleDragEnd}> <SortableContext items={items[0]} > {items[0].map((item) => ( <Item id={item} key={item} /> ))} </SortableContext> <SortableContext items={items[1]} > {items[1].map((item) => ( <Item id={item} key={item} /> ))} </SortableContext> <SortableContext items={items[2]} > {items[2].map((item) => ( <Item id={item} key={item} /> ))} </SortableContext> </DndContext> ) } const Item = ({id, ...} : {id: number, ...}) => { return ( <div ref={setNodeRef} style={style} {...attributes} {...listeners}> <Text> {id} </Text> <Button onClick={...} icon={... ? <ArrowRight /> : <ArrowLeft />} /> <Button onClick={...} icon={... ? <ArrowRight /> : <ArrowLeft />} /> </div> ) }
Unoptimized, re-renders entire board
The entire board re-renders for all updates. Unaffected items/columns always re-render, but it's often unnecessary.
1
2
3
4
5
6
Memoizing components
Each column is memoized, stopping unnecessary re-renders. It takes an object prop, but memo() uses shallow comparison so it's not 'equal'. Using the useMemo hook caches the object correctly.
1
2
3
4
5
6
Final optimization with useCallback
Like how defining objects returns a new object each time, defining functions does the same. Callbacks are memoized functions.