2

I have a very famous problem which I think everybody has at least once tackled. I want to persist the user logged-in in my react app even if the page is refreshed. I have read all the related questions and articles about how this can be done but unfortunately I got nowhere. In my ProtectedComponent I have the following code:

const ProtectedRoute = ({ notLoggedInPath }) => {
  
  const isLoggedIn = useSelector((state) => state.auth.isLoggedIn);

  return (
    <Fragment>
      {isLoggedIn && <RecorderPage />}
      {!isLoggedIn && <Redirect to={notLoggedInPath} />}
    </Fragment>
  );
};

As you can see I have implemented a variable so-called isLoggedIn in my initialState of auth reducer and if this variable is true the protected route will be accessible otherwise not.

In my Sign In component I store the received token from the api to the localStorage. This is completely done. But my main question is that when the user signs in and then navigates to a protected route, by refreshing the page my initialState(isLoggedIn) goes away and changes to false, making the user logged out. This is completely natural in the culture of ReactJS. But how can I implement a way in which when my app is being launched, it looks for authenticating the previously received token and if it has not expired yet it navigates the user to the page on which the app is refreshed. This is done by a gigantic number of websites so I know it can be done. But I don't know how?

My sign in component:

const SignInForm = () => {
  const dispatch = useDispatch();
  const history = useHistory();

  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');

  const handleSubmit = () => {
    axios({
      method: 'post',
      url: 'someurl/api/token/',
      data: {
        username: username,
        password: password,
      },
    })
      .then((res) => {
        const token = res.data.access;
        localStorage.setItem('token', token);    
        dispatch(updateUserInStore(token, username));
        dispatch(makeUserLoggedIn());
        history.push('/recorder');
      })
      .catch((err) => {
        console.log(err);
        console.log(err.response);
      });
  };
return (
<some jsx/>
)

It is worth mentioning that I have also used the hook useEffect in my mother-level component App. I mean when my app launches the callback in useEffect checks if the localStorage token can be authorised or not but because of async nature of js and also axios request this is not a solution since the initialState is set before the response of this axios request is received.

My App component:

const App = () => {
  const dispatch = useDispatch();
  const history = useHistory();
  const tokenLocalStored = localStorage.getItem('token');

  const checkIfUserCanBeLoggedIn = () => {
    const token = localStorage.getItem('token');
    axios({
      method: 'get',
      url: 'some-url/api/sentence',
      headers: {
        Authorization: `Bearer ${token}`,
      },
    })
      .then((res) => {
        dispatch(makeUserLoggedIn());
      })
      .catch((err) => {
        console.log(err);
        console.log(err.response);
        return false;
      });
  };

  useEffect(() => {
    checkIfUserCanBeLoggedIn();
  });

  return (
     <Some JSX>
)
RZAMarefat
  • 133
  • 8
  • `by refreshing the page my initialState(isLoggedIn) goes away and changes to false, making the user logged out. This is completely natural in the culture of ReactJS` this is not the natural behaviour, your isLoggedIn variable and state should be built by looking on the localStorage in the first place, not changed along when some components mount. May I suggest a storage handler that initialises by building the user's session? – savageGoat Jun 05 '21 at 07:26
  • I have initialised the store in a way which reads the token from the localStorage. This is of course easy. But how can I implement a function which first verifies the this token to make isLoggedIn variable true? – RZAMarefat Jun 05 '21 at 07:32
  • You call the server with that token to retrieve that session and update your state by its response. I think what you are looking for is a transitive state from "checking" to "logged/loggedout", the router should hold the load of the route until that first check is done, with a "loading app" route for example – savageGoat Jun 05 '21 at 07:45

1 Answers1

1

When the page reloads execute the async logic in useEffect hook on App.js. Use a state like authChecking to show a loader while the auth state is being checked.

const [authChecking, updateAuthChecking] = useState(true)

useEffect(() => {
  asyncFunc
  .then(updateUserObjToStore)
  .finally(() => updateAuthChecking(false))
}, [])

I have also written a article on medium about this feel free to check it out if you have any doubts. https://medium.com/@jmathew1706/configuring-protected-routes-in-react-using-react-router-af3958bfeffd

Bonus tip: Try to keep this logic in a custom hook will ensure proper separation of concerns.

  • Thank you for your answer. I agree with your solution and I have already implemented this. I updated my question and I included my main App.js component. when my app re-launches firstly it looks for authenticating the previously received token.This is being done correctly. I mean when we have the token the isLoggedIn variable sets to be true. But how can I redirect the user to the page on which the app has been refreshed. I have used and windows.location.href but these make an infinite loop in which the app relaunches everytime. – RZAMarefat Jun 05 '21 at 07:51
  • Dont load the router component unless the authChecking is complete. – Mathew Joseph Jun 05 '21 at 08:01
  • The problem right now is the redirection is happening before you have done the authChecking. So lets say you went to /home which is a protected route since the isLoggedIn is false it will keep on redirecting to the login page. So to prevent that only render the router component when the authChecking is complete. Put if (authChecking) return 'Loading...' before – Mathew Joseph Jun 05 '21 at 08:19