React Router Redux - Duplicate Renders when using Link or Back Button - javascript

I am seeing duplicate renders when I click a Link or use the back button. I can go to any page and do a real browser refresh and everything will work properly. My logger shows ##router/LOCATION_CHANGE twice and this is causing lots of warning and exceptions around duplication instances of things. This does not appear to be an issue of hashHistory vs browserHistory. As people have pointed out on github issues. I am on "react-router-redux": "4.0.7". Setting adjustUrlOnReplay to false doesn't appear to do anything. As always, really appreciate any help! Below are my configureStore and Routes js files. Can anyone help me find the problem here? Thanks! Phillip
configureStore.js
import { createStore, applyMiddleware, combineReducers, compose } from 'redux'
import thunk from 'redux-thunk'
import logger from 'redux-logger'
import rootReducer from '../reducers'
import createSagaMiddleware from 'redux-saga'
import rootSaga from '../sagas/sagas'
import promiseMiddleware from 'redux-promise-middleware'
import { syncHistoryWithStore} from 'react-router-redux'
import { browserHistory } from 'react-router'
import { apiMiddleware } from 'redux-api-middleware';
const sagaMiddleware = createSagaMiddleware()
const initialState = {
};
const enhancers = compose(
window.devToolsExtension ? window.devToolsExtension() : f => f
);
const store = createStore(rootReducer, initialState, compose(
applyMiddleware(apiMiddleware, thunk, logger(), sagaMiddleware),
typeof window === 'object' && typeof window.devToolsExtension !== 'undefined' ? window.devToolsExtension() : f => f
));
export const history = syncHistoryWithStore(browserHistory, store);
sagaMiddleware.run(rootSaga);
export default store;
routes.js
import App from './App'
import '...a bunch of different components'
import { Provider } from 'react-redux'
import { Router, Route, IndexRoute, browserHistory } from 'react-router'
import store, { history } from './store/configureStore.js'
const router = (
<Provider store={store}>
<Router history={history}>
<Route path="/" component={App}>
<IndexRoute component={TeamsContainer}/>
<Route path="teams" component={TeamsContainer} />
<Route path="teams/:teamId" component={TeamContainer} />
<Route path="teams/:teamId/add_member" component={AddMemberContainer} />
<Route path="teams/:teamId/team_members/:teamMemberId" component={TeamMemberContainer} />
</Route>
</Router>
</Provider>
)
if ( $('#app').length ) {
ReactDOM.render(router, document.getElementById('app'));
}

https://github.com/ReactTraining/history/issues/427
Updating to react-router v4 solve this.

Related

React Admin | Uncaught Error: Missing history prop

I am trying to implement react-admin in my project however upon render I get this message
Uncaught Error: Missing history prop. When integrating react-admin inside an existing redux Provider, you must provide the same 'history' prop to the <Admin> as the one used to bootstrap your routerMiddleware. React-admin uses this history for its own ConnectedRouter.
There is very little to be found about this issue and I'm not entirely sure how to go about setting the history. I've tried importing createHistory from 'history/createHashHistory' but then I get this error
Uncaught Could not find router reducer in state tree, it must be mounted under "router"
Is there a way to get this rendering properly? And if so, what would be the proper way to go about configuring the Admin Component's history?
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { createStore, compose, applyMiddleware} from 'redux';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
import { BrowserRouter as Router } from 'react-router-dom';
import { reducer } from './redux/reducer';
import * as serviceWorker from './serviceWorker';
const store = createStore(reducer, compose(
applyMiddleware(thunk),
window.navigator.userAgent.includes('Chrome') ?
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() : compose,
),
);
ReactDOM.render(
<React.StrictMode>
<Router>
<Provider store={store}>
<App />
</Provider>
</Router>
</React.StrictMode>,
document.getElementById('root')
);
AdminPage.js
import { Admin, Resource } from 'react-admin';
import jsonServerProvider from 'ra-data-json-server';
const dataProvider = jsonServerProvider('http://localhost:3000');
const AdminPage = () => {
return (
<Admin dataProvider={dataProvider}>
<Resource name="services" />
</Admin>
)
}
export default AdminPage;
App.js
import React from 'react';
import './App.css';
import AdminPage from './components/AdminPage';
import { Switch, Route } from 'react-router-dom';
const App = () => {
return (
<div className="app">
<Switch>
<Route exact path='/manage' component={ AdminPage } />
</Switch>
</div>
);
}
export default App;
The proper way to write a reducer in this case is documented at https://marmelab.com/react-admin/CustomApp.html#using-an-existing-redux-provider.
But from what I see, you don't need one. You just added the thunk middleware, which is useless in react-admin as it already handles sagas.

