Search code examples
reactjsreact-hooks

How to let useEffect to run only once as in React 18?


I need to fetch a base64 encoded image in useEffect and then create an image tag in the page, my (simplified) code is like the following:

const App => {
    const [image, SetImage] = useState('');
    useEffect(
        async function fetchImage() {
            //...
            await response = fetch('API-to-get-base64image');
            SetImage(response);
        }, [])

    return (
        <div><img src={image}/> </div>
    )
}

I only need to run this useEffect once because the image is large and running it twice slows down the page loading. But as pointed in many posts like this one: How to call loading function with React useEffect only once, as of React 18, setting the dependency as an empty array no longer does this trick. I wonder what is the new solution to let this useEffect to run only once?


Solution

  • I had this same problem, until I read the Deep Dive section in Fetching data with Effects in the React docs.

    The relevant bit is:

    This list of downsides is not specific to React. It applies to fetching data on mount with any library. Like with routing, data fetching is not trivial to do well, so we recommend the following approaches:

    • If you use a framework, use its built-in data fetching mechanism. Modern React frameworks have integrated data fetching mechanisms that are efficient and don’t suffer from the above pitfalls.
    • Otherwise, consider using or building a client-side cache. Popular open source solutions include React Query, useSWR, and React Router 6.4+. You can build your own solution too, in which case you would use Effects under the hood but also add logic for deduplicating requests, caching responses, and avoiding network waterfalls (by preloading data or hoisting data requirements to routes).

    In my case useSWR was already part of the project dependencies and looking at the example it is quite simple to use:

    import useSWR from 'swr'
     
    function Profile() {
      const { data, error, isLoading } = useSWR('/api/user', fetcher)
     
      if (error) return <div>failed to load</div>
      if (isLoading) return <div>loading...</div>
      return <div>hello {data.name}!</div>
    }
    

    Afterwards I verified that the Fetch only happens once, happy days...

    So for your code that would be something like:

    import useSWR from 'swr'
    
    const App => {
        const [image, SetImage] = useState('');
    
        async function fetchImage(url) {
            //...
            await response = fetch(url);
            SetImage(response);
        }
    
        const { data, error, isLoading } = useSWR('API-to-get-base64image', fetchImage)
    
        return (
            <div><img src={image}/> </div>
        )
    }