reactjs – React state behavior-ThrowExceptions

Exception or error:

So, recently I’ve started a new project. I’m only using functional components (not sure if that’s a relevant statement for this issue).

I have initialized a state variable like this

const [selectedFields, setSelectedFields] = useState([]);

In bellow function, I update the state

let sendMessage = (msg) => {
        let id = uuid4();
        if (msg.key === 'text') {
            msg = {...Fields.Text, action: 'addElement', style: Fields.styles, id};
        } else if (msg.key === 'email') {
            msg = {...Fields.Email, action: 'addElement', style: Fields.styles, id};
        } else if (msg.key === 'firstName') {
            msg = {...Fields.FirstName, action: 'addElement', style: Fields.styles, id};
        } else if (msg.key === 'lastName') {
            msg = {...Fields.LastName, action: 'addElement', style: Fields.styles, id};
        } else if (msg.key === 'dob') {
            msg = {...Fields.DoB, action: 'addElement', style: Fields.styles, id};
        } else if (msg.key === 'tel') {
            msg = {...Fields.Phone, action: 'addElement', style: Fields.styles, id};
        }
        console.log(selectedFields);
        setSelectedFields([...selectedFields, msg]);
        console.log(selectedFields);
        setTimeout(() => {
            console.log(selectedFields);
        }, 2000);
        iframeEl.contentWindow.postMessage(msg, '*');
    };

But all three console.log‘s prints [].
What am I doing wrong? TIA.

FYI, very new to React, in fact this is my first project.

How to solve:

React state updates are asynchronous and, separately, a state update results in your component function being called once the update has completed, which means you get a new selectedFields variable. The original selectedFields variable the first call to your component has never changes. That’s why even your setTimeout version didn’t pick up the change: It’s looking at the old selectedFields variable, not the new one.

Think of it this way: Your function is called when the component is in one state, and then called again when it’s in a new state.

Your function should use selectedFields to render, and issue state updates for it, but it should not have code in it that expects those state updates to have occurred other than that it gets the new state from the const [selectedFields, setSelectedFields] = useState([]); line.


Separately, because state updates are asynchronous and can be “batched” where an update isn’t done immediately and it’s possible for another one to occur before your component is called again, this line is often incorrect:

setSelectedFields([...selectedFields, msg]);

Instead, you need the callback form:

setSelectedFields(fields => [...fields, msg]);

That way, if there are multiple updates to the fields that are batched together, later ones don’t overwrite the changes from earlier ones.

Unfortunately, the React documentation is unclear about when you need to use the callback version of the state setter function and when you can use state directly when updating state. Some parts show using the callback (setCount(oldCount => oldCount + 1)), others show updating directly (setCount(count + 1)). I’ve tried to get an answer on this, but unfortunately haven’t been successful.

Leave a Reply

Your email address will not be published. Required fields are marked *