Jaki jest najlepszy sposób radzenia sobie z błędem pobierania w React Redux?

105

Mam jeden reduktor dla Klientów, drugi dla AppToolbar i kilka innych ...

Teraz powiedzmy, że utworzyłem akcję pobierania, aby usunąć klienta, a jeśli to się nie powiedzie, mam kod w reduktorze klientów, który powinien coś zrobić, ale chcę również wyświetlić jakiś globalny błąd w AppToolbar.

Ale Klienci i reduktory AppToolbar nie mają tej samej części stanu i nie mogę utworzyć nowej akcji w reduktorze.

Jak więc mam pokazać błąd globalny? Dzięki

AKTUALIZACJA 1:

Zapomniałem wspomnieć, że używam este devstack

AKTUALIZACJA 2: Oznaczyłem odpowiedź Erica jako poprawną, ale muszę powiedzieć, że rozwiązanie, którego używam z szacunkiem, jest bardziej jak połączenie odpowiedzi Erica i Dana ... Po prostu musisz znaleźć to, co najlepiej pasuje do twojego kodu. .

Dusan Plavak
źródło
2
Otrzymujesz bliskie głosy i prawdopodobnie dlatego, że nie podajesz zbyt wielu przykładowych kodów. Twoje pytanie i otrzymane odpowiedzi będą bardziej pomocne dla innych osób, jeśli problem zostanie wyraźniej przedstawiony.
acjay
Muszę się zgodzić w / @acjay, że to pytanie nie ma kontekstu. Odpowiedziałem poniżej (z przykładami kodu), podając ogólne rozwiązanie, ale twoje pytanie może wymagać doprecyzowania. Wygląda na to, że możesz mieć kilka oddzielnych problemów. 1) Obsługa akcji / błędów asynchronicznych. 2) Odpowiednie rozdzielenie stanu w drzewie stanów redux. 3) Dostarczanie komponentom danych, których potrzebują.
Erik Aybar
@ErikTheDeveloper dzięki, Twoja odpowiedź wygląda świetnie. Ale masz rację, zapomniałem wspomnieć o kontekście. Zredagowałem moje pytanie, używam este devstack i wygląda na to, że twoja odpowiedź nie ma tam zastosowania, ponieważ jest ...
Dusan Plavak

Odpowiedzi:

116

Jeśli chcesz mieć pojęcie „błędów globalnych”, możesz utworzyć errorsreduktor, który może nasłuchiwać akcji addError, removeError, itp. Następnie możesz podłączyć się do swojego drzewa stanu Redux pod adresem state.errorsi wyświetlać je w odpowiednim miejscu.

Istnieje wiele sposobów rozwiązania tego problemu, ale ogólna idea jest taka, że ​​globalne błędy / komunikaty zasługują na to, by ich własny reduktor działał całkowicie niezależnie od <Clients />/ <AppToolbar />. Oczywiście, jeśli którykolwiek z tych elementów wymaga dostępu, errorsmożesz przekazać errorsje jako rekwizyt, gdy zajdzie taka potrzeba.

Aktualizacja: przykład kodu

Oto jeden przykład tego, jak mogłoby to wyglądać, gdybyś przekazał „błędy globalne” errorsna najwyższy poziom <App />i warunkowo je wyrenderował (jeśli występują błędy). Korzystanie z React-Reduxconnect do połączenia <App />komponentu z niektórymi danymi.

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

A jeśli chodzi o twórcę akcji, spowodowałoby to ( redux-thunk ) niepowodzenie sukcesu zgodnie z odpowiedzią

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

Chociaż twój reduktor może po prostu zarządzać szeregiem błędów, odpowiednio dodając / usuwając wpisy.

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
źródło
1
Erik, mam coś podobnego do tego, co tu zasugerowałeś, ale zaskakująco nigdy nie udaje mi się uzyskać catchwywołania funkcji, jeśli someHttpClient.get('/resources')lub fetch('/resources')których używam w moim zwrocie kodu 500 Server Error. Czy masz jakieś myśli na czubku głowy, w których mógłbym popełnić błąd? Zasadniczo to, co robię, to fetchwysłanie żądania, które kończy się na my, w routesktórym wywołuję metodę na moim mongoosemodelu, aby zrobić coś bardzo prostego, na przykład dodanie tekstu lub usunięcie tekstu z bazy danych.
Kevin Ghaboosi,
2
Hej, przyszedłem tu z wyszukiwarki Google - chciałem tylko podziękować za świetny przykład .. Zmagałem się z tymi samymi problemami i to jest genialne. Oczywiście rozwiązaniem jest zintegrowanie błędów w sklepie. Dlaczego o tym nie pomyślałem… Pozdrawiam
Spock
2
Jak wykonać funkcję, gdy wystąpi błąd? np. muszę pokazać toast / alert w interfejsie użytkownika, a nie renderować komponentu Alert przez aktualizację właściwości komponentu nadrzędnego
Gianfranco P.
111

