Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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

like image 911
Kai23 Avatar asked Oct 28 '18 14:10

Kai23


People also ask

Does React Router change URL?

The react-router-dom package is great for rendering different React components based on the url path. Therefore, React components can lead to others by changing the url path.

Can I use React Router with Redux?

You can use the connected-react-router library (formerly known as react-router-redux ). Their Github Repo details the steps for the integration. Once the setup is complete, you can now access the router state directly within Redux as well as dispatch actions to modify the router state within Redux actions.

What is difference between push and replace in React Router?

You can either push a new record onto the top of the history stack or you can replace the top record. If you use push , and then hit the browser's back button, it will take you back to the page you are currently on, but if you use replace it will take you two pages back.

What is the difference between route and link in React?

So in a nutshell, the Link component is responsible for the transition from state to state (page to page), while the Route component is responsible to act as a switch to display certain components based on route state.


1 Answers

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);
like image 142
Matt Carlotta Avatar answered Oct 18 '22 20:10

Matt Carlotta