0

I am trying to call a hook in my App.js file using a hook. All the logic works, but I'm getting a warning error in console "React Hook useEffect has a missing dependency: 'initAuth'." I know there are a lot of issues on this ,but I'm not sure if this is related to the hook or the complexity I am doing at the high level of my app. The intent is to use the "initAuth" function to look at my local storage and get my user token, name, etc... I only want this on a hard page refresh, so it should only run once.

If I add initAuth (the function) or the authObject ( object), I get infinite loops.

function App() {

  const { initAuth, authObject } = useAuth();

  useEffect(() => {
    initAuth();
  }, []);
// this throws the warning.  I need to add dependency
}
1
  • is initAuth wrapped in useCallback? Commented Oct 26, 2021 at 14:21

3 Answers 3

2

If you only want this effect to run once when the component first loads, then you can ignore the warning. You can disable the warning so it doesn't keep showing up in the console with the following:

useEffect(() => {
  initAuth();

// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
Sign up to request clarification or add additional context in comments.

Comments

0

This is how I would implement this hook :

function App() {

  const { initialized, authObject, initAuth } = useAuth();

  useEffect(() => {
    if (!initialized) {
       initAuth();
    }
  }, [initialized, initAuth]);
  
  ...
}

Or, better yet :

function App() {

  const authObject = useAuth();   // let useAuth initialize itself

 
  ...
}

Typically, useAuth seems to be a multi-purpose hook, being used by various components, so it makes no sense to allow multiple components to call initAuth; the hook should only return the current state.

Preferably, you should implement that hook with a context

function App() {

  return (
    <AuthProvider>
      <AppContent />
    </AuthProvider>
  );
}

function AppContent() {
  const authObject = useAuth();

  ...

}

The contract, therefore, goes to the AuthProvider, and notifies every component using useAuth on state changes.


From OP's own answer, added some suggested improvements :

import React, { createContext, useContext, useState, useMemo } from "react";

const AuthContext = createContext({
  isLoggedIn:false /* :Boolean */,
  authObject:null  /* :Object */, 
  login: (
     username /* :String */,
     password /* :String */
  ) /* :Preomise<Boolean> */ => { 
     throw new Error('Provider missing');
  }
]);

const AuthContextProvider = ({ children }) => {
  // init state with function so we do not trigger a
  // refresh from useEffect. Use useEffect if the
  // initial state is asynchronous
  const [state, setState] = useState(() => {
    const authObject = localStorage.getItem("authObject");
    const isLoggedIn = !!authObject;
    
    return { isLoggedIn, authObject };
  });

  // avoid refresh if state does not change
  const contextValue = useMemo(() => ({
     ...state,   // isLoggedIn, authObject
     login: async (username, password) => {

        // implement auth protocol, here
        // do not expose setState directly in order to 
        // control what state is actually returned

        // setState({ isLoggedIn:..., authObject:... });

        // return true|false
     }
  }), [state]);

  return (
    <AuthContext.Provider value={ contextValue }>
      { children }
    </AuthContext.Provider>
  );
};

/**
 Usage: const { isLoggedIn, authObject, login } = useAuthContext();
 */
const useAuthContext = () => useContext(AuthContext);

export { useAuthContext, AuthContextProvider };

6 Comments

I do have a context, I just didn't show the remainder of the code. On a context, how would you initialize itself? Wouldn't that happen on all the other components where I called the hook? I call it a lot as I use this to check login in status and roles.
As long as your context value does not change, the hook (i.e. useContext) won't trigger a component update. Thare are optimizations that you can use to prevent unnecessary state changes, so it really does not matter if useAuth is being used 100 times. All and all, your AuthProvider will have a useEffect or something similar that will initialize it's own context value, and notifying the using components, if necessary. Components should be self-contained as much as possible, and only provide external properties to customize them.
Yanick, your comment was very helpful, thanks! I posted answer, but credit is yours!
Note that instead of export { AuthContext }, which will require your code to import useContext, you can export { useAuthContext, AuthContextProvider } and set const useAuthContext = () => useContext(AuthContext); so that your code has a seemless integration.
@a2ron44 please read the above comment, and the update on my answer. Cheers.
|
0

Thanks to Yanick's comment, this is how I initiated to provider to set my authorization. My login function uses an auth service for http call, but I use this context function to set the data properly.

import React, { useContext, useMemo, useState } from "react";
import http from "services/http";

const AuthContext = React.createContext({});

const AuthContextProvider = ({ children }) => {
  const [state, setState] = useState(() => {
    const authObject = JSON.parse(localStorage.getItem("authObject"));
    if (authObject) {
      //sets axios default auth header
      http.setJwt(authObject.token);
    }
    const isLoggedIn = !!authObject;
    return { isLoggedIn, authObject };
  });

  // avoid refresh if state does not change
  const contextValue = useMemo(
    () => ({
      ...state, // isLoggedIn, authObject
      login(auth) {
        localStorage.setItem("authObject", JSON.stringify(auth));
        http.setJwt(auth.token);
        setState({ authObject: auth, isLoggedIn: true });
        return true;
      },
      logout() {
        http.setJwt("");
        localStorage.removeItem("authObject");
        setState({ authObject: null, isLoggedIn: false });
      },
    }),
    [state]
  );

  return (
    <AuthContext.Provider value={contextValue}>{children}</AuthContext.Provider>
  );
};

const useAuthContext = () => useContext(AuthContext);

export { useAuthContext, AuthContextProvider };

And my App.js simply uses the ContextProvider, no need to run useEffect anymore on App.js.

      <AuthContextProvider>
        <ThemeProvider theme={darkState ? dark() : light()}>
          <CssBaseline>
            <BrowserRouter>
              
            //...app.js stuff    
               
            </BrowserRouter>
          </CssBaseline>
        </ThemeProvider>
      </AuthContextProvider>

In any component, I can now get access to isLoggedIn or authObject using a call like:

  const { isLoggedIn } = useAuthContext();

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.