Problem in Redirecting Programmatically to a Route in React Router V6

Problem in redirecting programmatically to a route in react router v6

Issue

TypeError: Cannot read properties of undefined (reading 'push')

This is cause by you attempting to navigate from a navigate prop that doesn't exist, it's undefined.

this.props.navigate.push("/");

The useNavigate hook is only compatible with function components, so of you want/need to use navigate with a class component you must either convert AddContacts to a function component, or roll your own custom withRouter Higher Order Component to inject the "route props" like the withRouter HOC from react-router-dom v5.x did.

Solution

I won't cover converting a class component to function component. Here's an example custom withRouter HOC:

const withRouter = WrappedComponent => props => {
const navigate = useNavigate();
// etc... other react-router-dom v6 hooks

return (
<WrappedComponent
{...props}
navigate={navigate}
// etc...
/>
);
};

And decorate the AddContacts component with the new HOC.

export default withRouter(AddContacts);

This will now pass a navigate prop (and any others you set up) to the decorated components and this.navigate will now be defined.

Additionally, the navigation API changed from v5 to v6, it's no longer the direct history object being used. navigate is a function instead of an object. To use you invoke the function and pass 1 or 2 arguments, the first is the target path, the second is an optional "options" object with replace and/or state key/values.

interface NavigateFunction {
(
to: To,
options?: { replace?: boolean; state?: State }
): void;
(delta: number): void;
}

To navigate now as follows:

this.props.navigate("/");

React router v6 programmatically redirect

When you are on a given route:

<Route path="customerOrders/:id" element={<CustomerOrderForm />} />

and navigating to the same route already rendering the mounted component then the component needs to "listen" for changes to the route, in this case, specifically the id route match param that is updated. Use an useEffect hook with a dependency on the id route match param to rerun any logic depending on it.

import { useNavigate, useParams } from "react-router-dom";

...

const CustomerOrderForm = () => {
const navigate = useNavigate();
const { id } = useParams();

useEffect(() => {
// rerun logic depending on id value
}, [id]);

const save = async () => {
//
// logic to persist data goes here...
//

navigate(`/customerOrders/${customerOrderId}`);
};

...

};

How can I redirect in React Router v6?

I think you should use the no match route approach.

Check this in the documentation: Adding a "No Match" Route

import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom';

<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/lab" element={<Lab />} />
<Route
path="*"
element={<Navigate to="/" replace />}
/>
</Routes>
</BrowserRouter>

To keep the history clean, you should set replace prop. This will avoid extra redirects after the user click back.

Redirection in React Router Dom v6

Use the Navigate component to redirect. The conditional rendering logic still needs to be applied and components rendered out on the Route component's element prop.

Example:

<Route
path="/login"
element={user ? <Navigate to="/" replace /> : <LoginStandard />}
/>

It is often considered better practice to abstract this into a custom route protection component that conditionally renders an Outlet for nested routes or the Navigate component.

Example:

import { Navigate, Outlet } from 'react-router-dom';

const AnonymousRoute = ({ user }) => user
? <Navigate to="/" replace />
: <Outlet />;

...

<Route element={<AnonymousRoute user={user} />}>
<Route path="/login" element={<LoginStandard />} />
... other anonymous routes ...
</Route>
... other routes

Navigate on React router dom v6 loop problem

From what I could see you had an issue with absolute linking. Converting to relative linking seems to resolve looping issue. Either approach should work, but without digging deeper into your configs here's what worked for me.

views/index.js - append the wildcard * to the path so nested routes can be matched and rendered.

export const Views = (props) => {
const { token } = props;
return (
<>
<Routes>
<Route path={`${AUTH_PREFIX_PATH}/*`} element={<AuthLayout />} />
<Route
path="/*"
element={
<RouteInterceptor isAuthenticated={token} element={<AppLayout />} />
}
></Route>
</Routes>
</>
);
};

views/auth-views/index.js - Use relative routing, i.e. remove the AUTH_PREFIX_PATH prefixing.

const Login = React.lazy(() => import("./login"));

export const AppViews = () => {
return (
<Suspense fallback={<p>Loading...</p>}>
<Routes>
<Route path={"login"} element={<Login />} />
<Route path="/*" element={<Navigate to="login" replace />} />
</Routes>
</Suspense>
);
};

