Search code examples
javascriptreactjsreact-routerreact-context

Trouble with react private routes


I'm trying to create protected routes. I have a react context that returns true or false if the user is signed in or not.

The problem is that even if I'm authenticated I still can't visit the dashboard route (it redirects to login even if im signed in). I think this is because I send an api request to my server to check if the user is authenticated and in that time context value is undefined

I've tried to solve this to create by checking if the context was undefined before rendering the route but it still doesn't work.

App.js

const ctx = useContext(userContext);
  console.log(ctx.authed); // return true or false

  return (
    <div className='App'>
      <Router>
        <Switch>
          <Route path='/' exact component={HomePage}></Route>
          <Route exact path='/auth/register' component={Register}></Route>
          <Route exact path='/auth/login' component={Login}></Route>

          {!ctx.loading ? (
            <ProtectedRoute
              path='/dashboard'
              isAuth={ctx.authed}
              exact
              component={Dashboard}
            />
          ) : null}
        </Switch>
      </Router>
    </div>
  );

ProtectedRoute.js

function ProtectedRoute({ isAuth, component: Component, ...rest }) {
  return (
    <Route
      {...rest}
      render={(props) => {
        if (isAuth) {
          return <Component />;
        } else {
          return (
            <Redirect to={{ pathname: '/login', 
            state: { from: props.location } }} />
          );
        }
      }}
    />
  );
}

and my context looks like:

export const userContext = createContext({});
export default function Context(props) {
  const [authed, setAuthed] = useState(undefined);
  const [loading, setLoading] = useState(false);

  const fetchData = async () => {
    setLoading(true);
    await axios({
      method: 'GET',
      withCredentials: true,
      credentials: 'include',
      url: '/checkAuthentication',
    }).then((res) => {
      setAuthed(res.data.authenticated);
      setLoading(false);
    });
  };

  useEffect(() => {
    fetchData();
  }, []);

  return (
    <userContext.Provider value={{ loading, authed }}>
      {props.children}
    </userContext.Provider>
  );
}

I've been stuck on this for a while, I would appreciate any help. Thank you!


Solution

  • @Alyks solution helped me. Nothing was changed except the addition of the useEffect block.

      const ctx = useContext(myContext);
      const [isAuth, setIsAuth] = useState(ctx.authed);
    
      useEffect(() => {             //
        if (ctx.authed) {           //
          setIsAuth(ctx.authed);   <-- added this
        }                           //
      }, [ctx.authed]);             // 
    
      return (
        <div className='App'>
          <Router>
            <a href='/dashboard'>Dashboard</a>
            <a href='/auth/login'>Login</a>
            <Switch>
              <Route path='/' exact component={HomePage}></Route>
              <Route exact path='/auth/register' component={Register}></Route>
              <Route exact path='/auth/login' component={Login}></Route>
    
              {!ctx.loading ? (
                <ProtectedRoute
                  path='/dashboard'
                  isAuth={isAuth}
                  exact
                  component={Dashboard}
                />
              ) : null}
            </Switch>
          </Router>
        </div>
      );