-
Notifications
You must be signed in to change notification settings - Fork 200
Failing unit test demonstrating undefined 'getState' in transition hooks #62
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
base: master
Are you sure you want to change the base?
Conversation
Thank you! I <3 pull requests with failing tests. I'll get to this very soon. |
It seems // routerReplacement.js#L58
store = compose(
applyMiddleware(
replaceRoutesMiddleware(replaceRoutes)
),
next({
...options,
routes: createRoutes(routes)
})
)(createStore)(reducer, initialState); It's a hard problem with circular dependencies between redux store and react-router routes, @acdlite @gaearon Any ideas here? |
Would provide something like this solve the problem? function createRoutes({ getState, isReady, addReadyListener }) {
function requireAuth(nextState, replaceState, callback) {
if (isReady()) {
// ...
callback();
} else {
addReadyListener(function() {
// ...
callback();
});
}
}
//...
} But It looks not ideal... |
I'm running into this as well because |
Found another workaround: import { Route, Redirect } from 'react-router';
import * as containers from './containers';
const {
App,
LoginPage,
DashboardPage,
NotFoundPage
} = containers;
export function createRoutes({ getState }) {
function shouldNotAuth(nextState, replaceState, cb) {
setTimeout(() => {
const state = getState();
if (state.user.current) {
replaceState(null, '/dashboard');
}
cb();
}, 0);
}
function requireAuth(nextState, replaceState, cb) {
setTimeout(() => {
const state = getState();
if (!state.user.current) {
replaceState(null, '/login');
}
cb();
}, 0);
}
return (
<Route component={App}>
<Redirect from="/" to="login" />
<Route path="login" component={LoginPage} onEnter={shouldNotAuth} />
<Route onEnter={requireAuth}>
<Route path="dashboard" component={DashboardPage} />
</Route>
<Route path="*" component={NotFoundPage} />
</Route>
);
} But some warnings is inevitable:
|
Was looking into this issue a bit this evening. It is caused by the Changing the order of the compose arguments in client.js allows the tests to pass: export default compose(
useDefaults,
historySynchronization,
routeReplacement
)(reduxReactRouter); I think this might still be an issue though since the store is being accessed before the state is fully initialized ( Any thoughts @acdlite? |
Using |
Thanks for the workaround, @chentsulin! Has anyone figured out how to get rid of the warnings? |
This is a dirty workaround and I'm not sure why it works but doing the following removes the warnings... cc @chentsulin @lynndylanhurley :
Does anyone have a clue on first steps to fix this issue ? |
@cerisier - how and where are you defining |
@lynndylanhurley In the createRoutes function.
|
@cerisier don't know why this can works ha |
I've tried this, but I still get the warnings. I should add that I'm using server side rendering, and the warnings are only thrown by the client after a server-side redirect. |
Any progress on this issue? I'm running into the same thing and I believe the discussion in #66 is mostly based around this as well. |
It would be really nice if the store was just available to the function requireAuth(nextState, replaceState, store, cb) {
let state = store.getState();
//...
} The fact that it's not seriously complicates the redux configuration. |
Another way is to wrap |
I am expecting a same API just like @lynndylanhurley suggested. This is a very normal requirement for onEnter hook access the state tree. |
Thanks @ferdinandsalis - I'll try that ASAP. |
It would be nice to have access to dispatch as well as state. |
@dsteinbach - the access to the function requireAuth(nextState, replaceState, store, cb) {
// access current state
let state = store.getState();
// dispatch event
store.dispatch(someAction());
//...
} Whoever is maintaining this - will you accept a PR for this feature? |
For anybody who uses the workaround by @chentsulin , does the restricted route render first before it gets redirected to log in page? |
@hsin421 - it doesn't for me. |
I'm using a workaround based on a higher-order component that wraps any components/views requiring authentication. I'm pretty new to this so if anyone has any comments or feedback I'd appreciate it. routes.js import {requireAuthentication} from 'components/AuthComponent';
export default (
<Route path='/' component={CoreLayout}>
<Route path="login" component={LoginView} />
<Route path="protected" component={requireAuthentication(ProtectedView)}/>
</Route>
); AuthComponent.js import React from 'react';
import { connect } from 'react-redux';
import { pushState } from 'redux-router';
export function requireAuthentication(Component) {
class AuthComponent extends React.Component {
componentWillMount() {
if (!this.props.isAuthenticated) {
// redirect to login and add next param so we can redirect again after login
this.props.dispatch(pushState(null, `/login?next=${this.props.location.pathname}`));
}
}
render() {
// render the component that requires auth (passed to this wrapper)
return (
<Component {...this.props} />
)
}
}
const mapStateToProps =
(state) => ({
token: state.auth.token,
isAuthenticated: state.auth.isAuthenticated
});
return connect(mapStateToProps)(AuthComponent);
}
Anyone see any issues with this approach? Seems to be working well for me though I admit I have not spent a lot of time with it. |
@joshgeller your higher component works like a charm. Really appreciate it !! I'll spend some more time with it and report any issues/improvements. |
You'll also have to do the check in componentWillReceiveProps so if the user logouts or an API responds with 401 you can respond client-side. This works great for me thanks @joshgeller ! |
Solved!!!!!!!!!!!!!!! You have to set the routes with getRoutes function when you render the ReduxRouter component instead of doing this when you compose the store... So that when you create the store, the routes configuration will not be calculated. Simple two phase configuration. Root.jsx import React, { Component, PropTypes } from 'react';
import { Provider } from 'react-redux';
import { ReduxRouter } from 'redux-router';
import getRoutes from '../routes';
export default class Root extends Component {
render() {
const { store } = this.props;
return (
<Provider store={store}>
<ReduxRouter>
{getRoutes(store)}
</ReduxRouter>
</Provider>
);
}
}
Root.propTypes = {
store: PropTypes.object.isRequired,
}; createStore.js import createHistory from 'history/lib/createBrowserHistory';
import { createStore, applyMiddleware, compose } from 'redux';
import { apiMiddleware } from 'redux-api-middleware';
import createLogger from 'redux-logger';
import { reduxReactRouter } from 'redux-router';
import thunk from 'redux-thunk';
import DevTools from '../containers/DevTools';
import rootReducer from '../reducers';
const finalCreateStore = compose(
applyMiddleware(thunk),
applyMiddleware(createLogger()),
reduxReactRouter( { createHistory } ),
applyMiddleware(apiMiddleware),
DevTools.instrument()
)(createStore);
export default function configureStore(initialState) {
const store = finalCreateStore(rootReducer, initialState);
if (module.hot) {
// Enable Webpack hot module replacement for reducers
module.hot.accept('../reducers', () => {
const nextRootReducer = require('../reducers');
store.replaceReducer(nextRootReducer);
});
}
return store;
} |
@joshgeller I really liked the approach of high-level component. Looks very clear and solid. Seems like @LucaColonnello also makes a lot of sense, will try that out. |
@joshgeller Pretty sweet approach. I really like how readable the routes declaration is, you can easily see which routes require authentication. So this is a issue coming up pretty often: It would be really appreciated if somebody could write something up, describing the workarounds and adding a simple example. |
I put together a sample repo and quick explanation of the higher-order component approach here: |
@joshgeller It looks like an amazing boilerplate.. Thanks for sharing! |
@joshgeller Thank your very much. I added your repo to our README. Of course this won´t fix the issue itself, but it is a pretty solid workaround for now. |
To add more fuel to the fire... the closure solution works for me, except for one client/server rendering scenario. It's the standard In that case, my const router = reduxReactRouter({
createHistory,
getRoutes
}); So for now, my solution is this, not the prettiest, but it works client and server: let getState = () => initialState;
const routes = createRoutes(() => getState());
const router = reduxReactRouter({
createHistory,
routes
});
const store = configureStore({initialState, router});
getState = () => store.getState(); |
Any news on this? |
@ysudo I you fall in this problem to implement auth in your app, the boilerplate from @joshgeller is just great. I've tried that and it works. I don't have other cases to use |
@ysudo I hope @joshgeller workaround works for you. I can´t predict if we will fix this any time soon. See #172 |
With @joshgeller 's solution we have to load the to-be-authorized component to see if the user is allowed to view it, which isn't great for dynamic routing/code splitting... ideally, i don't want to load the component from the server at all unless the user is authorized to see it. |
Thank you @petrometro . Now I'm looking at redux-simple-router, that is a great and simple implementation to let communication between Redux and React Router ... |
@LucaColonnello did you find how to access your store in |
@Elyx0 according to the This is because you simply use Router component from react-router and all the react-router-redux parts are only middlewares, reducers and action creators that doesn't act in the bootstrap phase of your app. import React from 'react'
import ReactDOM from 'react-dom'
import { createStore, combineReducers, applyMiddleware } from 'redux'
import { Provider } from 'react-redux'
import { Router, Route, browserHistory } from 'react-router'
import { syncHistory, routeReducer } from 'react-router-redux'
import reducers from '<project-path>/reducers'
const reducer = combineReducers(Object.assign({}, reducers, {
routing: routeReducer
}))
// Sync dispatched route actions to the history
const reduxRouterMiddleware = syncHistory(browserHistory)
const createStoreWithMiddleware = applyMiddleware(reduxRouterMiddleware)(createStore)
const store = createStoreWithMiddleware(reducer)
// Required for replaying actions from devtools to work
reduxRouterMiddleware.listenForReplays(store)
ReactDOM.render(
<Provider store={store}>
<Router history={browserHistory}>
<Route path="/" component={App}>
<Route path="foo" component={Foo}/>
<Route path="bar" component={Bar}/>
</Route>
</Router>
</Provider>,
document.getElementById('mount')
) |
It's funny how this long standing issue is fixed by simply swapping two lines of code. |
Failing test case to demonstrate that
getState
throw as error when evaluated in the context of anonEnter
transition hook.