Quelle est la meilleure façon de traiter une erreur de récupération dans React Redux?

105

J'ai un réducteur pour les clients, un autre pour AppToolbar et quelques autres ...

Disons maintenant que j'ai créé une action de récupération pour supprimer le client, et si cela échoue, j'ai du code dans le réducteur de clients qui devrait faire certaines choses, mais je veux aussi afficher une erreur globale dans AppToolbar.

Mais les clients et les réducteurs AppToolbar ne partagent pas la même partie de l'état et je ne peux pas créer une nouvelle action dans le réducteur.

Alors, comment suis-je supposé montrer une erreur globale? Merci

MISE À JOUR 1:

J'oublie de mentionner que j'utilise este devstack

MISE À JOUR 2: J'ai marqué la réponse d'Eric comme correcte, mais je dois dire que la solution que j'utilise en este ressemble plus à une combinaison de la réponse d'Eric et de Dan ... Il vous suffit de trouver ce qui vous convient le mieux dans votre code. .

Dusan Plavak
la source
2
Vous obtenez des votes serrés, et c'est probablement parce que vous ne fournissez pas beaucoup d'exemples de code. Votre question et les réponses que vous obtiendrez seront plus utiles aux autres si le problème est décrit plus clairement.
acjay du
Je suis d'accord avec @acjay que cette question manque de contexte. J'ai répondu ci-dessous (avec des exemples de code) avec une solution générale, mais votre question pourrait nécessiter un certain raffinement. Il semble que vous ayez quelques problèmes distincts. 1) Gestion des actions / erreurs asynchrones. 2) Diviser l'état de manière appropriée dans votre arbre d'états redux. 3) Obtenir à vos composants les données dont ils ont besoin.
Erik Aybar
@ErikTheDeveloper merci, votre réponse est superbe. Mais vous avez raison, j'oublie de mentionner le contexte. J'ai édité ma question, j'utilise este devstack et il semble que votre réponse n'y soit pas applicable telle quelle ...
Dusan Plavak

Réponses:

116

Si vous voulez avoir le concept d '"erreurs globales", vous pouvez créer un errorsréducteur, qui peut écouter les actions addError, removeError, etc .... Ensuite, vous pouvez vous connecter à votre arborescence d'états Redux sur state.errorset les afficher le cas échéant.

Vous pouvez aborder cela de plusieurs manières, mais l'idée générale est que les erreurs / messages globaux mériteraient que leur propre réducteur soit complètement séparé de <Clients />/ <AppToolbar />. Bien sûr, si l'un de ces composants a besoin d'accès, errorsvous pouvez les transmettre errorsen tant qu'accessoire là où c'est nécessaire.

Mise à jour: exemple de code

Voici un exemple de ce à quoi cela pourrait ressembler si vous deviez passer les "erreurs globales" errorsdans votre niveau supérieur <App />et les restituer conditionnellement (s'il y a des erreurs présentes). Utilisation de react-reduxconnect pour connecter votre <App />composant à certaines données.

// App.js
// Display "global errors" when they are present
function App({errors}) {
  return (
    <div>
      {errors && 
        <UserErrors errors={errors} />
      }
      <AppToolbar />
      <Clients />
    </div>
  )
}

// Hook up App to be a container (react-redux)
export default connect(
  state => ({
    errors: state.errors,
  })
)(App);

Et en ce qui concerne le créateur d'action, il enverrait ( redux-thunk ) échec de succès en fonction de la réponse

export function fetchSomeResources() {
  return dispatch => {
    // Async action is starting...
    dispatch({type: FETCH_RESOURCES});

    someHttpClient.get('/resources')

      // Async action succeeded...
      .then(res => {
        dispatch({type: FETCH_RESOURCES_SUCCESS, data: res.body});
      })

      // Async action failed...
      .catch(err => {
        // Dispatch specific "some resources failed" if needed...
        dispatch({type: FETCH_RESOURCES_FAIL});

        // Dispatch the generic "global errors" action
        // This is what makes its way into state.errors
        dispatch({type: ADD_ERROR, error: err});
      });
  };
}

Alors que votre réducteur pourrait simplement gérer un tableau d'erreurs, en ajoutant / supprimant des entrées de manière appropriée.

function errors(state = [], action) {
  switch (action.type) {

    case ADD_ERROR:
      return state.concat([action.error]);

    case REMOVE_ERROR:
      return state.filter((error, i) => i !== action.index);

    default:
      return state;
  }
}
Erik Aybar
la source
1
Erik, j'ai quelque chose de similaire à ce que vous avez suggéré ici mais étonnamment, je n'arrive jamais à obtenir des catchfonctions appelées si someHttpClient.get('/resources')ou fetch('/resources')que j'utilise dans mon code return 500 Server Error. Avez-vous des pensées sur lesquelles je pourrais faire une erreur? Essentiellement, ce que je fais est d' fetchenvoyer une demande qui se termine par mon routesdans lequel j'appelle une méthode sur mon mongoosemodèle pour faire quelque chose de très simple, comme ajouter un texte ou supprimer un texte de DB.
Kevin Ghaboosi
2
Hé, je suis venu ici à partir d'une recherche Google - je voulais juste vous remercier pour un excellent exemple. J'ai eu des difficultés avec les mêmes problèmes, et c'est génial. Bien sûr, la solution est d'intégrer les erreurs dans le magasin. Pourquoi n'ai-je pas pensé à ça ... Cheers
Spock
2
Comment exécuter une fonction lorsqu'une erreur se produit? par exemple, j'ai besoin d'afficher un toast / alerte dans l'interface utilisateur, pas de rendre un composant d'alerte en mettant à jour les accessoires du composant parent
Gianfranco P.
111

La réponse d'Erik est correcte mais je voudrais ajouter que vous n'avez pas à déclencher des actions séparées pour ajouter des erreurs. Une approche alternative consiste à avoir un réducteur qui gère toute action avec un errorchamp . C'est une question de choix personnel et de convention.

Par exemple, à partir de l' exemple Reduxreal-world qui a une gestion des erreurs:

// Updates error message to notify about the failed fetches.
function errorMessage(state = null, action) {
  const { type, error } = action

  if (type === ActionTypes.RESET_ERROR_MESSAGE) {
    return null
  } else if (error) {
    return error
  }

  return state
}
Dan Abramov
la source
Cela signifie-t-il que pour chaque demande de succès, nous devrions passer le type RESET_ERROR_MESSAGE au réducteur errorMessage?
Dimi Mikadze
2
@DimitriMikadze non, ce n'est pas le cas. Cette fonction est juste un réducteur de l'état des erreurs. Si vous passez RESET_ERROR_MESSAGE, tous les messages d'erreur seront supprimés. Si vous ne réussissez pas et qu'il n'y a pas de champ d'erreur, il ne fait que retourner l'état inchangé, donc s'il y avait des erreurs d'actions précédentes, elles seront toujours là après l'action réussie ....
Dusan Plavak
Je préfère cette approche car elle permet une réponse en ligne plus naturelle lorsque le consommateur attache le errorà la charge utile de l'action. Merci Dan!
Mike Perrenoud
1
Je ne peux pas vraiment comprendre comment cela fonctionne. Mis à part l'exemple du monde réel, avez-vous des documents / vidéos isolés et expliquant cela? C'est une exigence assez fondamentale de la plupart des projets, et j'ai trouvé peu de documentation facile à comprendre sur le sujet. Merci.
Matt Saunders
6
@MattSaunders En essayant de le comprendre, je suis tombé sur un cours Redux par Dan lui-même (le répondeur, qui est en fait le créateur de Redux), avec une section sur l' affichage des messages d'erreur qui, avec ces réponses et l'exemple du monde réel, l'ont conduit à la maison pour moi. Bonne chance.
Agustín Lado
2

L'approche que je prends actuellement pour quelques erreurs spécifiques (validation d'entrée utilisateur) consiste à demander à mes sous-réducteurs de lancer une exception, de l'attraper dans mon réducteur racine et de l'attacher à l'objet d'action. Ensuite, j'ai un redux-saga qui inspecte les objets d'action pour une erreur et met à jour l'arborescence d'état avec des données d'erreur dans ce cas.

