React Router V6 Navigate Outside of Components

react router v6 navigate outside of components

Well, it turns out you can duplicate the behavior if you implement a custom router that instantiates the history state in the same manner as RRDv6 routers.

Examine the BrowserRouter implementation for example:

export function BrowserRouter({
basename,
children,
window
}: BrowserRouterProps) {
let historyRef = React.useRef<BrowserHistory>();
if (historyRef.current == null) {
historyRef.current = createBrowserHistory({ window });
}

let history = historyRef.current;
let [state, setState] = React.useState({
action: history.action,
location: history.location
});

React.useLayoutEffect(() => history.listen(setState), [history]);

return (
<Router
basename={basename}
children={children}
location={state.location}
navigationType={state.action}
navigator={history}
/>
);
}

Create a CustomRouter that consumes a custom history object and manages the state:

const CustomRouter = ({ history, ...props }) => {
const [state, setState] = useState({
action: history.action,
location: history.location
});

useLayoutEffect(() => history.listen(setState), [history]);

return (
<Router
{...props}
location={state.location}
navigationType={state.action}
navigator={history}
/>
);
};

This effectively proxies the custom history object into the Router and manages the navigation state.

From here you swap in the CustomRouter with custom history object for the existing Router imported from react-router-dom.

export default function App() {
return (
<CustomRouter history={history}>
<div className="App">
<Routes>
<Route path="/" element={<Home />} />
<Route path="/profile" element={<Profile />} />
</Routes>
</div>
</CustomRouter>
);
}

Fork of your codesandbox:

Edit react-router-v6-navigate-outside-of-components

Update

react-router-dom@6 now also surfaces a history router. Unfortunately this has been removed since RRDv6.4.

HistoryRouter

<unstable_HistoryRouter> takes an instance of the history library as
prop. This allows you to use that instance in non-React contexts or as
a global variable.

import { unstable_HistoryRouter as HistoryRouter } from "react-router-dom";
import { createBrowserHistory } from "history";

const history = createBrowserHistory({ window });

ReactDOM.render(
<HistoryRouter history={history}>
{/* The rest of your app goes here */}
</HistoryRouter>,
root
);

There is this note:

This API is currently prefixed as unstable_ because you may
unintentionally add two versions of the history library to your app,
the one you have added to your package.json and whatever version React
Router uses internally. If it is allowed by your tooling, it's
recommended to not add history as a direct dependency and instead
rely on the nested dependency from the react-router package. Once we
have a mechanism to detect mis-matched versions, this API will remove
its unstable_ prefix.

How to navigate outside of react component using react router 6

I ended up with following solutions:

At first, react-router-dom 6 has navigate and not history. It is better to use navigate for the navigation trough the routes:

I create and fix my History object, so that it can continue work with 'push' and that I don't need big rework:

const History = {
navigate: null,
push: (page, ...rest) => History.navigate(page, ...rest),
};

export default History;

Then I made my own Component that set the navigation:

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

const NavigateSetter = () => {
History.navigate = useNavigate();

return null;
};

Then while defining the default router, I place as a child the setter:

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

<BrowserRouter>
<NavigateSetter />
<App />
</BrowserRouter>

After that you can use everywhere History.push or History.navigate with the default react-router-dom's navigate API.

React Router v6.0.0-alpha.5 history prop removal - navigating outside of react context

Thanks for the question, we know this is going to come up a lot. This is a common question we've gotten for years. Please be patient with us as we begin documenting all of these kinds of things, there's a lot to do!

Short answer: Typically people use thunks for async work that leads to wanting to navigate somewhere else (after a login, after a record is created, etc.). When your thunk is successful, change the state to something like "success" or "redirect" and then useEffect + navigate:

export function AuthForm() {
const auth = useAppSelector(selectAuth);
const dispatch = useAppDispatch();
const navigate = useNavigate();

useEffect(() => {
if (auth.status === "success") {
navigate("/dashboard", { replace: true });
}
}, [auth.status, navigate]);

return (
<div>
<button
disabled={auth.status === "loading"}
onClick={() => dispatch(login())}
>
{auth.status === "idle"
? "Sign in"
: auth.status === "loading"
? "Signing in..."
: null}
</button>
</div>
);
}

