acdlite / redux-router

Redux bindings for React Router – keep your router state inside your Redux store
MIT License
2.3k stars 200 forks source link

How to get redux application state in onEnter? #66

Closed rightaway closed 8 years ago

rightaway commented 9 years ago

When I add the onEnter to a route, I get several errors about ReduxRouterContext and the page doesn't load.

const routes = (
  <Route path='/' component={App}>
    <Route path='profile' component={Profile} onEnter={requireAuth} />
    <Route path='*' component={NotFound} />
  </Route>
);

...

React.render((
  <div>
    <Provider store={store}>
      {() => <ReduxRouter />}
    </Provider>
    <DebugPanel top right bottom>
      <DevTools store={store} monitor={LogMonitor} />
    </DebugPanel>
  </div>
), document.getElementById('app'));
Warning: Failed propType: Required prop `location` was not specified in `RoutingContext`. Check the render method of `ReduxRouterContext`. bundle.js:13484:8
Warning: Failed propType: Required prop `routes` was not specified in `RoutingContext`. Check the render method of `ReduxRouterContext`. bundle.js:13484:8
Warning: Failed propType: Required prop `params` was not specified in `RoutingContext`. Check the render method of `ReduxRouterContext`. bundle.js:13484:8
Warning: Failed propType: Required prop `components` was not specified in `RoutingContext`. Check the render method of `ReduxRouterContext`. bundle.js:13484:8
Warning: Failed Context Types: Required child context `location` was not specified in `RoutingContext`. Check the render method of `ReduxRouterContext`.
rightaway commented 9 years ago

