Home > database >  How to change places in an Array using React and useEffect?
How to change places in an Array using React and useEffect?

Time:04-09

There's a list of 5 elements. Per default, the list (array) should show ["A", "B", "C", "D", "E"]. Every second the elements should rotate by one position:

List after 1 second: ["B", "C", "D", "E", "A"];

After 2 seconds: ["C", "D", "E", "A", "B"];

After 3 seconds: ["D", "E", "A", "B", "C"];

I'm learning and I need help. How would you solve this problem? This is my incorrect solution:

import React, { useState, useEffect } from "react";
import "./App.css";

function App() {
  const [term, setTerm] = useState();
  const [letter, setLetter] = useState([]);

  let array = ["A", "B", "C", "D", "E"];

  useEffect(() => {
    const interval = () =>
      setInterval(() => {
        array.push(array.shift());
        setLetter(array);
       }, 1000);

    interval();

    return clearInterval(interval);
  }, [array]);

  return (
    <div className="center">
      <input
        type="text"
        value={term}
        onChange={(e) => setTerm(e.target.value)}
      />
      {letter &&
        letter.map((l, i) => {
          return (
            <ul key={l}>
              <li>{l[0]}</li>
              <li>{l[1]}</li>
              <li>{l[1]}</li>
              <li>{l[3]}</li>
              <li>{l[4]}</li>
            </ul>
          );
        })}
    </div>
  );
}

export default App;

I'm just a learner, I'm stuck, and I need help. It changes rapidly, like every 50ms.

I'll analyze the answer and learn from it because I can't do it by myself.

CodePudding user response:

What happens here is that you have a useEffect attached to the changes of array which changes multiple times inside the interval callback.

  1. Change: array.shift() -> Triggers the useEffect
  2. Change: array.push() -> Also triggers the useEffect

This compounds and starts changing the array rapidly.

I would suggest working with an array copy or something of the sort.

setInterval(() =>
  let copy = […array];
  copy.push(copy.shift());
  setLetter(copy);
  array = copy
}, 1000);

Just another suggestion. I would also try to not use the extra array variable as that data is already stored in the letter. Here is how I would go about doing this.

const [letter, setLetter] = useState(["A", "B", "C", "D", "E"]);

useEffect(() => {
  setTimeout(() =>
    let copy = […letter];
    copy.push(copy.shift());
    setLetter(copy);
  }, 1000);
}, [letter]);

I didn’t test this, so I hope it helps.

CodePudding user response:

I would suggest that you avoid modifying your array. Rather than using push and shift, generate a new version of the array by arr => [...arr .slice (1), arr [0]] or using concat:

const {useState, useEffect} = React;

const App = () => {
  const [letters, setLetters] = useState (["A", "B", "C", "D", "E"])

  useEffect(() => {
    const timer = setTimeout (
      () => setLetters (arr => arr .slice (1) .concat (arr [0])), 
      1000
    )
    return () => clearTimeout (timer)
 })

  return (<div>{letters .join (' ')}</div>)
}

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

Modifying data only through the setter makes things much more consistent.

Note that this will work fine without returning the clearTimeout, but it's probably a good habit to be in.

  • Related