How to manipulate history in React Router v4? - reactjs

I am using:
React, Redux, React-Router, Meteor
Following situation:
I have a modal with two tabs, based on the chosen tab a different list of selections will be shown in the content area, picking a selection will then give a list of subselection (all within the modal).
Desired behavior:
I want the user to be able to use the browser's back button to "undo" clicking on one of the tabs or picking a selection.
What I tried so far with no success:
use withRouter HOC to wrap my component, so that I would get access to history. I would then use history.push(currentUrl,{selectedTab:selection})
-> Problem: the state from push wasn't stored on this histories location object, but only in the state of the history object associated with my rootcomponent (way further up the tree)
(More promising so far) Simply import the history I created with createHistory in another module and put that into component state on construction.
I would then access the push method by using this.state.history.push(currentUrl,{selectedTab:selection}), which works fine and I can find the pushed state under this.state.history.location.state.selectedTab. However, using the browser's back button does not cause a rerender and therefore the selection will stay the same.
Any ideas?
Cheers

Another approach you could take would be to use query params with React router. And in your component change the tabs based on the query param. Since the url changed you should get the default behaviour your looking for.

I have a project with almost exactly that situation. The difference, is that instead of it being in a modal, I have it in my header. Here is what I did:
import React, { Component } from 'react';
import { Link, withRouter } from 'react-router-dom';
class Header extends Component {
render() {
return (
<ul className="tabs tabs-fixed-width z-depth-1">
<li className="tab">
<Link id="books-tab" to={'/books'}>Books</Link>
</li>
<li className="tab">
<Link id="stats-tab" to={'/stats'}>Stats</Link>
</li>
{Meteor.userId() &&
<li className="tab">
<Link id="user-tab" to={'/user'}>Account Settings</Link>
</li>}
</ul>
);
}
}
export default withRouter(Header);
The react-router-dom <Link>... being the operative item that allows this to happen.
Then, when I click on the tab, each link goes to the specified url and keeps it in history so I can go back and get to the previous tab.

