FAQ: The State Hook - Arrays in State

As per my understanding (which can be wrong. Hopefully someone will correct me if that is the case), in the code
const [cart, setCart] = useState([]);
the useState hook will assign the empty array as an initial value to cart and designate cart as the current state.

setCart is a function which can be used to update this state. We don’t have to define the setCart function. The useState hook takes care of this. setCart is already a function built into the useState hook and its code is not visible to us explicitly. If we simply call this function with a value, then setCart will overwrite the old state with this new value e.g. setCart(['Strawberry']) will replace the old cart with a new cart consisting of just this one element. By writing the statement setCart(['Strawberry']), we aren’t defining setCart. We are calling the setCart function with ['Strawberry'] as an argument.

As opposed to overwriting the old state, if arriving at the new state requires use of the old state, then we can do as shown in the snippet:

setCart((prev) => {
      return [item, ...prev];
    });

Again, we aren’t defining the setCart function. It is already defined somewhere in the internal useState hook code hidden from us.

(prev) => {
      return [item, ...prev];
    }

is a function but it isn’t being used to define the setCart function. setCart is already defined somewhere hidden from our view. That hidden code defines setCart in such a way that if setCart is called with a value, then it simply overwrites the old state with this new value. However, if setCart is called with a function with a parameter, then setCart will assign the old state to the parameter of our function and then whatever value is returned by our function, setCart will overwrite the old state with this returned value. Howsoever setCart is defined by useState’s hidden code makes it able to distinguish how it is being called and how it should proceed from thereon.

That hidden code defines setCart in such a way that if setCart is called with a value, then it simply overwrites the old state with this new value. However, if setCart is called with a function with a parameter, then setCart will assign the old state to the parameter of our function and then whatever value is returned by our function, setCart will overwrite the old state with this returned value.

So is it correct to assume that this “hidden code” is set up in such a way that, if the setCart is called with a function with a parameter, that parameter is automatically the previous value of cart (?)

That is my understanding. If I am wrong, hopefully someone will correct me.

Some links which may be related and of interest:

Excerpt from above link: …You’ll notice we’re using the functional or “updater” form of setSteps here. We’re passing a function instead of a value.

React will call that updater function with the previous value of the state, and whatever you return will replace the state with a new value. The argument is called prevState in the example but you can name it anything.

We could just call setSteps(steps + 1) and it would work the same in this example… but I wanted to show you the updater form, because it’ll be useful in case your update is happening in a closure which has captured a stale value of the state…

“When updating an array in state, we do not just add new data to the previous array. We replace the previous array with a brand new array.”

Why is no explanation given for this? I just have to memorize this rule and not understand it?

I’m so confused by this code. This is just awefull. Could someone explain to me whats happening here please?

const addItem = (item) => {
setCart((prev) => {
return [item, …prev];
});
};

