-
-
Notifications
You must be signed in to change notification settings - Fork 15.2k
Is it a good idea to access redux store in route? #1336
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
In the specific case of authentication/ access handling, I do access the store object directly from the router. I am using import React from 'react';
import {
Route,
IndexRoute
} from 'react-router';
import {
HomeView,
LoginView,
LogoutView,
ProjectView,
ProjectIndexView
} from './../views';
import store from './../store';
let requireAuthentication;
requireAuthentication = (nextState, replace) => {
let isAuthenticated;
isAuthenticated = store.getState().getIn(['authentication', 'isAuthenticated']);
if (!isAuthenticated) {
replace('/authentication/login');
}
};
export default <Route path='/'>
<IndexRoute component={HomeView} onEnter={requireAuthentication} />
<Route path='/authentication/login' component={LoginView} />
<Route path='/authentication/logout'component={LogoutView} />
<Route path='/' onEnter={requireAuthentication}>
<Route path='/projects' component={ProjectIndexView} />
<Route path='/project/:projectId' component={ProjectView} />
</Route>
</Route>;
I am assuming you have import _ from 'lodash';
import {
createStore,
applyMiddleware
} from 'redux';
import createLogger from 'redux-logger';
import thunk from 'redux-thunk';
import {
syncHistory
} from 'react-router-redux';
import {
browserHistory
} from 'react-router';
import Immutable from 'immutable';
import rootReducer from './reducers';
import {
ENVIRONMENT
} from './config';
let defaultInitialState;
defaultInitialState = Immutable.Map();
export default (initialState = defaultInitialState) => {
let createStoreWithMiddleware,
reduxRouterMiddleware,
store;
reduxRouterMiddleware = syncHistory(browserHistory);
if (ENVIRONMENT === 'production') {
createStoreWithMiddleware = applyMiddleware(reduxRouterMiddleware, thunk)(createStore);
}
if (ENVIRONMENT === 'development') {
let logger;
logger = createLogger({
collapsed: true,
stateTransformer: (state) => {
return state.toJS();
}
});
createStoreWithMiddleware = applyMiddleware(reduxRouterMiddleware, thunk, logger)(createStore);
}
store = createStoreWithMiddleware(rootReducer, initialState);
if (ENVIRONMENT === 'development') {
reduxRouterMiddleware.listenForReplays(store, (state) => {
return state.getIn(['route', 'location']).toJS();
});
}
if (module.hot) {
module.hot.accept('./reducers', () => {
return store.replaceReducer(require('./reducers').default);
});
}
return store;
}; Then you create a second file (to separate implementation from instructions), `./store.js, e.g. import createStore from './createStore';
export default createStore(); Since Redux app is using a single store, this approach (to the best of my understanding) is perfectly valid. |
This is fine for client-only apps but we don't recommend this approach because it is much harder to add (or experiment with) server rendering if you rely on a singleton store. Instead, we suggest to explicitly inject store into anything that needs it. For example instead of exporting routes, you could export |
I tried this approach with injecting store into router config, but I did not require/import the store, rather I exported a router config factory function which took the store as an argument. I discovered having the authentication logic in the routing config to be quite clunky. I came up with a different, more redux-way approach, not using route hooks for authentication. I still inject the store into router config for cases where I'd need to dispatch some action from route hooks regardless of the rendered view (like logout by visiting a /logout route). I've implemented an import React, { PropTypes, Component } from 'react';
import { connect } from 'react-redux';
import { routeActions } from 'react-router-redux';
import {
extractState as extractAuthState,
isAuthenticated,
} from 'redux/reducers/auth';
function getDisplayName(WrappedComponent) {
return WrappedComponent.displayName || WrappedComponent.name || 'Component';
}
export default function authDecoratorFactory(componentDoesNotRequireAuthentication) {
// Use inverse logic to have `@authenticated()` for components that require authentication.
const componentRequiresAuthentication = !componentDoesNotRequireAuthentication;
// TODO: Move these URLs to the decorator options or global config.
const urls = {
preLoginDefault: '/',
logInDefault: '/',
loginForm: '/login',
};
return function authDecorator(WrappedComponent) {
class AuthenticatedComponentImpl extends Component {
static propTypes = {
auth: PropTypes.object.isRequired,
location: PropTypes.object,
dispatch: PropTypes.func.isRequired,
}
componentDidMount() {
this._redirectIfNeeded(this.props);
}
componentWillReceiveProps(nextProps) {
this._redirectIfNeeded(nextProps);
}
_redirect(redirectPathnameAndQueryString, currentPathnameAndQueryString) {
if (redirectPathnameAndQueryString === currentPathnameAndQueryString) {
// Avoid infinite redirect.
return;
}
this.props.dispatch(routeActions.push(redirectPathnameAndQueryString));
}
_redirectIfNeeded(nextProps) {
if (!nextProps.location) {
return;
}
const currentPathnameAndQueryString = nextProps.location.pathname;
const currentPathnameAndQueryParsed = require('url').parse(currentPathnameAndQueryString);
const currentPathname = currentPathnameAndQueryParsed.pathname;
const currentQueryString = currentPathnameAndQueryParsed.query;
const currentQuery = require('qs').parse(currentQueryString || '');
if (!isAuthenticated(this.props.auth) && isAuthenticated(nextProps.auth)) {
// Became authenticated, redirect to post-login section.
this._redirect(currentQuery && currentQuery.next || urls.logInDefault, currentPathnameAndQueryString);
}
else if (isAuthenticated(this.props.auth) && !isAuthenticated(nextProps.auth)) {
// Became non-authenticated, redirect to pre-login section.
this._redirect(urls.preLoginDefault, currentPathnameAndQueryString);
}
else if (componentRequiresAuthentication && !isAuthenticated(nextProps.auth)) {
// Should not be on a page with this component, redirect to login page.
if (currentPathname === urls.loginForm) {
// Avoid infinite redirect.
return;
}
const redirectQuery = {};
if (nextProps.location.pathname && nextProps.location.pathname !== urls.logInDefault) {
redirectQuery.next = nextProps.location.pathname;
}
const redirectQueryString = require('qs').stringify(redirectQuery);
this._redirect(urls.loginForm + (redirectQueryString ? '?' + redirectQueryString : ''));
}
else if (isAuthenticated(nextProps.auth)) {
// Should not be on the login page, redirect to post-login section.
if (currentPathname === urls.loginForm) {
this._redirect(currentQuery && currentQuery.next || urls.logInDefault);
}
}
}
render() {
if (componentRequiresAuthentication) {
if (!isAuthenticated(this.props.auth)) {
return null;
}
}
return (
<WrappedComponent {...this.props} />
);
}
}
const AuthenticatedComponent = connect(
(state) => ({
auth: extractAuthState(state),
location: state.routing.location,
})
)(AuthenticatedComponentImpl);
AuthenticatedComponent.displayName = 'AuthenticatedComponent(' + getDisplayName(WrappedComponent) + ')';
return AuthenticatedComponent;
};
} Example usage: // routes.js
export default (store) => {
return (
<Route path="/" component={App}>
<Route component={PostLoginLayout}>
</Route>
<Route component={PreLoginLayout}>
<Route path="login" component={LoginPage} />
<Route path="logout" onEnter={() => {
store.dispatch(logOut());
}} />
</Route>
</Route>
);
}; // The authentication is not required, but we'd like
// to get redirected from this view to a post-login experience
// upon getting authenticated.
@authenticated(true)
export default class PreLoginLayout extends Component { // The authentication is required, and we'd like
// to get redirected to the login form if we're not authenticated.
@authenticated()
export default class PostLoginLayout extends Component { |
Thank you all for sharing |
An implementation of the approach I proposed above as a more configurable higher-order component has popped up in a sibling thread about authentication -- @fortunebubble probably would be interested: https://github.com/mjrussell/redux-auth-wrapper |
I am thinking to redirect a user to homepage after he/she successfully login. My question is should I do access the login user info(in redux store) in the route level? If yes, how do I do that? or should I access the info within the homepage component using the
connect
from react-reduxThe text was updated successfully, but these errors were encountered: