React state change not recognized on first update

Issue

I have 2 components, Index.js and Menu.js. Index.js contains Menu.js.

I have a state declared in the top level, Index.js. When you click on the edit icon existing in Index.js, it triggers a toggle on the css display attribute of the state variable. When this is toggled, it does not immediately show the item that now has a changed css value from display: none to display: block. However, if I click inside the area where the Menu.js component is, it then renders and shows the div (mainly because I have some functionality that forces a render with document.onmouseup). UseEffect was placed into Menu.js and is not triggered by the change. It too will only trigger if I click inside the component. Additionally, if no clicking is done inside of the component, it will be one step behind on the toggle.

Menu.js

useEffect(() => {
  console.log("do something");
}, [form.display]);

return (
  <div style={{ height: "500px", display: form.display }} id="editForm"></div>
); 

Index.js

const [form, setForm] = useState({
      display: "none",
    });
    
    const handleEditForm = () => {
      var updateForm;
      console.log("triggered");
      if (form.display == "none") {
        updateForm = form;
        updateForm.display = "block";
        setForm(updateForm);
      } else {
        updateForm = form;
        updateForm.display = "none";
        setForm(updateForm);
      }
    };
    
    return (
      <div>
        <img
          onClick={handleEditForm}
          style={{
            position: "relative",
            top: "5px",
            paddingLeft: "10px",
            width: "30px",
          }}
          src="/public/static/images/edit_icon.svg"
          alt="edit icon"
        />
      </div>
    );


Note: There is a conditional onMouseDown, onMouseMove and OnMouseUp function 

Solution

This is a basic concept of Javascript and React. React compares objects before deciding a state variables has changed. Since objects are compared with referential equality, your changing it (or mutating it, in JS terms) does not trigger a rerender.

As you can notice, your objects still point to the same address:

//according to your code
let form = { display : 'none' };    
let updateForm;
updateForm = form;
updateForm.display = "block";
console.log(updateForm === form);

You need to create a new address. Notice how below two objects are not equal even when having same properties.

//How to create a different object
const updateForm = { display : "none" };
        updateForm.display = "block";
        const updateForm2 = { ...updateForm, display : "block"};
        
        console.log(updateForm === updateForm2);
        

You have to do something similar. I have used spread operator but you can create your object again (since only one property). No need to create that extra variable

        setForm({display : "none"});

        setForm({display : "block"});

Answered By – Tushar Shahi

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply

(*) Required, Your email will not be published