Search code examples
reactjsreact-router-dom

React Router v6 redirect with Params and SearchParams


I am currently trying to set up my routing for a new project. What I am attempting to implement is if a user access /home/user/settings and they are not logged in, it will redirect them to /login?redirectTo=/home/user/settings.

Or if they go to /home/device/claim?deviceid=123456789 and they are not logged in they will get redirected to /login?redirectTo=/home/device/claim?deviceid=123456789.

I currently have it working but I feel it cant be the best way of doing this. An issues I ran into when coming up with this idea were I don't know how many levels the URL will have.

My current code:

          <Route>
            <Route
              path=":path1/:path2/:path3/:path4"
              element={<RedirectAfterAuth />}
            />
            <Route
              path=":path1/:path2/:path3"
              element={<RedirectAfterAuth />}
            />
            <Route path=":path1/:path2" element={<RedirectAfterAuth />} />
            <Route path=":path1" element={<RedirectAfterAuth />} />
            <Route path="*" element={<Navigate to="/login" />} />
            <Route path="login" element={<Login />} />
            <Route path="/" element={<LandingPage />} />
          </Route>
export default function RedirectAfterAuth() {
  let { path1, path2, path3, path4 } = useParams();
  let [searchParams, setSearchParams] = useSearchParams();
  return (
    <Navigate
      to={`/login?redirectTo=${path1}${path2 ? "/" + path2 : ""}${
        path3 ? "/" + path3 : ""
      }${path4 ? "/" + path4 : ""}${searchParams ? "?" + searchParams : ""}`}
    />
  );
}

I was wondering if there was a way to not have to put loads of different paths and account for all possibilities by just have a single param/searchparam.

Thank you for your time


Solution

  • You don't need to capture all the path segments when redirecting to an authentication route, you can simply capture the current location object and forward that as a "referrer" value for the login component to redirect back.

    Create an authentication layout route component that will be used to protect routes, capture the location, and redirect to the auth route.

    Example:

    import { Navigate, Outlet, useLocation } from 'react-router-dom';
    
    const AuthLayout = () => {
      const location = useLocation(); // <-- current location being accessed
      const { isAuthenticated } = /* auth state from local state/context/redux/etc */
    
      return isAuthenticated
        ? <Outlet />
        : (
          <Navigate
            to="/login"
            replace                    // <-- redirect
            state={{ from: location }} // <-- forward location
          />
        );
    };
    
    export default AuthLayout;
    

    Import and wrap the routes you want to protect with the authentication layout route.

    Example:

    import AuthLayout from '../path/to/AuthLayout';
    
    ...
    
    <Router>
      {/* Unprotected routes */}
      <Route path="login" element={<Login />} />
      <Route path="/" element={<LandingPage />} />
      ...
      <Route element={<AuthLayout />}>
        {/* Protected routes */}
        <Route path="/profile" element={<Profile />} />
        ...
      </Route>
    </Router>
    

    To redirect back to the original route being accessed, the login function accesses the pathname and search (and any route state) that was forwarded when getting redirected.

    Example:

    import { useLocation, useNavigate } from 'react-router-dom';
    
    ...
    
    const navigate = useNavigate();
    const location = useLocation();
    
    ...
    
    const loginHandler = async e => {
      e.preventDefault();
    
      try {
        await /* call authentication endpoint/set state/etc */
    
        // auth success
        const { from } = location.state || {};
        const { pathname, search, state } = from || {};
        navigate(
          (pathname ?? "/") + (search ?? ""),
          {
            replace: true,
            state
          }
        );
      } catch (error) {
      }
    };
    
    ...