Forked codesandbox from your github repo

Edit navigate-on-react-router-dom-v6-loop-problem

I have a problem with React Router Dom V6 and useState

react-router-dom is optimized to not unnecessarily unmount and remount components.

<Route path='user/:id' element={<UserForm />} />
<Route path='user' element={<UserForm />} />

When switching between "/user" and "/user/:id" the UserForm component will only get rerendered. The same is true if you navigate from one "/user/:id" path to a different "/user/:id" path. It's clear that UserForm has a dependency on this id route path parameter. You've a couple options for handling this.

  1. Use a useEffect hook with a dependency on the id route param and rerun/reset any state.

    const UserForm = () => {
    const navigate = useNavigate();
    const { id } = useParams<FormParams>();
    // const [searchParams, setSearchParams] = useSearchParams();
    const [loading, setLoading] = useState<boolean>(true);
    const [message, setMessage] = useState<MessageProps>();
    const [data, setData] = useState<UserModel>(newData);

    useEffect(() => {
    // reset state or re-issue side-effect based on id param
    }, [id]);
    ...
    }
  2. Add a React key to the UserForm component in the route so React will unmount/mount a new instance when switching between "/user" and "/user/:id". Note however that you'd still need a useEffect hook to rerun/reset logic/state when staying on the same route and only the id changes.

    <Route path='user/:id' element={<UserForm key="root" />} />
    <Route path='user' element={<UserForm key="id" />} />

Navigate using react-router-dom v6 after performing user action

In react-router-dom@6 the way to issue imperative navigation actions is to use the navigate function returned from the useNavigate hook. The code you've shared in the snippet is from a class component though, so you'll need to create a Higher Order Component to use the useNavigate hook and inject the navigate function as a prop.

Example:

import { useNavigate } from 'react-router-dom';

const withNavigate = Component => props => {
const navigate = useNavigate();
return <Component {...props} navigate={navigate} />;
};

Decorate the component in your snippet with this withNavigate HOC.

export withNavigate(MyComponent);

Access the navigate function from props.

render(){
const { navigate } = this.props;

return (
<div>
<Routes>
<Route
path="/"
element={(
<div>
<Title title={'Arijit - Photowall'}/>
<Photowall posts={this.state.posts} onRemovePhoto={this.removePhoto} />
</div>
)}
/>
<Route
path="/addPhotos"
element={(
<AddPhoto
onAddPhoto={(addedPost) => {
this.addPhoto(addedPost);
navigate("/");
}}
/>
)}
/>
</Routes>
</div>
);
}

Using Typescript

interface WithRouter {
location: ReturnType<typeof useLocation>;
navigate: ReturnType<typeof useNavigate>;
params: ReturnType<typeof useParams>;
}

const withRouter = <P extends {}>(Component: React.ComponentType<P>) => (
props: Omit<P, keyof WithRouter>
) => {
const location = useLocation();
const navigate = useNavigate();
const params = useParams();

return <Component {...(props as P)} {...{ location, navigate, params }} />;
};

Example Usage:

interface MyComponentProps {
foo: string;
}

type MyComponentPropsWithRouter = MyComponentProps & WithRouter

class MyComponent extends React.Component<MyComponentPropsWithRouter> {
render() {
const { foo, navigate, location, params } = this.props;
const { bar } = params as { bar?: string };

return (
<>
<h1>MyComponent: {location.pathname}</h1>
<h2>Foo prop: {foo}</h2>
<h2>Param?: {bar}</h2>
<button type="button" onClick={() => navigate("/test")}>
Navigate
</button>
</>
);
}
}

const MyDecoratedComponent = withRouter(MyComponent);

React Router 6 doesn't redirect to page

You should be using the Navigate component since you are in JSX, the useNavigate hook is for when you are outside, like so :

import {Navigate} from "react-router-dom" // what to use inside JSX
export function PrivateRoute({ children, ...rest }) {
const { props } = rest;

const navigate = useNavigate(); // that is for when you are outside of JSX
navigate("/someRoute"); // how you would redirect when you are outside of JSX

// inside JSX, you would use the Navigate component like below.
return (
<Routes>
<Route
{...rest}
render={() => (props === 'admin' ? children : <Navigate to ={url}/>)}
/>
</Routes>
);
}


Related Topics



Leave a reply



Submit