Render A component render happens when: parent component renders state is changed Parent component render triggers all its direct child components render, but not passed within its tags in props.children .
const style = { border: '2px solid LightGrey', padding: '10px', margin: '10px', maxWidth: '500px' }
function blink(el) {
el.style.borderColor = 'red'
setTimeout(() => { el.style.borderColor = 'LightGrey' }, 500)
}
function MyParent(props) {
const ref = useRef()
const [state, setState] = useState(0)
const updateState = () => setState(state + 1)
const [stateForChild, setStateForChild] = useState(0)
const updateStateForChild = () => setStateForChild(stateForChild + 1)
ref.current && blink(ref.current)
return (
<div style={style} ref={ref}>
<h3>Parent</h3>
<button onClick={updateState}>Add +1</button>
<div>Value: <b>{state}</b></div>
<ChildWithOwnState />
<ChildWithoutStateInJSX />
<ChildWithParentState state={stateForChild} updateState={updateStateForChild}/>
{props.children}
</div>
)
}
function ChildWithOwnState() {
const [state, setState] = useState(0)
const updateState = () => setState(state + 1)
const ref = useRef()
ref.current && blink(ref.current)
return (
<div style={style} ref={ref}>
<h3>Child with own state</h3>
<div>Incrementor: <b>{state}</b></div>
<button onClick={updateState}>Add +1</button>
</div>
)
}
function ChildWithoutStateInJSX() {
const [state, setState] = useState(0)
const updateState = () => setState(state + 1)
const ref = useRef()
ref.current && blink(ref.current)
return (
<div style={style} ref={ref}>
<h3>Child with own state, but w/o state on screen</h3>
<button onClick={updateState}>Add +1</button>
</div>
)
}
function ChildWithParentState(props) {
const ref = useRef()
ref.current && blink(ref.current)
return (
<div style={style} ref={ref}>
<h3>Child with state from parent</h3>
<div>Value: <b>{props.state}</b></div>
<button onClick={props.updateState}>Add +1</button>
</div>
)
}
function ChildPassedInPropsChildren() {
const [state, setState] = useState(0)
const updateState = () => setState(state + 1)
const ref = useRef()
ref.current && blink(ref.current)
return (
<div style={style} ref={ref}>
<h3><>Child - passed in <Code>props.children</Code></></h3>
<div>Incrementor: <b>{state}</b></div>
<button onClick={updateState}>Add +1</button>
</div>
)
}
<MyParent>
<ChildPassedInPropsChildren/>
</MyParent>
Optimization Ones parent component is rendered, Children components are also render, which may be undesirable. There are ways for optimization provided by React. React.memo() If a component has same props & renders the same result we can wrap it into React.memo() to skip a render. Note that React.memo() does a shallow comparison of props and objects of props. We can bring our own comparison function React.memo(Component, areEqual(prevProps, nextProps)) React.memo() is a higher-order component, which means it takes a component and returns a new component. useCallback(func, [dep]) Function created inside a component and passed in props is not equal to itself on next render, because they are objects and they reference to different variables. But if we wrap it into useCallback(func, [dep]) , then it is memoized until any variable inside dependency array is changed, and React.memo() remember it and prevent re-renders.
const style = { border: '2px solid LightGray', padding: '5px', margin: '5px', maxWidth: '500px' }
const externalObj = { name: 'John' }
function Parent() {
const [state, setState] = useState(false)
const ref = useRef()
useEffect(() => { blinkWithCssProp({ el: ref.current }) })
const num = 1
const arr = [1, 2, 3]
const obj = { name: 'John' }
const func = () => alert('hi')
const memoizedFunc = useCallback(func, [])
const memoizedFuncWithDepArr = useCallback(func, [obj])
return (
<div style={style} ref={ref}>
<div>Parent</div>
<button onClick={() => setState(!state)}>Update state</button> 
<span>State: <b>{state.toString()}</b></span> 
<Child name={'Child'} />
<MemoizedChild name={'React.memo(Child) with number in props'} arg={num} />
<MemoizedChild name={'React.memo(Child) with external obj in props'} arg={externalObj} />
<MemoizedChild name={'React.memo(Child) with object in props'} arg={obj} />
<MemoizedChildWithCustomComparison name={'React.memo(Child, customComparisonFunc) with object in props'} arg={obj} />
<MemoizedChild name={'React.memo(Child) with array in props'} arg={arr} />
<MemoizedChild name={'React.memo(Child) with func in props'} arg={func} />
<MemoizedChild name={'React.memo(Child) with useCallback(func, []) in props'} arg={memoizedFunc} />
<MemoizedChild name={'React.memo(Child) with useCallback(func, [obj]) in props'} arg={memoizedFuncWithDepArr} />
</div>
)
}
function Child(props) {
const ref = useRef()
useEffect(() => { blinkWithCssProp({ el: ref.current }) })
return (
<div style={style} ref={ref}>
{props.name}
</div>
)
}
const MemoizedChild = React.memo(Child)
const areNamesSame = (prevProps, nextProps) => prevProps.name === nextProps.name
const MemoizedChildWithCustomComparison = React.memo(Child, areNamesSame)
<Parent />
useMemo() It is not about skipping a component render, but skipping a function execution. With const memoizedResult = useMemo(func, [dep]) we may remember the result returned from a function and use it as long as variables in dependency array stays the same. Basically we can memoise a result of a heavy function, use it in the component and it will re-run the function only if some variable from the dep array changes. Look at the example, that memoised function's results are not recalculated with empty dependency array and sometimes recalculated if variable changes between 1 and 2
function Component() {
const [state, setState] = useState(true)
const toggleState = () => setState(!state)
const oneOrTwo = randomNumFromTo(1, 2)
return (
<>
<p>State: <b>{state.toString()}</b></p>
<button onClick={toggleState}>Toggle state</button>
<p><Code inline >{'randomNumFromTo(1, 1000)'}</Code>: <b>{randomNumFromTo(1, 1000)}</b></p>
<p><Code inline >{'useMemo(() => randomNumFromTo(1, 1000), [])'}</Code>: <b>{useMemo(() => randomNumFromTo(1, 1000), [])}</b></p>
<p>OneOrTwo = <b>{oneOrTwo}</b></p>
<p><Code inline >{'useMemo(() => randomNumFromTo(1, 1000), [OneOrTwo])'}</Code>: <b>{useMemo(() => randomNumFromTo(1, 1000), [oneOrTwo])}</b></p>
</>
)
}
<Component />