How to Re-Render a Component When a Value Changes in React

Beginner8 min interview
Skills tested:
Understanding the four triggers for a React re-render: state change, new props, context change, parent re-renderUsing useState correctly to store values that should trigger re-renders when they changeKnowing when to use useRef instead of useState for values that should not cause re-rendersUnderstanding that a parent re-rendering causes all its children to re-render by defaultUsing React.memo to prevent child re-renders when props have not changed

Advertisement

🧩 Scenario

Understanding what causes re-renders is the foundation of React performance optimization. Unnecessary re-renders are the most common source of React performance issues. Knowing what React watches and what it ignores lets you architect components that update exactly when needed and not when they should not.

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 typeTriggers re-renderPersists across rendersUse for
Local variableNoNoComputation within a single render
useRefNoYesTimer IDs, DOM nodes, previous values
useStateYesYesValues displayed in the UI
Derived (computed)No (tracks source)NoValues 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.memo and derived values as optimizations

Follow-Up Questions

  1. How does useMemo differ from computing a value directly in the component function, and when does the performance difference matter?
  2. If React.memo uses 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?
  3. How does React 18's automatic batching affect the number of re-renders from multiple state setters in one event handler?
  4. What is useTransition and how does it mark a state update as non-urgent to keep the UI responsive during expensive re-renders?
  5. How does React.memo interact with useContext: 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 useEffect to 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 useState for timer IDs and DOM node references that do not need to cause re-renders
  • Thinking useRef is 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.memo to 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.

Related Videos
Watch these videos to learn more about this topic
Frequently Asked Questions

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