Home > database >  NextJS useLayoutEffect not wait async function
NextJS useLayoutEffect not wait async function

Time:11-08

I want to check if the user is logged in on page reload (F5). To do this, I call the useLayoutEffect hook in the _app.tsx file:

  const { authenticated, setAuthenticated } = useContext(AuthContext)

  const fetchRefresh = async () => {
    const status = await AuthStore.checkAuth()
    if (status == 200) {
      setAuthenticated && setAuthenticated(true)
    } else {
      setAuthenticated && setAuthenticated(false)
    }
    return status
  }

  useLayoutEffect(() => {
    if (localStorage.getItem("access_token")) {
      fetchRefresh()
    }
  }, [])

My index.tsx is wrapped in a Layout where I call useEffect and check the user state from the context:

export const withLayout = <T extends Record<string, unknown> & IAuthContext>(Component: FunctionComponent<T>) => {
    return function withLayoutComponent(props: T): JSX.Element {
        const { authenticated } = useContext(AuthContext)
        const router = useRouter()

        useEffect(() => {
            if (!authenticated) router.push("/auth/login")
        }, [authenticated])

        return (
            <Layout>
                <Component {...props} />
            </Layout>
        );
    };
};

The order of the logs in the console is as follows:

  1. useLayoutEffect(_app.tsx)
  2. use effect (index.tsx)
  3. complete the fetchRefresh function

The login page also has a useEffect that redirects to the "/" page if the user is logged in. So when I reload the page, I have a flicker and a few redirects that I want to get rid of.

And another question, why?

setAuthenticated && setAuthenticated(true)
console.log(authenticated) // print false

CodePudding user response:

First reason is technical: you don't await for fetchRefresh(). It just runs async operation and promise returns is ignored, nothing rely/check it.

Second reason, useEffect/useLayoutEffect are not blocking rendering, and even does not expect async operations. So (even) if you do it as

useLayoutEffect(() => 
    if (localStorage.getItem("access_token")) {
      await fetchRefresh()
    }

first render would still happen anyway, and with authenticated == true.

You need to change your logic in some way. Either add a boolean isLoading so first render would not show anything since data is still being fetched/verified. Or instead of boolean for authenticated you can use enum yes|true|undefined and for last value does not do/show anything. Or do something else.

  • Related