React: React useState Hook don't rerender the changes

Created on 9 May 2019  路  4Comments  路  Source: facebook/react

import React, { useState } from "react";

function Example() {
  // Declare a new state variable, which we'll call "count"
  const [count, setCount] = useState({
    name: "pankaj",
    age: 12,
    city: "alwar"
  });
  const [data, updateData] = useState([
    { id: 1, name: "Pankaj 1" },
    { id: 2, name: "Pankaj 2" },
    { id: 3, name: "Pankaj 3" },
    { id: 4, name: "Pankaj 4" }
  ]);

  const updateStateData = id => {
    let item = data.find(item => item.id == id);
    item.name += " updated 1";
    updateData(data);
  };

  return (
    <div>
      <p>You clicked {count.name} name</p>
      <p>You clicked {count.age} age</p>
      <p>You clicked {count.city} city</p>
      <button
        onClick={() =>
          setCount({
            name: count.name + " updated",
            age: count.age,
            city: count.city
          })
        }
      >
        Update Name
      </button>
      <button
        onClick={() =>
          setCount({ name: count.name, age: count.age + 10, city: count.city })
        }
      >
        Update Age
      </button>
      <button
        onClick={() =>
          setCount({
            name: count.name,
            age: count.age,
            city: count.city + " updated"
          })
        }
      >
        Update City
      </button>

      <div>
        {data.map(item => {
          console.log({ item });
          return (
            <div>
              <p>{item.id}</p>
              <p>{item.name}</p>
              <button onClick={() => updateStateData(item.id)}>Update</button>
            </div>
          );
        })}
      </div>
    </div>
  );
}

export default Example;

I create a functional component and in this component, I am using the useState hook, the second useState([data,updateData]) hook contains an array of objects and I am displaying all items in the component. With each rendered item there is button and onClick of this button I take the id of item and update the name of that particular item and assign newly updated array to "updateData" method that will update data of useState hook.
But now the issue is that array is going update(data) but changes are not reflecting in view.
If I make the below replace the code of "updateStateData" method with below code then its work fine

const updateStateData = (id) => {
let item = data.find(item => item.id == id);
item.name += ' updated 1';
updateData([...data]);
}

So, why I need to assign a new array([...data]) to reflect the changes.

Most helpful comment

exactly as @malerba118 said, state hooks will not rerender if you pass the same value/object to it. React encourages an immutable approach to ensure consistency, especially between renders. I'd go further and rewrite your handler to -

function updateStateData(id){
  return data.map(item => {
    if(item.id !== id) return item 
    return {...item, name: item.name + ' updated'}
  })  
}

Hope this helped! This isn't a bug or a feature request, so I'm closing this issue.

All 4 comments

The state updater returned by useState will not rerender the component's children if you set a new value that equals the current value. https://reactjs.org/docs/hooks-reference.html#bailing-out-of-a-state-update

Why this behavior is different from Class component.

import React, { useState } from "react";

export default class StateFullComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      data: [
        { id: 1, name: "Pankaj 1" },
        { id: 2, name: "Pankaj 2" },
        { id: 3, name: "Pankaj 3" },
        { id: 4, name: "Pankaj 4" }
      ]
    };
  }

  updateStateData = id => {
    let item = this.state.data.find(item => item.id == id);
    item.name += " updated";
    console.log(this.state.data);
    this.setState({
      data: this.state.data
    });
  };

  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <div>
          {this.state.data.map(item => (
            <div key={item.id}>
              <p>{item.id}</p>
              <p>{item.name}</p>
              <button onClick={() => this.updateStateData(item.id)}>
                Update
              </button>
            </div>
          ))}
        </div>
      </div>
    );
  }
}

This Class component code is similar to our functional hook component. But in this class component changes are rerender but not in functional hook component. Does react uses the Object.is comparison algorithm only in case of Hooks?

I'm not sure on the exact reasoning, but yes, class components behave differently. Class components will rerender even if the next state equals the previous state. Also, the state of a class component must be an object and supports shallow merging during updates. Contrastingly, useState does not require the state to be an object and will not shallowly merge the next state onto the previous state while updating.

exactly as @malerba118 said, state hooks will not rerender if you pass the same value/object to it. React encourages an immutable approach to ensure consistency, especially between renders. I'd go further and rewrite your handler to -

function updateStateData(id){
  return data.map(item => {
    if(item.id !== id) return item 
    return {...item, name: item.name + ' updated'}
  })  
}

Hope this helped! This isn't a bug or a feature request, so I'm closing this issue.

Was this page helpful?
0 / 5 - 0 ratings