Push method in React Hooks (useState)?
Asked Answered
P

9

373

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 ?

Prouty answered 13/2, 2019 at 18:21 Comment(0)
D
793

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>
Disabled answered 13/2, 2019 at 18:25 Comment(7)
...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 are needed for values from future rendersAvisavitaminosis
@Avisavitaminosis - Not quite sure what you're saying with that useEffect example...?Disabled
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.Avisavitaminosis
@Avisavitaminosis - 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?Disabled
Is there a way to do it without incurring the overhead of copying the array each time?Armchair
@Armchair - 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 (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, ...Disabled
...PureComponent, 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.Disabled
J
126

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

Julieannjulien answered 25/10, 2019 at 9:44 Comment(1)
The problem is that setTheArray is not updating immediatly the array, inside component handleMethod you can't call theArray value.Refugee
W
28

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]);
Woofer answered 22/12, 2020 at 17:25 Comment(0)
N
17

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.

Nephograph answered 9/4, 2020 at 14:21 Comment(1)
trigger setName(names => [...names, "newval"]) onChange, and console.log(names) returns []. Why isn't updated right away?Refugee
A
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

Animate answered 13/5, 2020 at 18:20 Comment(0)
C
2

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

Constrained answered 13/12, 2019 at 5:20 Comment(1)
The deep cloning you mention, does it mean receive as objA. UseState and concat the objA with objB? Same as this link? gist.githubusercontent.com/kahsing/…Bascule
T
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]);
Tenaille answered 13/11, 2020 at 10:40 Comment(0)
S
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;
        });
    };
Sheaff answered 16/11, 2020 at 14:0 Comment(0)
P
0

lets assume you have this state :

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

then in your function push to your array like this :

set_array([...array, new_array]);
Paulsen answered 9/3 at 13:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.