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

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"
}
}

This is probably because there is no "exact" word on the Entry route (for App component):
<Switch>
<Route exact path={routes.ENTRY} component={App}></Route>
<Route path={routes.MAIN} component={Page2}></Route>
</Switch>

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;

Related

Warning: Please use `require("history").createBrowserHistory` instead of `require("history/createBrowserHistory")`

hi I am getting this warning and I want this warning to be solved and wouldn't be displayed on the console tab so I could upload my code to netlify and as far as I have read the documentation and policies of netlify, it says warnings and errors may be the reason that it can't be uploaded so I need this to be solved
index.js file
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { BrowserRouter, Route, Link } from 'react-browser-router';
import "../src/assets/css/index.scss";
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<BrowserRouter>
<App />
</BrowserRouter>
);
reportWebVitals();
package.json
{
"name": "daryaft-yar",
"version": "0.1.0",
"private": true,
"dependencies": {
"#testing-library/jest-dom": "^5.16.5",
"#testing-library/react": "^13.4.0",
"#testing-library/user-event": "^13.5.0",
"axios": "^1.1.3",
"lodash": "^4.17.21",
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-browser-router": "^2.1.2",
"react-cookie": "^4.1.1",
"react-dom": "^18.2.0",
"react-icons": "^4.6.0",
"react-paginate": "^8.1.4",
"react-scripts": "5.0.1",
"sass": "^1.55.0",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
app.js
import './App.css';
import { Switch, Route, Redirect } from 'react-router-dom';
import React, { Component } from 'react';
import Shop from './Components/shop-bot/shop';
import Home from './Components/shop-bot/botHome';
import Coin from './Components/Coin/coin';
import Wallet from './Components/Wallet/wallet';
import AddCoin from './Components/AddCoin/add-coin';
import Cart from './Components/cart/cart';
import FinalCart from './Components/final-cart/final-cart';
import UserForm from './Components/user-from/user-form';
class App extends Component {
componentDidMount() {
}
render() {
return (
<React.Fragment>
<Switch >
<Route path="/bot/shop" component={Shop}/>
<Route path="/bot/home" component={Home} />
<Route path="/bot/coin" component={Coin} />
<Route path="/bot/buy-coin" component={AddCoin} />
<Route path="/bot/wallet" component={Wallet} />
<Route path="/bot/cart" component={Cart} />
<Route path="/bot/cart-final" component={FinalCart} />
<Route path="/bot/user-data" component={UserForm} />
<Redirect from="/" exact to="/bot/shop" />
</Switch>
</React.Fragment>
);
}
}
export default App;
I'll be honest, "react-browser-router": "^2.1.2", in the package.json file seems like a mistake.
I don't think it's necessary
You are missing react-router-dom as a dependency.
I suggest uninstalling react-browser-router and installing react-router-dom#5 so the Switch and Redirect components can still be imported. Installing the latest RRDv5 is important since there are potential issues with react#18 and the React.StrictMode component and RRDv5 versions below v5.3.3, and if you don't specify v5 then the latest v6 version will be installed which has a lot of breaking changes.
From the terminal in the root project directory run:
npm uninstall --save react-browser-router
npm install --save react-router-dom#5
Update the index.js file to import the BrowserRouter from react-router-dom.
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { BrowserRouter } from 'react-router-dom';
import "../src/assets/css/index.scss";
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<BrowserRouter>
<App />
</BrowserRouter>
);
From here you should restart the app locally to ensure all is well and working before deploying the app again.

Server Rendering-React Router Dom V^5.2.0 Error: Invariant failed: Browser history needs a DOM

I'm a beginner in React and stuck with some problem.I'm using server rendering and uses express as server and Getting an error message : Error: Invariant failed: Browser history needs a DOM. I have checked fro solutions from various sites and have applied their solutions but getting different errors when applying different solution. As earlier I was getting an error : TypeError: Cannot read property 'location' of undefined for this I changed Router to BrowserRouter by importing -import { BrowserRouter as Router } from 'react-router-dom and after that I'm getting the below error.
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import Route from 'react-router-dom';
import App from './components/App';
ReactDOM.hydrate(
<Router><App /></Router> ,
document.getElementById('mountNode'),
);
App.js
import React, { useState } from 'react';
import {Route,Switch,browserHistory} from "react-router-dom";
import { BrowserRouter as Router } from 'react-router-dom';
import { createMemoryHistory } from 'history';
import HomePage from './HomePage';
import About from './About';
export default function App()
{
//const history = createMemoryHistory();
return (
<Router history={browserHistory}>
<Switch>
<Route path="/" exact component={HomePage}/>
<Route path="/about" component={About}/> 
</Switch>
</Router>
);
}
error
Error: Invariant failed: Browser history needs a DOM
at invariant (C:\LMS-APP\node_modules\tiny-invariant\dist\tiny-invariant.cjs.js:13:11)
at Object.createHistory [as createBrowserHistory] (C:\LMS-APP\node_modules\history\cjs\history.js:273:16)
at new BrowserRouter (C:\LMS-APP\node_modules\react-router-dom\modules\BrowserRouter.js:11:13)
at processChild (C:\LMS-APP\node_modules\react-dom\cjs\react-dom-server.node.development.js:2995:14)
at resolve (C:\LMS-APP\node_modules\react-dom\cjs\react-dom-server.node.development.js:2960:5)
at ReactDOMServerRenderer.render (C:\LMS-APP\node_modules\react-dom\cjs\react-dom-server.node.development.js:3435:22)
at ReactDOMServerRenderer.read (C:\LMS-APP\node_modules\react-dom\cjs\react-dom-server.node.development.js:3373:29)
at Object.renderToString (C:\LMS-APP\node_modules\react-dom\cjs\react-dom-server.node.development.js:3988:27)
at C:\LMS-APP\src\server\/server.js:10:40
at Layer.handle [as handle_request] (C:\LMS-APP\node_modules\express\lib\router\layer.js:95:5)
package.json
{
"name": "LMS-APP",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev-server": "nodemon --exec babel-node src/server/server.js --ignore dist/",
"dev-bundle": "webpack -w -d"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"#babel/core": "^7.9.6",
"#babel/node": "^7.8.7",
"#babel/preset-env": "^7.9.6",
"#babel/preset-react": "^7.9.4",
"babel-loader": "^8.1.0",
"express": "^4.17.1",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-router-dom": "^5.2.0",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.11"
},
"devDependencies": {
"babel-eslint": "^10.1.0",
"eslint": "^7.0.0",
"eslint-plugin-react": "^7.20.0",
"eslint-plugin-react-hooks": "^4.0.2",
"nodemon": "^2.0.4"
}
}
Since you are using express.js as a server, I believe you also need to configure your server.js code. As the error log on the bottom part tells the first file that was called was at C:\LMS-APP\src\server\/server.js:10:40.
In this website https://reacttraining.com/react-router/web/guides/server-rendering there is a "Putting it all together" section, you can follow and implement the right code in your server-side code also. Please do remember to adjust it to your code on the server.js since you are using express.js.
Also pay attention to the code example the website has. Like this block:
const html = ReactDOMServer.renderToString(
<StaticRouter location={req.url} context={context}>
<App />
</StaticRouter>
);
The code snippet above would be placed in your server.js file.
The <StaticRouter> component of React Router is needed to resolve that error about "Error: Invariant failed: Browser history needs a DOM".
server.get("*", (req, res) => {
// Render the component to a string.
const html = ReactDOMServer.renderToString(
<StaticRouter location={req.url} context={context}>
<App />
</StaticRouter>
);
const finalDocument =`<html>
<head>
<title>Your App Title</title>
</head>
<body>
<div id="root">${html}</div>
</body>
</html>
`;
res.send(finalDocument);
}
});
This would be the sample code you should have for your server.get() function. The server variable holds the express app.
If the error would still occur, you should change first argument in the server.get() function from server.get("/", ....) to server.get("*", ....).
The * asterisk signifies a wildcard which means any access to the server or the default URL would then be handled by your server-side, GET http://localhost:<port> as an example.

