Mohamad Shiralizadeh
Mohamad Shiralizadeh

Reputation: 8765

Why does React render component for the second time after setting the state to the same value?

I have a simple React component and I set the same value each time that I click on the button:

import React, { useState } from 'react';
import './style.css';

let data = { title: 'ABC' };

export default function App() {
  const [foo, setFoo] = useState();

  console.log('Rendered');

  return (
    <div>
      <button onClick={() => setFoo(data)}>Set Data</button>
      <h1>Data: {JSON.stringify(foo)}</h1>
    </div>
  );
}

But renders are a little bit strange because at the second button click React renders the component but I set the same value. Why React re-render the component although I set the same value?

Demo Image

Demo Here

Upvotes: 3

Views: 1866

Answers (2)

Dennis Vash
Dennis Vash

Reputation: 53874

The question is about why the component renders although the new state equals the previous state (shallow comparison)

// On second button click
const prevState = data

// State trigger
setFoo(data)

// Same state, it doesn't triggers a render
data === prevState

So, the component didn't trigger render due to state change.

But it happened due to another reason, as mentioned in docs "between the lines" under Hooks API Bailing out of a state update section:

Although in some cases React may still need to call your component before skipping the children, it shouldn’t affect your code (react.dev).

Note that React may still need to render that specific component again before bailing out (legacy.reactjs.org).

Unlike in class component, for function components, after setting the same state like in our case, sometimes, React will need another render to validate its equality. Its unfortunate edge case.

But it should not consider you as "performance issue" since it does not effect the React.Node tree, it won't continue in the reconciliation process if the state didn't change. It even won't make unnecessary hooks calls.

Another Simplified Example

Same goes here, there is another render for bail out, another log of "A".

Although there is a "bail out" render, notice that the useEffect does not run.

import React, { useEffect } from "react";
import ReactDOM from "react-dom";

const App = () => {
  const [state, setState] = React.useState(0);

  useEffect(() => {
    console.log("B");
  });

  console.log("A");

  return (
    <>
      <h1>{state}</h1>
      <button onClick={() => setState(42)}>Click</button>
    </>
  );
};

ReactDOM.render(<App />, document.getElementById("root"));

Edit  React state trigger

If you wondering on logs order ("Why 'A' logged before 'B'?"), try deep diving another question: React useEffect in depth / use of useEffect?

Upvotes: 5

Jetro Olowole
Jetro Olowole

Reputation: 123

In functional component, a component isn't re-rendered if it's the same value, i.e. a value that passes === comparison:

const [state, setState] = useState({});
...
setState(state => state); // no re-render

Otherwise a component is re-rendered:

setState(state => ({...state})); // re-render

As commented already, it may be "rerendered" by react, but the DOM will likely not change.

Checkout https://github.com/facebook/react/issues/17474

Upvotes: 1

Related Questions