Qual é a melhor maneira de lidar com um erro de busca no react redux?

105

Tenho um redutor para clientes, outro para AppToolbar e alguns outros ...

Agora, digamos que eu criei uma ação fetch para excluir o cliente e, se ela falhar, eu tenho um código no redutor de clientes que deve fazer algumas coisas, mas também quero exibir algum erro global no AppToolbar.

Mas os redutores Clients e AppToolbar não compartilham a mesma parte do estado e não consigo criar uma nova ação no redutor.

Então, como devo mostrar o erro global? obrigado

ATUALIZAÇÃO 1:

Esqueci de mencionar que uso este devstack

ATUALIZAÇÃO 2: Eu marquei a resposta de Eric como correta, mas devo dizer que a solução que estou usando neste caso é mais como a combinação da resposta de Eric e Dan ... Você só precisa encontrar o que se encaixa melhor em seu código. .

Dusan Plavak
fonte
2
Você está obtendo votos acirrados, provavelmente porque não está fornecendo muitos códigos de exemplo. Sua pergunta e as respostas que você obtém serão mais úteis para outras pessoas se o problema for descrito de forma mais clara.
acjay
Eu tenho que concordar com @acjay que esta questão está faltando no contexto. Eu respondi abaixo (com exemplos de código) com uma solução geral, mas sua pergunta precisa de algum refinamento. Parece que você pode ter alguns problemas separados. 1) Tratamento de ações / erros assíncronos. 2) Dividindo o estado apropriadamente em sua árvore de estado redux. 3) Obter os dados de que seus componentes precisam.
Erik Aybar
@ErikTheDeveloper obrigado, sua resposta parece ótima. Mas você tem razão, esqueci de mencionar o contexto. Eu editei minha pergunta, estou usando este devstack e parece que sua resposta não se aplica aqui, pois é ...
Dusan Plavak

Respostas:

116

Se você deseja ter o conceito de "erros globais", pode criar um errorsredutor, que pode escutar as ações addError, removeError, etc .... Então, você pode conectar-se à árvore de estado do Redux em state.errorse exibi-los sempre que apropriado.

Existem várias maneiras de abordar isso, mas a ideia geral é que erros / mensagens globais mereceriam seu próprio redutor para viverem completamente separados de <Clients />/ <AppToolbar />. Claro, se qualquer um desses componentes precisar de acesso, errorsvocê pode transmiti errors-los como um suporte sempre que necessário.

Atualização: Exemplo de Código

Aqui está um exemplo de como seria se você passasse os "erros globais" errorspara o nível superior <App />e os renderizasse condicionalmente (se houver erros). Usando react-redux'sconnect para conectar seu <App />componente a alguns dados.

// 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 no que diz respeito ao criador da ação, ele despacharia ( redux-thunk ) a falha de sucesso de acordo com a resposta

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

Embora seu redutor possa simplesmente gerenciar uma série de erros, adicionando / removendo entradas de forma adequada.

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
fonte
1
Erik, tenho algo semelhante ao que você sugeriu aqui, mas, surpreendentemente, nunca consigo fazer com que catchfunções sejam chamadas se someHttpClient.get('/resources')ou fetch('/resources')que eu uso no meu retorno de código 500 Server Error. Você tem alguma ideia do topo da sua cabeça onde eu possa estar cometendo um erro? Essencialmente, o que faço é fetchenviar um pedido que termina com meu, routesem que chamo um método no meu mongoosemodelo para fazer algo muito simples, como adicionar um texto ou remover um texto do banco de dados.
Kevin Ghaboosi de
2
Ei, eu vim aqui de uma pesquisa no Google - só queria agradecer por um ótimo exemplo. Tenho lutado com os mesmos problemas e isso é brilhante. Claro que a solução é integrar os erros na loja. Por que não pensei nisso ... saúde
Spock,
2
Como alguém executaria uma função quando ocorre um erro? por exemplo, preciso mostrar um toast / alerta na IU, não renderizar um componente Alerta atualizando os adereços do componente pai
Gianfranco P.
111

A resposta de Erik está correta, mas eu gostaria de acrescentar que você não precisa disparar ações separadas para adicionar erros. Uma abordagem alternativa é ter um redutor que lida com qualquer ação com um errorcampo . É uma questão de escolha pessoal e convenção.

Por exemplo, do exemplo Reduxreal-world que tem tratamento de erros:

// 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
fonte
Isso significa que em cada solicitação de sucesso devemos passar o tipo RESET_ERROR_MESSAGE para o redutor errorMessage?
Dimi Mikadze
2
@DimitriMikadze não, não. Esta função é apenas redutora do estado de erros. Se você passar RESET_ERROR_MESSAGE, apagará todas as mensagens de erro. Se você não passar e não houver campo de erro, ele apenas retorna o estado inalterado, então se houver alguns erros de ações anteriores, eles ainda estarão lá após a ação de sucesso ....
Dusan Plavak
Prefiro essa abordagem porque permite uma resposta sequencial mais natural conforme o consumidor anexa o errorà carga útil da ação. Obrigado Dan!
Mike Perrenoud
1
Não consigo entender como isso funciona. Além do exemplo do mundo real, você tem algum documento / vídeo isolado e explicando isso? É um requisito básico da maioria dos projetos, e não achei muito fácil entender a documentação sobre o assunto. Obrigado.
Matt Saunders
6
@MattSaunders Enquanto tentava entender, me deparei com um curso Redux do próprio Dan (o respondente, que na verdade é o criador do Redux), com uma seção sobre Exibindo Mensagens de Erro que, juntamente com essas respostas e o exemplo do mundo real, o levou a mim. Boa sorte.
Agustín Lado
2

A abordagem que estou adotando atualmente para alguns erros específicos (validação de entrada do usuário) é fazer com que meus sub-redutores lançem uma exceção, capturem-na em meu redutor raiz e anexem-na ao objeto de ação. Então eu tenho uma saga redux que inspeciona objetos de ação em busca de um erro e atualiza a árvore de estado com dados de erro nesse caso.

Assim:

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

E adicionar o erro à árvore de estado é como Erik descreve.

Eu o uso com moderação, mas evita que eu tenha que duplicar a lógica que pertence legitimamente ao redutor (para que possa se proteger de um estado inválido).

Gavin
fonte
1

escrever Middleware personalizado para lidar com todos os erros relacionados à API. Nesse caso, seu código ficará mais limpo.

   failure/ error actin type ACTION_ERROR

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

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

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

       }
}
Khalid Azam
fonte
1
Também torna mais difícil depurar IMHO
chrisjlee
2
Você não precisa de middleware para isso, você pode escrever exatamente o mesmo ifem um redutor
Juan Campa
Se houver mais de 50 api's, você precisará escrever em todos os lugares. Em vez disso, você pode escrever um middleware personalizado para verificar o erro.
Shrawan
0

o que faço é centralizar todo o tratamento de erros no efeito por efeito

/**
 * 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}`));
    });
pilha média
fonte
-8

Você pode usar o cliente axios HTTP. Ele já implementou o recurso Interceptors. Você pode interceptar solicitações ou respostas antes que sejam tratadas até então ou capturadas.

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ú Đỗ
fonte
Sim, mas você não está enviando nada para redux?
Eino Mäkitalo
Essa abordagem não é ruim. Normalmente store in redux é um singleton e você pode importar store in axios interceptors file e usar store.dispatch () para acionar quaisquer ações. Esta é uma abordagem unitária para lidar com todos os erros de API no sistema em um
único