No Time Dad

A blog about web development written by a busy dad

React useState Array Update Example

I can never remember how to update an array of objects using React’s useState hook. When I Google “useState array update”, most of the StackOverflow links are already purple. I’m writing a blog post on it and I’m still going to have to look it up later I’m sure.

There might be an example buried deep in the React docs somewhere I can reference, but the main idea is that the array of objects (or really any value) should be treated as immutable. Which is confusing, because it isn’t actually immutable. You can change it as you like. It’s just not “best practice” to do so.

Below is a simple React useState array update example. It’s essentially some functions that perform the basic CRUD (create read update delete) operations on the state array. Try clicking on the Add Item button to add some items to the list. Then try changing the values in the inputs.

Items

    import React, { useState } from "react";
    
    export const Crud = () => {
      const [items, setItems] = useState([]);
    
      const addItem = () => {
        const id = Math.max(...items.map(item => item.id), 0) + 1;
        setItems([...items, { id, name: `item ${id}` }]);
      }
    
      const removeItem = (id) => {
        setItems(items.filter(item => item.id !== id));
      }
    
      const updateItem = (event) => {
        const newItems = items.map(item => {
          if (event.target.id == item.id) {
            return { ...item, name: event.target.value}
          }
          return item;
        });
        setItems(newItems);
      }
    
      return (
        <div>
          <h3>Items</h3>
          <button onClick={addItem} type="button">Add Item</button>
          <ul>
            {items.map(item => (
              <li key={item.id}>
                <input id={item.id} type="text" value={item.name} onChange={updateItem} />
                <button onClick={() => removeItem(item.id)} type="button">Remove Item</button>
              </li>
            ))}
          </ul>
        </div>
      )
    }

    Adding items

    ...
    const addItem = () => {
      const id = Math.max(...items.map(item => item.id), 0) + 1;
      setItems([...items, { id, name: `item ${id}` }]);
    }
    ...

    So, this example is a little wonky but bare with me. Generally speaking, you would get your data from a server somewhere via a fetch request and it would contain an id from the database. You’d also send new data to the server and it’d generate an id for you.

    I’m generating my own id here, which is sort of a bad idea. But, I needed to do it to illustrate the point on how to add a new object to the array using useState. Feel free to ignore the Math.max stuff and focus on the setItems function call.

    setItems([...items, { id, name: `item ${id}` }])

    The main idea is that I’m creating a new array that contains the previous items via the spread operator. I’m then creating a new item object and adding it to the end of the array.

    Removing items

    Removing an item from the array is slightly easier than adding or updating one. For this, I’m using the filter method on the items array, which will return a new array that’s passed directly to the setItems method. The filter method is searching for any items that do not match the id parameter value and returning them in an array. Essentially, filtering out an items that match the id.

    ...
    const removeItem = (id) => {
      setItems(items.filter(item => item.id !== id));
    }
    ..

    I’m sure there are many different ways to accomplish this same outcome. The filter method is handy because if for some reason there isn’t a match, it’ll just return an empty array. So, no extra error handling is needed.

    Update item

    Updating an item in the array of objects is the most complex operation. For this, I’m creating a new array via map that checks if the id attribute of the target element matches an item in the state array and if it does it updates that object. Otherwise, it just returns the item.

    ...
    const updateItem = (event) => {
      const newItems = items.map(item => {
        if (event.target.id == item.id) {
          return { ...item, name: event.target.value}
        }
        return item;
      });
      setItems(newItems);
    }
    ...

    Again, more than few ways to accomplish this. I chose to pass the event object to the updateItem function, but this relies on an id attribute being set for the input element. Which is likely to be the case, but not always.

    <input id={item.id} type="text" value={item.name} onChange={updateItem} />

    So, this useState array update example is pretty basic but it can easily be built on and expanded. These are all common tasks that I find myself writing code for on a regular basis when working with React or really any web framework. Frontend CRUD? Is that a thing? I guess it is now.