What are the main factors that cause React components to re-render?

👨‍💻 Frontend Developer 🟡 Often Asked 🎚️ Medium
#React

Brief Answer

Main factors causing React component re-renders:

  1. State changes — calling setState or the setter function from useState
  2. Props changes — when the parent component passes new props
  3. Parent component re-renders — when the parent component re-renders
  4. Context changes — when data in React Context is updated
  5. Hook dependencies change — when dependencies in hooks like useMemo or useEffect change

Example of re-render triggered by state change:

function Counter() {
  const [count, setCount] = useState(0);
  
  // When the button is clicked, the state changes,
  // triggering a component re-render
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
    </div>
  );
}

Full Answer

React components re-render when the data they depend on changes. This is a fundamental principle of reactive programming in React, but understanding specific causes of re-renders is critical for creating optimized applications. 🔍

1. State Changes

Any change to a component’s internal state triggers a re-render:

function ToggleButton() {
  const [isOn, setIsOn] = useState(false);
  console.log("Component rendering"); // Runs every time state changes
  
  return (
    <button onClick={() => setIsOn(!isOn)}>
      {isOn ? 'Turn Off' : 'Turn On'}
    </button>
  );
}

Each time the button is clicked, isOn changes, which triggers a new render.

2. Props Changes

When a component receives new props from its parent, it re-renders:

// Child component
function Message({ text }) {
  console.log("Message rendering");
  return <p>{text}</p>;
}
 
// Parent component
function Chat() {
  const [message, setMessage] = useState("");
  
  return (
    <div>
      <input 
        value={message}
        onChange={(e) => setMessage(e.target.value)}
      />
      <Message text={message} /> {/* Re-renders on every input change */}
    </div>
  );
}

3. Parent Component Re-renders

When a parent component re-renders, all its child components re-render by default:

function Parent() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>
        Increment counter: {count}
      </button>
      <Child /> {/* Re-renders on every click even though props don't change */}
    </div>
  );
}
 
function Child() {
  console.log("Child rendering");
  return <div>Child component</div>;
}

4. Context Changes

Components that use React Context re-render when the context value changes:

const ThemeContext = React.createContext('light');
 
function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');
  
  return (
    <ThemeContext.Provider value={theme}>
      <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
        Toggle theme
      </button>
      {children}
    </ThemeContext.Provider>
  );
}
 
function ThemedButton() {
  const theme = useContext(ThemeContext);
  console.log("ThemedButton rendering");
  
  return <button className={theme}>Themed Button</button>;
}

When the theme changes, all components using ThemeContext will re-render.

5. Hook Dependencies Update

Changes to dependencies in hooks can trigger recalculations and re-renders:

function SearchResults({ query }) {
  // Re-computed when query changes
  const filteredData = useMemo(() => {
    console.log("Filtering performed");
    return expensiveFilter(query);
  }, [query]);
  
  return (
    <ul>
      {filteredData.map(item => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
}

How to Prevent Unnecessary Re-renders

1. React.memo for Functional Components

Prevents re-rendering if props haven’t changed:

// Won't re-render if name hasn't changed
const Greeting = React.memo(function Greeting({ name }) {
  console.log("Greeting rendering");
  return <h1>Hello, {name}!</h1>;
});

2. useMemo for Computed Values

Caches calculation results between renders:

function ProductList({ products, filter }) {
  // Filtering only runs when products or filter change
  const filteredProducts = useMemo(() => {
    return products.filter(product => product.category === filter);
  }, [products, filter]);
  
  return (
    <ul>
      {filteredProducts.map(product => (
        <li key={product.id}>{product.name}</li>
      ))}
    </ul>
  );
}

3. useCallback for Functions

Prevents creating new functions on every render:

function Parent() {
  const [count, setCount] = useState(0);
  
  // Function isn't recreated when count changes
  const handleClick = useCallback(() => {
    console.log('Clicked');
  }, []);
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>
        Count: {count}
      </button>
      <ExpensiveChild onClick={handleClick} />
    </div>
  );
}
 
const ExpensiveChild = React.memo(function({ onClick }) {
  console.log("ExpensiveChild rendering");
  return <button onClick={onClick}>Child button</button>;
});

Common Mistakes

1. Creating New Objects During Render

// ❌ Bad: new style object on every render
function Button() {
  return (
    <button 
      style={{ color: 'blue', fontSize: '14px' }} // New object on each render
    >
      Click me
    </button>
  );
}
 
// ✅ Good: constant outside the component
const buttonStyle = { color: 'blue', fontSize: '14px' };
 
function Button() {
  return <button style={buttonStyle}>Click me</button>;
}

2. Inline Functions in Props

// ❌ Bad: new function on every render
function Parent() {
  return (
    <Child onClick={() => console.log('Clicked')} /> // New function on each render
  );
}
 
// ✅ Good: using useCallback
function Parent() {
  const handleClick = useCallback(() => {
    console.log('Clicked');
  }, []);
  
  return <Child onClick={handleClick} />;
}

3. Incorrect Hook Dependencies

// ❌ Bad: missing data dependency
function DataList({ data }) {
  useEffect(() => {
    console.log("Data changed:", data);
  }, []); // Missing data dependency
  
  // ... rest of code
}
 
// ✅ Good: correct dependencies
function DataList({ data }) {
  useEffect(() => {
    console.log("Data changed:", data);
  }, [data]);
  
  // ... rest of code
}

Tools for Debugging Renders

  1. React DevTools Profiler — visualizes component renders
  2. why-did-you-render — library that notifies about unnecessary re-renders
  3. Console logs — simple console.log inside the component body
function MyComponent(props) {
  console.log("MyComponent rendering", { props });
  // ... component code
}

Summary

Main causes of React re-renders:

  • Changes to the component’s own state
  • Receiving new props from a parent
  • Parent component re-rendering
  • Context changes
  • Hook dependency changes

How to optimize re-renders:

  • React.memo — prevents renders when props don’t change
  • useMemo — caches computation results
  • useCallback — prevents function recreation
  • Avoid creating new objects and functions during render
  • Correctly specify hook dependencies

Understanding the reasons for re-renders and knowing how to optimize them is a key skill for developing performant React applications. Optimize consciously, based on real performance issues rather than prematurely. 🚀


Want more articles to prepare for interviews? Subscribe to EasyAdvice, bookmark the site, and improve yourself every day 💪