How to Re-Render a Component When a Value Changes in React
Advertisement
🧩 Scenario
Architecture Walkthrough
Four Re-Render Triggers
React re-renders a component in four situations. First, when useState or useReducer state is updated via a setter or dispatch call. Second, when new props arrive from a parent (the parent re-rendered and passed new prop values). Third, when a context value that the component subscribes to via useContext changes. Fourth, when the parent re-renders: by default, React re-renders all children when a parent re-renders, even if the children's props did not change.
None of the following trigger re-renders: changing a local variable, changing a useRef value, modifying an object or array in place without calling the setter, or calling a function that does not touch React state.
Variables vs State vs Refs
A local variable in a component function is recreated from scratch on every render. It does not persist between renders and React does not watch it. Setting a local variable does not schedule a re-render.
useRef returns a mutable object ({ current: value }) that persists across renders. Changing ref.current is immediately reflected in subsequent reads of the ref, but it does not trigger a re-render. Use refs for values that need to persist across renders but do not need to cause UI updates: timer IDs, DOM node references, the previous value of a prop, animation frame IDs.
useState both persists across renders and triggers a re-render when updated. Use state for values that are displayed in the UI or values whose changes should cause the component to update its output.
Computed Values and Derived State
If a value can be computed from existing state or props during render, do not store it in state. Storing derived values in state creates synchronization problems: the state can get out of sync with its source. Compute the derived value directly in the component function body, and React will always have the fresh computed value as part of each render.
Key Code Explained
// What triggers re-renders
function ReRenderDemo() {
const [count, setCount] = useState(0); // triggers re-render when changed
// Local variable: does NOT trigger re-render, does NOT persist
let localCount = 0;
localCount++; // this does nothing useful across renders
// useRef: persists across renders, does NOT trigger re-render
const renderCountRef = useRef(0);
renderCountRef.current++; // persists, but no re-render
// State: persists AND triggers re-render
return (
<div>
<p>Count: {count}</p>
<p>Render count: {renderCountRef.current}</p>
<button onClick={() => setCount((c) => c + 1)}>Increment</button>
</div>
);
}
// Derived state: compute during render, do not store in state
function ProductList({ products }: { products: Product[] }) {
const [filter, setFilter] = useState('');
const [minPrice, setMinPrice] = useState(0);
// Computed directly during render — no useState needed
// Always in sync with filter and minPrice
const filteredProducts = products.filter(
(p) => p.name.includes(filter) && p.price >= minPrice,
);
return (
<div>
<input value={filter} onChange={(e) => setFilter(e.target.value)} />
<input
type="number"
value={minPrice}
onChange={(e) => setMinPrice(Number(e.target.value))}
/>
<ul>
{filteredProducts.map((p) => (
<li key={p.id}>{p.name} - ${p.price}</li>
))}
</ul>
</div>
);
}
// Parent re-render causes child re-renders
function Parent() {
const [tick, setTick] = useState(0);
useEffect(() => {
const id = setInterval(() => setTick((t) => t + 1), 1000);
return () => clearInterval(id);
}, []);
// Child re-renders every second even though its props do not change
return <ExpensiveChild data="static" />;
}
// React.memo prevents re-render when props are shallowly equal
const ExpensiveChild = React.memo(function ExpensiveChild({ data }: { data: string }) {
console.log('ExpensiveChild rendered'); // only logs when data changes now
return <div>{data}</div>;
});
// useRef for values that should not cause re-renders
function HoverTracker({ children }: { children: React.ReactNode }) {
const hasBeenHovered = useRef(false); // persists, no re-render on change
// If you used useState, hovering would re-render the component
// unnecessarily on every hover event
return (
<div
onMouseEnter={() => {
hasBeenHovered.current = true; // no re-render
}}
>
{children}
</div>
);
}
The filteredProducts example is the most common derived-state mistake in React: developers store filteredProducts in state and use a useEffect to update it when filter or products change. This creates a two-render cycle: the filter changes, the first render shows the old filtered list, then the effect fires and updates the filtered state, causing a second render. Computing during render is simpler, always correct, and does not cause extra renders.
Tradeoffs
| Storage type | Triggers re-render | Persists across renders | Use for |
|---|---|---|---|
| Local variable | No | No | Computation within a single render |
useRef | No | Yes | Timer IDs, DOM nodes, previous values |
useState | Yes | Yes | Values displayed in the UI |
| Derived (computed) | No (tracks source) | No | Values that can be computed from state/props |
What Interviewers Actually Check
- Whether you can list the four re-render triggers
- Whether you know local variables and refs do not trigger re-renders
- Whether you know the difference between state (triggers re-render) and ref (does not)
- Whether you know a parent re-render causes child re-renders by default
- Whether you know
React.memoand derived values as optimizations
Follow-Up Questions
- How does
useMemodiffer from computing a value directly in the component function, and when does the performance difference matter? - If
React.memouses shallow equality for props comparison, how would you handle a component that receives an object or function prop that changes reference on every parent render? - How does React 18's automatic batching affect the number of re-renders from multiple state setters in one event handler?
- What is
useTransitionand how does it mark a state update as non-urgent to keep the UI responsive during expensive re-renders? - How does
React.memointeract withuseContext: if a context value changes, does a memoized child that reads that context still re-render?
Common Candidate Mistakes
- Storing a value in a local variable and wondering why the component does not re-render when it changes
- Storing derived values in state and using a
useEffectto keep them in sync, creating a double-render cycle - Not knowing that a parent re-render causes all children to re-render by default without
React.memo - Using
useStatefor timer IDs and DOM node references that do not need to cause re-renders - Thinking
useRefis only for DOM node references when it is the correct tool for any persisted, non-rendered value
Interview Readiness Checklist
Before you leave this question, make sure you can answer:
- Can you list the four things that trigger a React re-render?
- Can you explain why local variables and refs do not trigger re-renders?
- Can you distinguish between state (for rendered values) and refs (for non-rendered persistent values)?
- Can you use
React.memoto prevent unnecessary child re-renders? - Can you explain why derived values should be computed during render rather than stored in state?
Summary
React re-renders a component when state changes via a setter, when new props arrive from a parent, when a subscribed context value changes, or when the parent re-renders. Local variables are recreated on each render and are invisible to React. Refs persist across renders but changing them does not trigger a re-render. Only useState and useReducer provide values that both persist and trigger re-renders when updated.
By default, a parent re-rendering causes all its children to re-render even if their props did not change. React.memo wraps a child component in a shallow-equality check: the child only re-renders if its props changed. For function or object props, wrap them in useCallback or useMemo so their references remain stable across parent renders.
Values that can be computed from existing state or props should be computed directly in the component function during render. Storing derived values in state is an anti-pattern: it requires a useEffect to keep them in sync, creates an extra render cycle, and is always at risk of going stale. Direct computation is simpler, always correct, and has no re-render overhead beyond the natural render cycle.
Does changing a regular JavaScript variable trigger a re-render?
No. Only React state (useState, useReducer), new props from a parent, or a context value change triggers a re-render. Regular variables are recreated on each render and do not persist between renders.
Advertisement
Stay Updated
Get the latest frontend challenges, interview questions and tutorials delivered to your inbox.
Advertisement