HarryW
HarryW

Reputation: 55

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

Upvotes: 4

Views: 6561

Answers (1)

Drew Reese
Drew Reese

Reputation: 203587

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) {
  }
};

...

Upvotes: 6

Related Questions