hashHistory vs context.router - reactjs

What is the difference between hashHistory and context.router? Both of them route url and seem to work the same.

hashHistory is an instance of a history object created by the history module. It works by modifying the hash of a URL.
In React Router, a <Router> must be passed a history object. You can create and configure your own object, but for convenience, they also create hashHistory and browserHistory objects for you. Those objects can be imported and used anywhere throughout your project, including within your components. The downside to using them is that you cannot configure them yourself. If you need to use any of the history configuration options, then you will need to create your own history object.
import { hashHistory, Router } from 'react-router'
render((
<Router history={hashHistory}>...</Router>
), holder)
// or
import { Router } from 'react-router'
import { createHashHistory } from 'history'
const history = createHashHistory({ /* configuration options */ })
render((
<Router history={history}>...</Router>
), holder)
Within the components rendered by the <Router>, you can access the context.router object. That includes a number of methods from your history object. Caling those methods is the same as importing hashHistory within that file and calling whatever navigation function that you need.
const MyComponent = (props, context) => (
<div onClick={() => { context.router.push('/other-page') }}>Click Me!</div>
)
const MyComponent = (props) => (
<div onClick={() => { hashHistory.push('/other-page') }}>Click Me!</div>
)
The downside of this is that your components are less portable. Whereas using context.router will use whatever history object you passed to the <Router>, you would need to modify the component if you decided to switch from hashHistory to browserHistory.

Related

Alternative of the old CreateBrowserHistory() function in React Router v6

I want to push a new route in history of my app, and as you know, the UseHistory() hook and CreateBrowserHistory() function (to create a user-defined history) have been removed from new version of 6. I know that I can use UseNavigate hook and do this work with Navigate() function, but this hook cannot be called at the top level and must be called in a React function component. Codes below show how we could do this with React Router version 5.1:
Passing the history to the parent component and exporting the same history object for using it anywhere in Application:
import React from 'react'
import { Router } from 'react-router-dom'
import { createBrowserHistory } from 'history' // which comes along with React-Router
export const history = createBrowserHistory();
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)
root.render(
<Router history={history}>
<App />
</Router>
)
Using the exported history object in another place of Application at the top level:
switch (status) {
case 404:
history.push('/not-found');
break
if I want to do this at the top level in the new version of 6, What do you recommend?

Redirect to Route from Saga using React Router v6

I'm using redux saga & react router v6 and I want to redirect to a route from one of my sagas, is there a way to do it ?
There are multiple options
1 - Sending the navigate method as part of dispatched redux action
// component
const navigate = useNavigate()
dispatch({type: FOO, navigate})
// saga
yield takeEvery(FOO, function*(action) {
action.navigate('/foo')
})
Pros:
You are using the navigate method which is recommended by the react-router team
The API is unlikely to change
Cons
You have access to the navigate method only in specific sagas that received such action
You have unserializable data in your actions
2 - Another option is to store the navigate method in some way. E.g. you can create a dummy react component that will get the navigate method through useNavigate hook and then store it in some "global" variable. See this SO answer for a possible solution:
https://stackoverflow.com/a/70002872/2347716
This deals with the the cons from previous solution, but there are still some issues:
You need your React tree to render at least once before you have access to the navigate method
You are adding non-view complexity to your view layer by introducing the dummy component
3 - There is another solution, similar to what we had with react-router 5 that deals with the issues in the previous solution. It is to use the history object. It is not documented since its unstable, but there is a HistoryRouter implementation as part of the react-router-dom package. See https://github.com/remix-run/react-router/releases/tag/v6.1.1
import {unstable_HistoryRouter as HistoryRouter} from 'react-router-dom'
import { createBrowserHistory } from "history";
const history = createBrowserHistory()
// saga setup
sagaMiddleware.runSaga(rootSaga, history);
// react
<HistoryRouter history={history} />
The issue with this solution is that it is unstable because it might have some issues with some of React 18 features. Personally I prefer it since it solves everything else and we can deal with React 18 issues once its actually released and we know what they are.
My solution
// "HistoryRouter" implementation
import * as React from 'react'
import type {BrowserHistory} from 'history'
import {Router} from 'react-router-dom'
export interface HistoryRouterProps {
history: BrowserHistory
basename?: string
children?: React.ReactNode
}
export function HistoryRouter({
basename,
children,
history,
}: HistoryRouterProps) {
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}
/>
)
}
// Use
import {createBrowserHistory} from 'history'
export const history = createBrowserHistory()
ReactDOM.render(
<HistoryRouter history={history}>
<Routes>
<Route path="login" element={<LoginComponent />} />
<Route path="register" element={<RegisterComponent />} />
<Route path="*" element={<HomeComponent />} />
</Routes>
</HistoryRouter>
, root)
// call history push in file saga
function* fetchLoginSaga(action: FetchLoginRequest) {
try {
yield call(history.push, "/home")
} catch (e: any) {
}
}
I present my solution to the problem, maybe it is a bit convoluted but it is very clean and useful:
All of us will have a parent component in the routes, in my case I have called it App, and we can imagine that we will have this component connected to redux, therefore the following tree would remain:
index.js > Provider > Router > App (with Outlet) > Child Route.
That means that we can create a yield put that changes a props in the parent component, in my case App, which will contain a useEffect that will launch the navigate:
import { useEffect } from 'react'
import { Outlet, useNavigate } from "react-router-dom";
const AppContainer = ({ url }) => {
const navigate = useNavigate()
useEffect(()=>{
if (url) {
navigate(url)
}
}, [url])
return (
<div>
<Outlet />
</div>
)
}
export default AppContainer;
Now whenever I want to navigate from the sagas I simply have to execute a yield put, in this way the props url is modified and the useEffect is launched that sends me to the requested url.
Since my SPA system is a bit complex, I'll leave you to solve that part of connecting the dispatch with the component
There is an option that works well for me. You should just take a router (the result of createBrowserRouter() call) and set it to sagaMiddleware context and then use it from saga wherever you want.
App.tsx
const router = createBrowserRouter([
{
path: '/',
element: <div>Home</div>
},
]);
sagaMiddleware.setContext({
router,
});
export const App = () => {
return (
<RouterProvider router={router}/>
);
}
saga.ts
export function* selectSmthSaga(action: ActionType<typeof selectSmthAction>) {
const router: ReturnType<typeof createBrowserRouter> = yield getContext('router');
router.navigate(`/path/${action.payload}/subpath`);
}
you can use #lagunovsky/redux-react-router instead of connected react router because it's support react router v6 ve

