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

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

Related

Unable to save redux state with redux-persist

I'm currently using redux-persist to persistent data storage in my react-redux application. I have set it up according to the documentation. I have tested it on a separate project with npx create-react-app. The persistent storage was working properly.
When I integrate it in my actual working project, the state saving doesn't work. Can someone help me with this?
Here is my store.js:
import { createStore, applyMiddleware, compose } from 'redux';
import { routerMiddleware } from 'react-router-redux';
import thunk from 'redux-thunk';
import rootReducers from './reducers';
// imports for persistance redux state
import { persistStore, persistReducer } from 'redux-persist'
import storage from 'redux-persist/lib/storage' // defaults to localStorage for web
const middleware = [thunk, routerMiddleware(history)];
const initialState = {};
//config for redux-persist
const persistConfig = {
key: 'root',
storage,
}
const persistedReducer = persistReducer(persistConfig, rootReducers)
function composeWithApplyMiddlewares() {
if (window.__REDUX_DEVTOOLS_EXTENSION__) {
return compose(applyMiddleware(...middleware), window.__REDUX_DEVTOOLS_EXTENSION__());
}
return compose(applyMiddleware(...middleware));
}
const createMyStore = () =>
createStore(
persistedReducer,
initialState,
composeWithApplyMiddlewares(),
);
let persistor = persistStore(createMyStore());
export default createMyStore();
index.js:
import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import { syncHistoryWithStore } from 'react-router-redux';
import { Router, Route, browserHistory } from 'react-router';
import RandomID from 'random-id';
import 'normalize.css/normalize.css';
import 'plottable/plottable.css';
import store from './store';
import SignInContainer from './containers/Auth/Signin';
import SignUpContainer from './containers/Auth/Signup';
import ForgotPasswordContainer from './containers/Auth/ForgotPassword';
import ResetPasswordContainer from './containers/Auth/ResetPassword';
import CompleteSignupContainer from './containers/Auth/CompleteSignup';
import UsersListContainer from './containers/Users/List';
import CreateEndpointContainer from './containers/Endpoints/CreateEndpoint';
import TestMessagesContainer from './containers/TestMessages';
import Dashboard from './containers/Dashboard';
import initialize from './actions/initialize';
import Setting from './containers/Setting/Main';
const root = document.getElementById('root');
const history = syncHistoryWithStore(browserHistory, store);
const rand = RandomID(16, 'A0');
localStorage.setItem('authStateKey', rand);
const reduxOauthConfig = {
backend: {
apiUrl: 'http://0.0.0.0:4000', // TODO: Change backend URL here
signOutPath: null, // Add signout path to destroy session token
tokenValidationPath: '/svr/ping',
authProviderPaths: {
facebook: '/svr/auth/facebook',
github: '/svr/auth/github',
gitlab: '/svr/auth/gitlab',
google: '/svr/auth/google',
twitter: '/svr/auth/twitter',
},
},
};
const requireAuth = (nextState, replace) => {
const state = store.getState();
const isSignedIn = state.auth.authUser.isSignedIn;
// const isSignupCompleted = state.auth.authUser.isSignupCompleted;
// Complete signup first before user can do anything
// if (!isSignupCompleted && nextState.location.pathname !== '/complete-signup') {
// replace({
// pathname: '/complete-signup',
// state: { nextPathname: nextState.location.pathname },
// });
// }
if (!isSignedIn) {
replace({
pathname: '/signin',
state: { nextPathname: nextState.location.pathname },
});
}
};
const publicOnly = (nextState, replace) => {
const state = store.getState();
const isSignedIn = state.auth.authUser.isSignedIn;
if (isSignedIn) {
replace({
pathname: '/',
state: { nextPathname: nextState.location.pathname },
});
}
};
store.dispatch(initialize(reduxOauthConfig)).then(
() => {
render(
<Provider store={store}>
<Router history={history}>
<Route exact path="/signup" component={SignUpContainer} onEnter={publicOnly} />
<Route exact path="/signin" component={SignInContainer} onEnter={publicOnly} />
<Route exact path="/forgot-password" component={ForgotPasswordContainer} onEnter={publicOnly} />
<Route exact path="/reset-password" component={ResetPasswordContainer} onEnter={publicOnly} />
<Route exact path="/complete-signup" component={CompleteSignupContainer} onEnter={requireAuth} />
<Route exact path="/users" component={UsersListContainer} onEnter={requireAuth} />
<Route exact path="/chat" component={TestMessagesContainer} onEnter={requireAuth} />
<Route exact path="/add-endpoint" component={CreateEndpointContainer} onEnter={requireAuth} />
<Route exact path="/" component={Dashboard} onEnter={requireAuth} />
<Route exact path="/:userId/setting" component={Setting} onEnter={publicOnly} />
</Router>
</Provider>
, root,
);
},
);
reducers/index.js:
import { combineReducers } from 'redux';
import { routerReducer } from 'react-router-redux';
import message from './message';
import users from './users';
import endpoints from './endpoints';
import toasts from './toasts';
import auth from './auth';
export default combineReducers({
routing: routerReducer,
users,
endpoints,
ui: toasts,
chat: message,
auth,
});
Thanks for your attention.

