2

I am making a fetch request to my server with the hook useEffect, and I keep getting this warning:

Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.

I understand the reason for this warning, but how do I clean it up? I have tried methods suggested in this article with no luck: https://dev.to/pallymore/clean-up-async-requests-in-useeffect-hooks-90h

Here is my code:

import React, { useEffect, useState } from 'react';
import { Redirect, Route } from 'react-router-dom';

export default function ProtectedRoute({ component: Component, ...rest }) {
    
    const [ user, setUser ] = useState('');

    useEffect(() => {
        const fetchUser = async () => {
            try {
                const response = await fetch('http://localhost:5000/user', {
                    credentials: 'include'
                });

                const data = await response.json();

                setUser(data.session.passport.user);
            
            } catch (error) {
                console.log(error);
            }
        }

        fetchUser();

        return () => {
         // Do some cleanup   
        }
    }, [])

    return (
         <Route { ...rest } render={ () => user ? <Component /> : <Redirect to="/login" /> }/>
    )
}

Any help is appreciated :)

UPDATE: Thanks to Tony Nguyen's solution below, here is my working code:

import React, { useEffect, useState } from 'react';
import { Redirect, Route } from 'react-router-dom';

import Loading from './Loading';
import Error from './Error';

export default function ProtectedRoute({ component: Component, ...rest }) {
    
    const [ user, setUser ] = useState('');
    const [ fetchingUser, setFetchingUser ] = useState(true);
    const [ noError, setNoError ] = useState(true);

    useEffect(() => {
        const fetchUser = async () => {
            try {
                const response = await fetch('http://localhost:5000/user', {
                    credentials: 'include'
                });

                if (fetchingUser) {
                    const data = await response.json();
                    setUser(data.session.passport.user);
                }

                setFetchingUser(false);

            } catch {
                setNoError(false);
            }
        }

        fetchUser();
    }, [])

    return (

        <div>
            <Route { ...rest } render={ () => { if (!noError) return <Error />} }/>
            <Route { ...rest } render={ () => { if (fetchingUser && noError) return <Loading />} }/>
            <Route { ...rest } render={ () => { if (user && !fetchingUser && noError) return <Component />} }/>
            <Route { ...rest } render={ () => { if (!user && !fetchingUser && noError) return <Redirect to="/login" />} }/>
        </div>
    )
}
husseinfawazbc
  • 301
  • 1
  • 2
  • 11
  • 1
    I guess the warning comes from `return` part of the component, not the `useEffect` – user0101 Jun 01 '20 at 06:29
  • @user0101 I believe you are right, I removed the contents of the return statement and the error disappeared. – husseinfawazbc Jun 01 '20 at 06:36
  • 2
    I assume for your case clean up is not needed. – Wesley Loh Jun 01 '20 at 06:37
  • @WesleyLoh I'm not sure, anytime I try to return something including the 'user' I receive the same warning. – husseinfawazbc Jun 01 '20 at 06:41
  • Does this answer your question? [React Hook Warnings for async function in useEffect: useEffect function must return a cleanup function or nothing](https://stackoverflow.com/questions/53332321/react-hook-warnings-for-async-function-in-useeffect-useeffect-function-must-ret) – Michael Freidgeim Feb 21 '23 at 19:02

2 Answers2

5

Your first piece of code is ok just need some modification that is adding fetchingUser to let the component know you fetching user, do not redirect

import React, { useEffect, useState } from 'react';
import { Redirect, Route } from 'react-router-dom';

export default function ProtectedRoute({ component: Component, ...rest }) {
     // add fetchingUser state with defaut is true
     const [ fetchingUser, setFetchingUser ] = useState(true);
    const [ user, setUser ] = useState('');

    useEffect(() => {
        const fetchUser = async () => {
            try {
                const response = await fetch('http://localhost:5000/user', {
                    credentials: 'include'
                });

                const data = await response.json();
                setUser(data.session.passport.user);
                // setFetching is false here
                setFetchingUser(false)

            } catch (error) {
                // You can create an error state and set error here
                console.log(error);
            }
        }

        fetchUser();

        return () => {
         // Do some cleanup   
        }
    }, [])
  
  
  // add fetchingUser into conditions
  // You can do better job that is if fetching show Loading component
  // If error show error component
  // if not fetching not error and have user show user component
  // if not fetching not error and not user redirect to login
    return (
         <Route { ...rest } render={ () => user && !fetchingUser ? <Component /> : <Redirect to="/login" /> }/>
    )
}
Tony Nguyen
  • 3,298
  • 11
  • 19
2

This is what worked for me: Link to article

Please follow steps #1 thru #3 in the code below:

 const [isFetching, setIsFetching] = useState(false);

 useEffect(() => {
    if (user.address && user.token) {
        setIsFetching(true);

        // STEP #1
        const abortController = new AbortController();

        (async function fetchWalletInfo() {
            try {
                const response = await fetch(`${process.env.NEXT_PUBLIC_WEB_API}/wallet-info`, {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({
                        address: user.address,
                        token: user.token,
                    }),
                   // STEP #2
                    signal: abortController.signal,
                });

                 setIsFetching(false);

                if (response.status == 200) {
                    const { balance } = await response.json();
                   
                    setWallet({ balance });
                } else {
                    alert(await response.json());
                }
            } catch (error) {
                console.log(error);
            }
        })();

         // STEP #3
        return () => abortController.abort();
    }
}, [user.address, user.token]);