React rendering/effect/cleanup order
I did some tests to get insight into how React orders its render, effect, and cleanup functions. I share the results in this post.
I used react version: 18.3.1. The main component is defined below.
let id = 0
function App({children}: {children: ReactNode}) {
const myId = ++id
console.log('render', myId)
const [rerenderCheck] = useState(myId)
console.log('rerenderCheck', rerenderCheck)
const [, setState] = useState(0)
useEffect(() => {
console.log('effect', myId)
let ended = false
setTimeout(() => !ended && id <= 2 && setState(id), 3000)
return () => {
ended = true
console.log('cleanup', myId)
}
})
return children
}
Variables explanation:
myId
: identify render.rerenderCheck
: check if the render is a rerender or a fresh render (after an amount, or, first render).
First version: no <StrictMode> wrapper, no state update
Component:
let id = 0
function App({children}: {children: ReactNode}) {
const myId = ++id
console.log('render', myId)
const [rerenderCheck] = useState(myId)
console.log('rerenderCheck', rerenderCheck)
useEffect(() => {
console.log('effect', myId)
return () => console.log('cleanup', myId)
})
return children
}
Results:
render 1
rerenderCheck 1
effect 1
Comments: nothing special.
Second version: with <StrictMode> wrapper
Component: same as the previous, but with <StrictMode>
wrapper
Result:
render 1
rerenderCheck 1
[grey] render 2
[grey] rerenderCheck 2
effect 2
cleanup 2
effect 2
Comments:
rerenderCheck 2
:<StrictMode>
unmounts and re-mount the component. It does NOT re-render.effect 1
,cleanup 1
do not appear: the first mount renders only, effect does not get run.effect 2
runs twice, with a singlecleanup 2
in the middle: in the second mount,<StrictMode>
run effect →cleanup →effect of the second mount. It does NOT run the effect of the first mount.
Third version: no <StrictMode> wrapper, with state update
Component:
let id = 0
export default function App({children}: {children: ReactNode}) {
const myId = ++id
console.log('render', myId)
const [rerenderCheck] = useState(myId)
console.log('rerenderCheck', rerenderCheck)
const [, setState] = useState(0)
useEffect(() => {
console.log('effect', myId)
let ended = false
setTimeout(() => !ended && id <= 2 && setState(id), 3000)
return () => {
ended = true
console.log('cleanup', myId)
}
})
return children
}
Results:
Comments:
- The order
render 2
→cleanup 1
: react re-renders (render 2
) the component first, then clean the effect of the previous render (cleanup 1
), then run the effect for the re-render (effect 2
).
Fourth version: with <StrictMode> wrapper, with state update
Component: same as the previous, but with <StrictMode>
wrapper
Results:
render 1
[grey] render 2
effect 2
cleanup 2
effect 2
[wait]
render 3
[grey] render 4
cleanup 2
effect 4
Comments:
[grey] render 4
: even for the re-render triggered by an event (setTimeout
),<StrictMode>
re-renders the component twice, and only runs the effect of the second render (effect 4
).
Conclusion
<StrictMode>
's behavior at React 18.3.1 version:
For the first render of the global app
- The app gets unmounted and re-mounted, NOT re-rendered.
- The effect of the first mount NOT run.
- The effect of the re-mount get run TWICE, with a cleanup called in between.
For the subsequent re-renders
- The component gets re-rendered twice.
- The effect of the first re-render NOT run.
- The effect of the second rerender run ONCE.
React 19: with strict mode
Version: 19.0.0-beta-26f2496093-20240514
Component:
let id = 0
function App({children}: {children: ReactNode}) {
const myId = ++id
console.log('render', myId)
const [rerenderCheck] = useState(myId)
console.log('rerenderCheck', rerenderCheck)
const [, setState] = useState(0)
useEffect(() => {
console.log('effect', myId)
let ended = false
setTimeout(() => !ended && id <= 2 && setState(id), 3000)
return () => {
ended = true
console.log('cleanup', myId)
}
})
return children
}
Results:
render 1
rerenderCheck 1
[grey] render 2
[grey] rerenderCheck 1
effect 2
[wait]
render 3
rerenderCheck 1
[grey] render 4
[grey] rerenderCheck 1
cleanup 2
effect 4
Comments:
rerenderCheck 1
: always 1, never be 2, which means in react 19,<StrictMode>
does not unmount the component in the first render, it re-uses the component and re-renders the component.effect 2
shows only once before[wait]
: in react 19,<StrictMode>
does NOT run the effect twice anymore.[grey] render 4
shows: in react 19,<StrictMode>
repeat the re-render after a state update, same as of react 18 behavior.