5

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

Matteo Alessani
  • 9,864
  • 4
  • 37
  • 56
Kai23
  • 1,436
  • 1
  • 16
  • 27

2 Answers2

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, becayse 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);
Matt Carlotta
  • 13,627
  • 3
  • 24
  • 39
  • 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 me – Kai23 Oct 28 '18 at 16:52
  • 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: https://github.com/ReactTraining/history/blob/v3/docs/GettingStarted.md#navigation – Matt Carlotta Oct 28 '18 at 17:04
  • Updated the answer to include a second option, although it's not recommended. – Matt Carlotta Oct 28 '18 at 19:48
  • 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 : https://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](https://github.com/supasate/connected-react-router/blob/master/FAQ.md#how-to-navigate-with-redux-action). – Kai23 Oct 29 '18 at 10:34
  • I sure won't use the `dispatch` when I can use a `` 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 it – Kai23 Oct 29 '18 at 10:36
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

Sakhi Mansoor
  • 6,103
  • 4
  • 17
  • 31
  • 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: https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/guides/redux.md#deep-integration. – Matt Carlotta Oct 28 '18 at 19:51