Home > Back-end >  React Animation / setTimeout for State change
React Animation / setTimeout for State change

Time:07-26

In React I'm trying to build a user contact page that cycles through various text phrases at 1 second intervals. I'm trying to achieve this animation through changing the state every 1 second using the method phraseChange() which uses a for loop and setTimeout(). The phrases that I want to cycle through are stored in the list contactPhrases which the for loop iterates through.

The initial text state change seems to work. The issue is that every text change after the first one seems to cycle at an extremely rapid pace which also crashes my page.

How can I achieve this 'text-swap-over-one-second' effect properly?

Repl: https://replit.com/@evanjscallan/setTimeoutTest3?v=1

Code in question:

import React, { useState } from 'react'

const Phrases = (props) => {
  const [phrase, setPhrase] = React.useState("Let's get in touch!")

  const phraseChange = () => {

    let contactPhrases = [
      "Let's get in touch.", "Let's have a conversation.",
      "Let's communicate.", "Contáctame",
      , "Hit me up.", "Bana ulaşın",
      "Wanna Talk?", "让我们谈谈",
      "Vil du snakke med meg?",
      "We need to talk.", "Կապվեք ինձ հետ", "Let's coallesce.",
      "Επικοινώνησε μαζί μου", "Dost thou need to speak?", "Свяжитесь со мной",
      "저에게 연락", "Kontaktiere mich", "What's up?", "ما سره اړیکه ونیسئ",
      "Liên hệ với tôi", "Зв'яжіться зі мною",
      "Comments/questions/queries?", "चल बात करते है",
      "Contactez moi", "צור איתי קשר",
      "Reach out.", "اتصل بي", "私に連絡して", "New phone who dis?"]

    for (let i = 0; i < contactPhrases.length; i  ) {
      setTimeout(() => setPhrase(contactPhrases[i]), 1000)
    }
  }

  React.useEffect(() => {
    phraseChange()
  }, [phrase])

  return (
    <>
      <div>
        <p>{phrase}</p>
      </div>

    </>
  )
}


export default Phrases;

CodePudding user response:

so first:

Remove

let contactPhrases = [
            "Let's get in touch.", "Let's have a conversation.",
            "Let's communicate.", "Contáctame",
            "Hit me up.", "Bana ulaşın",
            "Wanna Talk?", "让我们谈谈",
            "Vil du snakke med meg?",
            "We need to talk.", "Կապվեք ինձ հետ", "Let's coallesce.",
            "Επικοινώνησε μαζί μου", "Dost thou need to speak?", "Свяжитесь со мной",
            "저에게 연락", "Kontaktiere mich", "What's up?", "ما سره اړیکه ونیسئ",
            "Liên hệ với tôi", "Зв'яжіться зі мною",
            "Comments/questions/queries?", "चल बात करते है",
            "Contactez moi", "צור איתי קשר",
            "Reach out.", "اتصل بي", "私に連絡して", "New phone who dis?"]

Out of Phrase component, because variable would be recreated on each rerender

You should put outside of component scope eg:

import “React” from “react”;
…

const contactPhrases = […];

const Phrases = () => {…}

Then, you should define index as state variable e.g. :

const [index, setIndex] = useState(0);

Then

const [phrase, setPhrase] = useState(
contactPhrases[index])

So now you ca define two useEffects, first one would trigger on component first time appears on screen, and would increment index:

useEffect(()=> {
 cont timer = setInterval(() => setIndex(i => (i   1) % contactPhrases.length), 1000);
 return () => clearInterval(timer);
},[]);
// notice the clear interval, that’s how you dispose interval, otherwise it would keep counting in background

useEffect(() => setPhrase(contactPhrases[index]), [index])

Second one would trigger on index change and setPhrase state variable

And then you can do:

<p>{phrase}</p>

CodePudding user response:

Your for loop creates 1 second timers as many as there are elements in the "contactPhrases" array, all of which start and end at the same time. Causing many changes to the "phrase" state, which causes your component to re-render every time since "phrase" is in the dependency array of useEffect ultimately causing the page to crash.

You need 1 timer inside your useEffect that will call the change function.

This code should work as long as elements in "contactPhrases" are unique:

import React, { useState } from 'react'

const Phrases = (props) => {
    const [phrase, setPhrase] = React.useState("Let's get in touch!")

    const phraseChange = () => {

        let contactPhrases = [
            "Let's get in touch.", "Let's have a conversation.",
            "Let's communicate.", "Contáctame",
            "Hit me up.", "Bana ulaşın",
            "Wanna Talk?", "让我们谈谈",
            "Vil du snakke med meg?",
            "We need to talk.", "Կապվեք ինձ հետ", "Let's coallesce.",
            "Επικοινώνησε μαζί μου", "Dost thou need to speak?", "Свяжитесь со мной",
            "저에게 연락", "Kontaktiere mich", "What's up?", "ما سره اړیکه ونیسئ",
            "Liên hệ với tôi", "Зв'яжіться зі мною",
            "Comments/questions/queries?", "चल बात करते है",
            "Contactez moi", "צור איתי קשר",
            "Reach out.", "اتصل بي", "私に連絡して", "New phone who dis?"]

        let current_phrase_index = contactPhrases.indexOf(phrase);

        if (contactPhrases.length-1 === current_phrase_index) {
            // end of contactPhrases. setting the value back to the first element.
            setPhrase(contactPhrases[0]);
        }
        else {
            // Set the phrase to the next element.
            setPhrase(contactPhrases[current_phrase_index   1]);
        }
    }

    React.useEffect(() => {
        setTimeout(() => phraseChange(), 1000)
    }, [phrase])

    return (
        <>
            <div>
                <p>{phrase}</p>
            </div>

        </>
    )
}
export default Phrases;

Sandbox example

CodePudding user response:

The problem is here:

for (let i = 0; i < contactPhrases.length; i  ) {
  setTimeout(() => setPhrase(contactPhrases[i]), 1000)
}

You set all timers at once. So after 1 second a firework begin :)

Using delay function you could implement it like this:

const delay = (time) => {
  return new Promise((resolve) => setTimeout(resolve, time));
}

for (let i = 0; i < contactPhrases.length; i  ) {
  await delay(1000);
  setPhrase(contactPhrases[i]);
}

Full example

  • Related