I refined the second approach and it is working now. It does still feel hacky to import the history into my module (as a local variable), and thus bypassing react tree, redux store and react router altogether.
However, it is working and not causing any bugs so far. So here we go ...
I instantiate my history object in: store.js module:
import createHistory from 'history/createBrowserHistory';
...
const history = createHistory();
...
export { history , store }
that as you can see exports history. I then feed said history to my Router (ConnectedRouter in my case, as I am using react-router-redux) in: routes.js module
import { store , history } from '/imports/client/store';
const MyStoreRouter = () => {
return (
<Provider store={store}>
<ConnectedRouter history={ history }>
<div>
<Switch>
<Route exact path="/" component={Landing}/>
<Route path="/" component={MainApp}/>
</Switch>
</div>
</ConnectedRouter>
</Provider>
);
};
Now for the wacky part. I import the history into a display component and use it to set up a listener (to state changes in history.location) and change the state accordingly:
DisplayComponent.js module:
import { history } from '/imports/client/store';
....
export default class EventCreationModal extends Component {
constructor() {
super();
this.handleSelect = this.handleSelect.bind(this);
this.state = {
selectedCategory: (history.location.state && history.location.state.category) ? history.location.state.category : 'sports',
};
history.listen((location,action) => {
this.setState({
selectedCategory: (history.location.state && history.location.state.category) ? history.location.state.category : 'sports',
})
});
}
handleSelect(key) {
history.push("/Home",{'category':key})
}
render () {
return (
<Tabs activeKey={this.state.selectedCategory} onSelect={this.handleSelect} id="SportOrSocialSelector">
<Tab eventKey={'sports} .../>
<Tab eventKey={'social} .../>
</Tabs>
)
}

Related

Whys is this useEffect not being triggered despite the location changing? [duplicate]

With react-router I can use the Link element to create links which are natively handled by react router.
I see internally it calls this.context.transitionTo(...).
I want to do a navigation. Not from a link, but from a dropdown selection (as an example). How can I do this in code? What is this.context?
I saw the Navigation mixin, but can I do this without mixins?
UPDATE: 2022: React Router v6.6.1 with useNavigate
The useHistory() hook is now deprecated. If you are using React Router 6, the proper way to navigate programmatically is as follows:
import { useNavigate } from "react-router-dom";
function HomeButton() {
const navigate = useNavigate();
function handleClick() {
navigate("/home");
}
return (
<button type="button" onClick={handleClick}>
Go home
</button>
);
}
React Router v5.1.0 with hooks
There is a new useHistory hook in React Router >5.1.0 if you are using React >16.8.0 and functional components.
import { useHistory } from "react-router-dom";
function HomeButton() {
const history = useHistory();
function handleClick() {
history.push("/home");
}
return (
<button type="button" onClick={handleClick}>
Go home
</button>
);
}
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 create a 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 (React's Context API is stable as of v16).
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 v6+ Answer
TL;DR: You can use the new useNavigate hook.
import { useNavigate } from "react-router-dom";
function Component() {
let navigate = useNavigate();
// Somewhere in your code, e.g. inside a handler:
navigate("/posts");
}
The useNavigate hook returns a function which can be used for programmatic navigation.
Example from the react router documentaion
import { useNavigate } from "react-router-dom";
function SignupForm() {
let navigate = useNavigate();
async function handleSubmit(event) {
event.preventDefault();
await submitForm(event.target);
navigate("../success", { replace: true });
// replace: true will replace the current entry in
// the history stack instead of adding a new one.
}
return <form onSubmit={handleSubmit}>{/* ... */}</form>;
}
React-Router 5.1.0+ Answer (using hooks and React >16.8)
You can use the useHistory hook on Functional Components and Programmatically navigate:
import { useHistory } from "react-router-dom";
function HomeButton() {
let history = useHistory();
// use history.push('/some/path') here
};
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
};
NOTE: this.props.history does not exist in the case your component was not rendered by <Route>. You should use <Route path="..." component={YourComponent}/> to have this.props.history in YourComponent
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.
React-Router 4.x answer
On my end, I like to have a single history object that I can carry even outside components. I like 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>;
}
}
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>;
}
}
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 v6
I haven't touched React in a while, but want to thank and highlight the comment below by Shimrit Snapir:
on React-Router 6.0 <Redirect /> changed to <Navigate />
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>
)
}
}
A full example is here. Read more here.
PS. The example uses ES7+ Property Initializers to initialise state. Look here as well, if you're interested.
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
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'})'
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;
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'));
It may not be the best approach but... Using react-router v4, the following TypeScript code 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.
"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')
);
In React Router v4, I follow these two ways to route programmatically.
this.props.history.push("/something/something")
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
In React Router v6
import { useNavigate } from "react-router-dom";
function Invoices() {
let navigate = useNavigate();
return (
<div>
<NewInvoiceForm
onSubmit={async event => {
let newInvoice = await createInvoice(event.target);
navigate(`/invoices/${newInvoice.id}`);
}}
/>
</div>
);
}
Getting Started with React Router v6
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);
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);
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.
Based on the previous answers from José Antonio Postigo and Ben Wheeler:
The novelty? Is to be written in TypeScript and uses decorators
or a 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"
For those who are already using React Router v6, this can be done using useNavigate hook provided by react-router.
Navigation with this hook is pretty simple:
import { generatePath, useNavigate } from 'react-router';
navigate(-1); // navigates back
navigate('/my/path'); // navigates to a specific path
navigate(generatePath('my/path/:id', { id: 1 })); // navigates to a dynamic path, generatePath is very useful for url replacements
For Latest react-router-dom v6
useHistory() is replaced with useNavigate().
You need to use:
import { useNavigate } from 'react-router-dom';
const navigate = useNavigate();
navigate('/your-page-link');
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 and
context.history are deprecated. Please use context.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;
React Router v6 with hooks
import {useNavigate} from 'react-router-dom';
let navigate = useNavigate();
navigate('home');
And to move across the browser history,
navigate(-1); ---> Go back
navigate(1); ---> Go forward
navigate(-2); ---> Move two steps backward.
If you are using hash or browser history then you can do
hashHistory.push('/login');
browserHistory.push('/login');
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"));
trippler
Those who are facing issues in implementing this in React Router v4.
Here is a working solution for navigating through the React app from redux actions.
File history.js
import createHistory from 'history/createBrowserHistory'
export default createHistory()
Files App.js/Route.jsx
import { Router, Route } from 'react-router-dom'
import history from './history'
...
<Router history={history}>
<Route path="/test" component={Test}/>
</Router>
File *another_file.js or redux file
import history from './history'
history.push('/test') // This should change the URL and rerender Test component
All thanks to this comment on GitHub:
ReactTraining issues comment
You can also use the useHistory hook in a stateless component. Example from the documentation:
import { useHistory } from "react-router"
function HomeButton() {
const history = useHistory()
return (
<button type="button" onClick={() => history.push("/home")}>
Go home
</button>
)
}
Note: Hooks were added in react-router#5.1.0 and require react#>=16.8
Programmatically navigate in class-based components.
import { Redirect } from "react-router-dom";
class MyComponent extends React.Component{
state = {rpath: null}
const goTo = (path) => this.setState({rpath: path});
render(){
if(this.state.rpath){
return <Redirect to={this.state.rpath}/>
}
.....
.....
}
}
In my answer there are three different ways to redirect programmatically to a route. Some of the solutions has been presented already, but the following ones focused only for functional components with an additional demo application.
Using the following versions:
react: 16.13.1
react-dom: 16.13.1
react-router: 5.2.0
react-router-dom: 5.2.0
typescript: 3.7.2
Configuration:
So first of all the solution is using HashRouter, configured as follows:
<HashRouter>
// ... buttons for redirect
<Switch>
<Route exact path="/(|home)" children={Home} />
<Route exact path="/usehistory" children={UseHistoryResult} />
<Route exact path="/withrouter" children={WithRouterResult} />
<Route exact path="/redirectpush" children={RedirectPushResult} />
<Route children={Home} />
</Switch>
</HashRouter>
From the documentation about <HashRouter>:
A <Router> that uses the hash portion of the URL (i.e. window.location.hash) to keep your UI in sync with the URL.
Solutions:
Using <Redirect> to push using useState:
Using in a functional component (RedirectPushAction component from my repository) we can use useState to handle redirect. The tricky part is once the redirection happened, we need to set the redirect state back to false. By using setTimeOut with 0 delay we are waiting until React commits Redirect to the DOM and then getting back the button in order to use it the next time.
Please find my example below:
const [redirect, setRedirect] = useState(false);
const handleRedirect = useCallback(() => {
let render = null;
if (redirect) {
render = <Redirect to="/redirectpush" push={true} />
// In order wait until committing to the DOM
// and get back the button for clicking next time
setTimeout(() => setRedirect(false), 0);
}
return render;
}, [redirect]);
return <>
{handleRedirect()}
<button onClick={() => setRedirect(true)}>
Redirect push
</button>
</>
From <Redirect> documentation:
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 (HTTP 3xx) do.
Using useHistory hook:
In my solution there is a component called UseHistoryAction which represents the following:
let history = useHistory();
return <button onClick={() => { history.push('/usehistory') }}>
useHistory redirect
</button>
The useHistory hook gives us access to the history object which helps us programmatically navigate or change routes.
Using withRouter, get the history from props:
Created one component called WithRouterAction, displays as below:
const WithRouterAction = (props:any) => {
const { history } = props;
return <button onClick={() => { history.push('/withrouter') }}>
withRouter redirect
</button>
}
export default withRouter(WithRouterAction);
Reading from withRouter documentation:
You can get access to the history object's properties and the closest <Route>'s match via the withRouter higher-order component. withRouter will pass updated match, location, and history props to the wrapped component whenever it renders.
Demo:
For better representation I have built a GitHub repository with these examples, please find it below:
React Router Programmatically Redirect Examples
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, 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 you happen to pair RR4 with redux through react-router-redux, using the routing action creators from react-router-redux is an 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 you 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.

