Dmitry Malugin
Dmitry Malugin

Reputation: 921

Unable to redirect programmatically with react-router 4

I've read a lot of docs and SOF questions about this subject I used a lot of methods to do this but none of them worked in a way I need. I suppose I am missing something important. It's hard to provide detailed and relevant code on every navigation method I used, I'll try it.

Here some introdactory imformation. I had react-redux app, I need to get state from server and redirect to page according to this state.

Simplified version of my App class render method:

 render() {
    console.log("App.render");
    console.log(this.state);
    return (

      <div id='game-container' width="1126" height="634">
        <Router>
        <div>
          <Route exact path="/" component={Page1}/>
          <Route path="/bets" component={Page2}/>
          <Route path="/games" component={Page3}/>
          <Route path="/newpage" component={Page4}/>
          <Route path="/info" component={ Info }/>
        </div>
        </Router>
        <Overlay/>
      </div>
    );
  }
} 

App component wrapped with BrowserRouter in index.js file

ReactDOM.render(
  <Provider store={myStore}>
    <BrowserRouter>
      <App assets={ assets } locale={ qs['locale']} token={ qs['token']} sounds={ sounds } />
    </BrowserRouter>
  </Provider>
  , document.querySelector('.game-wrapper'));

Method 1. withRouter

For it's realisation I added this code:

  App.propTypes = {
      history: React.PropTypes.shape({
      push: React.PropTypes.func.isRequired,
      }).isRequired,
      location: React.PropTypes.object.isRequired,
    };


function mapStateToProps(state) {
    console.log("app#mapStateToProps");
    console.log(state.session);
    return { session:  state.session }
}

function mapDispatchToProps(dispatch) {
    return bindActionCreators({ fetchInitialSession }, dispatch);
}

export default withRouter(connect(mapStateToProps, { fetchInitialSession })(App));

and then when I am getting session info I try:

 this.props.history.push("/newpage");

url in browser is changing but visual page does not.

Method 2. with context router

For this just added

App.contextTypes = {
  router: React.PropTypes.object
};

and then when I am getting session info I try:

 this.context.router.history.push("/newpage");

Same result as with first method (url is changed but page doesn't).

Method 3. Redirect (found here Programmatically navigate using react router V4)

So my render method looks like this:

render() {
    console.log("App.render");
    console.log(this.state);
    return (

      <div id='game-container' width="1126" height="634">
        <Router>
        <div>
      <Redirect to={{ pathname: this.state.redirectTo }}/>
          <Route exact path="/" component={Page1}/>
          <Route path="/bets" component={Page2}/>
          <Route path="/games" component={Page3}/>
          <Route path="/newpage" component={Page4}/>
          <Route path="/info" component={ Info }/>
        </div>
        </Router>
        <Overlay/>
      </div>
    );
  }
} 

And when I want to change page I set new state:

this.setState({ redirectTo: "/newpage" });

This method gives strange results. When I set initial state in constructor it redirects to any page. But when I use this method anywhere in code I see in debug that render method called with my new state but redirect never happens!

Upvotes: 3

Views: 1430

Answers (1)

Ahmed Wagdi
Ahmed Wagdi

Reputation: 934

In v4 method 3 should be the correct method (using the Redirect component). The problem you're having seems to be because Redirect only takes effect the first time it's mounted but you put Redirect at the top level in your router and it only gets mounted once.

So if you render <Redirect to={{ pathname: this.state.redirectTo }}/> it will only redirect the very first time it's rendered, but it won't redirect if you update the state using:

this.setState({ redirectTo: "/newpage" });

This is why it only worked for you when you set the redirectTo in the constructor but didnt redirect again after that.

Instead you're probably going to have to use Redirect in every component/route you want to trigger a redirect, it's not as tedious as it sounds though because you can just create a custom route component to use instead of the typical Route component and handle all the redirect logic once in your custom component.

You can find an example here where a PrivateRoute and PublicRoute are used instead of the normal Route to check if the user is logged in and redirect accordingly: https://github.com/tylermcginnis/react-router-firebase-auth/blob/master/src/components/index.js

Here's how the PrivateRoute which should only be accessible to logged in users looks like:

function PrivateRoute ({component: Component, authed, ...rest}) {
  return (
    <Route
      {...rest}
      render={(props) => authed === true
        ? <Component {...props} />
        : <Redirect to={{pathname: '/login', state: {from: props.location}}} />}
    />
  )
}

Upvotes: 2

Related Questions