DEV Community

Mohamed Idris
Mohamed Idris

Posted on

When Does a Component Re-render in React?

In React, a component may re-render in the following cases:

  1. When the Component’s State or Props Change
    React automatically re-renders a component whenever its state or props change. This ensures that the component reflects the latest data.

  2. When the Parent Component Re-renders
    Even if the state or props of a child component remain unchanged, the child will re-render if its parent re-renders. This is the default behavior in React.

Tip: To optimize re-renders, you can use React.memo for functional components or shouldComponentUpdate for class components to prevent unnecessary re-renders when props haven't changed.

Credits: John Smilga's course

Top comments (2)

Collapse
 
edriso profile image
Mohamed Idris

Optimizing Re-Renders by Moving State

One way to improve performance and reduce unnecessary re-renders is by lowering the state, especially if it only affects a specific part of the component tree.

For example, let's consider a parent component that has a button to change a count state. By moving this state change to a separate component, we can prevent the entire parent from re-rendering.

Before (with performance issue):

import { useState } from 'react';
import { data } from '../../../../data';
import List from './List';

const App = () => {
  const [people, setPeople] = useState(data);
  const [count, setCount] = useState(0);

  return (
    <section>
      <button
        className='btn'
        onClick={() => setCount(count + 1)}
        style={{ marginBottom: '1rem' }}
      >
        count {count}
      </button>
      <List people={people} />
    </section>
  );
};
export default App;
Enter fullscreen mode Exit fullscreen mode

In this setup, every time the count state changes, the entire App component (including the List component) re-renders. This is not efficient.

Profiler Highlight

Solution: Move the counter button to a separate Counter component

import { useState } from 'react';
import { data } from '../../../../data';
import List from './List';
import Counter from './Counter';

const App = () => {
  const [people, setPeople] = useState(data);

  return (
    <section>
      <Counter />
      <List people={people} />
    </section>
  );
};
export default App;
Enter fullscreen mode Exit fullscreen mode

By moving the counter button to a separate Counter component, we ensure that only the Counter component re-renders when the count changes, instead of the entire App component.

Updated Counter Component:

import { useState } from 'react';

const Counter = () => {
  const [count, setCount] = useState(0);

  return (
    <button
      className='btn'
      onClick={() => setCount(count + 1)}
      style={{ marginBottom: '1rem' }}
    >
      count {count}
    </button>
  );
};

export default Counter;
Enter fullscreen mode Exit fullscreen mode

The React Profiler

In the React Profiler, you’ll see that only the Counter component re-renders when the button is clicked, not the List component or its child components (like Person).

Profiler Settings:

Below are screenshots of the React Profiler settings with the re-rendered component highlighted:


Summary

By separating the state that affects specific UI elements (like the counter button) into its own component, we can reduce unnecessary re-renders and improve performance.

Collapse
 
edriso profile image
Mohamed Idris

Refactoring to Reduce Re-renders on Input Change

In the initial code, every time the user types into the input field, the entire component re-renders, including the List component. This happens because setPeople is being called when a new person is added, which causes the entire component (including the form) to re-render. To optimize this, we can move the form into its own component so that only the form re-renders when the input changes, not the entire App component.

Original Code (With Re-renders on Input Change):

import { useState } from 'react';
import { data } from '../../../../data';
import List from './List';

const App = () => {
  const [people, setPeople] = useState(data);
  const [name, setName] = useState('');

  const handleSubmit = (e) => {
    e.preventDefault();
    if (!name) {
      alert('Please Provide Name Value');
      return;
    }
    addPerson();
    setName('');
  };

  const addPerson = () => {
    const fakeId = Date.now();
    const newPerson = { id: fakeId, name };
    setPeople([...people, newPerson]);
  };

  return (
    <section>
      <form className="form" onSubmit={handleSubmit}>
        <div className="form-row">
          <label htmlFor="name" className="form-label">name</label>
          <input
            type="text"
            name="name"
            id="name"
            className="form-input"
            value={name}
            onChange={(e) => setName(e.target.value)}
          />
        </div>
        <button className="btn btn-block" type="submit">submit</button>
      </form>
      <List people={people} />
    </section>
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

Problem:
Every time the user types, the App component re-renders, including the List component, which is unnecessary.

Refactored Code: Moving the Form to a Separate Component

To fix this, we can move the form into its own AddPersonForm component. This way, only the form will re-render when the input changes, not the entire app or the list of people.

New AddPersonForm Component:

import { useState } from 'react';

const AddPersonForm = ({ addPerson }) => {
  const [name, setName] = useState('');

  const handleSubmit = (e) => {
    e.preventDefault();
    if (!name) {
      alert('Please Provide Name Value');
      return;
    }
    addPerson(name);  // Call the function passed as a prop to update the people list
    setName('');  // Reset the name after adding
  };

  return (
    <form className="form" onSubmit={handleSubmit}>
      <div className="form-row">
        <label htmlFor="name" className="form-label">name</label>
        <input
          type="text"
          name="name"
          id="name"
          className="form-input"
          value={name}
          onChange={(e) => setName(e.target.value)}  // Update name state
        />
      </div>
      <button className="btn btn-block" type="submit">submit</button>
    </form>
  );
};

export default AddPersonForm;
Enter fullscreen mode Exit fullscreen mode

In this refactor, the AddPersonForm component handles the form input and submission. It only re-renders when the name state changes, not the entire App.

Updated App Component:

import { useState } from 'react';
import { data } from '../../../../data';
import List from './List';
import AddPersonForm from './AddPersonForm';

const App = () => {
  const [people, setPeople] = useState(data);

  const addPerson = (name) => {
    const fakeId = Date.now();
    const newPerson = { id: fakeId, name };
    setPeople([...people, newPerson]);  // Update the list of people
  };

  return (
    <section>
      <AddPersonForm addPerson={addPerson} />  {/* Pass addPerson function as prop */}
      <List people={people} />
    </section>
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

Key Changes:

  • The form is now handled by the AddPersonForm component.
  • The addPerson function, which updates the people list, is passed from the App component to the AddPersonForm as a prop. This ensures the list is updated when a new person is added, but the form itself doesn't cause unnecessary re-renders of the List component.

Benefits:

  • Reduced re-renders: The form only re-renders when the input value changes, and the List component remains unaffected by those changes.
  • Component separation: Moving the form into its own component helps keep the code more modular and easier to maintain.