Programmatically navigate using with React router v4 [duplicate]

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?
If you are targeting browser environments, you need to use react-router-dom package, instead of react-router. They are following the same approach as React did, in order to separate the core, (react) and the platform specific code, (react-dom, react-native ) with the subtle difference that you don't need to install two separate packages, so the environment packages contain everything you need. You can add it to your project as:
yarn add react-router-dom
or
npm i react-router-dom
The first thing you need to do is to provide a <BrowserRouter> as the top most parent component in your application. <BrowserRouter> uses the HTML5 history API and manages it for you, so you don't have to worry about instantiating it yourself and passing it down to the <BrowserRouter> component as a prop (as you needed to do in previous versions).
In V4, for navigating programatically you need to access the history object, which is available through React context, as long as you have a <BrowserRouter> provider component as the top most parent in your application. The library exposes through context the router object, that itself contains history as a property. The history interface offers several navigation methods, such as push, replace and goBack, among others. You can check the whole list of properties and methods here.
Important Note to Redux/Mobx users
If you are using redux or mobx as your state management library in your application, you may have come across issues with components that should be location-aware but are not re-rendered after triggering an URL update
That's happening because react-router passes location to components using the context model.
Both connect and observer create components whose shouldComponentUpdate methods do a shallow comparison of their current props and their next props. Those components will only re-render when at least one prop has changed. This means that in order to ensure they update when the location changes, they will need to be given a prop that changes when the location changes.
The 2 approaches for solving this are:
Wrap your connected component in a pathless <Route />. The current location object is one of the props that a <Route> passes to the component it renders
Wrap your connected component with the withRouter higher-order component, that in fact has the same effect and injects location as a prop
Setting that aside, there are four ways to navigate programatically, ordered by recommendation:
1.- Using a <Route> Component It promotes a declarative style. Prior to v4, <Route /> components were placed at the top of your component hierarchy, having to think of your routes structure beforehand. However, now you can have <Route> components anywhere in your tree, allowing you to have a finer control for conditionally rendering depending on the URL. Route injects match, location and history as props into your component. The navigation methods (such as push, replace, goBack...) are available as properties of the history object.
There are 3 ways to render something with a Route, by using either component, render or children props, but don't use more than one in the same Route. The choice depends on the use case, but basically the first two options will only render your component if the path matches the url location, whereas with children the component will be rendered whether the path matches the location or not (useful for adjusting the UI based on URL matching).
If you want to customise your component rendering output, you need to wrap your component in a function and use the render option, in order to pass to your component any other props you desire, apart from match, location and history. An example to illustrate:
import { BrowserRouter as Router } from 'react-router-dom'
const ButtonToNavigate = ({ title, history }) => (
<button
type="button"
onClick={() => history.push('/my-new-location')}
>
{title}
</button>
);
const SomeComponent = () => (
<Route path="/" render={(props) => <ButtonToNavigate {...props} title="Navigate elsewhere" />} />
)
const App = () => (
<Router>
<SomeComponent /> // Notice how in v4 we can have any other component interleaved
<AnotherComponent />
</Router>
);
2.- Using withRouter HoC
This higher order component will inject the same props as Route. However, it carries along the limitation that you can have only 1 HoC per file.
import { withRouter } from 'react-router-dom'
const ButtonToNavigate = ({ history }) => (
<button
type="button"
onClick={() => history.push('/my-new-location')}
>
Navigate
</button>
);
ButtonToNavigate.propTypes = {
history: React.PropTypes.shape({
push: React.PropTypes.func.isRequired,
}),
};
export default withRouter(ButtonToNavigate);
3.- Using a Redirect component Rendering a <Redirect> will navigate to a new location. But keep in mind that, by default, the current location is replaced by the new one, like server-side redirects (HTTP 3xx). The new location is provided by to prop, that can be a string (URL to redirect to) or a location object. If you want to push a new entry onto the history instead, pass a push prop as well and set it to true
<Redirect to="/your-new-location" push />
4.- Accessing router manually through context A bit discouraged because context is still an experimental API and it is likely to break/change in future releases of React
const ButtonToNavigate = (props, context) => (
<button
type="button"
onClick={() => context.router.history.push('/my-new-location')}
>
Navigate to a new location
</button>
);
ButtonToNavigate.contextTypes = {
router: React.PropTypes.shape({
history: React.PropTypes.object.isRequired,
}),
};
Needless to say there are also other Router components that are meant to be for non browser ecosystems, such as <NativeRouter> that replicates a navigation stack in memory and targets React Native platform, available through react-router-native package.
For any further reference, don't hesitate to take a look at the official docs. There is also a video made by one of the co-authors of the library that provides a pretty cool introduction to react-router v4, highlighting some of the major changes.
The easiest way to get it done:
this.props.history.push("/new/url")
Note:
You may want to pass the history prop from parent component down to the component you want to invoke the action if its not available.
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.
Steps:
Install the history npm 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.
Use useHistory hook if you're using function components
You 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.
Use history property inside page components
React 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>
);
}
}
Wrap child components withRouter to inject router properties
withRouter 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;
You can also simply use props to access history object: this.props.history.push('new_url')
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.
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 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.
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;
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'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.
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)
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>
)
}
}
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');

