Accessing Redux Store from routes set up via React Router

前端 未结 4 1211
暖寄归人
暖寄归人 2021-01-30 07:20

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 route

相关标签:
4条回答
  • 2021-01-30 07:28

    After trying out a few of the above suggestions, I found the best way to track the state of your store with updates is to use React-Redux's useSelector function which basically connects a functional component to the store.

    import * as React from "react";
    import {Redirect, Route, Switch} from "react-router";
    import {Provider, useSelector} from "react-redux";
    import { createBrowserHistory } from "history";
    
    // Your imports
    import {IApplicationState,} from "./store/store";
    import {Login} from "./routes/login/login.component";
    import {getToken} from "./store/helpers/httpHelpers";
    
    
    function handleRedirect() {
        if(!getToken()) {
            return <Redirect to="/login"/>;
        }
    }
    
    const restricted = (Component: _ComponentType, isLoggedIn: boolean) => {
       // Don't redirect here if there is a token in localStorage.
       // This is happening when we are on a restricted route and the user
       // refreshes & the isLoggedIn state hasn't been updated yet.
        return !isLoggedIn ? (
            () => handleRedirect()
        ) : () => <Route component={Component}/>
    };
    
    const AuthenticateRoutes = () => {
        const isLoggedIn = useSelector((state: IApplicationState) => state.auth.isLoggedIn);
        return (
            <Switch>
                <Route path="/login" component={Login} />
                <Route path="/downloads" render={restricted(Download, isLoggedIn)} />
            </Switch>
        );
    };
    
    export function App() {
        return (
            <Provider store={store}>
                <>
                    <Router history={createBrowserHistory()}>
                        <AuthenticateRoutes />
                    </Router>
                </>
            </Provider>
        );
    }
    
    0 讨论(0)
  • 2021-01-30 07:29

    Lots have changed over the time. onEnter no longer exists on react-router-4

    The following is from my real project for your reference

    export const getRoutes = (store) => {
      const PrivateRoute = ({ component: Component, ...rest }) => (
        <Route {...rest} render={props => (
          checkIfAuthed(store) ? (
            <Component {...props}/>
          ) : (
            <Redirect to={{
              pathname: '/login'
            }}/>
          )
        )}/>
      )
    
      return (
        <Router>
          <div>
            <PrivateRoute exact path="/" component={Home}/>
            <Route path="/login" component={Login} />
          </div>
        </Router>
      )
    }

    0 讨论(0)
  • 2021-01-30 07:38

    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.

    0 讨论(0)
  • 2021-01-30 07:42

    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.

    0 讨论(0)
提交回复
热议问题