Skip to content

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 bacause `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.

Answer

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

const { setUser } = useAuth()

useEffect(() => {

....
}, [setUser])