avatarDr. Derek Austin 🥳

Summary

The web content explains how to properly update an array in React state using hooks, emphasizing the use of .concat() and the spread operator ... over .push() to maintain immutability.

Abstract

The article discusses the common React practice of updating state arrays using hooks, particularly useState. It clarifies why the .push() method is ineffective for state updates, as it mutates the array directly and doesn't trigger a re-render. Instead, the author recommends using .concat() or the spread operator to create a new array, ensuring immutability and proper state management. The article also covers the importance of using a "wrapper function" when updating state to avoid potential bugs due to stale state references. The concepts of immutability and the correct usage of React Hooks are reinforced through examples and a live demo.

Opinions

  • Directly mutating state with .push() is discouraged in React as it violates the principle of immutability.
  • The use of .concat() or the spread operator ... is preferred for updating arrays in state because they return a new array without altering the original.
  • Immutability in React helps with performance and prevents unwanted side effects.
  • A "wrapper function" is recommended when updating state to ensure the update is based on the most current state value.
  • The author emphasizes the importance of understanding how React Hooks work to effectively manage state.
  • The article suggests that adhering to best practices for state management, such as immutability, can lead to more robust and bug-free applications.

How to Add to an Array in React State using Hooks

The .push() function will not work, but the .concat() function can update state in React when state is a JavaScript array — as can …, the spread operator.

Photo by Glenn Carstens-Peters on Unsplash

“One of the most common questions is how to add an item to an array in React state. Since you are not allowed to mutate the state directly, you cannot simply push an item to an array.” — Robin Wieruch in a 2018 blog post

When React state is an array, it is not obvious how to add items to the array, such as when trying to update state using React Hooks.

This question might come up when state is a list of items. In this article, I explore an example of the recent search queries entered by the user.

A more complex example would be storing a list of JavaScript objects as an array in the React state, such as student records.

The question is how do I add an item to the array in React State?

Photo by Emma Matthews on Unsplash

Creating React state as an array

A typical code example using React Hooks to keep a list of items would initialize React state using a call like useState([]).

“useState is a Hook […] We call it inside a function component to add some local state to it. React will preserve this state between re-renders.” — React Docs

That is called using the useState Hook. In this example, the command useState([]) initializes the state to contain the empty array, [].

An array is a valid parameter to pass to useState(), as is shown here:

In the next section I try various methods to add to the array that is currently in the React state — accessible via searches and mutable via setSearches.

Photo by Giammarco Boscaro on Unsplash

How to push to an array in React state

My first idea to add an item to a React state was using .push(), a typical JavaScript method to append to the end of an array.

The .push() method is defined on Array.prototype, so it is called using the dot-reference operator on the array like so:

I want to add to the array in React State, so it seems to make sense to try to .push() a new item to the end of the list, and thereby update state.

Unfortunately, the code searches.push() will not work in React:

The rub is when I actually try to save the search term to React State this way, nothing happens at all — no error, nothing. What is going on?

Photo by Kelly Sikkema on Unsplash

I forgot to use the React Hooks setter

The reason it does not work is that I tried to modify the React state directly — while .push() does modify the array found in the variable searches, that modification never “hooks” over to React to update on the re-render.

React Hooks require a specific setter function, which is the second part returned from the useState call originally, what I called setSearches.

“useState returns a pair: the current state value and a function that lets you update it.” — React Docs

Put another way, state should be considered immutable in React and thus should not be changed (or mutated) directly.

This is why the React state itself (the searches variable and setSearches hook) were defined as constants using const — as a reminder of that.

This is by design, and that means that updating state with React Hooks involves the associated setter functions returned by useState().

Photo by Daria Nepriakhina on Unsplash

setSearches(searches.push(query)) doesn’t work

Wrapping the .push() in the setter function from React Hooks does not work either, even though it sort of seems like it should.

In fact, setSearches(searches.push(query)) crashes with a TypeError:

In this case, the app is broken and I am probably panicking!

Fortunately, the working solution is very similar to the above, except substituting .concat() for the .push() function call.

But why does searches.push() fail, and why does .concat() work?

Photo by Roman Synkevych on Unsplash

Why does .push() fail with React Hooks?

The previous code example did not work because .push() returns the length of the array after modification, not the array itself.

The React Hooks setter function setSearches(), which is also called a reducer, updates the current state to the value that is passed in.

In the above example, the state has been updated to a number from an array — which is why the TypeError is that searches.map is not a function.

The React state, searches, was replaced from its state of [] to the length of the array following .push.length became 1, so searches is now 1.

And while [].map() worked, the code 1.map() is nonsensical in JavaScript.

