React router browser back button isn't working - javascript

I'm trying to create a multi step form using React JS and react-router.
The form step is changed with state. If I click on the button next the state step is incremented and the next step is shown.
But this way if I'm on for example third step and I click on the back in the browser I'm redirected to the home page instead of a previous step.
My main component is something like this :
componentWillMount(){
console.log(this.props.params.id);
}
onButtonClick(name, event) {
event.preventDefault();
switch (name) {
case "stepFourConfirmation":
if(this.validation("four")) {
this.props.actions.userRegistrationThunk(this.state.user);
this.setState({step: 5, user: {}});
}
break;
case "stepTwoNext":
if(this.validation("two")) {
this.setState({step: 3});
this.context.router.push("stepThree");
}
break;
case "stepThreeFinish":
this.setState({step: 4});
break;
default:
if(this.validation("one")) {
this.setState({step: 2});
this.context.router.push('stepTwo');
}
}
}
On every button click I push the parameter to the url and change the step. When I click next it's working perfect. In componentWillMount I'm trying to get the parameter from the url and I would than decrement the step depending on the parameter.
But only when the first step is loaded I se stepOne in the console. If I click on next I get [react-router] Location "stepTwo" did not match any routes
My routes.js file is as follows :
import React from 'react';
import {IndexRoute, Route} from 'react-router';
import App from './components/App';
import HomePage from './components/HomePage';
import Registration from './components/registration/RegistrationPage';
import UserPage from './components/user/userHome';
import requireAuth from './common/highOrderComponents/requireAuth';
import hideIfLoggedIn from './common/highOrderComponents/hideIfLoggedIn';
import PasswordReset from './common/passwordReset';
const routes = (
<Route path="/" component={App}>
<IndexRoute component={HomePage}/>
<Route path="registration/:id" component={hideIfLoggedIn(Registration)}/>
<Route path="reset-password" component={PasswordReset} />
<Route path="portal" component={requireAuth(UserPage)} />
</Route>
);
export default routes;
Any advice how to solve it?

Try pushing an absolute path. Instead of
this.context.router.push("stepTwo");
try
this.context.router.push("/registration/stepTwo");
Additionally you have a problem with your lifecycle methods. componentWillMount is only called once. When you push the next route, the component is not mounted again, so you don't see the log with the next param. Use componentWillMount for the initial log, componentWillReceiveProps for the next ones:
componentWillMount() {
console.log(this.props.params.id);
}
componentWillReceiveProps(nextProps) {
console.log(nextProps.params.id);
}
Btw: here is a discussion on github. The creators state, that react router is not supporting relative paths

Related

How can I listen to route change in my main App component, using a custom history object?

I have a header that appears in 95% of pages in my site, so I mount it in my main App component. For the other 5% of pages though I need it to be gone.
I figured a good way to do it would be to change a state from true to false based on the current route, and that state would determine whether the header mounts or not.
at first I tried just using window.location.href as a useEffect dependency but that didn't seem to work. I've read about the option to listen to the location of the history, but I keep getting Cannot read property 'history' of undefined. I thing that perhaps it's because I am using a custom history component, or maybe because I try to do so in my main App component? Not sure.
This is my history object:
import { createBrowserHistory } from 'history';
export default createBrowserHistory();
I use it in my Router like this:
<Router history={history}>
CONDITIONAL COMPONENT
<Switch>
...routes
</Switch>
</Router>
This is how I try to listen, where history is my custom history object
const MyHistory = useHistory(history)
useEffect(() => {
return MyHistory.listen((location) => {
console.log(`You changed the page to: ${location.pathname}`)
})
},[MyHistory])
You are trying to use useHistory within a component that renders Router which is a Provider. In order for useHistory to Work it needs to have a Router higher up in the hierarchy.
So either you wrap the App component with Router like
export default () => (
<Router history={history}><App /><Router>
)
or since you define a custom history you can use it directly without using useHistory
useEffect(() => {
return history.listen((location) => {
console.log(`You changed the page to: ${location.pathname}`)
})
},[])
This is how I control every route change in my App. I created a component that listen to the pathname property given by useLocation
import { useEffect } from "react";
import { useLocation } from "react-router-dom";
export default function AppScrollTop() {
const { pathname } = useLocation();
useEffect(() => {
console.log(`You changed the page to: ${pathname}`)
}, [pathname]);
return null;
}
Then I put the component inside the <Router>
<BrowserRouter>
<AppScrollTop />
<Switch>
<Route path="/login" component={Login} />
</Switch>
</BrowserRouter>
I hope this helps.

Redirecting to default route

Is there a way to load a default component for any given route? Lets say for example the only specified route I have is:
<Route path="/car/about-you" component={car} />
However, a user decides to navigate to /car/about-the-car but this route doesn't exist in my app, is there a way to load the default component i.e. in this example the car component?
Yup, add a Route at the end of your Switch component that has path='*'
You can do it by Redirect Component:
import { Route, Redirect} from 'react-router-dom';
...
<Redirect to="/car/about-you" />
it must be added at the end of the Switch Component.

