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
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.
Use a
useEffect
hook with a dependency on theid
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]);
...
}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 auseEffect
hook to rerun/reset logic/state when staying on the same route and only theid
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
What's the Purpose of Prototype
Doesn't JavaScript Support Closures with Local Variables
Problem in Redirecting Programmatically to a Route in React Router V6
"Object Doesn't Support This Property or Method" Ie10/11
Find and Remove Objects in an Array Based on a Key Value in JavaScript
Adding 'Click' Event Listeners in Loop
How to Count Certain Elements in Array
Removeeventlistener on Anonymous Functions in JavaScript
Is There a JSON Equivalent of Xquery/Xpath
What's the Difference Between Window.Location= and Window.Location.Replace()
Set Additional Data to Highcharts Series
Load Jquery with JavaScript and Use Jquery
Differencebetween Bower and Npm
How to Extend Function with Es6 Classes