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.
- Change: array.shift() -> Triggers the
useEffect
- 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.