7

I am trying to change a text (it begins automatically when the screen appears) at every time interval in react, but the problem is that, the time given isn't respected, and the text is changing at a random time interval. This is a part of my code:

const names = [
    'tony', 'elias', 'fadi'
]

const [newName, setnewName] = useState(0);

useEffect(() => {
    for (const [index, value] of names.entries()) {
        setTimeout(() => { shuffle(value) }, 5000);
    }
})

const shuffle = (value) => {
    setnewName(value);
}

And thank you!

Henry Woody
  • 14,024
  • 7
  • 39
  • 56
  • Does this help. Instead of time call shuffle: https://stackoverflow.com/questions/63604185/setinterval-updating-state-in-react-but-not-recognizing-when-time-is-0/63604505#63604505 – Domino987 Nov 19 '20 at 17:25

2 Answers2

14

Couple things here, but the main issue is the use of setTimeout in a useEffect call with no dependency array. So you're calling shuffle 5000ms after each render, which is why the updates seem to occur at random times. Additionally, the way shuffle is called looks like it will pose some issues.

You should modify your code so that the shuffle function selects a random element from the names array on its own and only call shuffle one time (you might also consider renaming shuffle to something like selectRandomName). Then change setTimeout to setInterval and only call that on mount (instead of on each render).

Here's a full example:

const names = [
    'tony', 'elias', 'fadi'
]

function MyComponent() {
    const [newName, setnewName] = useState("");

    const shuffle = useCallback(() => {
        const index = Math.floor(Math.random() * names.length);
        setnewName(names[index]);
    }, []);

    useEffect(() => {
        const intervalID = setInterval(shuffle, 5000);
        return () => clearInterval(intervalID);
    }, [shuffle])

    return(
        <Text>name:{newName}</Text>
    )
}

Note the use of useCallback here is to prevent useEffect from running on each render while also preventing linter warnings from showing up.

Henry Woody
  • 14,024
  • 7
  • 39
  • 56
1

There is also another way of doing it using setTimeout like so:

import { useState, useEffect } from "react"

const names = [
  'tony', 'elias', 'fadi'
]

export default function MyComponent() {
  const [currentName, setCurrentName] = useState(names[0]);

  function setRandomName() {
    const index = Math.floor(Math.random() * names.length);
    let newName = names[index]
    if (newName == currentName) { setRandomName() }
    else { setCurrentName(newName) }
    return
  }

  useEffect(() => {
    setTimeout(() => {
      setRandomName()
    }, 1000);
  }, [currentName])

  return (
    <div>
      <h1>name:{currentName}</h1>
    </div>
  )
}

Here I'm using a recursive function for setting the random name because by doing it this way if the name name is repeated on an interval then the function runs again until a new different name is set.

The actual time interval is happening within useEffect, what I'm doing here is simply setting a timeout. This works because I'm passing currentName as a dependency to useEffect so the code within useEffect will run every time currentName changes, which will continue happening becaue every time useEffect runs it changes the value of currentName.

To show the simplicity of the actual time interval here's an example where we simply go through the list in order instead of randomly every second until we reach the end of the array.

import { useState, useEffect } from "react"

const names = [
  'tony', 'elias', 'fadi'
]

export default function MyComponent() {
  const [index, setIndex] = useState(0)
  useEffect(() => {
    if (index == names.length - 1) return
    setTimeout(() => {
      setIndex(index + 1)
    }, 1000);
  }, [index])

  return (
    <div>
      <h1>name:{names[index]}</h1>
    </div>
  )
}

Here we use the index starting in 0 as a dependency instead and we increment it by one every time the interval runs as long as it's less than the lenght of names.