0

I am creating a random quote generator. Everything works fine. But the quote doesn't appear at the page reload. I have to click new quote button to get a new quote. It should also display a quote when the DOM is loaded.

My Code:

import { useCallback, useEffect, useState } from 'react';
import './App.css';

function App() {
     const quotesURL ='https://gist.githubusercontent.com/camperbot/5a022b72e96c4c9585c32bf6a75f62d9/raw/e3c6895ce42069f0ee7e991229064f167fe8ccdc/quotes.json';

const [quotes, setQuotes] = useState([]);
const [randomQuote, setRandomQuote] = useState('');

const fetchQuotes = useCallback(() => {
    fetch(quotesURL)
        .then(response => response.json())
        .then(data => setQuotes(data.quotes))
        .catch(err => console.error(err));

    // console.log(quotes);
}, [quotesURL]);

// eslint-disable-next-line react-hooks/exhaustive-deps
const handleNewQuote = () => {
    const randomIndex = Math.floor(Math.random() * quotes.length);
    setRandomQuote(quotes[randomIndex]);
};

useEffect(() => {
    fetchQuotes();
}, []);

return (
    <div>
        <div id='quote-box'>
            <div id='text'>{randomQuote.quote}</div>
            <div id='author'>{randomQuote.author}</div>
            <button id='new-quote' onClick={handleNewQuote}>
                New Quote
            </button>
            <a href='https://www.twitter.com/intent/tweet' id='tweet-quote'>
                Tweet Quote
            </a>
        </div>
        {quotes?.map((quote, index) => (
            <div key={index}>{JSON.stringify(quote)}</div>
        ))}
    </div>
);
}

export default App;

What I want is to display a quote at the reload of the page. It currently works only when the button is clicked!

The problem according to my understanding is that the useEffect calls fetchQuotes() after handleNewQuote(), so there is no quote to load

1
  • Actually you are wrong, handleNewQuote will only be called when clicking your "New Quote" button. fetchQuotes will be called right after the component has been mounted, and thus not making possible to be called if you have not clicked the button. Commented Aug 10, 2022 at 16:11

2 Answers 2

1

Try including the following in your component:

useEffect(() => {
    if (!randomQuote) {
        const randomIndex = Math.floor(Math.random() * quotes.length);
        setRandomQuote(quotes[randomIndex]);
    }
}, [quotes]);

This runs when the quotes have been loaded and but random quote is still null.

Or even better, call setRandomQuote() at the time you fetch quotes.

const fetchQuotes = useCallback(() => {
    fetch(quotesURL)
        .then(response => response.json())
        .then(data => {
           const randomIndex = Math.floor(Math.random() * data.quotes.length);
           setRandomQuote(data.quotes[randomIndex]);
           setQuotes(data.quotes)
        })
        .catch(err => console.error(err));
}, [quotesURL]);

Sandbox

Sign up to request clarification or add additional context in comments.

Comments

1

There's a lot of ways to do it, but I think the best one would be to set random quote when the data is loaded. It can be done after fetch or in useEffect

So I'd do following

const handleNewQuote = useCallback(() => {
    const randomIndex = Math.floor(Math.random() * quotes.length);
    setRandomQuote(quotes[randomIndex]);
}, [quotes, setRandomQuote])

// useCallback so it can be passed to useEffect safely without infinite loop (memoize function when component rerenders)



useEffect(() => {
  handleNewQuote()
}, [quotes, handleNewQuote])

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.