'React login redirect based on a state set by an API call

I have a react webapp that uses an API to validate a user (using a cookie). I would also like to implement route protection in the frontend.

Basically I have a state:

const [loggedIn, setLoggedIn] = useState(false);

then I use the useEffect hook to check whether this user is logged in:

async function loggedInOrNot() {
    var loggedIn = await validateUserAsync();
      setLoggedIn(loggedIn); 
    }
  }

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

I use the react router dom to redirect to "/login" page if the user failed the validation:

function ProtectedRoute({ children, ...restOfProps }) {
return (
  <Route {...restOfProps}>
    {loggedIn ? React.cloneElement(children) : <Redirect to="/login" />}
  </Route>
);}

the route is composed like this:

<ProtectedRoute exact path="/post/edit">
                    <EditPosts
                      inDarkMode={inDarkMode}
                      userName={userName && userName}
                    />
</ProtectedRoute>

If I don't refresh the page, it works well, but if I logged in, then I refresh my page, the initial render will cause the redirect, since the setState is asynchronous, and the loggedIn state (false by default) is not updated before the route switches to the log in page.

I have tried to use useRef hook together with the state, didn't manage to make it work. I checked online to use local storage, but it seems to be a work around and in this case there will be two sources of truth, local storage and the state.

so I would like to ask you guys what is the cleaner way to make this work.

Thanks in advance!



Solution 1:[1]

You might have to have a loading view or something to display while checking if logged in.

Perhaps loggedIn can be boolean | null:

function ProtectedRoute({ children, ...restOfProps }) {
  const [loggedIn, setLoggedIn] = useState(null);

  // some code omitted for brevity

  if (loggedIn === null) {
    return 'Loading...'
  }

  return (
    <Route {...restOfProps}>
      {loggedIn ? React.cloneElement(children) : <Redirect to="/login" />}
    </Route>
  );
}

Solution 2:[2]

I think you should also be maintaining a session to check whether the user is logged in or not and set state as per that. Else for every refresh your state will get reinitiated and also you'll endup making numerous api calls to validate user, that's not advised.

So, before you make the first api call to validate user, you should be checking whether a user session is available, use cookies to maintain session. If session is there then load the session status as your state else make the api call and load the state and initiate the user session with the api response.

Also, if you don't want to maintain session and wanted to make api calls. You can show loader until you finish your call and direct them to respected page. Please see below code to implement the same.

function ProtectedRoute({ children, ...restOfProps }) {

const [loggedIn, setLoggedIn] = useState(false);
const [isLoading, setLoading] = useState(true);

const loggedInOrNot = async () => {
    var loggedIn = await validateUserAsync();
      setLoggedIn(loggedIn);
      setLoading(false);
    };

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

return (
<Route>
     {isLoading ? (<SomeLoader />) : (<LoadRoute loggedIn={loggedIn} >{children}</LoadRoute>)}
</Route>);
}

function LoadRoute({ children, loggedIn }){
return (
  {loggedIn ? React.cloneElement(children) : <Redirect to="/login" />}
);
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 twharmon
Solution 2