React Router Redux upgrade from v3 to v4

I am trying to upgrade react-router from v3 to v4 and getting the following error when running the application :
Uncaught Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in.
reducer :
import { combineReducers } from 'redux';
import { routerReducer } from 'react-router-redux';
import { login } from './login-reducer';
const reducers = combineReducers({
routing: routerReducer,
login
})
export default reducers;
store :
import { createStore, applyMiddleware, compose } from 'redux'
import thunk from 'redux-thunk';
import createHistory from 'history/createBrowserHistory'
import { routerMiddleware } from 'react-router-redux'
import reducers from '../reducers';
export const history = createHistory();
const routerMiddle = routerMiddleware(history);
const composeEnhancers =
typeof window === 'object' &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
// Specify extension’s options like name, actionsBlacklist, actionsCreators, serialize...
}) : compose;
let middleware = [routerMiddle, thunk]
const enhancer = composeEnhancers(
applyMiddleware(...middleware),
// other store enhancers if any
);
const store = createStore(reducers, enhancer);
export default store;
index :
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux'
import store, { history } from './store/createStore';
import { ConnectedRouter } from 'react-router-redux'
import { getRoutes } from './routes';
import './index.css';
ReactDOM.render(
<Provider store={store}>
<ConnectedRouter history={history}>
<div>{ getRoutes(store) }</div>
</ConnectedRouter>
</Provider>,
document.getElementById('root')
);
routes :
import React from 'react';
import { Route } from 'react-router';
import App from './containers/App';
import Login from './containers/Login';
import Protected from './components/Protected';
export const getRoutes = (store) => {
const authRequired = (nextState, replaceState) => {
// Now you can access the store object here.
const state = store.getState();
if (!state.login.loggedIn || state.login.loggedIn == null) {
// Not authenticated, redirect to login.
replaceState({
pathname: '/login',
state: { nextPathname: nextState.location.pathname }
});
}
};
return (
<div>
<Route exact path="/" component={App} />
<Route path="/login" component={Login} />
<Route path="/protected" component={Protected} onEnter={authRequired} />
</div>
);
}
app :
import React, { Component } from 'react';
import { Link } from 'react-router';
import { connect } from 'react-redux'
import logo from '../logo.svg';
import './App.css';
class App extends Component {
render() {
const isLoggedIn = this.props.isLoggedIn;
return (
<div className="App">
<div className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h2><Link to="/">Welcome to React</Link></h2>
<div className="app-nav">
<nav>
<Link to='about'>About</Link>
<Link to='login'>{( isLoggedIn ? 'Logout' : 'Login' )}</Link>
<Link to='protected'>Protected</Link>
</nav>
</div>
</div>
{this.props.children}
</div>
);
}
}
const mapStateToProps = (state) => {
return {
isLoggedIn: (state.login.loggedIn ? state.login.loggedIn : false)
}
}
App = connect (
mapStateToProps
)(App)
export default App;
Not sure what I'm missing to get this to work
**NOTE : I am still seeing this error even when I only have one route / and render a static component (single div with text)
A) You dont need react router redux at all with react router 4, specially if all you want is to render authenticated components.
B) The onEnter hook dont work this way in RR4, its the older way of doing route validation
{this.props.children} why are you still rendering child routes using props.children? All the child routes goes into the component it resides in.
If you are trying to learn rr4, i recommend checking this boilerplate for it https://github.com/touqeerkhan11/The-Ultimate-Boilerplate
The problem is probably here
import { Link } from 'react-router';
react-router doesn't export Link, react-router-dom does.
import { Link } from 'react-router-dom';
You should probably import the Route component from react-router-dom as well.
The issue had to do with the version of react-router-redux that was being used. When I added it to the project, I left off the #next

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

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.

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