106 votes

React/Redux - répartition des actions au chargement/initialisation de l'application

J'ai une authentification par jeton à partir d'un serveur, donc lorsque mon application Redux est chargée initialement, je dois faire une demande à ce serveur pour vérifier si l'utilisateur est authentifié ou non, et si oui, je dois obtenir le jeton.

J'ai découvert que l'utilisation des actions INIT du noyau de Redux n'est pas recommandée, alors comment puis-je envoyer une action avant que l'application ne soit rendue ?

80voto

Serhii Baraniuk Points 2405

Vous pouvez répartir une action dans Root componentDidMount et dans render vous pouvez vérifier le statut d'authentification.

Quelque chose comme ça :

class App extends Component {
  componentDidMount() {
    this.props.getAuth()
  }

  render() {
    return this.props.isReady
      ? <div> ready </div>
      : <div>not ready</div>
  }
}

const mapStateToProps = (state) => ({
  isReady: state.isReady,
})

const mapDispatchToProps = {
  getAuth,
}

export default connect(mapStateToProps, mapDispatchToProps)(App)

1 votes

Pour moi componentWillMount() a fait le truc. J'ai défini une fonction simple appelant toutes les actions liées à la répartition dans mapDispatchToProps() de App.js et l'a appelé dans componentWillMount() .

0 votes

C'est très bien, mais utiliser mapDispatchToProps semble plus descriptif. Comment justifiez-vous l'utilisation de mapStateToProps ?

0 votes

@adc17 Oooops :) merci pour le commentaire. J'ai modifié ma réponse !

36voto

Chris Kemp Points 522

Je n'ai pas été satisfait des solutions proposées à ce sujet, et puis je me suis rendu compte que je pensais aux classes qui devaient être rendues. Et si je créais simplement une classe pour le démarrage et que je poussais les choses dans la classe componentDidMount et avoir simplement la méthode render afficher un écran de chargement ?

<Provider store={store}>
  <Startup>
    <Router>
      <Switch>
        <Route exact path='/' component={Homepage} />
      </Switch>
    </Router>
  </Startup>
</Provider>

Et ensuite avoir quelque chose comme ça :

class Startup extends Component {
  static propTypes = {
    connection: PropTypes.object
  }
  componentDidMount() {
    this.props.actions.initialiseConnection();
  }
  render() {
    return this.props.connection
      ? this.props.children
      : (<p>Loading...</p>);
  }
}

function mapStateToProps(state) {
  return {
    connection: state.connection
  };
}

function mapDispatchToProps(dispatch) {
  return {
    actions: bindActionCreators(Actions, dispatch)
  };
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Startup);

Ensuite, écrivez quelques actions redux pour initialiser votre application de manière asynchrone. Cela fonctionne très bien.

0 votes

C'est la solution que je cherchais ! Je crois que votre vision des choses est parfaitement juste. Merci.

11voto

Shota Points 2760

Mise à jour 2020 : Parallèlement à d'autres solutions, j'utilise un middleware Redux pour vérifier à chaque demande les tentatives de connexion échouées :

export default () => next => action => {
  const result = next(action);
  const { type, payload } = result;

  if (type.endsWith('Failure')) {
    if (payload.status === 401) {
      removeToken();

      window.location.replace('/login');
    }
  }

  return result;
};

Mise à jour 2018 : Cette réponse est pour Routeur React 3

J'ai résolu ce problème en utilisant react-router onEnter props. Voici à quoi ressemble le code :

// this function is called only once, before application initially starts to render react-route and any of its related DOM elements
// it can be used to add init config settings to the application
function onAppInit(dispatch) {
  return (nextState, replace, callback) => {
    dispatch(performTokenRequest())
      .then(() => {
        // callback is like a "next" function, app initialization is stopped until it is called.
        callback();
      });
  };
}

const App = () => (
  <Provider store={store}>
    <IntlProvider locale={language} messages={messages}>
      <div>
        <Router history={history}>
          <Route path="/" component={MainLayout} onEnter={onAppInit(store.dispatch)}>
            <IndexRoute component={HomePage} />
            <Route path="about" component={AboutPage} />
          </Route>
        </Router>
      </div>
    </IntlProvider>
  </Provider>
);

11 votes

Pour être clair, react-router 4 ne supporte pas onEnter.

0 votes

L'IntlProvider devrait vous donner un indice d'une meilleure solution Voir ma réponse ci-dessous.

0 votes

Cette utilisation de l'ancien react-router v3, regardez ma réponse

1voto

matt carlotta Points 2027