Instead of changing the old state directly, I need to pass the updated state directly to React Hooks state setter (the reducer function), setSearches.

Photo by Lukas Blazek on Unsplash

The solution is .concat()

To the rescue is the Array.prototype.concat() method, short for concatenate, which works like .push() but with a twist.

The reason .concat() works to update state is that .concat() creates a new array, leaving the old array intact, and then returns the changed array.

On the other hand, .push() mutates the old array in place, but returns the length of the mutated array instead of the mutated array itself.

Thus, using .push() means the state gets overwritten with the length of the new array — a simple bug caused by not knowing what .push() returned.

The below code will work because .concat() returns a new, updated array:

While respecting the immutability of state in React, I used .concat() to achieve the result of adding an item to the end of an array in React State.

And if I had wanted to prepend the item to the front of list, I would have just reversed the order of operations like so:

But, there is an important step I left out — using a “wrapper function” can prevent bugs in React components that update rapidly.

I explain how to use the ES6 feature (the spread operator) as well as the potential benefits of a wrapper function in the next section.

Photo by Mike Kenneally on Unsplash

Best solution: Spread operator … & a wrapper

The JavaScript spread operator () helps when copying and combining arrays, so it can also be used to add an item to an array in React state.

  • [...searches, query] to append an item to the end of the array
  • [query, ...searches] to prepend an item to the front of the array

Note in the code example I also used a wrapper function — instead of passing the state, I passed a call-back function taking in the current state.

  • setSearches(searches => searches.concat(query)) with .concat()
  • setSearches(searches => [...searches, query]) with spread

In the wrapper function passed to the setSearches Hook, I update the state searches by returning the updated state: searches.concat(query) using .concat() or [...searches, query] with the spread operator e>….

Photo by Bench Accounting on Unsplash

Why use a wrapper function?

Using a wrapper function inside the React Hooks setter function is a better solution than the implementation just using .concat() or .

Jasper Dunn wrote in response to this article about the advantages of always using a wrapper function when working with React state:

“The value of `searches` accessed from the initial hook may be different than is expected, and this could [cause] unwanted side effects.” — Jasper Dunn

Use of the wrapper function is highly encouraged so that the current state is accessed when the re-render actually occurs, not at some other time.

The wrapper function is also called a callback function, since it refers to passing a function to another function.

Photo by Kim S. Ly on Unsplash

Recap: How to add to an array in state

By mastering the wrapper function, I finally solved my problem of how to add to an array in React state in three easy steps:

  1. Use useState([]) Hook to set state to [] and get state setter function
  2. Pass wrapper function to state setter function (as a call-back function)
  3. Update array using .concat or spread inside the wrapper function

But I still wondered why is React state immutable in the first place?

Photo by Andrew Neel on Unsplash

Why .concat() or … are the right tools in React

State is supposed to be immutable in React. For example, that is why React Hooks are usually defined with const — as a reminder not to touch.

Why is this the case? Esteban Herrer explains it on the LogRocket blog:

“For two arrays of the same size, the only way to know if they are equal is by comparing each element. A costly operation for large arrays.

The most simple solution is to use immutable objects.

If the object needs to be updated, a new object with the new value has to be created, because the original one is immutable and cannot be changed.”

Because state is immutable, React knows exactly when state changes — when a new value is created and assigned to React state.

Using .concat() or the spread operator duplicates the state into a new array, which we pass to React as the updated state.

React does not have to update the state and re-render the screen until it is informed state actually changed by the React Hooks setter function.

This is good for performance of your React apps.

Photo by Ross Findon on Unsplash

The benefits of immutability

Obviously, at some point in the app, we are updating the state — it just happens by providing React a new state, overwriting the old state.

That means we cannot mutate the state in-place, we update it by replacing it with a new state — for an array, using .concat() or spread .

Using an immutable state helps bug-proof our code, in addition to signaling the Virtual DOM to update when the reference changes.

Immutability ensures we only ever mutate the state when we actually want to — not accidentally, where we might lose user data or have other terrible things happen in production. 😱

Photo by Chris Lawton on Unsplash

Try it yourself

I included a live demo and its complete code at the end of the article, demonstrating a simple React app that saves search terms in state.

Each phrase is added to React state with the spread operator and a wrapper function, as shown above.

This is a screenshot of what it looks like in action:

Photo by Kyle Glenn on Unsplash

Live demo at CodeSandbox.io

Source code for live demo

Further reading

Photo by Robin Benzrihem on Unsplash

Dr. Derek Austin is the author of Career Programming: How You Can Become a Successful 6-Figure Programmer in 6 Months, now available on Amazon.

JavaScript
Programming
Web Development
Software Engineering
Technology
Recommended from ReadMedium