How to run useEffect and setState multipletime on useEffect

Viewed 59

I am trying to re-arrange a state two times, however, the state only takes in effect on the last useState, what can I use to re-arrange a state multiple time?

Code as below:

import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";

import "./styles.css";

function App() {

  const [array, setArray] = useState([6,2,5,4,1]);

  // async update from useEffect
  useEffect(() => {
    updateArray(array,2,1)
    updateArray(array,4,2)
  }, []);

  const updateArray = (localArray, to, from) => {
    let tempArray = [...localArray];
    tempArray.splice(to, 0, tempArray.splice(from, 1)[0]);
    setArray(tempArray);
  };
 

  console.log("render", array);
  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h2>Start editing to see some magic happen!</h2>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

I know a way is by using setTimeOut and update one by one, however, if my state gets re-render late, it will eventually glitch. Is there any alternative?

I am expecting the array to be [6, 5, 4, 1, 2] because first change to [6, 5, 2, 4, 1] and seconds change to [6,5,4,1,2]

However, it only runs the last one which becomes [6, 2, 4, 1, 5]

Expected Output

First setState: [6, 5, 4, 1, 2]
Second setState: [6, 5, 2, 4, 1]

Current Output

Second setState: [6, 2, 4, 1, 5]

Can I also know the reason for this happening?

CodeSandBox link

3 Answers

Issue

When enqueueing more than 1 state update within a render cycle you should use a functional state update to update from the previous state, not the state from the previous render cycle. When the latter happens each enqueued update overwrites the previous update so the last update wins.

Solution

Use a functional state update.

useEffect(() => {
  updateArray(2, 1);
  updateArray(4, 2);
}, []);

const updateArray = (to, from) => {
  setArray(prevState => {
    const tempArray = [...prevState];
    tempArray.splice(to, 0, tempArray.splice(from, 1)[0]);
    return tempArray
  });
};

Edit how-to-run-useeffect-and-setstate-multipletime-on-useeffect

this should work:

  useEffect(() => {
    setArray(currentArray => updateArray(currentArray, 2, 1))
    setArray(currentArray => updateArray(currentArray, 4, 2))
  }, []);

  const updateArray = (localArray, to, from) => {
    let tempArray = [...localArray];
    tempArray.splice(to, 0, tempArray.splice(from, 1)[0]);
    return tempArray
  };

useEffect will only run once when you use []

useEffect(() => {
  updateArray(array,2,1)
  updateArray(array,4,2)
}, []);

remove [] to run it over and over

useEffect(() => {
  updateArray(array,2,1)
  updateArray(array,4,2)
});

or add the properties that you are waiting for update so when this property change useEffect will trigger

 useEffect(() => {
  updateArray(array,2,1)
  updateArray(array,4,2)
}, [array]);
Related