Search code examples
cssreactjsstaterenderuse-effect

React state change not recognized on first update


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"});