I have just replaced react-router
from v3 to v4.
But I am not sure how to programmatically navigate in the member function of a Component
.
i.e in handleClick()
function I want to navigate to /path/some/where
after processing some data.
I used to do that by:
import { browserHistory } from 'react-router'
browserHistory.push('/path/some/where')
But I can't find such interfaces in v4.
How can I navigate using v4?
This question is related to
reactjs
ecmascript-6
react-router-v4
useHistory
hook if you're using function componentsYou can use useHistory
hook to get history
instance.
import { useHistory } from "react-router-dom";
const MyComponent = () => {
const history = useHistory();
return (
<button onClick={() => history.push("/about")}>
Click me
</button>
);
}
The useHistory
hook gives you access to the history instance that you may use to navigate.
history
property inside page componentsReact Router injects some properties including history
to page components.
class HomePage extends React.Component {
render() {
const { history } = this.props;
return (
<div>
<button onClick={() => history.push("/projects")}>
Projects
</button>
</div>
);
}
}
withRouter
to inject router propertieswithRouter
wrapper injects router properties to components. For example you can use this wrapper to inject router to logout button component placed inside user menu.
import { withRouter } from "react-router";
const LogoutButton = withRouter(({ history }) => {
return (
<button onClick={() => history.push("/login")}>
Logout
</button>
);
});
export default LogoutButton;
I had a similar issue when migrating over to React-Router v4 so I'll try to explain my solution below.
Please do not consider this answer as the right way to solve the problem, I imagine there's a good chance something better will arise as React Router v4 becomes more mature and leaves beta (It may even already exist and I just didn't discover it).
For context, I had this problem because I occasionally use Redux-Saga
to programmatically change the history object (say when a user successfully authenticates).
In the React Router docs, take a look at the <Router>
component and you can see you have the ability to pass your own history object via a prop. This is the essence of the solution - we supply the history object to React-Router
from a global module.
yarn add history
or npm install history --save
create a file called history.js
in your App.js
level folder (this was my preference)
// src/history.js
import createHistory from 'history/createBrowserHistory';
export default createHistory();`
Add this history object to your Router component like so
// src/App.js
import history from '../your/path/to/history.js;'
<Router history={history}>
// Route tags here
</Router>
Adjust the URL just like before by importing your global history object:
import history from '../your/path/to/history.js;'
history.push('new/path/here/');
Everything should stay synced up now, and you also have access to a way of setting the history object programmatically and not via a component/container.
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.
The easiest way to get it done:
this.props.history.push("/new/url")
Note:
history
prop
from parent component down to the component you want to invoke the action if its not available.As sometimes I prefer to switch routes by Application then by buttons, this is a minimal working example what works for me:
import { Component } from 'react'
import { BrowserRouter as Router, Link } from 'react-router-dom'
class App extends Component {
constructor(props) {
super(props)
/** @type BrowserRouter */
this.router = undefined
}
async handleSignFormSubmit() {
await magic()
this.router.history.push('/')
}
render() {
return (
<Router ref={ el => this.router = el }>
<Link to="/signin">Sign in</Link>
<Route path="/signin" exact={true} render={() => (
<SignPage onFormSubmit={ this.handleSignFormSubmit } />
)} />
</Router>
)
}
}
You can also simply use props to access history object: this.props.history.push('new_url')
For those of you who require to redirect before fully initalizing a router using React Router
or React Router Dom
You can provide a redirect by simply accesing the history object and pushing a new state onto it within your constructur of app.js
. Consider the following:
function getSubdomain(hostname) {
let regexParse = new RegExp('[a-z\-0-9]{2,63}\.[a-z\.]{2,5}$');
let urlParts = regexParse.exec(hostname);
return hostname.replace(urlParts[0], '').slice(0, -1);
}
class App extends Component {
constructor(props) {
super(props);
this.state = {
hostState: true
};
if (getSubdomain(window.location.hostname).length > 0) {
this.state.hostState = false;
window.history.pushState('', '', './login');
} else {
console.log(getSubdomain(window.location.hostname));
}
}
render() {
return (
<BrowserRouter>
{this.state.hostState ? (
<div>
<Route path="/login" component={LoginContainer}/>
<Route path="/" component={PublicContainer}/>
</div>
) : (
<div>
<Route path="/login" component={LoginContainer}/>
</div>
)
}
</BrowserRouter>)
}
}
Here we want to change the output Routes dependant on a subdomain, by interacting with the history object before the component renders we can effectively redirect while still leaving our routes in tact.
window.history.pushState('', '', './login');
You can navigate conditionally by this way
import { useHistory } from "react-router-dom";
function HomeButton() {
const history = useHistory();
function handleClick() {
history.push("/path/some/where");
}
return (
<button type="button" onClick={handleClick}>
Go home
</button>
);
}
I think that @rgommezz covers most of the cases minus one that I think it's quite important.
// history is already a dependency or React Router, but if don't have it then try npm install save-dev history
import createHistory from "history/createBrowserHistory"
// in your function then call add the below
const history = createHistory();
// Use push, replace, and go to navigate around.
history.push("/home");
This allows me to write a simple service with actions/calls that I can call to do the navigation from any component I want without doing a lot HoC on my components...
It is not clear why nobody has provided this solution before. I hope it helps, and if you see any issue with it please let me know.
My answer is similar to Alex's. I'm not sure why React-Router made this so needlessly complicated. Why should I have to wrap my component with a HoC just to get access to what's essentially a global?
Anyway, if you take a look at how they implemented <BrowserRouter>
, it's just a tiny wrapper around history.
We can pull that history bit out so that we can import it from anywhere. The trick, however, is if you're doing server-side rendering and you try to import
the history module, it won't work because it uses browser-only APIs. But that's OK because we usually only redirect in response to a click or some other client-side event. Thus it's probably OK to fake it:
// history.js
if(__SERVER__) {
module.exports = {};
} else {
module.exports = require('history').createBrowserHistory();
}
With the help of webpack, we can define some vars so we know what environment we're in:
plugins: [
new DefinePlugin({
'__SERVER__': 'false',
'__BROWSER__': 'true', // you really only need one of these, but I like to have both
}),
And now you can
import history from './history';
From anywhere. It'll just return an empty module on the server.
If you don't want use these magic vars, you'll just have to require
in the global object where it's needed (inside your event handler). import
won't work because it only works at the top-level.
I struggled with this for a while - something so simple, yet so complicated, because ReactJS is just a completely different way of writing web applications, it's very alien to us older folk!
I created a separate component to abstract the mess away:
// LinkButton.js
import React from "react";
import PropTypes from "prop-types";
import {Route} from 'react-router-dom';
export default class LinkButton extends React.Component {
render() {
return (
<Route render={({history}) => (
<button {...this.props}
onClick={() => {
history.push(this.props.to)
}}>
{this.props.children}
</button>
)}/>
);
}
}
LinkButton.propTypes = {
to: PropTypes.string.isRequired
};
Then add it to your render()
method:
<LinkButton className="btn btn-primary" to="/location">
Button Text
</LinkButton>
Since there's no other way to deal with this horrible design, I wrote a generic component that uses the withRouter
HOC approach. The example below is wrapping a button
element, but you can change to any clickable element you need:
import React from 'react';
import PropTypes from 'prop-types';
import { withRouter } from 'react-router-dom';
const NavButton = (props) => (
<Button onClick={() => props.history.push(props.to)}>
{props.children}
</Button>
);
NavButton.propTypes = {
history: PropTypes.shape({
push: PropTypes.func.isRequired
}),
to: PropTypes.string.isRequired
};
export default withRouter(NavButton);
Usage:
<NavButton to="/somewhere">Click me</NavButton>
this.props.history.push("/url")
If you have not found this.props.history available in your component , then try this
import {withRouter} from 'react-router-dom'
export default withRouter(MyComponent)
I've been testing v4 for a few days now and .. I'm loving it so far! It just makes sense after a while.
I also had the same question and I found handling it like the following worked best (and might even be how it is intended). It uses state, a ternary operator and <Redirect>
.
In the constructor()
this.state = {
redirectTo: null
}
this.clickhandler = this.clickhandler.bind(this);
In the render()
render(){
return (
<div>
{ this.state.redirectTo ?
<Redirect to={{ pathname: this.state.redirectTo }} /> :
(
<div>
..
<button onClick={ this.clickhandler } />
..
</div>
)
}
In the clickhandler()
this.setState({ redirectTo: '/path/some/where' });
Hope it helps. Let me know.
This works:
import { withRouter } from 'react-router-dom';
const SomeComponent = withRouter(({ history }) => (
<div onClick={() => history.push('/path/some/where')}>
some clickable element
</div>);
);
export default SomeComponent;
Step 1: There is only one thing to import on top:
import {Route} from 'react-router-dom';
Step 2: In your Route, pass the history:
<Route
exact
path='/posts/add'
render={({history}) => (
<PostAdd history={history} />
)}
/>
Step 3: history gets accepted as part of props in the next Component, so you can simply:
this.props.history.push('/');
That was easy and really powerful.
Source: Stackoverflow.com