Paweenwat Maneechai
Paweenwat Maneechai

Reputation: 347

How to prevent useEffect() run twice after running a function in context consumer and prevent useContext() to re-render

I'm learned that React will re-render after state changed e.g. setState from useState(), calling the function or variable from useContext() variable. But now I'm don't understand that why I get the ESLint warning call the context function inside the useCallback() without dependency in the list. If I put the dependency in the list, useCallback() will be re-rendered and useEffect() dependency from useCallback() variable will do again. So how to fix the react-hooks/exhaustive-deps when calling the function inside the useContext() variable?

Auth.js

import React, { createContext, useContext, useEffect, useMemo, useState } from "react";
import * as AuthAPI from "../API/AuthAPI"
import Loading from "../Page/Loading"

const AuthContext = createContext()

export const AuthProvider = ({children}) => {

  const [user,setUser] = useState()
  const [loadingInitial,setLoadingInitial] = useState(true)

  useEffect(()=>{
    AuthAPI.getCurrentUser()
    .then((user)=>setUser(user))
    .catch((error)=>{console.log(error)})
    .finally(()=>setLoadingInitial(false))
  },[])

  const login = async (email,password) => {
    const user = await AuthAPI.login({email,password})
    setUser(user)
    return user
  }

  const register = async (firstname,lastname,email,password) => {
    const user = await AuthAPI.register({firstname,lastname,email,password})
    setUser(user)
    return user
  }

  const logout = async () => {
    const response = await AuthAPI.logout()
    setUser(undefined)
  }

  const value = useMemo(()=>({
      user,
      setUser,
      login,
      register,
      logout
  }),[user])

  return (
    <AuthContext.Provider value={value}>
      {loadingInitial ? <Loading/> : children}
    </AuthContext.Provider>
  )
}

export const useAuth = () => {
  return useContext(AuthContext)
}

Logout.js

import { useCallback, useEffect, useState } from "react";
import { Navigate, useLocation, useNavigate } from "react-router-dom";
import { useAuth } from "../Hooks/Auth";
import * as AuthAPI from "../API/AuthAPI"
import Loading from "./Loading";

function Logout() {
  const auth = useAuth()
  const location = useLocation()
  const navigate = useNavigate()

  const [isLoggedOut,setIsLoggedOut] = useState(false)

  const logout = useCallback(async () => {
    console.log("Logging out!")
    await AuthAPI.logout()
    auth.setUser((prevState)=>(undefined))
    setIsLoggedOut(true)
  },[auth]) // --> re-rendered because `auth` context in re-rendered when set `user` state.

  useEffect(()=>{
    logout()
  },[logout]) // --> this also to run again from `logout` callback is being re-rendered.

  if (!isLoggedOut) {
    return <Loading/>
  }

  return (
    <Navigate to="/login" replace/>
  )
}

export default Logout

Any help is appreciated.

Upvotes: 1

Views: 2181

Answers (3)

Drew Reese
Drew Reese

Reputation: 202686

There is no need for creating a memoized logout callback function if logout isn't used/passed as a callback function. Just apply the logging out logic in the useEffect hook.

Render the Loading component and issue the imperative redirect from the resolved Promise chain of the return AuthAPI.logout Promise.

Example:

import { useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { useAuth } from "../Hooks/Auth";
import * as AuthAPI from "../API/AuthAPI"
import Loading from "./Loading";

function Logout() {
  const auth = useAuth();
  const navigate = useNavigate();
    
  useEffect(() => {
    console.log("Logging out!");
    AuthAPI.logout()
      .then(() => auth.setUser(undefined))
      .finally(() => navigate("/login", { replace: true }));
  }, []);
    
  return <Loading />;
}
    
export default Logout;

Upvotes: 1

Kim Skovhus Andersen
Kim Skovhus Andersen

Reputation: 388

How about destructuring your auth context, since you are only using setUser inside useEffect?

const { setUser } = useAuth()

useEffect(() => {

....
}, [setUser])

Upvotes: 1

Stephen Melben Corral
Stephen Melben Corral

Reputation: 319

Can you try to replace your useEffect code into this: useEffect(logout, [])

Upvotes: 0

Related Questions