web-dev-qa-db-fra.com

Accès au Redux Store à partir des routes configurées via React Router

Je voudrais utiliser le gestionnaire onEnter de react-router afin d'inviter les utilisateurs à s'authentifier lors de la saisie d'un itinéraire restreint.

Jusqu'à présent, mon fichier routes.js Ressemble à ceci:

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>
)

Idéalement, je voudrais que ma fonction requireAuth soit une action redux qui a accès au magasin et à l'état actuel, qui fonctionne comme ceci: store.dispatch(requireAuth()).

Malheureusement, je n'ai pas accès au magasin dans ce fichier. Je ne pense pas pouvoir vraiment utiliser connect dans ce cas pour accéder aux actions pertinentes que je souhaite. Je ne peux pas non plus simplement import store À partir du fichier où le magasin est créé, car cela n'est pas défini lors du premier chargement de l'application.

38
robinnnnn

La façon la plus simple d'y parvenir est de passer votre boutique à une fonction qui renvoie vos itinéraires (plutôt que de renvoyer vos itinéraires directement). De cette façon, vous pouvez accéder au magasin dans onEnter et à d'autres méthodes de routeur de réaction.

Donc pour vos itinéraires:

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>
  );
)

Mettez ensuite à jour votre composant principal pour appeler la fonction getRoutes, en passant dans le magasin:

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

Quant à l'envoi d'une action depuis requireAuth, vous pouvez écrire votre fonction comme ceci:

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();
    });
};

J'espère que cela t'aides.

Si vous voulez que vous puissiez écrire route.js comme ceci:

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>
    );
);

J'ai configuré un exemple avec lequel vous pourriez jouer dans ce codepen .

Je ne sais pas si le déclenchement d'une action pour gérer l'authentification est une bonne idée. Personnellement, je préfère gérer l'auth de façon différente :

Au lieu d'utiliser un crochet onEnter, j'utilise une fonction d'habillage. Je veux que la section admin de mon blog soit protégée, j'ai donc encapsulé le composant AdminContainer dans les routes avec une fonction, requireAuthentication, voir ci-dessous.

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 est une fonction qui

  • si l'utilisateur est authentifié, rend le composant encapsulé,
  • sinon redirige vers Login

Vous pouvez le voir ci-dessous:

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);
}

De plus, requireAuthentication protégera toutes les routes sous /admin. Et vous pouvez le réutiliser où vous le souhaitez.

12
Alex Chirițescu

Beaucoup de choses ont changé au fil du temps. onEnter n'existe plus sur react-router-4

Ce qui suit est de mon vrai projet pour votre référence

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>
  )
}
4
catsky