ReactJS -How to create multistep component/form with single path using React Router

I want to switch between components after the user entered the requested info.
Components that will be shown to user by this order:
{MobileNum } Enter mobile number
{IdNumber } ID number
{CreatePassword } Create Password
When all these steps are completed the browser will switch to the home page.
The user must not be able to move between pages until he filled each request in each component.
Now I want a better way with router as if I had 3-4 components inside Login, and must be in a secured whey, also the user must not be able to switch components manually through the URL.
import React, { Component } from 'react';
import {
BrowserRouter as Router,
Redirect,
Route,
Switch,
} from 'react-router-dom';
import MobileNum from './MobileNum.jsx';
import IdNumber from './IdNum.jsx';
import CreatePassword from './createPassword .jsx';
class SignUp extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<Router>
<Switch>
//// Here needs to know how to navigate to each component on its turn
<Route path='/' component={MobileNum} />
<Route path='/' component={IdNumber} />
<Route path='/' component={CreatePassword } />
</Switch>
</Router>
</div>
);
}
}
export default SignUp ;
I searched the web in reactrouter.com and many others as here for a clean solution but found no answer.
Any Idea what's the best way to do it ?
Thanks
Since router variable like location are immutable, conditional rendering itself would be better option, you can try switch if you don't want to use if else.
I have given an example below, you have to fire that afterSubmit when values are submitted in each component .If you use redux, you could implement it better as you can store the value in redux state and set it directly from each component using dipatch.
//App.js
import React, { useState } from 'react';
import MobileNum from './MobileNum.jsx';
import IdNumber from './IdNum.jsx';
import CreatePassword from './createPassword .jsx';
function App (){
const [stage,setStage]= useState(1);
switch(stage){
case 2:
return <IdNumber afterSubmit={setStage.bind(null,3)}/>
break;
case 3:
return <CreatePassword afterSubmit={setStage.bind(null,4)} />
case 4:
return <Home />
break;
default:
return <MobileNum afterSubmit={setStage.bind(null,2)}/>
}
}
export default App;
//Root
import React, { Component } from 'react';
import App from './App.jsx';
import {
BrowserRouter as Router,
Redirect,
Route,
Switch,
} from 'react-router-dom';
class Login extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<Router>
<Switch>
<Route path='/' component={App} />
</Switch>
</Router>
</div>
);
}
}
//Add on - Sign up form class based
class SignUp extends React.Component {
constructor(props) {
super(props);
this.state = { stage: 1 };
}
render() {
switch (this.state.stage) {
case 2:
return <IdNumber afterSubmit={() => this.setState({ stage: 3 })} />;
break;
case 3:
return <CreatePassword afterSubmit={() => this.setState({ stage: 4 })} />;
case 4:
return <Home />;
break;
default:
return <MobileNum afterSubmit={() => this.setState({ stage: 2 })} />;
}
}
}
It will take special handling in React Router to meet your security requirements. I personally would load the multi-step wizard on one URL rather than changing the URL for each step as this simplifies things and avoids a lot of potential issues. You can get the setup that you want, but it is much more difficult than it needs to be.
Path-Based Routing
I am using the new React Router v6 alpha for this answer, as it makes nested routes much easier. I am using /signup as the path to our form and URLs like /signup/password for the individual steps.
Your main app routing might look something like this:
import { Suspense, lazy } from "react";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Header from "./Header";
import Footer from "./Footer";
const Home = lazy(() => import("./Home"));
const MultiStepForm = lazy(() => import("./MultiStepForm/index"));
export default function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<BrowserRouter>
<Header />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/signup/*" element={<MultiStepForm/>} />
</Routes>
<Footer />
</BrowserRouter>
</Suspense>
);
}
You'll handle the individual step paths inside the MultiStepForm component. You can share certain parts of the form across all steps. The part which is your Routes should just be the part that is different, ie. the form fields.
Your nested Routes object inside the MultiStepForm is essentially this:
<Routes>
<Route path="/" element={<Mobile />} />
<Route path="username" element={<Username />} />
<Route path="password" element={<Password />} />
</Routes>
But we are going to need to know the order of our route paths in order to handle "Previous" and "Next" links. So in my opinion it makes more sense to generate the routes based on a configuration array. In React Router v5 you would pass your config as props to a <Route/>. In v6 you can skip that step and use object-based routing.
import React, { lazy } from "react";
const Mobile = lazy(() => import("./Mobile"));
const Username = lazy(() => import("./Username"));
const Password = lazy(() => import("./Password"));
/**
* The steps in the correct order.
* Contains the slug for the URL and the render component.
*/
export const stepOrder = [
{
path: "",
element: <Mobile />
},
{
path: "username",
element: <Username />
},
{
path: "password",
element: <Password />
}
];
// derived path order is just a helper
const pathOrder = stepOrder.map((o) => o.path);
Note that these components are called with no props. I am assuming that they get all of the information that they need through contexts. If you want to pass down props then you will need to refactor this.
import { useRoutes } from "react-router-dom";
import { stepOrder } from "./order";
import Progress from "./Progress";
export default function MultiStepForm() {
const stepElement = useRoutes(stepOrder);
return (
<div>
<Progress />
{stepElement}
</div>
);
}
Current Position
This is the part where things start to become convoluted. It seems that useRouteMatch has been removed in v6 (for now at least).
We can access the matched wildcard portion on the URL using the "*" property on the useParams hook. But this feels like it might be a bug rather than an intentional behavior, so I'm concerned that it could change in a future release. Keep that in mind. But it does work currently.
We can do this inside of a custom hook so that we can derive other useful information.
export const useCurrentPosition = () => {
// access slug from the URL and find its step number
const urlSlug = useParams()["*"]?.toLowerCase();
// note: will be -1 if slug is invalid, so replace with 0
const index = urlSlug ? pathOrder.indexOf(urlSlug) || 0 : 0;
const slug = pathOrder[index];
// prev and next might be undefined, depending on the index
const previousSlug = pathOrder[index - 1];
const nextSlug = pathOrder[index + 1];
return {
slug,
index,
isFirst: previousSlug === undefined,
isLast: nextSlug === undefined,
previousSlug,
nextSlug
};
};
Next Step
The user must not be able to move between pages until he filled each request in each component.
You will need some sort of form validation. You could wait to validate until the user clicks the "Next" button, but most modern websites choose to validate the data every time that the form changes. Packages like Formik and Yup are a huge help with this. Check out the examples in the Formik Validation docs.
You will have an isValid boolean which tells you when the user is allowed to move on. You can use that to set the disabled prop on the "Next" button. That button should have type="submit" so that its clicks can be handled by the onSubmit action of the form.
We can make that logic into a PrevNextLinks component which we can use in each form. This component uses the formik context so it must be rendered inside of a <Formik/> form.
We can use the info from our useCurrentPosition hook to render a Link to the previous step.
import { useFormikContext } from "formik";
import { Link } from "react-router-dom";
import { useCurrentPosition } from "./order";
/**
* Needs to be rendered inside of a Formik component.
*/
export default function PrevNextLinks() {
const { isValid } = useFormikContext();
const { isFirst, isLast, previousSlug } = useCurrentPosition();
return (
<div>
{/* button links to the previous step, if exists */}
{isFirst || (
<Link to={`/form/${previousSlug}`}>
<button>Previous</button>
</Link>
)}
{/* button to next step -- submit action on the form handles the action */}
<button type="submit" disabled={!isValid}>
{isLast ? "Submit" : "Next"}
</button>
</div>
);
}
Here's an example of how one step might look:
import { Formik, Form, Field, ErrorMessage } from "formik";
import React from "react";
import { useDispatch } from "react-redux";
import { useNavigate } from "react-router";
import Yup from "yup";
import "yup-phone";
import PrevNextLinks from "./PrevNextLinks";
import { useCurrentPosition } from "./order";
import { saveStep } from "../../store/slice";
const MobileSchema = Yup.object().shape({
number: Yup.string()
.min(10)
.phone("US", true)
.required("A valid US phone number is required")
});
export default function MobileForm() {
const { index, nextSlug, isLast } = useCurrentPosition();
const dispatch = useDispatch();
const navigate = useNavigate();
return (
<div>
<h1>Signup</h1>
<Formik
initialValues={{
number: ""
}}
validationSchema={MobileSchema}
validateOnMount={true}
onSubmit={(values) => {
// I'm iffy on this part. The dispatch and the navigate will occur simoultaneously,
// so you should not assume that the dispatch is finished before the target page is loaded.
dispatch(saveStep({ values, index }));
navigate(isLast ? "/" : `/signup/${nextSlug}`);
}}
>
{({ errors, touched }) => (
<Form>
<label>
Mobile Number
<Field name="number" type="tel" />
</label>
<ErrorMessage name="number" />
<PrevNextLinks />
</Form>
)}
</Formik>
</div>
);
}
Preventing Access via URL
The user must not be able to switch components manually through the URL.
We need to redirect the user if they attempt to access a page which they are not permitted to view. The Redirects (Auth) example in the docs should give you some ideas on how this is implemented. This PrivateRoute component in particular:
// A wrapper for <Route> that redirects to the login
// screen if you're not yet authenticated.
function PrivateRoute({ children, ...rest }) {
let auth = useAuth();
return (
<Route
{...rest}
render={({ location }) =>
auth.user ? (
children
) : (
<Redirect
to={{
pathname: "/login",
state: { from: location }
}}
/>
)
}
/>
);
}
But what is your equivalent version of useAuth?
Idea: Look at the Current Progress
We could allow the visitor to view their current step and any previously entered steps. We look to see if the user is allowed to view the step which they are attempting to access. If yes, we load that content. If no, you can redirect them to their correct step or to the first step.
You would need to know what progress has been completed. That information needs to exist somewhere higher-up in the chain like localStorage, a parent component, Redux, a context provider, etc. Which you choose is up to you and there will be some differences. For example using localStorage will persist a partially-completed form while the others will not.
Where you store is less important that What you store. We want to allow backwards navigation to previous steps and forwards navigation if going to a previously-visited step. So we need to know which steps we can access and which we can't. The order matters so we want some sort of array. We would figure out the maximum step which we are allowed to access and compare that to the requested step.
Your component might look like this:
import { useRoutes, Navigate } from "react-router-dom";
import { useSelector } from "../../store";
import { stepOrder, useCurrentPosition } from "./order";
import Progress from "./Progress";
export default function MultiStepForm() {
const stepElement = useRoutes(stepOrder);
// attempting to access step
const { index } = useCurrentPosition();
// check that we have data for all previous steps
const submittedStepData = useSelector((state) => state.multiStepForm);
const canAccess = submittedStepData.length >= index;
// redirect to first step
if (!canAccess) {
return <Navigate to="" replace />;
}
// or load the requested step
return (
<div>
<Progress />
{stepElement}
</div>
);
}
CodeSandbox Link. (Note: Most of the code in the three step forms can and should be combined).
This is all getting rather complicated, so let's try something simpler.
Idea: Require that the URL Be Accessed from a Previous/Next Link
We can use the state property of a location to pass through some sort of information that lets us know that we've come from the correct place. Like {fromForm: true}. Your MultiStepForm can redirect all traffic that lacks this property to the first step.
const {state} = useLocation();
if ( ! state?.fromForm ) {
return <Navigate to="" replace state={{fromForm: true}}/>
}
You would make sure that all of your Link and navigate actions inside of the form are passing this state.
<Link to={`/signup/${previousSlug}`} state={{fromForm: true}}>
<button>Previous</button>
</Link>
navigate(`/signup/${nextSlug}`, {state: { fromForm: true} });
With No Path Change
After having written quite a lot of code and explanation about authenticating a path, I've realized that you haven't explicitly said that the path needs to change.
I just need to use react-router-dom properties to navigate.
So you could make use of the state property on the location object to control the current step. You pass the state through your Link and navigate the same as above, but with an object like {step: 1} instead of {fromForm: true}.
<Link to="" replace state={{step: 2}}>Next</Link>
You can majorly simplify your code by doing this. Though we come back to a fundamental question of why. Why use React Router if the important information is a state? Why not just use a local component state (or Redux state) and call setState when you click on the "Next" button?
Here's a good article with a fully-implemented code example using local state and the Material UI Stepper component:
Build a multi-step form with React Hooks, Formik, Yup and MaterialUI by Vu Nguyen
CodeSandbox
When all these steps are completed the browser will switch to the home page.
There are two ways to handle your final redirect to the home page. You can conditionally render a <Navigate/> component (<Redirect/> in v5) or you can call navigate() (history.push() in v5) in response to a user action. Both versions are explained in detail in the React Router v6 Migration guide.
I don't think adding React Router or any library changes the way how we solve a problem in React.
Your earlier approach was fine. You could wrap all it in a new component, like,
class MultiStepForm extends React.Component {
constructor(props) {
super(props);
this.state = {
askMobile: true,
};
};
askIdentification = (passed) => {
if (passed) {
this.setState({ askMobile: false });
}
};
render() {
return (
<div className='App-div'>
<Header />
{this.state.askMobile ? (
<MobileNum legit={this.askIdentification} />
) : (
<IdNumber />
)}
</div>
);
}
}
Then use this component on your Route.
...
<Switch>
<Route path='/' component={MultiStepForm} />
// <Route path='/' component={MobileNum} />
// <Route path='/' component={IdNumber} />
// <Route path='/' component={CreatePassword } />
</Switch>
...
Now how you'd like to move on with this is a completely new question.
Also, I have corrected the spelling of askIdentification.