A bit more explanation:

For example, how would we keep our history in sync in order to navigate from inside redux

We've always considered this bad practice and reluctantly provided the history objects as first-class API to stop having philosophical conversations about app state and the URL .

But today things are a bit different. The conversation isn't just philosophical anymore but has some concrete bugs when mixed with React's recent async rendering, streaming, and suspense features. To protect react router apps from synchronization bugs with the URL (that developers can't do anything about), v6 no longer exposes the history object.

Hopefully this explanation will help:

Changing the URL is a side-effect, not state. Thunks are used to perform side-effects that eventually figure out some state for the state container but aren't used for the side-effect in and of itself (at least that's my understanding).

For example, you may want to change the focus on the page after your redux state changes. You probably wouldn't try to synchronize and control the document's focus at all times through redux actions and state. Scroll position is the same. Ultimately the user is in control of these things: they can hit the tab key, click on something, or scroll around. Your app doesn't try to own or synchronize that state, it just changes it from time to time in response to actions and state that you do control.

The URL is the same. Users can type whatever they want into the address bar, click back, forward, or even click and hold the back button to go 3 entries back! It's the same kind of state as focus and scroll positions: owned by the user. The container can't ever truly own the URL state because it can't control the actions surrounding it. Mix in React's new and upcoming features and you're gonna lose that game.

In a nutshell: Change redux state > useEffect in the UI > navigate. Hope that helps!

component not rendering when using react router dom v6

The issues I see are the Routes components are rendering routes with paths without a trailing "*" wildcard character to allow descendent route path matching. From what I can see you want the user to navigate to "/account-selection" and render the ViewAccountSelection component which under the correct conditions will navigate the user to "/demoUser", i.e. the "/:account" route renders ViewDashboard component. The ViewDashboard then navigates the user to "/demoUser/reports/dashboard" to render the LayoutReport component.

Add the missing wildcard path characters to the routes that need it.

App

<Routes>
{/* Public Facing */}
<Route path="*" element={<ViewLogin />} />
<Route
path="/forgotten-password/*"
element={<ViewForgottenPassword />}
/>
<Route path="/user/signup" element={<ViewUserSignup />} />

{/* User Protected */}
<Route
path="/account-selection/"
element={
<ViewAccountSelection
account={account}
user={{ isLoggedIn: true }}
/>
}
/>
<Route path="/sign-out" element={<ViewSignOut />} />
<Route path="/user" element={<ViewUserConfiguration />} />
<Route path="/404" element={<ViewError404 />} />
<Route path="/:account/*" element={<ViewDashboard />} /> // <-- here

{/* Error Pages */}
<Route element={<Navigate to="/404" />} />
</Routes>

ViewDashboard

const ViewDashboard = () => (
<div className="dashboard-container">
...
<div className="dashboard-view__content">
<Routes>
<Route
index // <-- index route, i.e. "/:account"
element={<Navigate to="reports/dashboard" />} // <-- navigate relative
/>
<Route
path="/account-management/*"
element={<LayoutAccountManagement />}
/>
<Route path="/reports/*" element={<LayoutReport />} /> // <-- add trailing *
<Route element={<Navigate to="/404" />} />
</Routes>
</div>
...
</div>
</div>
);

Edit component-not-rendering-when-using-react-router-dom-v6

how to navigate from one page to other page using react-router-dom-v6 in class-based component?

You can use Navigate component for navigation in class components.

import * as React from "react";
import { Navigate } from "react-router-dom";

class LoginForm extends React.Component {
state = { user: null, error: null };

async handleSubmit(event) {
event.preventDefault();
try {
let user = await login(event.target);
this.setState({ user });
} catch (error) {
this.setState({ error });
}
}

render() {
let { user, error } = this.state;
return (
<div>
{error && <p>{error.message}</p>}
{user && (
<Navigate to="/dashboard" replace={true} />
)}
<form onSubmit={event => this.handleSubmit(event)}>
<input type="text" name="username" />
<input type="password" name="password" />
</form>
</div>
);
}
}


Related Topics



Leave a reply



Submit