Search code examples
javascriptnode.jsreactjssettimeoutuse-effect

React Animation / setTimeout for State change


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;

Solution

  • 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