Seems like it was being caused by a wrong implementation of requireAuth on my part. Which leads me to the question how can I get the redux application state in onEnter? onEnter is passed 3 arguments, but inspecting them in the console it looks like none of them contain the application state (like the stuff you'd access in react components through this.props).

geekyme commented 9 years ago

Hmmm, how are u passing routes down to ReduxRouter?

I am also having the same problem but triggered by something else.

geekyme commented 9 years ago

https://github.com/rackt/redux-router/issues/67

rightaway commented 9 years ago
const createStoreWithMiddleware = compose(
  // ...
  reduxReactRouter({ routes, createHistory }),
  // ...
)(createStore);

I got it from the samples here. But I'm no longer getting the error you described in #67 because that was coming from a requireAuth that was actually just doing a console.log to see what arguments get passed in, but wasn't doing anything else.

But I'm still wondering how to get redux state in onEnter so that I can do the right thing based on the current state.

chentsulin commented 9 years ago

use getRoutes({ dispatch, getState }) api: see: https://github.com/rackt/redux-router/issues/68#issuecomment-141903408

but there is a bug blocking this usage, see: #62

rightaway commented 9 years ago

@chentsulin, thanks, do you have an example that shows how to use that code snippet you provided in that comment? I can't tell where and how to use it.

anthonator commented 9 years ago

I'm with @rightaway. Could use an example.

AndrewKarasek commented 9 years ago

I'm trying to use the declarative syntax to define our app's routes:

    <ReduxRouter>
      <Route path={HOME} name="Home" component={Home}>
        <Route path={LOGIN} name="Login" component={Login} />
        <Route path={SERVICE} name="Service" component={Service} onEnter={requireAuth} />
      </Route>
    </ReduxRouter>

It would be ideal if the onEnter hook were invoked with getStore as well as the nextState, replaceState, etc argument, ie:

function requireAuth({ getStore, nextState, replaceState }) {
  if (getStore().auth.grant.expired) {
    // redirect back to login.
    replaceState({ nextPathname: nextState.location.pathname }, LOGIN)
  }
}
jvanleeuwen commented 9 years ago

@AndrewKarasek ea

You can wrap your routes in a function that accepts the store object and provide this as the getRoutes parameter in your store create function. Example:

function getRoutes({ dispatch, getState }) {

    function onEnter(nextState, state) {
        // You should now have access to both dispatch and getState
    }

    return (
        <Route path="/" component={ App }>
            <Route path="dashboard" component={ Dashboard } onEnter={ onEnter } />
        </Route>
    );
}
const finalCreateStore = compose(
    applyMiddleware(...middleware),
    reduxReactRouter({
        getRoutes,
        history,
    })
)(createStore);
b5 commented 9 years ago

This example provided by @jvanleeuwen is working like a charm for me. Thanks!

stevenleeg commented 9 years ago

@jvanleeuwen I'm getting Uncaught ReferenceError: store is not defined(…) in my onEnter function while following that pattern. Any ideas as to why that'd be the case? (I can post more info about my setup if you need)

jvanleeuwen commented 9 years ago

@stevenleeg Unfortunately I have this issue too. For now you can use the links and work @chentsulin (thanks) did in #62 to get around this. Seems like the store is not fully created when getRoutes is called

nhagen commented 9 years ago

I'm also having this issue. Using the workaround, going to a disallowed route renders the disallowed route before redirecting, which is not acceptable behavior. @stevenleeg @jvanleeuwen are you also seeing that?

jvanleeuwen commented 9 years ago

@nhagen What happens when you turn the behaviour around.. Render the allowed route and when a authentication is successful redirect to the (no longer) disallowed route

nhagen commented 9 years ago

@jvanleeuwen didn't have time to try that sadly. As a workaround, @mjrussell suggested in discord to handle onEnter in componentWillMount, which is more reliable at this point, so I'm running with that.

edit: actually, this runs into the same issue--the component finishes the inital render cycle before transitioning :(

nhagen commented 9 years ago

Also, @steveoland wrote a helper for react-redux-universal-hot-example that addresses this problem in their project.

https://github.com/erikras/react-redux-universal-hot-example/blob/master/src/helpers/makeRouteHooksSafe.js

mwildehahn commented 9 years ago

i was using the above, but just "skipping" the onEnter handler if the store isn't defined doesn't work if the onEnter hook is an auth check. this doesn't seem to be an issue in the above link, but i'm also not doing server side rendering yet.

stevenleeg commented 9 years ago

yeah I'm running into that same issue trying to implement the helper in my project, unfortunately.

any other ideas/fixes? this issue has been driving me nuts for the last few days.

mwildehahn commented 9 years ago

:) me too.

aside from fixing this: https://github.com/rackt/redux-router/pull/62 i'm not really sure.

stevenleeg commented 9 years ago

I think I'm close to something here. Try using the setTimeout method along with the third argument to onEnter handlers: callback which will only permit the transition to proceed when called. For example:

  const requireAuth = (nextState, replaceState, callback) => {
    setTimeout(() => {
      console.log('requireAuth')
      function checkAuth() {
        console.log('checkAuth')
        const { auth } = store.getState()
        if (!auth.get('currentUser')) {
          replaceState({}, '/auth/login')
        }
        callback()
      }

      const { auth } = store.getState()
      if (!auth.get('loaded')) {
        AuthActions.initCurrentUser().then(checkAuth).catch(checkAuth)
      } else {
        checkAuth()
      }
    }, 0)
  }

This is mostly working but now I'm running into #67 warnings /sigh

mwildehahn commented 9 years ago

So I looked through my code and the only place I was leveraging ReduxRouter was to grab route parameters in selectors within connect. With reselect 2.0.0, you can accesss the props of the component: https://github.com/rackt/reselect#accessing-react-props-in-selectors. The places I need access to the parameters are all route handlers, which have params passed as a prop via: https://github.com/rackt/react-router/blob/master/docs/API.md#handler-components.

ideally everythign is flowing through redux via redux-router, but until onEnter is guaranteed to have the store, i'm ok relying on the above even though its a little messier.

kromit commented 8 years ago

@stevenleeg setTimeout and callback fixed the issue for me too. Cleanest workaround so far.

LucaColonnello commented 8 years ago

@kromit a setTimeout isn't a great solution and create an async flow that could cause problem in case of rehydratetion...

Consider to do this instead: https://github.com/rackt/redux-router/pull/62#issuecomment-155836940

feychenie commented 8 years ago

@rightaway can you check if the original warning are gone with pull-request #202 ?