Different Problem: React store.getState is not a function

Here is my code:
store.js
import { createStore, applyMiddleware, compose } from "redux";
import thunk from "redux-thunk";
import rootReducer from "./reducers";
const initialState = {};
const middleware = [thunk];
const store = createStore(
rootReducer,
initialState,
compose(
applyMiddleware(...middleware),
(window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__()) ||
compose
)
);
App.js
import React, { Component } from 'react';
import './sass/main.scss';
import { BrowserRouter as Router, Route } from "react-router-dom";
import Landing from './components/pages/Landing';
import { Provider } from "react-redux";
import store from "../src/store";
import Register from "./components/auth/Register";
import Login from "./components/auth/Login";
class App extends Component {
render() {
return (
<Provider store={store}>
<Router>
<div className="App">
<Route exact path='/' component={Landing}/>
<Route exact path="/login" component={Login}/>
<Route exact path="/register" component={Register}/>
</div>
</Router>
</Provider>
);
}
}
export default App;
I can't find where the problem is. I tried to debug it, but can't found what really make it those error. error: Uncaught TypeError: store.getState is not a function.
./src/App.js
Attempted import error: '../src/store' does not contain a default export (imported as 'store').
The first thing tried is adding a export default createStore but that bring up other error message saying Line 10: 'store' is assigned a value but never used and TypeError: store.getState is not a function
In your App.js you are trying to import store from "../src/store";. So system will try to import something from ../src/store.js but you never export any variable in that file.
You can update store.js to add export statement
const store = createStore(
rootReducer,
initialState,
compose(
applyMiddleware(...middleware),
(window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__()) ||
compose
)
);
export default store;

Adding hashrouter to routes makes 'push' to stop render components

