Qual è il modo migliore per gestire un errore di recupero in React Redux?


105

Ho un riduttore per i client, un altro per AppToolbar e alcuni altri ...

Ora diciamo che ho creato un'azione di recupero per eliminare il client, e se fallisce ho del codice nel riduttore Clients che dovrebbe fare alcune cose, ma voglio anche visualizzare alcuni errori globali in AppToolbar.

Ma i riduttori Client e AppToolbar non condividono la stessa parte dello stato e non posso creare una nuova azione nel riduttore.

Allora come suppongo di mostrare l'errore globale? Grazie

AGGIORNAMENTO 1:

Dimentico di dire che uso este devstack

AGGIORNAMENTO 2: Ho contrassegnato la risposta di Eric come corretta, ma devo dire che la soluzione che sto usando in este è più simile a una combinazione della risposta di Eric e Dan ... Devi solo trovare ciò che ti si adatta meglio nel tuo codice .. .


2
Stai ottenendo voti vicini, e probabilmente è perché non stai fornendo un sacco di codice di esempio. La tua domanda e le risposte che otterrai saranno più utili ad altre persone se il problema viene delineato in modo più chiaro.
acjay

Sono d'accordo con @acjay che questa domanda manca di contesto. Ho risposto di seguito (con esempi di codice) con una soluzione generale, ma la tua domanda potrebbe richiedere qualche perfezionamento. Sembra che tu possa avere alcuni problemi separati. 1) Gestione di azioni / errori asincroni. 2) Suddividere lo stato in modo appropriato nel tuo albero di stato redux. 3) Ottenere i tuoi componenti i dati di cui hanno bisogno.
Erik Aybar

@ErikTheDeveloper grazie, la tua risposta sembra ottima. Ma hai ragione, dimentico di menzionare il contesto. Ho modificato la mia domanda, sto usando este devstack e sembra che la tua risposta non sia applicabile lì perché è ...
Dusan Plavak

Risposte:


116

Se vuoi avere il concetto di "errori globali", puoi creare un errorsriduttore, che può ascoltare le azioni addError, removeError, ecc. Quindi, puoi collegarti al tuo albero di stato Redux su state.errorse visualizzarli dove appropriato.

Ci sono molti modi in cui potresti avvicinarti a questo, ma l'idea generale è che gli errori / messaggi globali meriterebbero il loro riduttore per vivere completamente separati da <Clients />/ <AppToolbar />. Ovviamente, se uno di questi componenti necessita di accesso, errorspuoi trasmetterlo errorsa loro come supporto ovunque sia necessario.

Aggiornamento: esempio di codice

Ecco un esempio di come potrebbe apparire se dovessi passare gli "errori globali" errorsal tuo livello superiore <App />e renderlo condizionatamente (se sono presenti errori). Uso di react-reduxconnect per collegare il tuo <App />componente ad alcuni dati.

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

E per quanto riguarda il creatore dell'azione, invierà un errore di successo ( redux-thunk ) in base alla risposta

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

Mentre il tuo riduttore potrebbe semplicemente gestire una serie di errori, aggiungendo / rimuovendo le voci in modo appropriato.

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

1
Erik, ho qualcosa di simile a quello che hai suggerito qui, ma sorprendentemente non riesco mai a ottenere catchfunzioni chiamate se someHttpClient.get('/resources')o fetch('/resources')che uso nel mio codice di ritorno 500 Server Error. Hai qualche pensiero fuori dalla testa in cui potrei sbagliarmi? In sostanza, quello che faccio è fetchinviare una richiesta che finisce con il mio in routescui chiamo un metodo sul mio mongoosemodello per fare qualcosa di molto semplice, come aggiungere un testo o rimuovere un testo dal DB.
Kevin Ghaboosi

2
Ehi, sono arrivato qui da una ricerca su Google - volevo solo ringraziarti per un ottimo esempio .. Ho lottato con gli stessi problemi, e questo è fantastico. Ovviamente la soluzione è integrare gli errori nel negozio. Perché non ci ho pensato ... applausi
Spock

