30
loading...
This website collects cookies to deliver better user experience
Item
. We will still update the parent state but won't pass any props to list items.React does not care whether "props changed" - it will render child components unconditionally just because the parent rendered!
Mark Erikson - A (Mostly) Complete Guide to React Rendering Behavior
const Item = memo(() => <div>Item</div>)
Item
with memo
. Here is a slightly simplified code.const Item = memo(({id, value, onChange}) => {
return (
<input
onChange={e => onChange(id, e.target.value)}
value={value} />
)
})
memo
re-renders, it means that one of its properties changes. Let's figure out which one.value
only changes for one item in the list. The id
property is also stable. So it must be onChange
property that changes. Let's check the Parent
code to see how we pass the props.const Parent = () => {
const [items, setItems] = useState([
{ value: '' },
{ value: '' },
{ value: '' }
])
return (
<div>
{items.map((item, index) => (
<Item
key={index}
id={index}
value={item.value}
onChange={(id, value) =>
setState(state.map((item, index) => {
return index !== id ? item : { value: value }
})}
/>
)}
</div>
)
}
onChange={(id, value) =>
setState(state.map((item, index) => {
return index !== id ? item : { value: value }
})}
onChange
property will change every time Parent
renders. To prevent that, we need to memoize it with useCallback. Let's do that:const Parent = () => {
...
const onChange = useCallback((id, value) => {
setItems(items.map((item, index) => {
return index !== id ? item : { value: value }
}))
}, [items])
return (
<div>
{items.map((item, index) => (
<Item
key={index}
id={index}
value={item.value}
onChange={onChange}
/>
)}
</div>
)
}
items
as a dependency for useCallback
. Every time items
update, useCallback
returns a new reference of the function. This causes onChange
prop to change, therefore updating every component in the list.items
as a dependency. We can achieve that with a functional state update:const onChange = useCallback((id, value) => {
setItems(prevItems => prevItems.map((item, index) => {
return index !== id ? item : { value: value }
}))
}, []) // No dependencies
Item
that changes is value
. And since we only update one value
at a time, it prevents other components in the list from re-rendering.memo
comes with a small performance cost as well. Optimize it when you have a lot of items in the list and your render function is expensive.I would assume that the same general advice applies for React.memo as it does for shouldComponentUpdate and PureComponent: doing comparisons does have a small cost, and there's scenarios where a component would never memoize properly (especially if it makes use of props.children). So, don't just automatically wrap everything everywhere. See how your app behaves in production mode, use React's profiling builds and the DevTools profiler to see where bottlenecks are, and strategically use these tools to optimize parts of the component tree that will actually benefit from these optimizations.
Mark Erikson - When should you NOT use React memo?