I have a ConnectedRouter to which I wanted to add hashes to all the routes so I added the HashRouter component like this:
// #flow
import React from 'react';
import { Router, Route,Switch } from 'react-router'
import { HashRouter } from 'react-router-dom'
import { ConnectedRouter } from 'react-router-redux';
import { routerActions } from 'react-router-redux';
import { UserAuthWrapper } from 'redux-auth-wrapper';
import StudiesViewContainer from './components/views/studies/StudiesViewContainer';
import NotificationsViewContainer from './components/views/notifications/NotificationsViewContainer';
import UserView from './components/views/user/UserView';
import StudyViewContainer from './components/views/studies/StudyViewContainer';
import { getUser } from './reducers';
import LoginView from './components/views/login';
import NotFoundView from './components/views/notFound';
import ForbiddenView from './components/views/forbidden';
const UserIsAuthenticated = UserAuthWrapper({
authSelector: getUser,
redirectAction: routerActions.replace,
failureRedirectPath: '/',
wrapperDisplayName: 'UserIsAuthenticated'
});
const configRouter = (history: Object) => {
return () =>
<ConnectedRouter history={ history }>
<HashRouter>
<Switch>
<Route path="/studies" component={ StudiesViewContainer } />
<Route path="/study/:id" component={ StudyViewContainer } />
<Route path="/user" component={ UserView } />
<Route path="/notifications" component={ NotificationsViewContainer } />
<Route path="/forbidden" component={ ForbiddenView } />
<Route path="/not-found" component={ NotFoundView } />
<Route path="/" component={ LoginView } />
<Route path="*" component={ NotFoundView } />
</Switch>
</HashRouter>
</ConnectedRouter>
};
export default configRouter;
The problem is that when I do something like this:
push('studies')
The route does not add the hash and the new components are not rendered.
I added the browser history to my store, here is the configureStore file:
// #flow
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import createHistory from 'history/createBrowserHistory'
import { routerMiddleware } from 'react-router-redux';
import createSagaMiddleware from 'redux-saga'
import {
persistStore,
autoRehydrate,
createTransform
} from 'redux-persist';
import mainSaga from '../sagas';
import reducer from '../reducers';
const history = createHistory();
const routingMiddleware = routerMiddleware(history);
const sagaMiddleware = createSagaMiddleware();
// Remove error field from persisted auth substate
let authTransform = createTransform(
(inboundState, key) =>
key === 'auth' ?
{ ...inboundState, error: undefined }:
inboundState,
outboundState => outboundState,
{
whitelist: [
'auth',
'permissions'
]
}
);
const configureStore = (): Promise<*> => {
let middlewares = [routingMiddleware,thunk, sagaMiddleware ];
let composeEnhancers = compose;
if(process.env.NODE_ENV !== 'production') {
composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
}
const store = createStore(
reducer,
composeEnhancers(
applyMiddleware(...middlewares),
autoRehydrate()));
sagaMiddleware.run(mainSaga);
return new Promise(resolve => {
persistStore(
store, {
whitelist: ['auth', 'permissions'],
debounce: 500,
transforms: [
authTransform
]
},
() => resolve({ store, history })
);
});
};
export default configureStore;
Can anyone help me get the push working as expected?
I am using the following versions of router in package json:
"react-router": "^4.2.0",
"react-router-dom": "^4.2.2",
"react-router-redux": "next",
I had a similar problem, I solved it by using
import createHistory from 'history/createHashHistory'
instead of
import createHistory from 'history/createBrowserHistory'
Also added withRouter when exporting components as suggested in
https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/guides/blocked-updates.md
I added the browser history to my store, here is the configureStore file:
Most likely that issue lies on difference between history-object, used in router, and proposed in redux state. Then history created in redux side const history = createHistory();, it seems that it had not linked with ConnectedRouter object, but it can't be followed by supposed code.
If valid history object is passed to ConnectedRouter, try to check that subsequent router actions are to intercepted with saga process or other middleware. Try to follow on emitting actions flow by https://github.com/gaearon/redux-devtools tool.
Also you can try to perform manual updating, by adding root component with full lifecycle (Inherited from React.Component class), and add there following code:
this.props.history.listen((location, action) => {
console.log("on route change");
});
Then you can discover had router state been changed, and if action is taken, just perform forceUpdate method in root component. Also consult this documentation: https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/guides/blocked-updates.md

Nothing happening and no errors with dispatch functions

