react-router connected to redux : works with links but only the URL change when dispatching push
Asked Answered
B

2

6

I'm trying to programmatically push an URL to navigate with react-router, redux, and connected-react-router

When clicking on a <Link /> button, it's working great, the URL is changing and the route too.

But when using a dispatch(push(url)), the URL only change and the content is not updated

I've made a minimal example here.

Any help would be really grateful,

Thanks

Bilander answered 28/10, 2018 at 14:40 Comment(0)
L
6

A lot of anti-pattern code, poor application structured, and mixing of packages is holding your application back.

I rewrote it entirely, here's what I've done:

  1. Reconfigured your application folder's structure to be standard.
  2. Don't mix Router (BrowserRouter) with ConnectedRouter.
  3. Don't place all of your components within the App.js file.
  4. Since the Header is always mounted, you don't need redux, instead you can just use withRouter (it exposes route props to the component).
  5. Your rootReducer is missing a reducer, so I added a dummyReducer that just returns state.
  6. Stick to Link or this.props.history when navigating. For this example, there's no need to use both. Also, you don't need to use ConnectedRouter's push function, because the history is passed as a prop when using withRouter.

Side note: If you want the Header to be a "router" where all route changes pass through here, then you'll need to create an action and a reducer that passes a string and stores it to the redux's store. The Header is then connected to the redux store and updates the route when this string has changed.

Working example: https://codesandbox.io/s/526p7kjqq4

components/Header.js

import React, { PureComponent, Fragment } from "react";
import { withRouter } from "react-router-dom";

class Header extends PureComponent {
  goTo = route => {
    this.props.history.push(route);
  };

  render = () => (
    <Fragment>
      <ul>
        <li>
          <button onClick={() => this.goTo("/")}> Announcements </button>
        </li>
        <li>
          <button onClick={() => this.goTo("/shopping")}> Shopping </button>
        </li>
      </ul>

      <div>
        <button onClick={() => this.goTo("/shopping")}>
          Click here to go shopping ! (if you can...)
        </button>
      </div>
    </Fragment>
  );
}

export default withRouter(Header);

routes/index.js

import React from "react";
import { Switch, Route } from "react-router-dom";
import Announcements from "../components/annoucements";
import Shopping from "../components/shopping";

export default () => (
  <div style={{ padding: "150px" }}>
    <Switch>
      <Route exact path="/" component={Announcements} />
      <Route path="/shopping" component={Shopping} />
    </Switch>
  </div>
);

components/App.js

import React, { Fragment } from "react";
import Routes from "../routes";
import Header from "./Header";

export default () => (
  <Fragment>
    <Header />
    <Routes />
  </Fragment>
);

Here is what you're trying to accomplish: https://codesandbox.io/s/8nmp95y8r2

However, I DO NOT recommend this as it's a bit unnecessary, when history is either already passed as a prop from the Route or can be exposed when using withRouter. According to the Redux docs, it's not recommended either. And instead to either use Link or pass the history prop to the redux action creator instead of programmatic navigation through redux state.

containers/Header.js

import React, { PureComponent, Fragment } from "react";
import { connect } from "react-redux";
import { push } from "connected-react-router";

class Header extends PureComponent {
  goTo = route => this.props.push(route); // this is equivalent to this.props.dispatch(push(route)) -- I'm just appending dispatch to the push function in the connect function below

  render = () => (
    <Fragment>
      <ul>
        <li>
          <button onClick={() => this.goTo("/")}> Announcements </button>
        </li>
        <li>
          <button onClick={() => this.goTo("/shopping")}> Shopping </button>
        </li>
      </ul>

      <div>
        <button onClick={() => this.goTo("/shopping")}>
          Click here to go shopping ! (if you can...)
        </button>
      </div>
    </Fragment>
  );
}

export default connect(
  null,
  { push }
)(Header);
Laryngoscope answered 28/10, 2018 at 16:32 Comment(5)
Hey, thanks for your answer. You have to know that this "example" is just a reaaaally small part of something bigger, and extremly simplified for the example. > Also, don't use ConnectedRouter's push function. can you tell me why ? The thing is, I don't get what you changed (appart all the application) to make it work. There are a lot of discussion we could have about application structuring, but that wasn't my case here. Thanks for having taken some times to answer meBilander
I've included all the changes above. Please be more specific about which part(s) you don't understand. Since you're utilizing history with ConnectedRouter, but trying to push to a new location in ConnectedRouter (and not updating history), there's a mismatch in the routing. See history API: github.com/ReactTraining/history/blob/v3/docs/…Laryngoscope
Updated the answer to include a second option, although it's not recommended.Laryngoscope
Hi, I know that what you did "works", but I don't get why you had to rewrite all to make it work. You just had to say something like "Why do you use BrowserRouter and ConnectedRouter, and why do you create two instances of history ? And then, give the same example I had but with just these two modifications : codesandbox.io/s/p2wp49mmp0 I still don't get why I shouldn't use push provided by connected-react-router. I mean, that's how they tell to do.Bilander
I sure won't use the dispatch when I can use a <Link /> but I also use saga and sometimes, you have to programmatically go to a new route. Again, my example wasn't my whole app, just a tiny part of itBilander
E
0

After reading the complete thread react-router-redux's push() not rendering new route, I came across this solution you need to use Router with passing down history as prop down to your app and don't use create from multiple files just import it from a common file.

Here is the working codesandbox: push rendering the new route

Ellord answered 28/10, 2018 at 18:21 Comment(1)
This shouldn't be the accepted answer as ConnectedRouter is supposed to replace Router and BrowserRouter. This is a hacky solution that should be avoided and isn't recommended by redux: github.com/ReactTraining/react-router/blob/master/packages/….Laryngoscope

© 2022 - 2024 — McMap. All rights reserved.