365

How to push element inside useState array React hook? Is that as an old method in react state? Or something new?

E.g. setState push example ?

skyboyer
  • 22,209
  • 7
  • 57
  • 64
Milos N.
  • 4,416
  • 6
  • 18
  • 31

8 Answers8

769

When you use useState, you can get an update method for the state item:

const [theArray, setTheArray] = useState(initialArray);

then, when you want to add a new element, you use that function and pass in the new array or a function that will create the new array. Normally the latter, since state updates are asynchronous and sometimes batched:

setTheArray(oldArray => [...oldArray, newElement]);

Sometimes you can get away without using that callback form, if you only update the array in handlers for certain specific user events like click (but not like mousemove):

setTheArray([...theArray, newElement]);

The events for which React ensures that rendering is flushed are the "discrete events" listed here.

Live Example (passing a callback into setTheArray):

const {useState, useCallback} = React;
function Example() {
    const [theArray, setTheArray] = useState([]);
    const addEntryClick = () => {
        setTheArray(oldArray => [...oldArray, `Entry ${oldArray.length}`]);
    };
    return [
        <input type="button" onClick={addEntryClick} value="Add" />,
        <div>{theArray.map(entry =>
          <div>{entry}</div>
        )}
        </div>
    ];
}

ReactDOM.render(
    <Example />,
    document.getElementById("root")
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.1/umd/react-dom.production.min.js"></script>

Because the only update to theArray in there is the one in a click event (one of the "discrete" events), I could get away with a direct update in addEntry:

const {useState, useCallback} = React;
function Example() {
    const [theArray, setTheArray] = useState([]);
    const addEntryClick = () => {
        setTheArray([...theArray, `Entry ${theArray.length}`]);
    };
    return [
        <input type="button" onClick={addEntryClick} value="Add" />,
        <div>{theArray.map(entry =>
          <div>{entry}</div>
        )}
        </div>
    ];
}

ReactDOM.render(
    <Example />,
    document.getElementById("root")
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.1/umd/react-dom.production.min.js"></script>
user2738206
  • 388
  • 2
  • 4
  • 14
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • 1
    ...and try `setTheArray(currentArray => [...currentArray, newElement])` if the above does not work. Most likely in `useEffect(...theArray..., [])`, it's important to realize that `theArray` is a constant, so [functional updates](https://reactjs.org/docs/hooks-reference.html#functional-updates) are needed for values from future renders – Aprillion Aug 08 '19 at 19:12
  • 1
    @Aprillion - Not quite sure what you're saying with that `useEffect` example...? – T.J. Crowder Aug 09 '19 at 08:08
  • 1
    If the `useEffect` has an empty dependency list `[]`, it will be executed only during the first render. So the value of `theArray` inside the effect will always be `initialArray`. In situations where `setTheArray([...initialArray, newElement])` would not make sense, and when `theArray` constant will always equal to `initialValue`, then functional updates are needed. – Aprillion Aug 09 '19 at 09:08
  • @Aprillion - I'm afraid I'm still not getting it, possibly being a bit dense. :-) Why would you need an effect with just the *initial* array value? (I mean, there may be an infrequent use case, but...) And even if you do, what does it have to do with the question? – T.J. Crowder Aug 09 '19 at 10:36
  • Is there a way to do it without incurring the overhead of copying the array each time? – AlwaysLearning Nov 05 '21 at 11:32
  • @AlwaysLearning - Officially, no. :-) You can't directly modify objects held in React state. With **really, really thorough testing** that you repeat on each React minor release, you might get away with a direct update followed by A) a call to [`forceUpdate`](https://reactjs.org/docs/react-component.html#forceupdate) (in a class component), or B) changing some other state member (to trigger a re-render). But any component using the array that optimizes by not re-rendering when the array doesn't change ([`memo`](https://reactjs.org/docs/react-api.html#reactmemo), ... – T.J. Crowder Nov 05 '21 at 11:35
  • ...[`PureComponent`](https://reactjs.org/docs/react-api.html#reactpurecomponent), etc.) will be confused by it. Or you could play games with a `Proxy` (where you replace the `Proxy` rather than copying the array), and provide the proxy to anything else that uses it. I wouldn't be surprised if that ended up being more overhead, rather than less, than just copying the array. – T.J. Crowder Nov 05 '21 at 11:37
124

To expand a little further, here are some common examples. Starting with:

const [theArray, setTheArray] = useState(initialArray);
const [theObject, setTheObject] = useState(initialObject);

Push element at end of array

setTheArray(prevArray => [...prevArray, newValue])

Push/update element at end of object

setTheObject(prevState => ({ ...prevState, currentOrNewKey: newValue}));

Push/update element at end of array of objects

setTheArray(prevState => [...prevState, {currentOrNewKey: newValue}]);

Push element at end of object of arrays

let specificArrayInObject = theObject.array.slice();
specificArrayInObject.push(newValue);
const newObj = { ...theObject, [event.target.name]: specificArrayInObject };
theObject(newObj);

Here are some working examples too. https://codesandbox.io/s/reacthooks-push-r991u

Elia Ahadi
  • 1,625
  • 2
  • 16
  • 20
  • The problem is that setTheArray is not updating immediatly the array, inside component handleMethod you can't call theArray value. – assayag.org Feb 26 '22 at 02:18
26

You can append array of Data at the end of custom state:

  const [vehicleData, setVehicleData] = React.useState<any[]>([]);
  setVehicleData(old => [...old, ...newArrayData]);

For example, In below, you appear an example of axios:

  useEffect(() => {
    const fetchData = async () => {
      const result = await axios(
        {
          url: `http://localhost:4000/api/vehicle?page=${page + 1}&pageSize=10`,
          method: 'get',
        }
      );
      setVehicleData(old => [...old, ...result.data.data]);
    };

    fetchData();
  }, [page]);
Mohammad Fallah
  • 976
  • 11
  • 14
16

Most recommended method is using wrapper function and spread operator together. For example, if you have initialized a state called name like this,

const [names, setNames] = useState([])

You can push to this array like this,

setNames(names => [...names, newName])

Hope that helps.

  • trigger setName(names => [...names, "newval"]) onChange, and console.log(names) returns []. Why isn't updated right away? – assayag.org Feb 26 '22 at 02:26
7
// Save search term state to React Hooks with spread operator and wrapper function

// Using .concat(), no wrapper function (not recommended)
setSearches(searches.concat(query))

// Using .concat(), wrapper function (recommended)
setSearches(searches => searches.concat(query))

// Spread operator, no wrapper function (not recommended)
setSearches([...searches, query])

// Spread operator, wrapper function (recommended)
setSearches(searches => [...searches, query])

https://medium.com/javascript-in-plain-english/how-to-add-to-an-array-in-react-state-3d08ddb2e1dc

Adarsh Pawar
  • 682
  • 6
  • 15
2

setTheArray([...theArray, newElement]); is the simplest answer but be careful for the mutation of items in theArray. Use deep cloning of array items.

Shivang Gupta
  • 3,139
  • 1
  • 25
  • 24
  • The deep cloning you mention, does it mean receive as objA. UseState and concat the objA with objB? Same as this link? https://gist.githubusercontent.com/kahsing/8b6701d4678aaedc71b3731bffbdea81/raw/be899c14b11587f0bcd3c6c25f7698044b60e61e/ItemList.js – Luiey Aug 14 '20 at 10:00
2

I tried the above methods for pushing an object into an array of objects in useState but had the following error when using TypeScript:

Type 'TxBacklog[] | undefined' must have a 'Symbol.iterator' method that returns an iterator.ts(2488)

The setup for the tsconfig.json was apparently right:

{
   "compilerOptions": {
   "target": "es6",
   "lib": [
      "dom",
      "dom.iterable",
      "esnext",
      "es6",
],

This workaround solved the problem (my sample code):

Interface:

   interface TxBacklog {
      status: string,
      txHash: string,
   }

State variable:

    const [txBacklog, setTxBacklog] = React.useState<TxBacklog[]>();

Push new object into array:

    // Define new object to be added
    const newTx = {
       txHash: '0x368eb7269eb88ba86..',
       status: 'pending'
    };
    // Push new object into array
    (txBacklog) 
       ? setTxBacklog(prevState => [ ...prevState!, newTx ])
       : setTxBacklog([newTx]);
Sergi Juanati
  • 1,230
  • 1
  • 9
  • 17
2

if you want to push after specific index you can do as below:

   const handleAddAfterIndex = index => {
       setTheArray(oldItems => {
            const copyItems = [...oldItems];
            const finalItems = [];
            for (let i = 0; i < copyItems.length; i += 1) {
                if (i === index) {
                    finalItems.push(copyItems[i]);
                    finalItems.push(newItem);
                } else {
                    finalItems.push(copyItems[i]);
                }
            }
            return finalItems;
        });
    };