You know that feeling when your React app suddenly behaves weirdly , state seems stale, effects run twice, or context values don’t update where you expect?
I’ve been there. That subtle bug where your component reads an outdated context value or a hook’s state seems frozen until you refresh. Turns out, to really understand and debug these issues, you need to peek under React’s hood.
Not just at components, but at how React schedules rendering phases, manages context propagation, and runs hooks.
The familiar headache: stale context or state inside hooks
Imagine this: you have a ThemeContext with a mode string that toggles between "light" and "dark".
const ThemeContext = React.createContext('light');
function ThemedButton() {
const theme = React.useContext(ThemeContext);
React.useEffect(() => {
console.log('Theme changed to', theme);
}, [theme]);
return <button className={theme}>Click me</button>;
}
You toggle the theme in a parent provider, and expect the button to update and log the new theme immediately. But sometimes, your console shows the old theme or the effect runs with the previous theme value.
What’s going on?
React’s render phases: the secret to consistency
React doesn’t just call your components and hooks once per update. Behind the scenes, it runs multiple render phases:
- Render phase: React calls your components purely to figure out what the UI should look like. No DOM mutations happen here , it’s a dry run.
- Commit phase: React applies changes to the DOM and runs effects.
This separation exists so React can pause, restart, or even abandon work (especially in Concurrent Mode).
Context propagation in render phase
Context values flow down the tree during render. When a provider’s value changes, React marks consumers to update. But each render phase captures the context value at that moment.
If React tries rendering a component multiple times in quick succession (say, because of a higher priority update), that component’s hook calls may see different context values over time.
Hooks and render phase interaction: why state and effects feel out of sync
Hooks like useState and useEffect behave differently in render vs commit phases:
-
useStatereturns the current state as of the latest committed render. -
useEffectruns after the commit phase.
So, if React re-renders a component multiple times before committing, your hooks see the state and context values for that particular render phase, even if they change in the next render.
This is why sometimes your effect logs or runs with a "stale" value , it’s tied to the value React had during the render phase that triggered the effect.
A concrete example: a double render mystery
Say React schedules a low-priority update to toggle a context value. During the render phase, it attempts the update but then pauses to process a higher-priority event. It discards the intermediate render and restarts.
Your component’s hooks ran twice:
- First with context value
A. - Then again with the new context
B.
But only the second render commits. Effects run with B. Yet if you have logging inside your render logic, you might see both A and B , confusing!
Why this matters for debugging
When you see weird state or context inconsistencies:
- Remember React might be calling your components multiple times before committing.
- Side effects inside render (like logging or fetches) can run multiple times unexpectedly.
- Your hooks’ dependencies might need careful attention to avoid stale closures.
Tips for taming render phase quirks
-
Avoid side effects in render. Keep render pure. Move logic into
useEffector callbacks. - Memoize context values in providers to avoid unnecessary re-renders.
-
Use
useCallbackanduseMemoto stabilize dependencies. - Understand your React version: Concurrent Mode brings more render phase interruptions.
Wrapping up
React’s internal dance between context, hooks, and render phases is what keeps your UI consistent , but it also means you occasionally get surprising behavior.
Next time your component seems to have stale state or effects run oddly, remember: React might be rendering more than once before it commits. Knowing this changes how you think about hooks and context and helps you debug those tricky lifecycle bugs.
Keep poking under the hood. React’s magic is complicated , but once you get the mechanics, it feels a lot less like magic and a lot more like a manageable machine.
Helpful learning resources
- W3C WAI accessibility guidance
- W3Schools accessibility tutorials
- MDN Web Docs Originally published at Under The Hood. Get the next deep dive in your inbox: subscribe to Under The Hood.
Top comments (0)