How to access react-router-v4 history prop from MobX store

Is there any way to create an action in your MobX store, which pushes your app to a new url using react router v4, e.g. this.props.history.push...
I constantly get a history undefined error, but am unsure how to access the history from my store.
The history push called from the component itself does work though..
many thanks! (this is driving me crazy..)
Since I stumbled across the same issue, I'll share my solution. I just put the RouterStore into its own file in my stores directory, then if I needed access to history or location or whatever, I would import the routing store into the store I was currently working in.
./stores/routing.ts
import { RouterStore } from 'mobx-react-router'
export default new RouterStore()
./stores/other-store.ts
import routing from './routing'
export class OtherStore {
#action
doSomething = () => {
routing.push('/new-route')
}
}
export default new OtherStore()
./index.ts
import { Router } from 'react-router-dom'
import { Provider } from 'mobx-react'
import createBrowserHistory from 'history/createBrowserHistory'
import { syncHistoryWithStore } from 'mobx-react-router'
import otherStore from './stores/other-store'
import routing from './stores/routing'
const browserHistory = createBrowserHistory()
const stores = {
otherStore,
routing,
}
const history = syncHistoryWithStore(browserHistory, routing)
ReactDOM.render(
<Provider {...stores}>
<Router history={history}>
<App />
</Router>
</Provider>,
document.getElementById('root'),
)
You can use mobx-react-router to put react-router in a mobx store and then use it by injecting it in components.
You can also pass the router store as a constructor argument to your other stores that need it. This way you have the router history instance available in your mobx store.
I would like to add a simpler solution that does not require any additional libraries. React Router version is 5.2
Among my stores i've created a HistoryStore.js with the following code:
import { createBrowserHistory } from 'history';
export class HistoryStore {
history = createBrowserHistory();
}
Then I create an instance of it in my contexts.js file but you could do it right away.
export const history = new HistoryStore();
After that you import it in your index.js and pass it as a history prop to the Router.
That's it. Now you could import this store into any other and use it there. When you use useHistory hook in your component it gets this history object, so your history in synchronized.

React router 4 history.listen never fires

