简体   繁体   中英

React difference of using value in state vs value in object in state

I am getting different behaviour depending on whether I am using a boolvalue on with useState, or whether I am using a bool value inside an object with useState.

This first bit of code will show the hidden text when the button is pressed. It uses contextMenuIsOpen which is a bool directly on the state, to control the visibility of the hidden text.

const Parent = () => {
const [contextMenuState, setContextMenuState] = useState({ isOpen: false, x: 0, y: 0, clipboard:null });
const [contextMenuIsOpen, setContextMenuIsOpen] = useState(false);

const openChild = ()=>{
    setContextMenuIsOpen(true);
}

return <div><h1>Hello</h1>
    <button onClick={openChild}>Open Child</button>
    
    {contextMenuIsOpen &&        
        <h1>hidden</h1> }       
    
</div>
}

export default Parent;

This next bit of code uses a property on an object which is on the state. It doesn't show the hidden text when I do it this way.

const Parent = () => {
const [contextMenuState, setContextMenuState] = useState({ isOpen: false, x: 0, y: 0, clipboard:null });
const [contextMenuIsOpen, setContextMenuIsOpen] = useState(false);

const openChild = ()=>{
    contextMenuState.isOpen = true;
     setContextMenuState(contextMenuState);
}

return <div><h1>Hello</h1>
    <button onClick={openChild}>Open Child</button>
    
    {contextMenuState.isOpen &&        
        <h1>hidden</h1> }       
    
</div>
}

export default Parent;

React checks objects for equality by checking their reference.

Simply, look at the below example.

 const x = { a : 1, b : 2}; xa = 3; console.log(x===x);

So when you do the below,

const openChild = ()=>{
    contextMenuState.isOpen = true;
    setContextMenuState(contextMenuState);
}

You are not changing the reference of contextMenuState . Hence, there is no real change of state and setContextMenuState does not lead to any rerender.

Solution:

Create a new reference. One example, is using spread operator:

const openChild = ()=>{
    setContextMenuState({ ...contextMenuState , isOpen : true });
}

The problem with your second approach is that React will not identify that the value has changed.

const openChild = () => {
  contextMenuState.isOpen = true;
  setContextMenuState(contextMenuState);
}

In this code, you refer to the object's field, but the object reference itself does not change. React is only detecting that the contextMenuState refers to the same address as before and from its point of view nothing has changed, so there is no need to rerender anything.

If you change your code like this, a new object will be created and old contextMenuState is not equal with the new contextMenuState as Javascript has created a new object with a new address to the memory (ie. oldContextMenuState !== newContextMenuState ).:

const openChild = () => {
  setContextMenuState({
    ...contextMenuState,
    isOpen: true
  });
}

This way React will identify the state change and will rerender.

State is immutable in react.
you have to use setContextMenuState() to update the state value.
Because you want to update state according to the previous state, it's better to pass in an arrow function in setContextMenuState where prev is the previous state.


const openChild = () =>{
    setContextMenuState((prev) => ({...prev, isOpen: true }))
}

Try change

contextMenuState.isOpen = true;

to:

setContextMenuState((i) => ({...i, isOpen: true}) )

never change state like this 'contextMenuState.isOpen = true;'

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM