I have written the following code in react development that runs without an error. But it appears buggy in a peculiar way. A race condition when running useEffect looks possible:
// Variables declared top-level outside the component
let initialized = false;
let componentIsMounted = true;
const PrivateRouteGuard = () => {
  const token = useSelector((state) => state?.auth?.token);
  const userIsAuthenticated = Boolean(token) || sessionStorage.getItem("isAuthenticated");
  const [displayPage, setDisplayPage] = useState(false);
  const dispatch = useDispatch();
  useEffect(() => {
    componentIsMounted = true;
    if (!initialized) {
      initialized = true;
      console.log("✅ Only runs once per app load");
      if (!token && userIsAuthenticated) {
        refreshAccessToken()
          .then((response) => {
            // Add the new Access token to redux store
            dispatch(addAuthToken({ token: response?.data?.accessToken }));
            return getUserProfile(); // Get authenticated user using token in redux store
          })
          .then((response) => {
            const user = response.data?.user;
            // Add authenticated user to redux store
            dispatch(addAuthUser({ user }));
          })
          .finally(() => {
            componentIsMounted && setDisplayPage(true);
          });
      } else {
        setDisplayPage(true);
      }
    }
    return () => componentIsMounted = false;
  }, []);
  if (!displayPage) {
    return "LOADING..."; // Display loading indicator here
  }
  if (!userIsAuthenticated) {
    return (
      <Navigate to="/login" />
    );
  }
  return <Outlet />;
};
export default PrivateRouteGuard;
With the code above, a user with an authentication token is considered authenticated. I have backed up authentication status in browser local storage since a page refresh, wipes out redux store.
Actually this is a react component that protects a route from an unauthenticated user.
With how react strict mode works in development, useEffect is called twice. However calling twice to get a new Access token (refreshAccessToke()), will cause a token to be invalidated(how the backend works). So I tried to mitigate useEffect from calling refreshAccessToken() twice by using a top-level initialized variable.
While this works, the code makes me cringe abit.
Why?
On page refresh:
- useEffectruns and- refreshAccessToken()is fired on the first app load.
- React cleans Up useEffectby running clean up function.
- React sets up useEffectagain; this time not callingrefreshAccessToken().
The problem
However I am doubtful of a race condition occuring at step 2. Say, while React is running clean-up function, refreshAccessToken() has resolved and is ready with its data. But when clean up function is run,the variable componentIsMounted is false. Therefore this piece of code:
.finally(() => {
  componentIsMounted && setDisplayPage(true);
})
Means the state will never be set.
React then heads to step 3, where everything is setup again. But refreshAccessToken() finished running(while at step 2) and it already decided not to set displayPage state i.e: componentIsMounted && setDisplayPage(true);. So there is a possibility to get stuck seeing a loading indicator as with this piece:
if (!displayPage) {
  return "LOADING..."; // Display loading indicator here
}
I might be missing a concept about the internals of react. Nevertheless code shared above looks like one that sometimes work and sometimes fail. I hope to get my doubts cleared.
