Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Accessing Redux Store from routes set up via React Router

I would like to make use of react-router's onEnter handler in order to prompt users to authenticate when entering a restricted route.

So far my routes.js file looks something like this:

import React from 'react'; import { Route, IndexRoute } from 'react-router';  export default (     <Route   path="/"         component={App}>       <IndexRoute             component={Landing} />       <Route path="learn"     component={Learn} />       <Route path="about"     component={About} />       <Route path="downloads" component={Downloads} onEnter={requireAuth} />     </Route> ) 

Ideally, I'd like my requireAuth function to be a redux action that has access to the store and current state, that works like this: store.dispatch(requireAuth()).

Unfortunately I don't have access to the store in this file. I don't think I can use really use connect in this case to access the relevant actions that I want. I also can't just import store from the file where the store is created, as this is undefined when the app first loads.

like image 432
robinnnnn Avatar asked Mar 07 '16 17:03

robinnnnn


People also ask

How is store connected to react routes?

Connected React Router is a Redux binding for React Router v4 and v5. It synchronizes router state with Redux store via a unidirectional flow and uses react-hot-loader to facilitate hot reloading of functional components while preserving state.

Does Redux work with react router?

You can use the connected-react-router library (formerly known as react-router-redux ). Their Github Repo details the steps for the integration. Once the setup is complete, you can now access the router state directly within Redux as well as dispatch actions to modify the router state within Redux actions.

How do you access the store in Redux?

It's simple to get access to the store inside a React component – no need to pass the store as a prop or import it, just use the connect function from React Redux, and supply a mapStateToProps function that pulls out the data you need. Then, inside the component, you can pass that data to a function that needs it.


2 Answers

The easiest way to accomplish this is to pass your store to a function that returns your routes (rather than return your routes directly). This way you can access the store in onEnter and other react router methods.

So for your routes:

import React from 'react'; import { Route, IndexRoute } from 'react-router';  export const getRoutes = (store) => (   const authRequired = (nextState, replaceState) => {     // Now you can access the store object here.     const state = store.getState();      if (!state.user.isAuthenticated) {       // Not authenticated, redirect to login.       replaceState({ nextPathname: nextState.location.pathname }, '/login');     }   };    return (     <Route   path="/"         component={App}>       <IndexRoute             component={Landing} />       <Route path="learn"     component={Learn} />       <Route path="about"     component={About} />       <Route path="downloads" component={Downloads} onEnter={authRequired} />     </Route>   ); ) 

Then update your main component to call the getRoutes function, passing in the store:

<Provider store={ store }>   <Router history={ history }>     { getRoutes(store) }   </Router> </Provider> 

As for dispatching an action from requireAuth, you could write your function like this:

const authRequired = (nextState, replaceState, callback) => {   store.dispatch(requireAuth())  // Assume this action returns a promise     .then(() => {       const state = store.getState();        if (!state.user.isAuthenticated) {         // Not authenticated, redirect to login.         replaceState({ nextPathname: nextState.location.pathname }, '/login');       }        // All ok       callback();     }); }; 

Hope this helps.

like image 200
Ashley 'CptLemming' Wilson Avatar answered Oct 01 '22 07:10

Ashley 'CptLemming' Wilson


If you want that you could write route.js like this:

var requireAuth = (store, nextState, replace) => {   console.log("store: ", store);   //now you have access to the store in the onEnter hook! }  export default (store) => {   return (       <Route path="/"           component={App}>         <IndexRoute             component={Landing} />         <Route path="learn"     component={Learn} />         <Route path="about"     component={About} />         <Route path="downloads" component={Downloads} onEnter={requireAuth.bind(this, store)} />       </Route>     ); ); 

I've setup an example which you could play with in this codepen.

Not sure if triggering an action in order to handle the auth is a good idea. Personally I prefer handling auth in a different way:

Instead of using an onEnter hook, I use a wrapping function. I want the admin section of my blog protected, therefore I wrapped the AdminContainer component in the routes with a function, requireAuthentication, see below.

export default (store, history) => {         return (             <Router history={history}>                 <Route path="/" component={App}>                     { /* Home (main) route */ }                     <IndexRoute component={HomeContainer}/>                     <Route path="post/:slug" component={PostPage}/>                     { /* <Route path="*" component={NotFound} status={404} /> */ }                 </Route>                  <Route path="/admin" component={requireAuthentication(AdminContainer)}>                     <IndexRoute component={PostList}/>                     <Route path=":slug/edit" component={PostEditor}/>                     <Route path="add" component={PostEditor}/>                 </Route>                 <Route path="/login" component={Login}/>             </Router>         );     }; 

requireAuthentication is a function that

  • if the user is authenticated, renders the wrapped component,
  • otherwise redirects to Login

You can see it below:

export default function requireAuthentication(Component) {     class AuthenticatedComponent extends React.Component {          componentWillMount () {             this.checkAuth();         }          componentWillReceiveProps (nextProps) {             this.checkAuth();         }          checkAuth () {             if (!this.props.isAuthenticated) {                 let redirectAfterLogin = this.props.location.pathname;                 this.context.router.replace({pathname: '/login', state: {redirectAfterLogin: redirectAfterLogin}});             }         }          render () {             return (                 <div>                     {this.props.isAuthenticated === true                         ? <Component {...this.props}/>                         : null                     }                 </div>             )          }     }      const mapStateToProps = (state) => ({         isAuthenticated: state.blog.get('isAuthenticated')     });      AuthenticatedComponent.contextTypes = {         router: React.PropTypes.object.isRequired     };      return connect(mapStateToProps)(AuthenticatedComponent); } 

Also, requireAuthentication will protect all routes under /admin. And you can reuse it wherever you like.

like image 40
Alex Chirițescu Avatar answered Oct 01 '22 07:10

Alex Chirițescu