Passing props from parent to sibling in React

I am recreating a simple React app that I have already created in Angular. The React app has two components: one (menus.js) for a side menu and a second (content.js) that will display the content from each item in the menu when each link is clicked (just like an iframe of sorts). In the App.js I am making a REST API call to populate the state for the menus.js component. Note that both components are in the App.js as follows:
App.js
import React,{Component} from 'react';
import Menus from './components/menus';
import Content from './components/content';
class App extends Component {
state = {
menus: []
}
componentDidMount(){
fetch('api address')
.then(res => res.json())
.then((data)=> {
this.setState({menus: data})
})
.catch(console.log)
}
render(){
return (
<div>
<div><Menus menus={this.state.menus} /></div>
<div><Content /></div>
</div>
);
}
}
export default App;
here is the menu.js component; it takes a prop (menus) from App.js and builds the menu links with items from it:
import React from 'react';
import { BrowserRouter as Router, Link,} from "react-router-dom";
const Menus = ({ menus }) => {
return (
<Router>
<div>
<center><h1>Lessons</h1></center>
{menus.map(menu => (
<li key={menu.lesson}>
<Link to={`/lesson/${menu.lesson}`}>{menu.lessonName}</Link>
</li>
))}
</div>
</Router>
);
};
export default Menus;
Here is what I need - how do I pass items from the same prop (from App.js) to the content component? FYI - I need this to happen each time a link in the menu in menu.js is clicked (which is why a key is used in the list The simple idea is content will update in the content component each time a menu link in the menu component is clicked.
**content.js**
import React from 'react'
const Content = () => {
return (
<div>{menu.content}</div>
)
};
export default Content
Based on your description of the problem and what I can see of what you've written, it seems to me like you are trying to build an application where the menu persists, but the content changes based on menu clicks. For a simple application, this is how I would structure it differently.
<ParentmostComponent>
<MenuComponent someProp={this.state.some_data} />
<Switch>
<Route path={"/path"} render={(props) => <Dashboard {...props} someProp={this.state.some_other_data_from_parents} />
</Switch>
</ParentMostComponent>
This would allow the menu to always stay there no matter what the content is doing, and you also won't have to pass the menu prop to two components.
In your menu.js, attach the menu object to the Link
...
{menus.map(menu => (
<li key={menu.lesson}>
<Link to={{
pathname: `/lesson/${menu.lesson}`,
state: menu
}}> {menu.lessonName} </Link>
</li>
))}
...
In your content.js receive the menu like this:
import React from 'react'
const Content = () => {
console.log(props.location.state.menu.content);
return (
<div>{props.location.state && props.location.state.menu.content }</div>
)
};
export default Content
Read more here
Your example uses React Router, so this answer uses it as well.
First of all, move the Router up the hierarchy from Menus to App to make the router props available to all components. Then wrap your Content inside a Route to render it conditionally (i.e. if the path matches "/lesson/:lesson"):
class App extends Component {
state = {
menus: [
{
lesson: '61355373',
lessonName: 'Passing props from parent to sibling in React',
content: 'I am recreating a simple React app…'
},
{
lesson: '27991366',
lessonName: 'What is the difference between state and props in React?',
content: 'I was watching a Pluralsight course on React…'
}
]
}
render() {
const { menus } = this.state
return (
<Router>
<div>
<div><Menus menus={menus}/></div>
<Route path="/lesson/:lesson" render={({ match }) => (
<div><Content menu={menus.find(menu => menu.lesson === match.params.lesson)}/></div>
)}>
</Route>
</div>
</Router>
)
}
}
With the help of the render prop, you can access the router props (in this case match.params.lesson) before rendering your child component. We use them to pass the selected menu to Content. Done!
Note: The basic technique (without React Router, Redux etc.) to pass props between siblings is to lift the state up.

Programmatically navigate using react router V4

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?
If you are targeting browser environments, you need to use react-router-dom package, instead of react-router. They are following the same approach as React did, in order to separate the core, (react) and the platform specific code, (react-dom, react-native ) with the subtle difference that you don't need to install two separate packages, so the environment packages contain everything you need. You can add it to your project as:
yarn add react-router-dom
or
npm i react-router-dom
The first thing you need to do is to provide a <BrowserRouter> as the top most parent component in your application. <BrowserRouter> uses the HTML5 history API and manages it for you, so you don't have to worry about instantiating it yourself and passing it down to the <BrowserRouter> component as a prop (as you needed to do in previous versions).
In V4, for navigating programatically you need to access the history object, which is available through React context, as long as you have a <BrowserRouter> provider component as the top most parent in your application. The library exposes through context the router object, that itself contains history as a property. The history interface offers several navigation methods, such as push, replace and goBack, among others. You can check the whole list of properties and methods here.
Important Note to Redux/Mobx users
If you are using redux or mobx as your state management library in your application, you may have come across issues with components that should be location-aware but are not re-rendered after triggering an URL update
That's happening because react-router passes location to components using the context model.
Both connect and observer create components whose shouldComponentUpdate methods do a shallow comparison of their current props and their next props. Those components will only re-render when at least one prop has changed. This means that in order to ensure they update when the location changes, they will need to be given a prop that changes when the location changes.
The 2 approaches for solving this are:
Wrap your connected component in a pathless <Route />. The current location object is one of the props that a <Route> passes to the component it renders
Wrap your connected component with the withRouter higher-order component, that in fact has the same effect and injects location as a prop
Setting that aside, there are four ways to navigate programatically, ordered by recommendation:
1.- Using a <Route> Component It promotes a declarative style. Prior to v4, <Route /> components were placed at the top of your component hierarchy, having to think of your routes structure beforehand. However, now you can have <Route> components anywhere in your tree, allowing you to have a finer control for conditionally rendering depending on the URL. Route injects match, location and history as props into your component. The navigation methods (such as push, replace, goBack...) are available as properties of the history object.
There are 3 ways to render something with a Route, by using either component, render or children props, but don't use more than one in the same Route. The choice depends on the use case, but basically the first two options will only render your component if the path matches the url location, whereas with children the component will be rendered whether the path matches the location or not (useful for adjusting the UI based on URL matching).
If you want to customise your component rendering output, you need to wrap your component in a function and use the render option, in order to pass to your component any other props you desire, apart from match, location and history. An example to illustrate:
import { BrowserRouter as Router } from 'react-router-dom'
const ButtonToNavigate = ({ title, history }) => (
<button
type="button"
onClick={() => history.push('/my-new-location')}
>
{title}
</button>
);
const SomeComponent = () => (
<Route path="/" render={(props) => <ButtonToNavigate {...props} title="Navigate elsewhere" />} />
)
const App = () => (
<Router>
<SomeComponent /> // Notice how in v4 we can have any other component interleaved
<AnotherComponent />
</Router>
);
2.- Using withRouter HoC
This higher order component will inject the same props as Route. However, it carries along the limitation that you can have only 1 HoC per file.
import { withRouter } from 'react-router-dom'
const ButtonToNavigate = ({ history }) => (
<button
type="button"
onClick={() => history.push('/my-new-location')}
>
Navigate
</button>
);
ButtonToNavigate.propTypes = {
history: React.PropTypes.shape({
push: React.PropTypes.func.isRequired,
}),
};
export default withRouter(ButtonToNavigate);
3.- Using a Redirect component Rendering a <Redirect> will navigate to a new location. But keep in mind that, by default, the current location is replaced by the new one, like server-side redirects (HTTP 3xx). The new location is provided by to prop, that can be a string (URL to redirect to) or a location object. If you want to push a new entry onto the history instead, pass a push prop as well and set it to true
<Redirect to="/your-new-location" push />
4.- Accessing router manually through context A bit discouraged because context is still an experimental API and it is likely to break/change in future releases of React
const ButtonToNavigate = (props, context) => (
<button
type="button"
onClick={() => context.router.history.push('/my-new-location')}
>
Navigate to a new location
</button>
);
ButtonToNavigate.contextTypes = {
router: React.PropTypes.shape({
history: React.PropTypes.object.isRequired,
}),
};
Needless to say there are also other Router components that are meant to be for non browser ecosystems, such as <NativeRouter> that replicates a navigation stack in memory and targets React Native platform, available through react-router-native package.
For any further reference, don't hesitate to take a look at the official docs. There is also a video made by one of the co-authors of the library that provides a pretty cool introduction to react-router v4, highlighting some of the major changes.
The easiest way to get it done:
this.props.history.push("/new/url")
Note:
You may want to pass the history prop from parent component down to the component you want to invoke the action if its not available.
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.
Steps:
Install the history npm 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.
Use useHistory hook if you're using function components
You 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.
Use history property inside page components
React 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>
);
}
}
Wrap child components withRouter to inject router properties
withRouter 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;
You can also simply use props to access history object: this.props.history.push('new_url')
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.
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 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.
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;
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'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.
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)
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>
)
}
}
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');

Resources