2
Come si esegue una funzione quando si verifica un errore? ad esempio, devo mostrare un avviso popup / avviso nell'interfaccia utente, non eseguire il rendering di un componente Avviso aggiornando gli oggetti di scena del componente padre
Gianfranco P.

111

La risposta di Erik è corretta ma vorrei aggiungere che non è necessario attivare azioni separate per l'aggiunta di errori. Un approccio alternativo consiste nell'avere un riduttore che gestisca qualsiasi azione con un errorcampo . Questa è una questione di scelta personale e convenzione.

Ad esempio, dalla Redux real-worldesempio che ha la gestione degli errori:

// 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
}

Significa che su ogni richiesta di successo dovremmo passare il tipo RESET_ERROR_MESSAGE al riduttore errorMessage?
Dimi Mikadze

2
@DimitriMikadze no, non lo fa. Questa funzione è solo una riduzione dello stato degli errori. Se superi RESET_ERROR_MESSAGE, verranno cancellati tutti i messaggi di errore. Se non si passa e non è presente alcun campo di errore, viene restituito lo stato invariato, quindi se ci sono stati degli errori da azioni precedenti, saranno ancora lì dopo l'azione di successo ....
Dusan Plavak

Preferisco questo approccio perché consente una risposta in linea più naturale quando il consumatore allega il errorcarico utile dell'azione. Grazie Dan!
Mike Perrenoud

1
Non riesco a capire come funziona. A parte l'esempio del mondo reale, hai documenti / video isolati che spiegano questo? È un requisito fondamentale della maggior parte dei progetti e ho trovato documentazione poco facile da capire sull'argomento. Grazie.
Matt Saunders

6
@MattSaunders Mentre cercavo di capirlo mi sono imbattuto in un corso Redux dello stesso Dan (il risponditore, che in realtà è il creatore di Redux), con una sezione sulla visualizzazione dei messaggi di errore che insieme a queste risposte e all'esempio del mondo reale lo hanno portato a casa per me. In bocca al lupo.
Agustín Lado

2

L'approccio che sto attualmente adottando per alcuni errori specifici (convalida dell'input dell'utente) è di fare in modo che i miei sub-riduttori generino un'eccezione, catturandola nel mio riduttore di root e collegandola all'oggetto azione. Quindi ho una redux-saga che ispeziona gli oggetti azione per un errore e aggiorna l'albero di stato con i dati di errore in quel caso.

Così:

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

Quindi aggiungere l'errore all'albero degli stati è come descritto da Erik.

Lo uso con parsimonia, ma mi evita di dover duplicare la logica che appartiene legittimamente al riduttore (quindi può proteggersi da uno stato non valido).


1

scrivere middleware personalizzato per gestire tutti gli errori relativi alle API. In questo caso il tuo codice sarà più pulito.

   failure/ error actin type ACTION_ERROR

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

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

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

       }
}

1
Inoltre rende più difficile eseguire il debug IMHO
chrisjlee

2
Non hai bisogno di middleware per questo, puoi scrivere lo stesso identico ifin un riduttore
Juan Campa,

Se sono presenti più di 50 API, è necessario scrivere ovunque. È invece possibile scrivere middleware personalizzato per verificare l'errore.
Shrawan,

0

quello che faccio è centralizzare tutta la gestione degli errori nell'effetto in base all'effetto

/**
 * 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}`));
    });

-8

Puoi usare il client HTTP axios. Ha già implementato la funzione Interceptors. È possibile intercettare le richieste o le risposte prima che vengano gestite in quel momento o catturare.

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


Sì ma non stai inviando nulla da redux?
Eino Mäkitalo

Questo approccio non è male. Di solito store in redux è un singleton ed è possibile importare store nel file axios interceptors e utilizzare store.dispatch () per attivare qualsiasi azione. Questo è un approccio unitario per gestire tutti gli errori API nel sistema in 1 posizione
mercoledì
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.