Similaire, mais une alternative à ce qui précède (cela semble fonctionner uniquement avec React-Router v3) :

Routes.js

import React from 'react';
import { Route, IndexRoute } from 'react-router';

import App from '../components/App';
import Home from '../views/Home';
import OnLoadAuth from '../containers/app/OnLoadAuth';

export default = (
  <Route path="/" component={OnLoadAuth(App)}>
    <IndexRoute component={Home} />
    {* Routes that require authentication *}
  </Route>
);

OnLoadAuth.js

import React, { Component } from 'react';
import { connect } from 'react-redux';

import { authenticateUser, fetchingUser } from '../../actions/AuthActionCreators';
import Spinner from '../../components/loaders/Spinner';

export default App => {
  class OnLoadAuth extends Component {
    componentDidMount = () => this.props.authenticateUser();

    render = () => (
      (this.props.isLoading === undefined || this.props.isLoading)
       ? <Spinner />
       : <App {...this.props} />
    )
 }

 return connect(
    state => ({ isLoading: state.auth.fetchingUser }),
      { authenticateUser, fetchingUser }
    )(OnLoadAuth);
};

-2voto

stackdave Points 1928

Utilisation : Apollo Client 2.0, React-Router v4, React 16 (Fiber)

La réponse choisie est l'utilisation de l'ancien React Router v3. J'avais besoin de faire un 'dispatch' pour charger les paramètres globaux de l'application. L'astuce consiste à utiliser componentWillUpdate, bien que l'exemple utilise le client Apollo, et non fetch, les solutions sont équivalentes. Vous n'avez pas besoin de boucle de

SettingsLoad.js

import React, { Component } from 'react';
import { connect } from 'react-redux';
import {bindActionCreators} from "redux";
import {
  graphql,
  compose,
} from 'react-apollo';

import {appSettingsLoad} from './actions/appActions';
import defQls from './defQls';
import {resolvePathObj} from "./utils/helper";
class SettingsLoad extends Component {

  constructor(props) {
    super(props);
  }

  componentWillMount() { // this give infinite loop or no sense if componente will mount or not, because render is called a lot of times

  }

  //componentWillReceiveProps(newProps) { // this give infinite loop
  componentWillUpdate(newProps) {

    const newrecord = resolvePathObj(newProps, 'getOrgSettings.getOrgSettings.record');
    const oldrecord = resolvePathObj(this.props, 'getOrgSettings.getOrgSettings.record');
    if (newrecord === oldrecord) {
      // when oldrecord (undefined) !== newrecord (string), means ql is loaded, and this will happens
      //  one time, rest of time:
      //     oldrecord (undefined) == newrecord (undefined)  // nothing loaded
      //     oldrecord (string) == newrecord (string)   // ql loaded and present in props
      return false;
    }
    if (typeof newrecord ==='undefined') {
      return false;
    }
    // here will executed one time
    setTimeout(() => {
      this.props.appSettingsLoad( JSON.parse(this.props.getOrgSettings.getOrgSettings.record));
    }, 1000);

  }
  componentDidMount() {
    //console.log('did mount this props', this.props);

  }

  render() {
    const record = resolvePathObj(this.props, 'getOrgSettings.getOrgSettings.record');
    return record
      ? this.props.children
      : (<p>...</p>);
  }
}

const withGraphql = compose(

  graphql(defQls.loadTable, {
    name: 'loadTable',
    options: props => {
      const optionsValues = {  };
      optionsValues.fetchPolicy = 'network-only';
      return optionsValues ;
    },
  }),
)(SettingsLoad);

const mapStateToProps = (state, ownProps) => {
  return {
    myState: state,
  };
};

const mapDispatchToProps = (dispatch) => {
  return bindActionCreators ({appSettingsLoad, dispatch }, dispatch );  // to set this.props.dispatch
};

const ComponentFull = connect(
  mapStateToProps ,
  mapDispatchToProps,
)(withGraphql);

export default ComponentFull;

App.js

class App extends Component<Props> {
  render() {

    return (
        <ApolloProvider client={client}>
          <Provider store={store} >
            <SettingsLoad>
              <BrowserRouter>
            <Switch>
              <LayoutContainer
                t={t}
                i18n={i18n}
                path="/myaccount"
                component={MyAccount}
                title="form.myAccount"
              />
              <LayoutContainer
                t={t}
                i18n={i18n}
                path="/dashboard"
                component={Dashboard}
                title="menu.dashboard"
              />

2 votes

Ce code est incomplet et doit être coupé pour les parties non pertinentes à la question.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X