Odpowiedź Erika jest poprawna, ale chciałbym dodać, że nie musisz uruchamiać oddzielnych akcji, aby dodać błędy. Alternatywnym podejściem jest posiadanie reduktora, który obsługuje dowolną akcję z errorpolem . To kwestia osobistego wyboru i konwencji.

Na przykład z przykładu Redux,real-world który ma obsługę błędów:

// 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
źródło
Czy to oznacza, że ​​przy każdym żądaniu sukcesu powinniśmy przekazywać typ RESET_ERROR_MESSAGE do reduktora errorMessage?
Dimi Mikadze
2
@DimitriMikadze nie, nie ma. Ta funkcja jest po prostu redukcją stanu błędów. Jeśli zdasz RESET_ERROR_MESSAGE, wszystkie komunikaty o błędach zostaną usunięte. Jeśli nie przejdziesz i nie ma pola błędu, po prostu zwróci niezmieniony stan, więc jeśli wystąpiły jakieś błędy z poprzednich akcji, nadal będą tam po udanej akcji ....
Dusan Plavak
Preferuję to podejście, ponieważ pozwala na bardziej naturalną odpowiedź w tekście, gdy konsument dołącza errordo ładunku działania. Dzięki Dan!
Mike Perrenoud
1
Nie wiem, jak to działa. Oprócz przykładu ze świata rzeczywistego, czy masz jakieś izolowane i dokumenty / filmy wyjaśniające to? Jest to dość podstawowy wymóg większości projektów i znalazłem mało łatwą do zrozumienia dokumentację na ten temat. Dzięki.
Matt Saunders
6
@MattSaunders Próbując to zrozumieć, natknąłem się na kurs Redux prowadzony przez samego Dana (odpowiadającego, który jest w rzeczywistości twórcą Redux), z sekcją dotyczącą wyświetlania komunikatów o błędach, która wraz z tymi odpowiedziami i przykładem ze świata rzeczywistego doprowadziła go do domu mnie. Powodzenia.
Agustín Lado
2

Podejście, które obecnie stosuję w przypadku kilku konkretnych błędów (sprawdzanie poprawności danych wejściowych użytkownika), polega na tym, że moje reduktory podrzędne zgłaszają wyjątek, przechwytują go w moim reduktorze głównym i dołączają go do obiektu akcji. Następnie mam sagę redux, która sprawdza obiekty akcji pod kątem błędu i aktualizuje drzewo stanu danymi o błędach w tym przypadku.

Więc:

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

A potem dodanie błędu do drzewa stanu jest zgodne z opisem Erika.

Używam go dość oszczędnie, ale dzięki temu nie muszę powtarzać logiki, która zgodnie z prawem należy do reduktora (dzięki czemu może chronić się przed nieprawidłowym stanem).

Gavin
źródło
1

napisz niestandardowe oprogramowanie pośredniczące, aby obsłużyć wszystkie błędy związane z interfejsem API. W takim przypadku twój kod będzie bardziej przejrzysty.

   failure/ error actin type ACTION_ERROR

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

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

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

       }
}
Khalid Azam
źródło
1
Utrudnia również debugowanie IMHO
chrisjlee
2
Nie potrzebujesz do tego oprogramowania pośredniego, możesz napisać dokładnie to samo ifw reduktorze
Juan Campa
Jeśli jest więcej niż 50 api, musisz pisać w każdym miejscu. Zamiast tego możesz napisać niestandardowe oprogramowanie pośredniczące, aby sprawdzić błąd.
Shrawan
0

To, co robię, to scentralizowanie całej obsługi błędów w efekcie na podstawie poszczególnych efektów

/**
 * 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
źródło
-8

Możesz użyć klienta HTTP axios. Posiada już zaimplementowaną funkcję Interceptors. Możesz przechwytywać żądania lub odpowiedzi, zanim zostaną obsłużone do tego czasu lub złapać.

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ú Đỗ
źródło
Tak, ale nie wysyłasz niczego do Redux?
Eino Mäkitalo
To podejście nie jest złe. Zwykle store w redux jest singletonem i możesz zaimportować store w pliku axios interceptors i użyć store.dispatch (), aby wywołać dowolne akcje. Jest to jednolite podejście do obsługi wszystkich błędów API w systemie w 1 miejscu
Wedmich