useState Callback? [random quote machine]

Tell us what’s happening:
context
Hello, I am trying to make the random quote machine project using functional components in React. I would like to load the quotes only once (when the page loads), then just select from the data when the button is pressed. Using class components I would utilize the didcomponentmount method. When using functional components, from what I can tell the useEffect() method is used in place of all the mounting/unmounting stuff.

The Problem
on the initial page load, the default quote value of “EMPTYQUOTE” is shown. From what I can tell, this is because the 3rd ‘.then’ function executed before the prior one has set the quote data. I would guess that this is because the setQuoteData method doesn’t have a callback that can be passed to the 3rd ‘.then’ function. When I log the console for data here it returns nothing as in a blank line in the console. How can I make the quote show on the initial page load using React?

Your code so far


function QuoteMachine(props){
  const[quote, setQuote] = useState("EMPTY QUOTE")
  const[author, setAuthor] = useState("author")
  const[quoteData, setQuoteData] = useState('')

  const newQuote = () =>{
    let randIndex = Math.floor(Math.random() * quoteData.length)
    let randQuote = quoteData[randIndex]
    console.log(quoteData)
    setQuote(randQuote.quote)
    setAuthor(randQuote.author)
  }

  useEffect(() => {
    const Data =  "https://gist.githubusercontent.com/camperbot/5a022b72e96c4c9585c32bf6a75f62d9/raw/e3c6895ce42069f0ee7e991229064f167fe8ccdc/quotes.json";
    const response = fetch(Data)
      .then((response) => response.json())
      .then((data) => {
        setQuoteData(data.quotes)
        console.log("1")
      })
      .then(() => {
        newQuote()
      })//^^HERE
    }, [])

    return (
      <div className = "quote-project">
        <div id ='quote-box' className = "quote-box">
          <div className="just-quote">
          <h2 id = 'text'>{quote}</h2>
          </div>
          <h3 id = 'author'>~ {author}</h3>
          <button id = 'new-quote' onClick = {newQuote}>New Quote</button>
          <a id = 'tweet-quote' href="twitter.com/intent/tweet"><i className="fab fa-twitter"></i></a>
        </div>
      </div>
    )
  }
  
function MarkdownPreviewer(props){
  return(
    <div className = "markdown-previewer">

    </div>
  )
}

Hello there,

You are correct in identifying the issue:

useEffect(() => {
    const Data =  "https://gist.githubusercontent.com/camperbot/5a022b72e96c4c9585c32bf6a75f62d9/raw/e3c6895ce42069f0ee7e991229064f167fe8ccdc/quotes.json";
    const response = fetch(Data)
      .then((response) => response.json())
      .then((data) => {
        setQuoteData(data.quotes)
        console.log("1")
      })
      .then(() => {
        newQuote()
      })//^^HERE
    }, [])

It is an issue, because this is not how .then is used. .then is invoked, once a promise is resolved. As it stands, you can see:

  • the first .then returns response.json()
  • the second .then is called, once response.json() is resolved, with the resolved value. Now, this .then does not return anything - promise or otherwise.
  • So, the third .then gets nothing, and is not told when it should be called.

Back to the question:

You have done well, using the useEffect hook with an empty dependency array. This is the typical method used to mimic componentDidMount (similar to onDocumentReady).

Now, you need to make use of the asynchronous nature of setQuoteData to tell newQuote when to run. Either, use the promise returned by setQuoteData, OR, use another useEffect which depends on the value of quoteData.

Hope this helps

1 Like

My understanding of promises is still shakey, but this is what I tried before

      .then((data) => {
        const promise = setQuoteData(data.quotes)
        console.log("1")
      })
      .then((promise) => {
        console.log(promise)
        newQuote()
      })

But it seems like the setQuoteData does not return a promise, or I’m trying to get the promise totally wrong. When I log the promise it’s undefined. I’ll look into another use effect function. I’m not sure how I would trigger it after the first one is finished, would it be inside the then function in the other use effect function? I’ll see what I can find. Thank you so much for your help.


Update:
I solved this using a useEffect() function with a dependency on quote data, exactly as @Sky020 said:

  const[quoteData, setQuoteData] = useState(' ')
...

...

  useEffect(() => {
    console.log(quoteData)
    newQuote()
  }, [quoteData])

You are right, it was my mistake to say the setQuoteData function returned a promise.

Glad you got it working, though.