Search code examples
reactjsaxiossetintervaluse-effect

How to fetch request in regular intervals using the useEffect hook?


What I'm trying to do is fetch a single random quote from a random quote API every 5 seconds, and set it's contents to a React component. I was able to fetch the request successfully and display it's contents, however after running setInterval method with the fetching method fetchQuote, and a 5 seconds interval, the contents are updated multiple times in that interval.

import { Badge, Box, Text, VStack, Container} from '@chakra-ui/react';
import React, { useState, useEffect } from 'react';
import axios from 'axios';


const  RandomQuotes = () => {
    const [quote, setQuote] = useState<Quote>(quoteObject);
    const [error, setError]: [string, (error: string) => void] = React.useState("");
    const [loading, setLoading] = useState(true);

    const fetchQuote = () => {
        axios.get<Quote>(randomQuoteURL)
            .then(response => {
                setLoading(false);
                setQuote(response.data);
            })
            .catch(ex => {
                setError(ex);
                console.log(ex)
            });
    }

    setInterval(() => setLoading(true), 5000);


    useEffect(fetchQuote, [loading, error]);

    const { id, content, author } = quote;

    return (
        <>
            <RandomQuote
                quoteID={id}
                quoteContent={content}
                quoteAuthor={author}
            />
        </>
    );

}


Solution

  • When any state or prop value gets updated, your function body will re-run, which is called a re-render.

    And you've put setInterval call in the main function(!!!), so each time the component re-renders, it will create another interval again and again. Your browser will get stuck after a few minutes.

    You need this interval definition once, which is what useEffect with an empty second parameter is for.

    Also, using loading flag as a trigger for an API call works, but semantically makes no sense, plus the watcher is expensive and not needed.

    Here's a rough correct example:

    useEffect(() => {
      const myInterval = setInterval(fetchQuote, 5000);
    
      return () => {
        // should clear the interval when the component unmounts
        clearInterval(myInterval);
      };
    }, []);
    
    const fetchQuote = () => {
      setLoading(true);
      
      // your current code
    };