Connecting a Container component to the Redux store in ReactJs but get an error saying BrowserRouter is expecting a string but gets an object

I'm trying to hook up a Container to the redux store but I get this error:
Element type is invalid: expected a string (for built-in components)
or a class/function (for composite components) but got: object.
Check the render method of Route.
I'm trying to follow the instructions here:
https://react-redux.js.org/using-react-redux/connect-mapdispatch
My attempts are commented out at the bottom. Swapping either for the normal export default statement produces this error but the normal way works fine so I know this is a problem with connect.
My container:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import axios from 'axios';
//import { bindActionCreators } from 'redux'
import queryString from 'query-string';
import { signIn } from "../actions";
class MomentumContainer extends Component {
state = {display: "Waiting"};
componentWillMount() {
const query = queryString.parse(this.props.location.search);
console.log(`query: ${JSON.stringify(query)}`);
if(query.user_id) {
console.log(`User logged in w/ id: ${query.user_id}`);
//this.props.dispatch(signIn(query.user_id));
}
}
async componentDidMount() {
const dev = process.env.REACT_APP_DEV_SERVER_ENDPOINT;
const prod = process.env.REACT_APP_PROD_SERVER_ENDPOINT;
const base_endpoint = process.env.REACT_APP_DEV_MODE ? dev : prod;
const api_endpoint = `${base_endpoint}momentum`;
//TODO above can be handled on app startup and managed by redux??
//console.log(api_endpoint);
const response = await axios.get(api_endpoint);
this.setState({display: response.data});
}
render() {
return <h1>{this.state.display}</h1>
}
}
// // Tried this
// const actionCreators = { signIn };
// export default connect(null, actionCreators)(MomentumContainer);
// // Tried this
//export default connect()(MomentumContainer);
// This works
export default MomentumContainer;
My index.js file:
import React from "react";
import ReactDOM from "react-dom";
import {Provider} from "react-redux";
import {createStore, compose, applyMiddleware} from "redux";
import thunk from "redux-thunk";
import promise from 'redux-promise-middleware';
import logger from "redux-logger";
import registerServiceWorker from "./registerServiceWorker";
import "./index.css";
import App from "./App";
import {BrowserRouter, Route, Switch} from "react-router-dom";
import reducers from './reducers';
import MomentumContainer from "./containers/MomentumContainer";
const { REACT_APP_DEV_MODE } = process.env;
const dev_mode = JSON.stringify(REACT_APP_DEV_MODE) === JSON.stringify("true");
const middleware = applyMiddleware(promise, thunk, logger);
let store_dev;
if (dev_mode === true) {
console.log('App starting in development mode.');
const allStoreEnhancers = compose(
middleware,
window.devToolsExtension && window.devToolsExtension()
);
store_dev = createStore(reducers, {}, allStoreEnhancers)
} else {
console.log('App starting in production mode');
}
const store_production = createStore(reducers, {}, middleware);
const store = dev_mode ? store_dev : store_production;
ReactDOM.render(
<Provider store={store}>
<BrowserRouter>
<Switch>
<Route path="/" exact component={App} />
<Route path="/Momentum" exact component={MomentumContainer} />
</Switch>
</BrowserRouter>
</Provider>,
document.getElementById("root")
);
registerServiceWorker();
My package.json:
{
"name": "ticker-alert-frontend",
"version": "0.1.0",
"private": true,
"dependencies": {
"#material-ui/core": "^3.9.3",
"ajv": "^6.10.0",
"axios": "^0.18.0",
"materialize-css": "^1.0.0-rc.2",
"query-string": "^6.1.0",
"react": "^16.8.6",
"react-dom": "^16.4.0",
"react-redux": "^7.0.3",
"react-router-dom": "^4.3.1",
"react-scripts": "1.1.4",
"redux": "^4.0.1",
"redux-logger": "^3.0.6",
"redux-promise": "^0.6.0",
"redux-promise-middleware": "^6.1.0",
"redux-thunk": "^2.3.0"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
}
}
Any help/suggestions would be greatly appreciated!
After reading here (https://github.com/ReactTraining/react-router/issues/4354) about a similar issue, and seeing a suggestion to update react-router-dom, I tried updating that package to version 5.0.0 from 4.3.1 and it didn't work.
After updating the rest of my packages by running npm update --save my app is working. If anyone is having a similar problem I recommend running this command.

React-Router cannot read property string of undefined

I am new to React and I am building my first application with React, Redux, and React Router, so far I have successfully set up my boiler plate, however when I want to use React-Router and create routes I get the following error
TypeError: Cannot read property 'string' of undefined
./node_modules/react-router-dom/es/BrowserRouter.js
node_modules/react-router-dom/es/BrowserRouter.js:38
35 | }(React.Component);
36 |
37 | BrowserRouter.propTypes = {
> 38 | basename: PropTypes.string,
39 | forceRefresh: PropTypes.bool,
40 | getUserConfirmation: PropTypes.func,
41 | keyLength: PropTypes.number,
This only happens when I try to import and use
import { BrowserRouter, Route } from 'react-router-dom';
Here is my index.js
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './store'
import { BrowserRouter, Route } from 'react-router-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
class Hello extends Component {
render(){
return(
<div>Hello</div>
)
}
}
class GoodBye extends Component {
render(){
return(
<div>GoodBye</div>
)
}
}
ReactDOM.render(<Provider store={store}>
<BrowserRouter>
<Route path="/hello" component={Hello}/>
<Route path="/goodbye" component={GoodBye}/>
</BrowserRouter>
</Provider>, document.getElementById('root'));
registerServiceWorker();
And my package.json
{
"name": "reactreduxrouterblog",
"version": "0.1.0",
"private": true,
"dependencies": {
"axios": "^0.16.2",
"react": "^16.0.0",
"react-dom": "^16.0.0",
"react-redux": "^5.0.6",
"react-router": "^2.0.0-rc5",
"react-router-dom": "^4.0.0",
"redux": "^3.7.2",
"redux-logger": "^3.0.6",
"redux-promise-middleware": "^4.4.1",
"redux-thunk": "^2.2.0"
},
"devDependencies": {
"react-scripts": "1.0.14"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
}
}
Any knowledge will be welcomed thanks!
Update your react-router-dom in package.json: react-router-dom ^4.2.2
Sometimes it may cause due to incompatible versions of "react-router-dom" and "react-router-prop-types". For me it worked with following configuration.
react-router-dom:"^4.2.2",
react-router-prop-type:"^3.2.1"
Try adding the PropTypes package to your app.
basename: PropTypes.string
Looks like you are just missing that package.
EDIT:
Be sure to run npm install to install all dependencies.
import React from 'react'; <--as normal
import PropTypes from 'prop-types'; <--add this as a second line
App.propTypes = {
monkey: PropTypes.string, <--omit "React."
cat: PropTypes.number.isRequired <--omit "React."
};
//Wrong: React.PropTypes.string
//Right: PropTypes.string

Why am I not able to route my app using react router?

I started learning React and React Router by making basic application but I am getting warnings that I am not able to resolve
Warning 1: React.createElement: type is invalid -- expected a string
(for built-in components) or a class/function (for composite components)
but got: undefined.
Warning 2: Failed prop type: The prop `history` is marked as required
in `Router`, but its value is `undefined`.
My code :
index.js
import React from 'react'
import ReactDOM from 'react-dom'
import routes from './routes'
ReactDOM.render(routes, document.getElementById('app'));
.
routes.js
import React from 'react'
import { Route } from 'react-router'
import Home from './components/home';
import Login from './components/login'
const routes = (
<Route path="/" component={Home}>
<Route path="/login" component={Login} />
</Route>
);
export default routes;
.
home.js
import React from 'react'
export default React.createClass({
render(){
return (
<div>
<h1>Hello From Home</h1>
</div>);
}
});
login.js
import React from 'react'
export default React.createClass({
render(){
return (<h1>Hello From Login Page</h1>);
}
});
this is my
package.json
{
"name": "basicapp",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"production": "webpack -p",
"start": "webpack-dev-server"
},
"author": "aviaTorX",
"license": "ISC",
"dependencies": {
"react": "^15.4.2",
"react-dom": "^15.4.2",
"react-router": "^4.0.0"
},
"devDependencies": {
"babel-core": "^6.24.0",
"babel-loader": "^6.4.0",
"babel-preset-react": "^6.23.0",
"html-webpack-plugin": "^2.28.0",
"webpack": "^2.2.1",
"webpack-dev-server": "^2.4.2"
}
}
It is giving Blank screen with warnings above mentioned.
if you're using react-router ^4.0.0, your root component need
a Router element. It is seems missing from your routes.js file.
Your routes.js should look like this:
import React from 'react'
import { Route, BrowserRouter as Router } from 'react-router'
import Home from './components/home';
import Login from './components/login'
const routes = (
<Router>
<Route path="/" component={Home}>
<Route path="/login" component={Login} />
</Route>
</Router>
);
export default routes;
Your routes.js should be
import React from 'react'
import {
Route,
Router,
IndexRoute,
hashHistory
} from 'react-router'
import Home from './components/home';
import Login from './components/login'
export const routes = () => {
return(
<Router history={hashHistory}>
<IndexRoute component={Home} />
<Route path="/login" component={Login} />
</Router>
);
};
export default routes;
You can directly use this.props.history.push('/list/').
But remember that, the version of my react-router is ^4.2.0.

Categories