Home > front end >  Where is the right place to keep on app mounting fetching logic with complex React v6 routing?
Where is the right place to keep on app mounting fetching logic with complex React v6 routing?

Time:12-09

I have a protected route component which basically handles several tasks:

  • Authorize user with Auth0.
  • Checks store if it is empty then makes a request to backend API for user data.
  • Pass function which returns access token to axios interceptor so that all request have a Bearer token attached to its header.
  • Now I need to add more API requests for fetching data on app loading.

Is it okay to have all this logic in a protected route component?

ProtectedRoute.tsx

const ProtectedRoute = () => {
  const dispatch = useAppDispatch();
  const location = useLocation();
  const { isAuthenticated, isLoading, getAccessTokenSilently } = useAuth0();
  const user = useAppSelector((state) => state.user);

  useEffect(() => {
    const fetchUser = async () => {
      try {
        await dispatch(getUser()); // here will be more dispatches for fetching data from backend
      } catch (e) {}
    };

    if (isAuthenticated && !user) {
      fetchUser();
    }
  }, [dispatch, isAuthenticated, user]);

  useEffect(() => {
    const getAccessToken = async () => {
      try {
        const token = await getAccessTokenSilently();
        return Promise.resolve(token);
      } catch (e) {
        console.log('getAccessToken error:', e);
      }
    };
    httpService(getAccessToken);
  }, [getAccessTokenSilently]);

  if (isLoading || (!user && isAuthenticated)) {
    return <Loader areaLoader />;
  }

  if (isLoading) {
    return <Loader areaLoader />;
  }

  return isAuthenticated ? <Outlet /> : <Navigate to="/auth" state={{ from: location }} replace />;
};

In App.tsx I have a huge router and it seems to me that the only place where to put all this logic is protected route component or mightbe BaseLayout component:

const router = createBrowserRouter([
  {
    element: <AuthProvider />,
    children: [
      {
        element: <ProtectedRoute />,
        children: [
          {
            element: <BaseLayout />,
            children: [
              {
                index: true,
                element: <ProjectsPage />,
              },
              {
                path: PATHS.faces,
                element: <FacesPage />,
              },
              {
                path: PATHS.files,
                element: <FilesPage />,
                children: [
                  // some children
                ],
              },
              // some other routes
            ],
          },
        ],
      },
      {
        path: PATHS.auth,
        element: <AuthPage />,
      },
    ],
  },
]);

BaseLayout.tsx

const BaseLayout = () => {
  return (
    <Stack direction={'row'} gap={32}>
      <Sidebar />
      <Box>
        <Outlet />
      </Box>
    </Stack>
  );
};

I tried to move fetching data logic to App.tsx but then no data is fetched. I think because of this (isAuthenticated comes from Auth0 hook and it false when App.tsx is loaded):

if (isAuthenticated && !user) {
  dispatch(fetchUser());
  dispatch(fetchRawMedias());
}

Problem with React Router v6 loader

On page reload request to Auth0 is made for token by AuthProvider. Then token is attached to all header requests. The problem is that on page reload React Router loader fires before Auth0..it sends request without token in header. How to handle this?

CodePudding user response:

For situations like this I generally apply the same practices as with declaring variables, i.e. pushing declarations down as much as possible to limit their scope.

Examples

  • If only 1 function needs the variable, declare it locally in the function.
  • If several functions/methods in a object/class need the variable, declare is locally in the object/class.

Where you declare React state functions similarly depending on the scope of the state and what needs to access it.

  • If only 1 React component needs the state, declare it locally in the component.
  • If several sibling component need the state, declare it in the closest common ancestor component and pass down.
  • If there becomes a great distance between "producing" and "consuming" components, move the state into a React context.

The idea here is to push the declarations up only as much as necessary.

So where is the "Right Place"?

The "right" place to keep fetching logic and state is the place that limits its scope as much as possible that meets demand. This can change over the course of an application's life as use cases and features change.

If you have "global app state" that should be fetched and managed when the app mounts this sounds like it would/should be located fairly high up in your app's ReactTree.

Is it okay to have all this logic in a protected route component?

The answer to this question is subject, you could do this but I'd suggest creating a new provider/layout component specifically for the state management and data fetching, mostly out of wanting to keep good "separation of concerns" and single responsibility principle. In other words, I wouldn't recommend making the ProtectedRoute do more than just protect routes, or making BaseLayout do more than UI layout.

Example provider component and placements:

const AppProvider = ({ children }) => {
  ... app state and fetching logic

  return (
    <AppContext.Provider value={... app state and anything else to expose out ...}>
      {children || <Outlet />}
    </AppContext.Provider>
  );
};
const router = createBrowserRouter([
  <-- Place AppProvider if entirety of app needs to access the state/fetch/etc
  {
    element: <AuthProvider />,
    children: [
      <-- Place AppProvider if dependency on authentication
      {
        element: <ProtectedRoute />,
        children: [
          <-- Place AppProvider if only protected routes need access
          {
            element: <BaseLayout />,
            children: [....],
          },
        ],
      },
      {
        path: PATHS.auth,
        <-- Place AppProvider if only non-auth routes need access
        element: <AuthPage />,
      },
    ],
  },
]);
  • Related