Programmatically navigate using react router
With react-router
I can use the Link
element to create links that are natively handled by react router.
I see internally it calls this.context.transitionTo(...)
.
I want to do a navigation, but not from a link, from a dropdown selection for example. How can I do this in code? What is this.context
?
I saw the Navigation
mixin, but can I do this without mixins?
React Router v4
With v4 of React Router, there are three approaches that you can take to programmatic routing within components.
- Use the
withRouter
higher-order component. - Use composition and render a
<Route>
- Use the
context
.
React Router is mostly a wrapper around the history
library. history
handles interaction with the browser's window.history
for you with its browser and hash histories. It also provides a memory history which is useful for environments that don't have a global history. This is particularly useful in mobile app development (react-native
) and unit testing with Node.
A history
instance has two methods for navigating: push
and replace
. If you think of the history
as an array of visited locations, push
will add a new location to the array and replace
will replace the current location in the array with the new one. Typically you will want to use the push
method when you are navigating.
In earlier versions of React Router, you had to create your own history
instance, but in v4 the <BrowserRouter>
, <HashRouter>
, and <MemoryRouter>
components will created browser, hash, and memory instances for you. React Router makes the properties and methods of the history
instance associated with your router available through the context, under the router
object.
1. Use the withRouter
higher-order component
The withRouter
higher-order component will inject the history
object as a prop of the component. This allows you to access the push
and replace
methods without having to deal with the context
.
import { withRouter } from 'react-router-dom'
// this also works with react-router-native
const Button = withRouter(({ history }) => (
<button
type='button'
onClick={() => { history.push('/new-location') }}
>
Click Me!
</button>
))
2. Use composition and render a <Route>
The <Route>
component isn't just for matching locations. You can render a pathless route and it will always match the current location. The <Route>
component passes the same props as withRouter
, so you will be able to access the history
methods through the history
prop.
import { Route } from 'react-router-dom'
const Button = () => (
<Route render={({ history}) => (
<button
type='button'
onClick={() => { history.push('/new-location') }}
>
Click Me!
</button>
)} />
)
3. Use the context*
*But you probably should not
The last option is one that you should only use if you feel comfortable working with React's context model. Although context is an option, it should be stressed that context is an unstable API and React has a section Why Not To Use Context in their documentation. So use at your own risk!
const Button = (props, context) => (
<button
type='button'
onClick={() => {
// context.history.push === history.push
context.history.push('/new-location')
}}
>
Click Me!
</button>
)
// you need to specify the context type so that it
// is available within the component
Button.contextTypes = {
history: React.PropTypes.shape({
push: React.PropTypes.func.isRequired
})
}
1 and 2 are the simplest choices to implement, so for most use cases they are your best bets.
React-Router 4.0.0+ Answer
In 4.0 and above, use the history as a prop of your component.
class Example extends React.Component {
// use `this.props.history.push('/some/path')` here
};
React-Router 3.0.0+ Answer
In 3.0 and above, use the router as a prop of your component.
class Example extends React.Component {
// use `this.props.router.push('/some/path')` here
};
React-Router 2.4.0+ Answer
In 2.4 and above, use a higher order component to get the router as a prop of your component.
import { withRouter } from 'react-router';
class Example extends React.Component {
// use `this.props.router.push('/some/path')` here
};
// Export the decorated class
var DecoratedExample = withRouter(Example);
// PropTypes
Example.propTypes = {
router: React.PropTypes.shape({
push: React.PropTypes.func.isRequired
}).isRequired
};
React-Router 2.0.0+ Answer
This version is backwards compatible with 1.x so there's no need to an Upgrade Guide. Just going through the examples should be good enough.
That said, if you wish to switch to the new pattern, there's a browserHistory module inside the router that you can access with
import { browserHistory } from 'react-router'
Now you have access to your browser history, so you can do things like push, replace, etc... Like:
browserHistory.push('/some/path')
Further reading: Histories and Navigation
React-Router 1.x.x Answer
I will not go into upgrading details. You can read about that in the Upgrade Guide
The main change about the question here is the change from Navigation mixin to History. Now it's using the browser historyAPI to change route so we will use pushState()
from now on.
Here's an exemple using Mixin:
var Example = React.createClass({
mixins: [ History ],
navigateToHelpPage () {
this.history.pushState(null, `/help`);
}
})
Note that this History
comes from rackt/history project. Not from React-Router itself.
If you don't want to use Mixin for some reason (maybe because of ES6 class), then you can access the history that you get from the router from this.props.history
. It will be only accessible for the components rendered by your Router. So, if you want to use it in any child components it needs to be passed down as an attribute via props
.
You can read more about the new release at their 1.0.x documentation
Here is a help page specifically about navigating outside your component
It recommends grabbing a reference history = createHistory()
and calling replaceState
on that.
React-Router 0.13.x Answer
I got into the same problem and could only find the solution with the Navigation mixin that comes with react-router.
Here's how I did it
import React from 'react';
import {Navigation} from 'react-router';
let Authentication = React.createClass({
mixins: [Navigation],
handleClick(e) {
e.preventDefault();
this.transitionTo('/');
},
render(){
return (<div onClick={this.handleClick}>Click me!</div>);
}
});
I was able to call transitionTo()
without the need to access .context
Or you could try the fancy ES6 class
import React from 'react';
export default class Authentication extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick(e) {
e.preventDefault();
this.context.router.transitionTo('/');
}
render(){
return (<div onClick={this.handleClick}>Click me!</div>);
}
}
Authentication.contextTypes = {
router: React.PropTypes.func.isRequired
};
React-Router-Redux
Note: if you're using Redux, there is another project called React-Router-Redux that gives you redux bindings for ReactRouter, using somewhat the same approach that React-Redux does
React-Router-Redux has a few methods available that allow for simple navigating from inside action creators. These can be particularly useful for people that have existing architecture in React Native, and they wish to utilize the same patterns in React Web with minimal boilerplate overhead.
Explore the following methods:
push(location)
replace(location)
go(number)
goBack()
goForward()
Here is an example usage, with Redux-Thunk:
./actioncreators.js
import { goBack } from 'react-router-redux'
export const onBackPress = () => (dispatch) => dispatch(goBack())
./viewcomponent.js
<button
disabled={submitting}
className="cancel_button"
onClick={(e) => {
e.preventDefault()
this.props.onBackPress()
}}
>
CANCEL
</button>
React-Router v2
For the most recent release (v2.0.0-rc5
), the recommended navigation method is by directly pushing onto the history singleton. You can see that in action in the Navigating outside of Components doc.
Relevant excerpt:
import { browserHistory } from 'react-router';
browserHistory.push('/some/path');
If using the newer react-router API, you need to make use of the history
from this.props
when inside of components so:
this.props.history.push('/some/path');
It also offers pushState
but that is deprecated per logged warnings.
If using react-router-redux
, it offers a push
function you can dispatch like so:
import { push } from 'react-router-redux';
this.props.dispatch(push('/some/path'));
However this may be only used to change the URL, not to actually navigate to the page.
Here's how you do this with react-router v2.0.0
with ES6. react-router
has moved away from mixins.
import React from 'react';
export default class MyComponent extends React.Component {
navigateToPage = () => {
this.context.router.push('/my-route')
};
render() {
return (
<button onClick={this.navigateToPage}>Go!</button>
);
}
}
MyComponent.contextTypes = {
router: React.PropTypes.object.isRequired
}
For this one, who does not control the server side and because of this is using hash router v2:
Place your history into separate file (e.g. app_history.js ES6):
import { useRouterHistory } from 'react-router'
import { createHashHistory } from 'history'
const appHistory = useRouterHistory(createHashHistory)({ queryKey: false });
export default appHistory;
And use it everywhere!
Your entry point for react-router (app.js ES6):
import React from 'react'
import { render } from 'react-dom'
import { Router, Route, Redirect } from 'react-router'
import appHistory from './app_history'
...
const render((
<Router history={appHistory}>
...
</Router>
), document.querySelector('[data-role="app"]'));
Your navigation inside any component (ES6):
import appHistory from '../app_history'
...
ajaxLogin('/login', (err, data) => {
if (err) {
console.error(err); // login failed
} else {
// logged in
appHistory.replace('/dashboard'); // or .push() if you don't need .replace()
}
})
React-Router 4.x Answer :
On my end, I like to have a single history object that I can carry even outside components. What I like to do is to have a single history.js file that I import on demand, and just manipulate it.
You just have to change BrowserRouter
to Router, and specify the history prop. This doesn't change anything for you except that you have your own history object that you can manipulate as you want.
You need to install history, the library used by react-router
.
Example usage, ES6 notation :
history.js
import createBrowserHistory from 'history/createBrowserHistory'
export default createBrowserHistory()
BasicComponent.js
import React, { Component } from 'react';
import history from './history';
class BasicComponent extends Component {
goToIndex(e){
e.preventDefault();
history.push('/');
}
render(){
return <a href="#" onClick={this.goToIndex}>Previous</a>;
}
}
EDIT April 16th, 2018 :
If you have to navigate from a component that is actually rendered from a Route
component, you can also access history from props, like that :
BasicComponent.js
import React, { Component } from 'react';
class BasicComponent extends Component {
navigate(e){
e.preventDefault();
this.props.history.push('/url');
}
render(){
return <a href="#" onClick={this.navigate}>Previous</a>;
}
}
Warning: this answer covers only ReactRouter versions before 1.0
I will update this answer with 1.0.0-rc1 use cases after!
You can do this without mixins too.
let Authentication = React.createClass({
contextTypes: {
router: React.PropTypes.func
},
handleClick(e) {
e.preventDefault();
this.context.router.transitionTo('/');
},
render(){
return (<div onClick={this.handleClick}>Click me!</div>);
}
});
The gotcha with contexts is that it is not accessible unless you define the contextTypes
on the class.
As for what is context, it is an object, like props, that are passed down from parent to child, but it is passed down implicitly, without having to redeclare props each time. See https://www.tildedave.com/2014/11/15/introduction-to-contexts-in-react-js.html
I tried at least 10 ways of doing this before something worked right!
@Felipe Skinner's withRouter
answer was a bit overwhelming to me, and I wasn't sure I wanted to make new "ExportedWithRouter" class names.
Here's the simplest and cleanest way to do it, circa current React-Router 3.0.0 and ES6:
React-Router 3.x.x with ES6:
import { withRouter } from 'react-router';
class Example extends React.Component {
// use `this.props.router.push('/some/path')` here
};
// Export the decorated class
export default withRouter(Example);
or, if it's not your default class, export like:
withRouter(Example);
export { Example };
Note that in 3.x.x, the <Link>
component itself is using router.push
, so you can pass it anything you would pass the <Link to=
tag, like:
this.props.router.push({pathname: '/some/path', query: {key1: 'val1', key2: 'val2'})'
React Router V4
tl:dr;
if (navigate) {
return <Redirect to="/" push={true} />
}
The simple and declarative answer is that you need to use <Redirect to={URL} push={boolean} />
in combination with setState()
push: boolean - when true, redirecting will push a new entry onto the history instead of replacing the current one.
import { Redirect } from 'react-router'
class FooBar extends React.Component {
state = {
navigate: false
}
render() {
const { navigate } = this.state
// here is the important part
if (navigate) {
return <Redirect to="/" push={true} />
}
// ^^^^^^^^^^^^^^^^^^^^^^^
return (
<div>
<button onClick={() => this.setState({ navigate: true })}>
Home
</button>
</div>
)
}
}
Full example here. Read more here.
PS. The example uses ES7+ Property Initializers to initialise state. Look here as well, if you're interested.
For ES6 + React components, the following solution worked for me.
I followed Felippe skinner, but added an end to end solution to help beginners like me.
Below are the versions I used:
"react-router": "^2.7.0"
"react": "^15.3.1"
Below is my react component where I used programmatic navigation using react-router:
import React from 'react';
class loginComp extends React.Component {
constructor( context) {
super(context);
this.state = {
uname: '',
pwd: ''
};
}
redirectToMainPage(){
this.context.router.replace('/home');
}
render(){
return <div>
// skipping html code
<button onClick={this.redirectToMainPage.bind(this)}>Redirect</button>
</div>;
}
};
loginComp.contextTypes = {
router: React.PropTypes.object.isRequired
}
module.exports = loginComp;
Below is the configuration for my router:
import { Router, Route, IndexRedirect, browserHistory } from 'react-router'
render(<Router history={browserHistory}>
<Route path='/' component={ParentComp}>
<IndexRedirect to = "/login"/>
<Route path='/login' component={LoginComp}/>
<Route path='/home' component={HomeComp}/>
<Route path='/repair' component={RepairJobComp} />
<Route path='/service' component={ServiceJobComp} />
</Route>
</Router>, document.getElementById('root'));
May not be the best approach but... Using react-router v4, the following Typescript could give an idea for some.
In the rendered component below, e.g. LoginPage
, router
object is accessible and just call router.transitionTo('/homepage')
to navigate.
Navigation code was taken from https://react-router.now.sh/Match.
"react-router": "^4.0.0-2",
"react": "^15.3.1",
import Router from 'react-router/BrowserRouter';
import { History } from 'react-history/BrowserHistory';
import createHistory from 'history/createBrowserHistory';
const history = createHistory();
interface MatchWithPropsInterface {
component: typeof React.Component,
router: Router,
history: History,
exactly?: any,
pattern: string
}
class MatchWithProps extends React.Component<MatchWithPropsInterface,any> {
render() {
return(
<Match {...this.props} render={(matchProps) => (
React.createElement(this.props.component, this.props)
)}
/>
)
}
}
ReactDOM.render(
<Router>
{({ router }) => (
<div>
<MatchWithProps exactly pattern="/" component={LoginPage} router={router} history={history} />
<MatchWithProps pattern="/login" component={LoginPage} router={router} history={history} />
<MatchWithProps pattern="/homepage" component={HomePage} router={router} history={history} />
<Miss component={NotFoundView} />
</div>
)}
</Router>,
document.getElementById('app')
);
To do the navigation programmatically, you need to push a new history to the props.history in your component
, so something like this can do the work for you:
//using ES6
import React from 'react';
class App extends React.Component {
constructor(props) {
super(props)
this.handleClick = this.handleClick.bind(this)
}
handleClick(e) {
e.preventDefault()
/* Look at here, you can add it here */
this.props.history.push('/redirected');
}
render() {
return (
<div>
<button onClick={this.handleClick}>
Redirect!!!
</button>
</div>
)
}
}
export default App;
In React-Router v4 and ES6
You can use withRouter
and this.props.history.push
.
import {withRouter} from 'react-router-dom';
class Home extends Component {
componentDidMount() {
this.props.history.push('/redirect-to');
}
}
export default withRouter(Home);
based on the previous answer
from José Antonio Postigo and Ben Wheeler
the novelty? is to be written in Typescript
and the use of decorators
OR static property/field
import * as React from "react";
import Component = React.Component;
import { withRouter } from "react-router";
export interface INavigatorProps {
router?: ReactRouter.History.History;
}
/**
* Note: goes great with mobx
* @inject("something") @withRouter @observer
*/
@withRouter
export class Navigator extends Component<INavigatorProps, {}>{
navigate: (to: string) => void;
constructor(props: INavigatorProps) {
super(props);
let self = this;
this.navigate = (to) => self.props.router.push(to);
}
render() {
return (
<ul>
<li onClick={() => this.navigate("/home")}>
Home
</li>
<li onClick={() => this.navigate("/about")}>
About
</li>
</ul>
)
}
}
/**
* Non decorated
*/
export class Navigator2 extends Component<INavigatorProps, {}> {
static contextTypes = {
router: React.PropTypes.object.isRequired,
};
navigate: (to: string) => void;
constructor(props: INavigatorProps, context: any) {
super(props, context);
let s = this;
this.navigate = (to) =>
s.context.router.push(to);
}
render() {
return (
<ul>
<li onClick={() => this.navigate("/home")}>
Home
</li>
<li onClick={() => this.navigate("/about")}>
About
</li>
</ul>
)
}
}
with whatever npm installed today.
"react-router": "^3.0.0" and
"@types/react-router": "^2.0.41"
with React-Router v4 on the horizon, there is now a new way of doing this.
import { MemoryRouter, BrowserRouter } from 'react-router';
const navigator = global && global.navigator && global.navigator.userAgent;
const hasWindow = typeof window !== 'undefined';
const isBrowser = typeof navigator !== 'undefined' && navigator.indexOf('Node.js') === -1;
const Router = isBrowser ? BrowserRouter : MemoryRouter;
<Router location="/page-to-go-to"/>
react-lego is an example app that shows how to use/update react-router and it includes example functional tests which navigate the app.
With the current React version (15.3), this.props.history.push('/location');
worked for me, but it showed the following warning:
browser.js:49 Warning: [react-router]
props.history
andcontext.history
are deprecated. Please usecontext.router
.
and I solved it using context.router
like this:
import React from 'react';
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.backPressed = this.backPressed.bind(this);
}
backPressed() {
this.context.router.push('/back-location');
}
...
}
MyComponent.contextTypes = {
router: React.PropTypes.object.isRequired
};
export default MyComponent;
If you are using hash or browser history then you can do
hashHistory.push('/login');
browserHistory.push('/login');
To use withRouter
with a class-based component, try something like this below.
Don't forget to change the export statement to use withRouter
:
import { withRouter } from 'react-router-dom'
class YourClass extends React.Component {
yourFunction = () => {
doSomeAsyncAction(() =>
this.props.history.push('/other_location')
)
}
render() {
return (
<div>
<Form onSubmit={ this.yourFunction } />
</div>
)
}
}
export default withRouter(YourClass);
React-Router V4
if you're using version 4 then you can use my library (Shameless plug) where you simply dispatch an action and everything just works!
dispatch(navigateTo("/aboutUs"));
https://www.npmjs.com/package/trippler
In react router v4. I follow this two way to route programmatically.
1. this.props.history.push("/something/something")
2. this.props.history.replace("/something/something")
Number two
Replaces the current entry on the history stack
To get history in props you may have to wrap your component with
withRouter https://reacttraining.com/react-router/core/api/withRouter
Please find below working code: As discussed in this article, it is very simple method to navigate using react Router:
class Register extends React.Component {
state = {
toDashboard: false,
}
handleSubmit = (user) => {
saveUser(user)
.then(() => this.setState(() => ({
toDashboard: true
})))
}
render() {
if (this.state.toDashboard === true) {
return <Redirect to='/dashboard' />
}
return (
<div>
<h1>Register</h1>
<Form onSubmit={this.handleSubmit} />
</div>
)
}
}
<Redirect/> is
Composable ✅ Declarative ✅ user event -> state change -> re-render ✅
Register component is being rendered by React Router, our code could look like this
class Register extends React.Component {
handleSubmit = (user) => {
saveUser(user).then(() =>
this.props.history.push('/dashboard')
))
}
render() {
return (
<div>
<h1>Register</h1>
<Form onSubmit={this.handleSubmit} />
</div>
)
}
}
by adding withRouter, it would look like this
import {
withRouter
} from 'react-router-dom'
class Register extends React.Component {
handleSubmit = (user) => {
saveUser(user).then(() =>
this.props.history.push('/dashboard')
))
}
render() {
return (
<div>
<h1>Register</h1>
<Form onSubmit={this.handleSubmit} />
</div>
)
}
}
export default withRouter(Register)
There are two ways to programmatically navigate with React Router - and history.push. Which you use is mostly up to you and your specific use case, though I try to favor Redirect.
The right answer was for me at the time of writing
this.context.router.history.push('/');
But you need to add PropTypes to your component
Header.contextTypes = {
router: PropTypes.object.isRequired
}
export default Header;
Don't forget to import PropTypes
import PropTypes from 'prop-types';
Maybe not the best solution but it gets the job done:
import { Link } from 'react-router-dom';
// create functional component Post
export default Post = () => (
<div className="component post">
<button className="button delete-post" onClick={() => {
// ... delete post
// then redirect, without page reload, by triggering a hidden Link
document.querySelector('.trigger.go-home').click();
}}>Delete Post</button>
<Link to="/" className="trigger go-home hidden"></Link>
</div>
);
Basically, a logic tied to one action (in this case a post deletion) will end up calling a trigger for redirect. This is not ideal because you will add a DOM node 'trigger' to your markup just so you can conveniently call it when needed. Also, you will directly interact with the DOM, which in a React component may not be desired.
Still, this type of redirect is not required that often. So one or two extra, hidden links in your component markup would not hurt that much, especially if you give them meaningful names.
If happen to pair RR4 w/ redux through react-router-redux, use the routing action creators from react-router-redux
is a option as well.
import { push, replace, ... } from 'react-router-redux'
class WrappedComponent extends React.Component {
handleRedirect(url, replaceState = true) {
replaceState
? this.props.dispatch(replace(url))
: this.props.dispatch(push(url))
}
render() { ... }
}
export default connect(null)(WrappedComponent)
If use redux thunk/saga to manage async flow, import the above action creators in redux actions and hook to react components using mapDispatchToProps might be better.
Simply just use this.props.history.push('/where/to/go');
Comments
Post a Comment