Search code examples
javascriptreactjsreduxreact-routerconnected-react-router

React-router component does not update on <Link> navigation


As many other people have had the same issue, I'm struggling to interact with a <Link> element to change routes and render a new component. I am able to click on the link and the path for my application changes in my redux store, but no component gets updated. My root component does not seem to respond to shouldComponentUpdate, even though props are changing and the full setup is similar to how connected-react-router describes it needs to be.

To test

The sample code (git repo) is a MVP (minimum viable product) - it can easily replicate the problem I am seeing.

git clone https://github.com/reZach/electron-webpack-template.git
cd electron-webpack-template
npm i
npm run dev

If you'd prefer to look at files, I've included the necessary files below

index.js

import React from "react";
import ReactDOM from "react-dom";
import Root from "../app/components/core/root";
import store, { history } from "./redux/store/store";

ReactDOM.render(
    <Root store={store} history={history}></Root>,
    document.getElementById("root")
);

store.js

import { configureStore, getDefaultMiddleware } from "@reduxjs/toolkit";
import { createBrowserHistory } from "history";
import { routerMiddleware } from "connected-react-router";
import rootReducer from "../reducers/rootReducer";

export const history = createBrowserHistory();

const store = configureStore({
    reducer: rootReducer(history),
    middleware: [...getDefaultMiddleware(), routerMiddleware(history)]
});

export default store;

root.jsx

import React from "react";
import { ConnectedRouter } from "connected-react-router";
import { Provider, connect } from "react-redux";
import Routes from "../core/routes";

class Root extends React.Component {
    render() {
        return (
            <Provider store={this.props.store}>
                <ConnectedRouter history={this.props.history}>
                    <Routes></Routes>
                </ConnectedRouter>
            </Provider>
        );
    }
}

export default Root;

routes.jsx

import React from "react";
import { Switch, Route } from "react-router";
import routes from "../../constants/routes";
import App from "../app/app";
import Page2 from "../page2/page2";

class Routes extends React.Component {    
    render() {
        return (
            <Switch>
                <Route path={routes.ENTRY} component={App}></Route>
                <Route path={routes.MAIN} component={Page2}></Route>
            </Switch>
        );
    }
}

export default Routes;

routes.json

{
    "ENTRY": "/",
    "MAIN": "/main"
}

package.json

{
  "name": "electron-webpack-template",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "npm run build-dev-webpack && npm run start-dev-app",
    "build-dev-webpack": "webpack --mode development --config ./app/configs/webpack/webpack.config.js",
    "start-dev-app": "cross-env NODE_ENV=development electron app/main.js",
    "prod": "npm run build-prod-webpack && npm run start-prod-app",
    "build-prod-webpack": "webpack --mode production --config ./app/configs/webpack/webpack.config.js",
    "start-prod-app": "cross-env NODE_ENV=production electron app/main.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/reZach/electron-webpack-template.git"
  },
  "keywords": [],
  "author": "",
  "license": "GPL-3.0-only",
  "bugs": {
    "url": "https://github.com/reZach/electron-webpack-template/issues"
  },
  "homepage": "https://github.com/reZach/electron-webpack-template#readme",
  "devDependencies": {
    "@babel/core": "^7.7.7",
    "@babel/plugin-proposal-json-strings": "^7.7.4",
    "@babel/plugin-transform-react-jsx": "^7.7.7",
    "@babel/preset-env": "^7.7.7",
    "babel-loader": "^8.0.6",
    "cross-env": "^6.0.3",
    "csp-html-webpack-plugin": "^3.0.4",
    "devtron": "^1.4.0",
    "electron": "^7.1.7",
    "html-webpack-plugin": "^3.2.0",
    "lockfile-lint": "^3.0.5",
    "webpack": "^4.41.4",
    "webpack-cli": "^3.3.10",
    "webpack-dev-server": "^3.10.1"
  },
  "dependencies": {
    "@reduxjs/toolkit": "^1.2.1",
    "connected-react-router": "^6.6.1",
    "react": "^16.12.0",
    "react-dom": "^16.12.0",
    "react-redux": "^7.1.3",
    "react-router": "^5.1.2",
    "react-router-dom": "^5.1.2",
    "redux": "^4.0.5"
  }
}

Solution

  • The solution to these routing woes was to add the exact attribute as drazewski suggested, and in addition change the history object I was using for my <ConnectedRouter>. The problem was caused by me using createBrowserHistory and the fact that I am not using a webserver in my application to host my files. The default path of my application when loading my app was file:///C:/...index.html. This is the path value when I looked in my Redux store when the app loaded.

    I had to change to use createHashHistory instead, which works when you are not hosting a webserver to host your app. The default path when using createHashHistory is a /, which matches routes.ENTRY in my routes.jsx file and allows me to navigate to /main. Apparently, you can't navigate to another path unless your path matches an existing path you define in your <Route>s (this is what I think what was happening as I described my problem above - although I don't know why my home/page1 component rendered at all then...).

    Here are good references that pointed me in the direction that I had to change my history (here and here). In addition to those links, here is a good blog post (mirror) explaining more in-detail the various types of history for react-router.

    new routes.jsx

    import React from "react";
    import { Switch, Route } from "react-router";
    import routes from "../../constants/routes";
    import App from "../app/app";
    import Page2 from "../page2/page2";
    
    class Routes extends React.Component {    
        render() {
            return (
                <Switch>
                    <Route exact path={routes.ENTRY} component={App}></Route> <!-- updated -->
                    <Route path={routes.MAIN} component={Page2}></Route>
                </Switch>
            );
        }
    }
    
    export default Routes;
    

    new store.js

    import { configureStore, getDefaultMiddleware } from "@reduxjs/toolkit";
    import { createHashHistory } from "history"; // < updated!
    import { routerMiddleware } from "connected-react-router";
    import rootReducer from "../reducers/rootReducer";
    
    export const history = createHashHistory(); // < updated!
    
    const store = configureStore({
        reducer: rootReducer(history),
        middleware: [...getDefaultMiddleware(), routerMiddleware(history)]
    });
    
    export default store;