I'm managing my React application route with react-router and react-router-redux.
I setting router with following configuration:
import {Provider} from 'react-redux'
import {Router, browserHistory} from 'react-router'
import {createStore, combineReducers} from 'redux'
import {routerReducer} from 'react-router-redux';
import {syncHistoryWithStore} from 'react-router-redux'
import Reducers from './reducers'; // contains all my reducers,
const store = createStore(combineReducers(
{...Reducers, routerReducer}
));
const history = syncHistoryWithStore(browserHistory, store);
const AppRoute = (
<Provider store={store}>
<Router history={history}>
<Route path='login' components={PageLogin}/>
<Route path='system' components={PageSystem}>
<Route path="profile" component={PageProfile}/>
</Route>
</Router>
</Provider>
);
export default AppRoute;
When I try dispatch any router function:
import React from 'react';
import {Link, Route} from 'react-router';
import {connect} from 'react-redux';
import {replace, push} from 'react-router-redux';
class SystemMenu extends React.Component {
constructor(props) {
super(props);
this.logout = this.logout.bind(this);
}
logout() {
this.props.logout();
}
render() {
return (
<ul className="system-menu">
<li className={this.isActual("/system/profile")}>
<Link to="/system/profile">Profile</Link>
</li>
<li className={this.isActual("/system/unit")}>
<Link to="/system/unit">Units</Link>
</li>
<li>
<button onClick={this.logout}>Logout</button>
</li>
</ul>
)
}
}
const mapStateToProps = (state) => {
return {
router: state.router,
actualPage: state.menu.actualPage
};
};
const mapDispatchToProps = (dispatch) => {
return {
logout() {
dispatch(push('/login'));
}
};
};
export default connect(mapStateToProps, mapDispatchToProps)(SystemMenu);
When I click que Link component, pages is changing, But when I click the Logout button to debug, is executing dispatch(push('/login')); line, but nothing has happening, and no occur any error in the console.
What I worse in the configuration?
I tried console.log the logout inside the mapDispatchToProps function, and returning logout is not defined
const mapDispatchToProps = (dispatch) => {
console.log(push, logout);
return {
logout() {
dispatch(push('/login'));
}
};
};
combineReducers should take an object as the first argument, like this:
const store = createStore(
combineReducers({
...reducers,
routing: routerReducer
})
)
You're giving it an Array instead.
I suspect the reason why push doesn't work is because the router reducer is not registered properly.
I resolved this problem.
This problem is occurring because I has not using react-routerredux` middleware, so when I dispatching, maybe is not updating browser URL by state.
Following my route configuration:
import {Provider} from 'react-redux'
import {Router, browserHistory} from 'react-router'
import {createStore, combineReducers, applyMiddleware} from 'redux'
import {routerReducer} from 'react-router-redux';
import {syncHistoryWithStore, routerMiddleware} from 'react-router-redux'
import Reducers from './reducers'; // contains all my reducers,
const middleware = [routerMiddleware(browserHistory)];
const store = createStore(combineReducers(Reducers), compose(
applyMiddleware(...middleware)
));
const history = syncHistoryWithStore(browserHistory, store);
const AppRoute = (
<Provider store={store}>
<Router history={history}>
<Route path='login' components={PageLogin}/>
<Route path='system' components={PageSystem}>
<Route path="profile" component={PageProfile}/>
</Route>
</Router>
</Provider>
);
export default AppRoute;

Uncaught Error: Expected the reducer to be a function

Trying to hook up a React component to my Redux state, but I am getting the error:
Uncaught Error: Expected the reducer to be a function
Not quite sure where I am going wrong:
import React from 'react';
import { render } from 'react-dom';
import { Router, Route, IndexRoute, browserHistory } from 'react-router';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import ReduxPromise from 'redux-promise';
import store, { history } from './store';
// Import components
import App from './components/App';
import Expenditure from './components/Expenditure';
import Income from './components/Income';
import Transactions from './components/Transactions';
import Single from './components/Single';
// import css
import css from './styles/style.styl';
const createStoreWithMiddleware = applyMiddleware(ReduxPromise)(createStore);
const router = (
<Provider store={createStoreWithMiddleware(store)}>
<Router history={history}>
<Route path="/" component={App}>
<IndexRoute component={Expenditure} />
<Route path="/income" component={Income} />
<Route path="/expenditure" component={Expenditure} />
<Route path="/transactions" component={Transactions} />
<Route path="/expenditure/:id" component={Single} />
<Route path="/income/:id" component={Single} />
<Route path="/transaction/:id" component={Single} />
</Route>
</Router>
</Provider>
);
render(router, document.getElementById('root'));
My rootReducer:
import { combineReducers } from 'redux';
import { routerReducer } from 'react-router-redux';
import expenditure from './expenditure';
const rootReducer = combineReducers({
expenditure,
routing: routerReducer
});
export default rootReducer;
And my actionCreators.js:
import axios from 'axios';
export const FETCH_EXPENDITURE = 'FETCH_EXPENDITURE';
export function fetchExpenditure() {
const request = axios.get('/api/expenditure');
return {
type: FETCH_EXPENDITURE,
payload: request
}
};
Then in my component, I am using connect to pull in the Redux state as props with the following below the Expenditure react class:
function mapStateToProps(state) {
console.log('state');
return {
expenditure: state.expenditure.all
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(actionCreators, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(Expenditure);
Any tips would be incredible :)
are you using applyMiddleware correctly?
the docs have it passing applyMiddleware to the createStore function, not the other way around. i also dont see you using your rootReducer anywhere at all
try changing
const createStoreWithMiddleware = applyMiddleware(ReduxPromise)(createStore);
to something like
// import rootReducer from '/path/to/rootReducer.js';
const createStoreWithMiddleware = createStore(
rootReducer,
[ ... ], //=> initial state
applyMiddleware(ReduxPromise)
)
then change your router input to
<Provider store={createStoreWithMiddleware}>
...
</Provider>

Categories

Resources