Was ist der beste Weg, um mit einem Abruffehler in React Redux umzugehen?

105

Ich habe einen Reduzierer für Clients, einen anderen für AppToolbar und einige andere ...

Nehmen wir nun an, ich habe eine Abrufaktion zum Löschen des Clients erstellt. Wenn dies fehlschlägt, befindet sich im Clients-Reduzierer Code, der einige Aufgaben ausführen sollte, aber ich möchte auch einen globalen Fehler in der AppToolbar anzeigen.

Die Clients und die AppToolbar-Reduzierungen teilen sich jedoch nicht denselben Teil des Status, und ich kann keine neue Aktion im Reduzierer erstellen.

Wie soll ich also einen globalen Fehler anzeigen? Vielen Dank

UPDATE 1:

Ich vergessen zu erwähnen , dass ich este devstack

UPDATE 2: Ich habe Erics Antwort als richtig markiert, aber ich muss sagen, dass die Lösung, die ich in Wertschätzung verwende, eher einer Kombination aus Eric und Dans Antwort entspricht ... Sie müssen nur herausfinden, was am besten zu Ihrem Code passt. .

Dusan Plavak
quelle
2
Sie erhalten enge Abstimmungen, und dies liegt wahrscheinlich daran, dass Sie nicht viele Beispielcodes bereitstellen. Ihre Frage und die Antworten, die Sie erhalten, sind für andere hilfreicher, wenn das Problem klarer umrissen ist.
Acjay
Ich muss w / @acjay zustimmen, dass diese Frage im Kontext fehlt. Ich habe unten (mit Codebeispielen) mit einer allgemeinen Lösung geantwortet, aber Ihre Frage könnte etwas verfeinert werden. Es scheint, als hätten Sie einige separate Probleme. 1) Behandlung von asynchronen Aktionen / Fehlern. 2) Teilen Sie den Status in Ihrem Redux-Statusbaum entsprechend auf. 3) Erhalten Sie Ihren Komponenten die Daten, die sie benötigen.
Erik Aybar
@ErikTheDeveloper danke, deine Antwort sieht gut aus. Aber Sie haben Recht, ich vergesse, den Kontext zu erwähnen. Ich habe meine Frage bearbeitet, ich benutze est devstack und es sieht so aus, als ob Ihre Antwort dort nicht anwendbar ist, wie sie ist ...
Dusan Plavak

Antworten:

116

Wenn Sie das Konzept "globaler Fehler" haben möchten, können Sie einen errorsReduzierer erstellen , der auf Aktionen von addError, removeError usw. warten kann. Anschließend können Sie sich in Ihren Redux-Statusbaum unter einbinden state.errorsund diese nach Bedarf anzeigen.

Es gibt eine Reihe von Möglichkeiten, wie Sie dies angehen können, aber die allgemeine Idee ist, dass globale Fehler / Nachrichten ihren eigenen Reduzierer verdienen würden, um völlig getrennt von <Clients />/ zu leben <AppToolbar />. Wenn eine dieser Komponenten Zugriff benötigt, können errorsSie sie natürlich errorsüberall dort als Requisite weitergeben, wo dies erforderlich ist.

Update: Codebeispiel

Hier ist ein Beispiel dafür, wie es aussehen könnte, wenn Sie die "globalen Fehler" errorsan Ihre oberste Ebene übergeben <App />und unter bestimmten Bedingungen rendern würden (wenn Fehler vorhanden sind). Verwenden Sie React-Reduxconnect , um Ihre <App />Komponente mit einigen Daten zu verbinden.

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

Und was den Aktionsersteller betrifft, würde er gemäß der Antwort einen Erfolgsfehler ( Redux-Thunk ) auslösen

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

Während Ihr Reduzierer einfach eine Reihe von Fehlern verwalten kann, können Sie Einträge entsprechend hinzufügen / entfernen.

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
quelle
1
Erik, ich habe etwas Ähnliches wie das, was Sie hier vorgeschlagen haben, aber überraschenderweise schaffe ich es nie, catchFunktionen aufzurufen, wenn someHttpClient.get('/resources')oder fetch('/resources')die ich in meiner Code-Rückgabe verwende 500 Server Error. Haben Sie irgendwelche Gedanken, bei denen ich einen Fehler machen könnte? Im Wesentlichen sende ich fetcheine Anfrage, die mit meiner endet, routeswobei ich eine Methode für mein mongooseModell aufrufe, um etwas sehr Einfaches zu tun, wie das Hinzufügen eines Textes oder das Entfernen eines Textes aus der Datenbank.
Kevin Ghaboosi
2
Hey, ich bin von einer Google-Suche hierher gekommen - wollte mich nur für ein großartiges Beispiel bedanken. Ich habe mit den gleichen Problemen zu kämpfen, und das ist brillant. Die Lösung besteht natürlich darin, Fehler in den Laden zu integrieren. Warum habe ich nicht daran gedacht ... Prost
Spock
2
Wie würde man eine Funktion ausführen, wenn ein Fehler auftritt? zB muss ich einen Toast / Alarm in der Benutzeroberfläche anzeigen, keine Alarmkomponente rendern, indem ich die Requisiten der übergeordneten Komponente aktualisiere
Gianfranco P.
111