Alors:

function rootReducer(state, action) {
  try {
    // sub-reducer(s)
    state = someOtherReducer(state,action);
  } catch (e) {
    action.error = e;
  }
  return state;
}

// and then in the saga, registered to take every action:
function *errorHandler(action) {
  if (action.error) {
     yield put(errorActionCreator(error));
  }
}

Et puis ajouter l'erreur à l'arborescence d'état est comme Erik le décrit.

Je l'utilise avec parcimonie, mais cela m'évite d'avoir à dupliquer la logique qui appartient légitimement au réducteur (afin qu'il puisse se protéger d'un état invalide).

Gavin
la source
1

écrire un middleware personnalisé pour gérer toutes les erreurs liées à l'API. Dans ce cas, votre code sera plus propre.

   failure/ error actin type ACTION_ERROR

   export default  (state) => (next) => (action) => {

      if(ACTION_ERROR.contains('_ERROR')){

       // fire error action
        store.dispatch(serviceError());

       }
}
Khalid Azam
la source
1
Rend
2
Vous n'avez pas besoin de middleware pour cela, vous pouvez écrire exactement la même chose ifdans un réducteur
Juan Campa
S'il y a plus de 50 API, vous devez écrire partout. Au lieu de cela, vous pouvez écrire un middleware personnalisé pour vérifier l'erreur.
Shrawan le
0

ce que je fais est de centraliser toute la gestion des erreurs dans l'effet sur une base par effet

/**
 * central error handling
 */
@Effect({dispatch: false})
httpErrors$: Observable<any> = this.actions$
    .ofType(
        EHitCountsActions.HitCountsError
    ).map(payload => payload)
    .switchMap(error => {
        return of(confirm(`There was an error accessing the server: ${error}`));
    });
Meanstack
la source
-8

Vous pouvez utiliser le client HTTP axios. Il a déjà implémenté la fonction Intercepteurs. Vous pouvez intercepter les demandes ou les réponses avant qu'elles ne soient traitées d'ici là ou catch.

https://github.com/mzabriskie/axios#interceptors

// Add a request interceptor
axios.interceptors.request.use(function (config) {
    // Do something before request is sent
    return config;
  }, function (error) {
    // Do something with request error
    return Promise.reject(error);
  });

// Add a response interceptor
axios.interceptors.response.use(function (response) {
    // Do something with response data
    return response;
  }, function (error) {
    // Do something with response error
    return Promise.reject(error);
  });

Phú Đỗ
la source
Oui mais vous n'envoyez rien à redux?
Eino Mäkitalo
Cette approche n'est pas mauvaise. Habituellement, stocker dans redux est un singleton et vous pouvez importer le magasin dans le fichier d'intercepteurs axios et utiliser store.dispatch () pour déclencher des actions. Il s'agit d'une approche unitaire pour gérer toutes les erreurs d'API dans le système en 1 endroit
Wedmich