Switched to router v4 and history v4.5.1 and now history listener not working
import createBrowserHistory from 'history/createBrowserHistory'
const history = createBrowserHistory()
history.listen((location, action) => {
console.log(action, location.pathname, location.state) // <=== Never happens
})
render(
<Provider store={store}>
<Router history={history}>
...
</Router>
</Provider>,
document.getElementById('root')
)
Any ideas why it is being ignored?
Since you are using BrowserRouter(with import alias Router as mentioned in comments of the question), it doesn't care the history prop you pass in. Instead of that it internally creates and assigns new browser history to the Router. So the history instance that you listen and being used in Router is not the same. That's why your listener doesn't work.
Import the original Router.
import { Router } from 'react-router-dom';
It will work as you expect.
The problem is that you are creating your own history object and passing it into the router. However, React Router v4 already provides this object for you, via this.props. (Importing Router has nothing to do with this)
componentDidMount() {
this.props.history.listen((location, action) => console.log('History changed!', location, action));
}
You may need to layer your app a bit more though, like below, and put this componentDidMount method in your MyApp.jsx and not directly at the very top level.
<Provider store={store}>
<BrowserRouter>
<MyApp/>
</BrowserRouter>
</Provider>
(Or use NativeRouter instead of BrowserRouter if you're doing React Native)
works like a magic: 🧙‍♂️🧙‍♂️🧙‍♂️
const history = useHistory();
useEffect(() => {
if (history) { // for jest sake ))
history.listen(() => {
store.reset();
});
}
}, []);

Automatic redirect after login with react-router

I wanted to build a Facebook login into my react/react-router/flux application.
I have a listener registered on the login event and would like to redirect the user to '/dashboard' if they are logged in. How can I do that? location.push didn't work very well, except after reloading the page completely.
React Router v3
This is what I do
var Router = require('react-router');
Router.browserHistory.push('/somepath');
React Router v4
Now we can use the <Redirect>component in React Router v4.
Rendering a <Redirect> will navigate to a new location. The new location will override the current location in the history stack, like server-side redirects.
import React, { Component } from 'react';
import { Redirect } from 'react-router';
export default class LoginComponent extends Component {
render(){
if(this.state.isLoggedIn === true){
return (<Redirect to="/your/redirect/page" />);
}else{
return (<div>Login Please</div>);
}
}
}
Documentation https://reacttraining.com/react-router/web/api/Redirect
React Router v0.13
The Router instance returned from Router.create can be passed around (or, if inside a React component, you can get it from the context object), and contains methods like transitionTo that you can use to transition to a new route.
React Router v2
Even though the question is already answered, I think it's relevant to post the solution that worked for me, since it wasn't covered in any of the solutions given here.
First, I'm using the router context on my LoginForm component
LoginForm.contextTypes = {
router: React.PropTypes.object
};
After that, I can access the router object inside my LoginForm component
handleLogin() {
this.context.router.push('/anotherroute');
}
PS: working on React-router version 2.6.0
React Router v3
Navigating Outside of Components
create your app with Router like this
// Your main file that renders a <Router>:
import { Router, browserHistory } from 'react-router'
import routes from './app/routes'
render(
<Router history={browserHistory} routes={routes} />,
mountNode
)
Somewhere like a Redux middleware or Flux action:
import { browserHistory } from 'react-router'
// Go to /some/path.
browserHistory.push('/some/path')
// Go back to previous location.
browserHistory.goBack()
react-router/tree/v3/docs
React Router v4.2.0
I am using React-16.2.0 & React-router-4.2.0
And I get solution by this code
this.props.history.push("/");
My working code:
.then(response => response.json())
.then(data => {
if(data.status == 200){
this.props.history.push("/");
console.log('Successfully Login');
}
})
I was following this document redirect-on-login-and-logout
I was also try by return <Redirect to='/' /> But unlucky, this not working for me.
React router v5 using hooks
These steps are for authorisation redirect. But can be used for login/logout redirection also.
The <Redirect/> accepts to prop as a string or an object. We can utilise the object to pass the redirection path after login/logout using hooks easily.
Get the pathname of url from where the <Redirect/> is called using
useLocation()
const {pathname} = useLocation()
In the to prop of <Redirect/> pass in the following object:
<Redirect to={{pathname:'/login',state: {referrer: pathname}}/>
In the Login component access the route state variable using useLocation() hook and use the useHistory() hook to redirect after successful login.
const history = useHistory();
const location = useLocation();
const login() => {
// After login success
const {state: {referrer}} = location;
history.push(referrer)
};
Check the official docs here
React Router v3
Navigating inside components
You should use withRouter decorator when it's necessary to redirect inside a component. The decorator uses context instead of you.
import {withRouter} from 'react-router'
fucntion Foo(props) {
props.router.push('/users/16');
}
export default withRouter(Foo);
withRouter(Component, [options])
A HoC (higher-order component) that wraps another component to enhance
its props with router props.
withRouterProps = {
...componentProps,
router,
params,
location,
routes
}
Pass in your component and it will return the
wrapped component.
You can explicit specify router as a prop to the wrapper component to
override the router object from context.
In your store:
data.router.transitionTo('user');
And router has:
"Route name="user" handler={User}"
User is route handler

Resources