Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

react router v6 navigate outside of components

In react-router v5 i created history object like this:

import { createBrowserHistory } from "history";
export const history = createBrowserHistory();

And then passed it to the Router:

import { Router, Switch, Route, Link } from "react-router-dom";
<Router history={history}>
 ... my routes
</Router>

I did it for the opportunity to usage history outside of component:

   // store action
    logout() {
        this.user = null;
        history.push('/');
    }

This way I moved the logic to the store and the components were kept as clean as possible. But now, in react router v6 i cant do the same. I can still navigate using useNavigate() inside my component, but i cannot create a navigate to use its into my store. Is there any alternative?

like image 298
kofyohugna Avatar asked Nov 07 '21 11:11

kofyohugna


People also ask

How do I navigate in react router?

The react-router-dom package makes it simple to create new routes. To begin, you wrap the entire application with the <BrowserRouter> tag. We do this to gain access to the browser's history object. Then you define your router links, as well as the components that will be used for each route.

How do you use history outside react component?

We would obviously want some additional logic in there, but the point is that we can access the history object through props , even though no props were passed to Login , and use history. push to push a new entry onto the history stack.

How do I navigate to a specific path in react router?

React Router provides a few ways of accomplishing it. 1. Using this.props.history If you are trying to do it in the Route component (in short, it's a component which is definied in your routes configuration ) then you could take history object off of the props and then use history 's push method to navigate to the path you want.

Can I use React router for a single page application?

The answer is no. React Router – like the name implies – helps you route to/navigate to and render your new component in the index.html file. So as a single page application, when you navigate to a new component using React Router, the index.html will be rewritten with the component's logic.

Does react-router-Dom 6 have navigate and not history?

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:

How does react router work?

In React, the page contents are created from our components. So what React Router does is intercept the request being sent to the server and then injects the contents dynamically from the components we have created.


Video Answer


3 Answers

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.

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.

like image 164
Drew Reese Avatar answered Oct 27 '22 10:10

Drew Reese


TypeScript solution of accepted answer

history object:

import { createBrowserHistory } from "history";

const customHistory = createBrowserHistory();

export default customHistory;

BrowserRouterProps is a react-router type.

export interface BrowserRouterProps {
  basename?: string;
  children?: React.ReactNode;
  window?: Window;
}

CustomRouter:

import { useLayoutEffect, useState } from "react";
import { BrowserRouterProps, Router } from "react-router-dom";
import { BrowserHistory } from "history";
import customHistory from "./history";
interface Props extends BrowserRouterProps {
  history: BrowserHistory;
}
export const CustomRouter = ({ basename, history, children }: Props) => {
  const [state, setState] = useState({
    action: history.action,
    location: history.location,
  });
  useLayoutEffect(() => history.listen(setState), [history]);
  return (
    <Router
      navigator={customHistory}
      location={state.location}
      navigationType={state.action}
      children={children}
      basename={basename}
    />
  );
};  

use CustomRouter instead BrowserRouter

ReactDOM.render(
  <React.StrictMode>
    <CustomRouter history={customHistory}>
      <App />
    </CustomRouter>
  </React.StrictMode>,
  document.getElementById("root")
);
like image 23
Okan Karadag Avatar answered Oct 27 '22 09:10

Okan Karadag


Typescript solution with HistoryRouter from react-router-dom

Different from other answers, this solution uses the HistoryRouter imported from react-router-dom and using TypeScript.

1. Create a new file with your CustomRouter component. I'm placing it at "./components/CustomRouter.tsx" in this example.

import React, { FC, PropsWithChildren } from "react";
import { unstable_HistoryRouter as HistoryRouter } from "react-router-dom";
import { createBrowserHistory } from "history";

const history = createBrowserHistory({ window });

const CustomRouter: FC<PropsWithChildren> = ({ children, ...props }) => {
    return (
    <HistoryRouter history={history} {...props}>
        {children}
    </HistoryRouter>
    );
};

export const rootNavigate = (to: string) => {
    history.push(to);
};

export default CustomRouter;

2. Import the CustomRouter and use it in place of the BrowserRouter

[...]
import CustomRouter from "./components/CustomRouter";
[...]
ReactDOM.render(
    <CustomRouter>
    [...]
    </CustomRouter>,
    root
);

3. Import and use "rootNavigate" anyware to navigate.

import { rootNavigate } from "../components/CustomRouter";

function doAnything() {
    alert("Ok, will do");
    rootNavigate("/anywhere-you-want");
}
like image 44
leonardomdr Avatar answered Oct 27 '22 11:10

leonardomdr