Eriks Antwort ist richtig, aber ich möchte hinzufügen, dass Sie keine separaten Aktionen auslösen müssen, um Fehler hinzuzufügen. Ein alternativer Ansatz besteht darin, einen Reduzierer zu haben, der jede Aktion mit einem errorFeld verarbeitet . Dies ist eine Frage der persönlichen Wahl und Konvention.

Beispiel aus dem Redux- real-worldBeispiel mit Fehlerbehandlung:

// 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
quelle
Bedeutet dies, dass wir bei jeder Erfolgsanforderung den Typ RESET_ERROR_MESSAGE an den Reducer errorMessage übergeben sollten?
Dimi Mikadze
2
@ DimitriMikadze nein, tut es nicht. Diese Funktion ist nur ein Reduzierer für den Fehlerzustand. Wenn Sie RESET_ERROR_MESSAGE übergeben, werden alle Fehlermeldungen gelöscht. Wenn Sie nicht bestehen und kein Fehlerfeld vorhanden ist, wird nur der unveränderte Status zurückgegeben. Wenn also einige Fehler aus früheren Aktionen
aufgetreten sind
Ich bevorzuge diesen Ansatz, weil er eine natürlichere Inline-Reaktion ermöglicht, wenn der Verbraucher errordie Nutzlast an die Aktion anfügt. Danke Dan!
Mike Perrenoud
1
Ich kann mir nicht vorstellen, wie das funktioniert. Haben Sie, abgesehen vom realen Beispiel, isolierte Dokumente / Videos, die dies erklären? Es ist eine ziemlich grundlegende Anforderung der meisten Projekte, und ich habe wenig leicht verständliche Dokumentation zu diesem Thema gefunden. Vielen Dank.
Matt Saunders
6
@MattSaunders Während ich versuchte, es zu verstehen, stieß ich auf einen Redux-Kurs von Dan selbst (dem Antwortenden, der eigentlich der Schöpfer von Redux ist) mit einem Abschnitt über das Anzeigen von Fehlermeldungen, der zusammen mit diesen Antworten und dem Beispiel aus der Praxis nach Hause fuhr mich. Viel Glück.
Agustín Lado
2

Der Ansatz, den ich derzeit für einige bestimmte Fehler (Validierung von Benutzereingaben) verfolge, besteht darin, dass meine Unterreduzierer eine Ausnahme auslösen, sie in meinem Stammreduzierer abfangen und an das Aktionsobjekt anhängen. Dann habe ich eine Redux-Saga, die Aktionsobjekte auf einen Fehler untersucht und in diesem Fall den Statusbaum mit Fehlerdaten aktualisiert.

So:

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

Und dann ist das Hinzufügen des Fehlers zum Statusbaum wie von Erik beschrieben.

Ich benutze es ziemlich sparsam, aber es verhindert, dass ich Logik duplizieren muss, die legitimerweise in den Reduzierer gehört (damit es sich vor einem ungültigen Zustand schützen kann).

Gavin
quelle
1

Schreiben Sie eine benutzerdefinierte Middleware, um alle API-bezogenen Fehler zu behandeln. In diesem Fall wird Ihr Code sauberer.

   failure/ error actin type ACTION_ERROR

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

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

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

       }
}
Khalid Azam
quelle
1
Macht
2
Sie brauchen keine Middleware dafür, Sie können genau das gleiche ifin einen Reduzierer schreiben
Juan Campa
Wenn es mehr als 50 APIs gibt, müssen Sie überall schreiben. Stattdessen können Sie benutzerdefinierte Middleware schreiben, um den Fehler zu überprüfen.
Shrawan
0

Ich zentralisiere die gesamte Fehlerbehandlung im Effekt pro Effekt

/**
 * 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
quelle
-8

Sie können den axios HTTP-Client verwenden. Die Interceptors-Funktion wurde bereits implementiert. Sie können Anforderungen oder Antworten abfangen, bevor sie bis dahin bearbeitet oder abgefangen werden.

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ú Đỗ
quelle
Ja, aber Sie versenden nichts an Redux?
Eino Mäkitalo
Dieser Ansatz ist nicht schlecht. Normalerweise ist store in redux ein Singleton, und Sie können store in axios interceptors file importieren und store.dispatch () verwenden, um Aktionen auszulösen. Dies ist ein einheitlicher Ansatz, um alle API-Fehler im System an einem Ort zu behandeln
Mittwoch,