React course menu
Intermediate React Interview Questions and Answers
Intermediate React interview questions with answers and live examples: useEffect dependencies and cleanup, state updates and batching, stale closures, lifting state, context vs props, useReducer, useRef, custom hooks, and when components re-render.
On this page
- Hooks
- 1. Explain the useEffect dependency array.
- 2. Why might an effect run twice in development?
- 3. What does this print after one click, and why?
- 4. What is a stale closure in React?
- 5. useRef vs useState?
- 6. When would you use useReducer instead of useState?
- 7. What is a custom hook?
- Data flow
- 8. How do you share state between two sibling components?
- 9. Context vs props: when to use which?
- 10. When does a component re-render?
- 11. How do you fetch data in a component, and what problems must you handle?
- Key takeaway
These questions test whether you understand how React behaves, not just its syntax. Good answers include a short example and the reason behind the rule.
Hooks#
1. Explain the useEffect dependency array.#
Show solution
- No array: the effect runs after every render.
[]: it runs once after the first render (mount), and its cleanup runs on unmount.[a, b]: it runs after the first render and again wheneveraorbchanged (compared withObject.is).
Every value from the component used inside the effect should be listed. Before re-running, and on unmount, React calls the cleanup function the effect returned.
2. Why might an effect run twice in development?#
Show solution
With <StrictMode>, React mounts each component, unmounts it, and mounts it again in development, so effects run, clean up, and run again. This exposes missing cleanup (for example a subscription that is never removed). It does not happen in production. The fix is a correct cleanup function, not removing Strict Mode.
3. What does this print after one click, and why?#
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1);
setCount(count + 1);
console.log(count);
}Show solution
It logs 0, and the count becomes 1, not 2. State is a snapshot for each render: inside this handler count is 0, so both calls set 1, and the log shows the value from the current render. React batches the updates into one re-render. To add twice, use the updater form: setCount((c) => c + 1) twice.
import { useState } from "react";
export default function App() {
const [count, setCount] = useState(0);
const [log, setLog] = useState("");
function handleClick() {
setCount(count + 1);
setCount(count + 1);
setLog(`logged: ${count}`);
}
return (
<div>
<button onClick={handleClick}>Click</button> count = {count} ({log || "not clicked"})
</div>
);
}4. What is a stale closure in React?#
Show solution
A function created in one render "remembers" the props and state of that render. If it keeps running later (an interval, an event listener, a callback stored somewhere) it keeps seeing old values. Example: setInterval(() => setCount(count + 1), 1000) in an effect with [] always adds to 0. Fixes: the updater form (setCount((c) => c + 1)), listing the value in the dependencies, or reading the latest value from a ref.
5. useRef vs useState?#
Show solution
Both persist across renders. Changing state triggers a re-render; changing ref.current does not. Use state for anything displayed; use refs for DOM elements (focus, scroll, measure), timer ids, and other values the screen does not show. Don't read or write refs during rendering.
6. When would you use useReducer instead of useState?#
Show solution
When several related values change together or one piece of state changes in many different ways. A reducer (state, action) => newState puts all update logic in one pure, testable function, and handlers just dispatch({ type: "added", ... }). For a single simple value, useState is simpler.
7. What is a custom hook?#
Show solution
A function starting with use that calls other hooks to package reusable stateful logic, such as useFetch, useLocalStorage or useDebounce. Each component that calls it gets its own state: custom hooks share logic, not data. They follow the same rules of hooks.
Data flow#
8. How do you share state between two sibling components?#
Show solution
Lift the state up to their closest common parent, pass the value down as props and pass a setter (or callback) down so children can request changes. If many distant components need it, use context, possibly with a reducer, or a state library.
9. Context vs props: when to use which?#
Show solution
Props are explicit and best for data that goes one or two levels down. Context avoids "prop drilling" for values many components at different depths need: theme, current user, language, a cart. Every component reading a context re-renders when its value changes, so split contexts by concern and keep fast-changing values out of large contexts. Passing JSX as children often removes drilling without context.
10. When does a component re-render?#
Show solution
When its state changes, when its parent re-renders (by default children re-render even if their props are the same), or when a context it reads changes. A re-render means calling the function again and comparing the result; React only updates the DOM where something changed. memo can skip re-rendering children with unchanged props.
11. How do you fetch data in a component, and what problems must you handle?#
Show solution
In an effect: start the request, then store the result in state, and handle three states (loading, error, success). Handle race conditions (ignore or abort stale responses in the cleanup), check response.ok, and clean up on unmount. In production apps, a library such as TanStack Query or a framework's data loading handles caching, retries and deduplication.
Key takeaway#
Finished this lesson?