0

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:

  1. useEffect runs and refreshAccessToken() is fired on the first app load.
  2. React cleans Up useEffect by running clean up function.
  3. React sets up useEffect again; this time not calling refreshAccessToken().

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.

hane Smitter
  • 516
  • 3
  • 11
  • @JBallin but then the dispatch will be called twice – Eldar B. Jan 28 '23 at 19:25
  • I doubt a race condition exists, enlighten me if I'm wrong but the clean-up function can be called only when the component is unmounted, in which case the state should not be updated. – Eldar B. Jan 28 '23 at 19:28
  • Does this answer your question? [My React Component is rendering twice because of Strict Mode](https://stackoverflow.com/questions/61254372/my-react-component-is-rendering-twice-because-of-strict-mode) – JBallin Jan 28 '23 at 19:31
  • Related: https://github.com/facebook/react/issues/24502#issuecomment-1157699844 – JBallin Jan 28 '23 at 19:33
  • @EldarB. Yes the clean Up function is called when a component is unmounted. The problem is that when this component was first mounted, `refreshAccessToken()` network request is fired. When react is doing its round of unmounting the component, the network requst is done and comes back possibly finding the component not ready. And it leaves everything from that point – hane Smitter Jan 28 '23 at 20:24
  • @JBallin No it doesn't answer my question because I am well aware "double invoking" is because of enabling Strict mode. My problem is how to overcome the race condition or if there is actually no race condition in the first place – hane Smitter Jan 28 '23 at 20:29

0 Answers0