React Authentication Context being null initially
Asked Answered
B

1

6

I'm using React contexts in order to hold my authentication state for my application. Currently, I'm having an issue where whenever I try and hit /groups/:id, it always redirects me to /login first and then to /UserDash. This is happening because the context of my AuthProvider isn't updating fast enough, and my Private Route utilized the AuthContext to decide whether to redirect or not.

<AuthProvider>
    <Router>
        <Switch>
            <LoggedRoute exact path = "/" component = {Home}/>
            <Route exact path = "/login" component = {Login}/>
            <PrivateRoute exact path = "/groups/:id" component = {GroupDash}/>
            <PrivateRoute exact path = "/UserDash" component = {UserDash}/>
        </Switch>
    </Router>
</AuthProvider>

In another file:

export const AuthContext = React.createContext();

export const AuthProvider = ({children}) => {
  const [currentUser, setCurrentUser] = useState(null);
  useEffect(() => {
      firebaseApp.auth().onAuthStateChanged(setCurrentUser);
  },[]);
    return (
        <AuthContext.Provider
            value = {{currentUser}}
        >
            {children}
        </AuthContext.Provider>
    );

My private route:

const PrivateRoute = ({component: RouteComponent, ...rest}) => {
    const {currentUser} = useContext((context) => AuthContext);
    return (
        <Route
            {...rest}
            render={routeProps => (currentUser) ? (<RouteComponent{...routeProps}/>) : (<Redirect to={"/login"}/>)}
        />
    )
};

And my login page

 const {currentUser} = useContext(AuthContext);
    if (currentUser) {
        return <Redirect to = "/UserDash" />
    }
    return (
        <button onClick={(e) => {googleLogin(history)}}>Log In</button>
    );

Is there any way to force the context to load before the private route redirects the user to the login page? I've tried using firebase.auth().currentUser inside my PrivateRoute instead, but that also fails and I still get redirected to "/login", before getting pushed to "/UserDash".

Badman answered 29/5, 2020 at 4:54 Comment(0)
E
10

Since useEffect runs after a render and the value being fetched in useEffect is from a async call what can do is to actually maintain a loading state till data is available like

export const AuthContext = React.createContext();

export const AuthProvider = ({children}) => {
  const [currentUser, setCurrentUser] = useState(null);
  const [isLoading, setIsLoading] = useState(true);
  useEffect(() => {
      firebaseApp.auth().onAuthStateChanged((user) => {
          setCurrentUser(); setIsLoading(false)
      });
  },[]);
    return (
        <AuthContext.Provider
            value = {{currentUser, isLoading}}
        >
            {children}
        </AuthContext.Provider>
    );
  }

Then in privateRoute

const PrivateRoute = ({component: RouteComponent, ...rest}) => {
    const {currentUser, isLoading} = useContext((context) => AuthContext);
    if(isLoading) return <div>Loading...</div>
    return (
        <Route
            {...rest}
            render={routeProps => (currentUser) ? (<RouteComponent{...routeProps}/>) : (<Redirect to={"/login"}/>)}
        />
    )
};
Ethbin answered 29/5, 2020 at 5:5 Comment(2)
Is there a way to run the async call before the render occurs so I can reduce loading time?Badman
Even if you run it initially, since it's an async call there is no guarantee it will resolve before you use it. Anyways useEffect is the correct place to write this. In future react-suspense might lead to a change in thisEthbin

© 2022 - 2024 — McMap. All rights reserved.