react-router v4 dispatch redux action on page change

I would like an action to be dispatched and then intercepted by the reducers on every react-router page change.
My use case is: I have the page state persisted in redux store. The state is represented as {loading, finished}. When the user clicks a button, a request is made to the server and loading becomes true. When the response comes back positive loading becomes false and finished becomes true. When the user navigates away from the page, I want the state to be reset to its initial values (loading=false, finished=false).
The following answer is great, but not work on v4 anymore because onEnter onChange and onLeave were removed.
React Router + Redux - Dispatch an async action on route change?
Edit: The best solution would be something scalable. In the future there would be multiple such pages and the state for each of them should reset on page nagivation
Thank you!
You could create your history instance separately, and listen to that and dispatch an action when something changes.
Example
// history.js
import createHistory from 'history/createBrowserHistory';
const history = createHistory();
history.listen(location => {
// Dispatch action depending on location...
});
export default history;
// App.js
import { Router, Route } from 'react-router-dom';
import Home from './components/Home';
import Login from './components/Login';
import history from './history';
class App extends React.Component {
render() {
return (
<Router history={history}>
<div>
<Route exact path="/" component={Home} />
<Route path="/login" component={Login} />
</div>
</Router>
)
}
}
v4 has the location object which could be used to check if the app has navigated away from the current page.
https://reacttraining.com/react-router/web/api/location

React Router Dom (4) Programmatically Navigate

I am working on a React app that is 'remote controlled' and am trying to connect the app's tab navigation via Pusher. I am using react-router-dom and have set up my connection to Pusher and the channels it listens to in the componentDidMount. I need to have the app change URL navigation each time a message is returned but cannot figure out what the correct way to 'push' it is. I have google many threads about programmatically navigating react-router-dom and have tried this.history.push(), this.props.history.push() and it's always throwing an error that history is undefined. The listeners reside in the same Component as the routing itself.
import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import axios from 'axios';
import Pusher from 'pusher-js';
import Component1 from './Component1';
import Component2 from './Component2';
export default class AppRouter extends Component{
componentDidMount() {
const pusher = new Pusher('8675309', { cluster: 'us2', encrypted: true });
const nav_channel = pusher.subscribe('nav');
nav_channel.bind('message', data => { this.props.history.push(data.message) });
}
...
render(){
return(
<Router>
<Switch>
<Route path='/path1' render={() => <Component1 navigate={this.handleNavChange} />} />
<Route path="/path2" render={() => <Component2 navigate={this.handleNavChange} />} />
</Switch>
</Router>
)
}
}
I need the app to change the URL or the routing each time the message is received from another connected app but I cannot figure out how to make react-router-dom (v4) do it within the same component as the Router itself. Any information or pointing me to a resource would be highly appreciated.
You need to use the withRouter decorator from React-Router to get access to match, location, and history.
import { withRouter } from 'react-router'
Wrap your component when exporting
export default withRouter(yourComponent)
Then you will have access to history from props.
https://reacttraining.com/react-router/core/api/withRouter
To add on to andrewgi's answer:
After using withRouter() on your component, OR a parent component,
try the following code in your component:
static contextTypes = {
router: PropTypes.object,
location: PropTypes.object
}
These should be defined, you can try console.log(this.context.router, this.context.location)
Then, to navigate programmatically, call
this.context.router.history.push('/someRoute');
Note: the context API is not stable. See Programmatically navigate using react router and https://reactjs.org/docs/context.html#why-not-to-use-context.

React-Router Redirect not working at all, using React-Redux-Electron

I am trying to conditionally redirect to other pages within a modal using from react-router.
I have implemented withRouter at the bottom of the relevant components as well as in the connect function. I am currently not using the Reducer because I have a switch in a root modal component which catches a type and then renders a component. Below is a snippet from the switch.
case type.componentName
return <Redirect to='/component' />
break;
However, the new component is still not rendering. It is as if Redirect is not being registered at all. My route is below.
<App>
<Switch>
<Route path="/" component={HomePage} />
<Route component={upperComponent}>
<Route path="/modal" component={rootComponent}>
<Route path="/component" component={component} />
</Route>
</Route>
</Switch>
</App>
I was originally rendering pages by modifying the state through a boolean and based upon it, a different component would be rendered. This worked just fine but I would rather have some history from using React-Router when I render new pages. Is there something fundamentally wrong about how I am trying to call Redirect or should I use an entirely different strategy all together?
The code below was requested in a comment. This is in my container component and I have one per component.
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import rootComponent from '../components/rooComponent';
import { withRouter } from 'react-router-dom';
import * as rootComponentlActions from '../actions/rootComponent';
function mapStateToProps(state) {
return {
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(rootComponentActions, dispatch);
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(rootComponent));
I have also tried adding export default withRouter(component); as well in my child component to the root as well as in the root to test it out based upon some examples that I have read in the past. As far as I can tell, it made not difference, good or bad.

Categories

Resources