nevermind, it makes sense now, we are basically overriding the setCart function by using the default previous state value and returning it as an array that gets item added to it (I’m not sure how the spread operator works … , but I know it replaces the old array entirely with a new one, that is a copy, plus, the latest “item”, as we see on [item, …prev]

they use static check,pay more attention

Does anyone have a simple approach to adding react to an existing code in vsCode. I can use create-react-app but that produces a lot of extraneous files and is time consuming. I would like to add the project from Arrays In State. The react tutorials have simple processes but they all assume your component is in a script tag on index.html.

but addItem does not accept any parameter?

const addItem = (item) => {
    setCart((prev) => {
      return [item, ...prev];
      });
   };

The above is from the file GroceryCart.js
addItem does have a parameter.

In the line,

<ItemList items={produce} onItemClick={addItem} />

we are not calling the addItem function yet. We have just passed the name of the function i.e. a reference to the addItem function is provided to onItemClick.
Later in the file ItemList.js in the handleClick function, we make the call

onItemClick(item);

which is equivalent to the function call

addItem(item);
1 Like

so a function calling another function inside , this is not covered in any lesson, what is that concept called? what should i google to learn more about this concept?
I am stuck here … Appreciates your help!!

1 Like

https://www.codecademy.com/resources/docs/javascript/callbacks

https://www.codecademy.com/courses/introduction-to-javascript/lessons/higher-order-functions/exercises/functions-as-parameters

https://en.wikipedia.org/wiki/Callback_(computer_programming)#JavaScript

1 Like

This was helpful for me as well. Thank you!

1 Like

Thank you for doing such a great job explaining.

1 Like

When updating an array in a state, we do not just add new data to the previous array. We replace the previous array with a brand new array. This means that any information that we want to save from the previous array needs to be explicitly copied over to our new array. That’s what this spread syntax does for us: ...prev .

Why is the items copied? Isn’t it faster and more efficient to mutate the array then return it, rather than having to create a new array, then copy over each item?

const [array, setArray] = useState([]);

// Instead of this ...
const addItem = item => setArray(arr => [item, ...arr])

// ... do this
const addItem = item => setArray(arr => {
    arr.push(item);
    return arr;
}
1 Like

Let’s conduct an experiment.

Suppose we have set up a hook like,

//
const [textArray, setTextArray] = useState(['|+|']);

where textArray is supposed to be an array of strings.

And an accompanying button is set up like,

<button onClick={addElement}>{textArray}</button>

where every button click is supposed to add a string element '|+|' to the textArray array.

Now, let’s create two versions of addElement.
One version will use the spread syntax to create a new array.
The second version will mutate the previous array and then return the mutated array so that setTextArray can update the state.

  • Version A: (Using the spread syntax to create new array)
export default function App() {
  const [textArray, setTextArray] = useState(['|+|']);
  
  const addElement = () => 
      setTextArray(arr => {
        return [...arr, '|+|'];
      });
  
  return (
    <div className="App">
      <h1>Version A (spread syntax)</h1>
      <button onClick={addElement}>{textArray}</button>
    </div>
  );
}

This is what happens after I click the button 5 times (you can open the screenshot in another tab for better zoom),

  • Version B: (Mutating the previous array)
export default function App() {
  const [textArray, setTextArray] = useState(['|+|']);

  const addElement = () => 
      setTextArray(arr => {
        arr.push('|+|');
        console.log(textArray);
        return arr;
      });
  
  return (
    <div className="App">
      <h1>Version B (mutate state)</h1>
      <button onClick={addElement}>{textArray}</button>
    </div>
  );
}

This is what happens after I click the button 5 times (you can open the screenshot in another tab for better zoom),

As you can see in the screenshot, I have mutated the textArray 5 times as evidenced by the logged console statements. I have also returned this mutated array to setTextArray, but it has not triggered any re-renders.

Even though I am mutating the textArray after every click, React doesn’t acknowledge this. React doesn’t want us to mutate states. You have an initial/previous state. You want to go to a new state. Then, create a new state. DON’T mutate the existing state. When we mutate states, things can go wrong in many unexpected and hard to trace ways.

If you have mutated the existing state to try to go to the next state, then React fails to detect this. It looks at the reference of the previous array. If you mutate this array and pass it to the state setter (so that a re-render is triggered), React will not go deep into the array. It will just compare the reference/memory address of the previous array to the mutated array. Since both have the same reference, so React will decide that state hasn’t changed. It will not inspect the arrays deeply to decide whether the elements of the two arrays are identical or not. It will just look at the reference of the arrays. If the reference is the same, then React will decide nothing has changed and a re-render is not necessary.

Bottom line: Don’t mutate states. Create a new state.

2 Likes

Hi guys,

Would someone be able to clarify why this callback function from the previous excercise :

const goBack = () => setQuestionIndex(prevQuestionIndex => prevQuestionIndex - 1);

Does not require curly brackets like this example in the current lesson :

onst addItem = (item) => {
setCart((prev) => {
return [item, …prev];
})
};

Any help would be appreciated :slight_smile:

First, have a look at this post.

If something is still unclear, share your thoughts on what you find confusing.

With the above in mind, if we wished to rewrite an explicit version of goBack, we could do:

const goBack = () => {
    setQuestionIndex(prevQuestionIndex => {
        return prevQuestionIndex - 1;
    })
}

// We could also have written it as:
const goBack = () => {
    return setQuestionIndex(prevQuestionIndex => {
        return prevQuestionIndex - 1;
    })
}
// but a state setter while it changes the state, the state setter itself
// just returns undefined, so the return keyword doesn't accomplish anything useful.
// But the return keyword in the statement return prevQuestionIndex - 1; is important
// and necessary because we used curly braces for the nested/second arrow function.

Similarly, we could have written an implicit version of the second snippet as:

const addItem = (item) => setCart(prev => [item, ...prev])
1 Like