¿Cuál es la mejor manera de lidiar con un error de recuperación en react redux?

105

Tengo un reductor para Clientes, otro para AppToolbar y algunos otros ...

Ahora digamos que creé una acción de recuperación para eliminar el cliente, y si falla, tengo un código en el reductor de Clientes que debería hacer algunas cosas, pero también quiero mostrar algún error global en AppToolbar.

Pero los reductores Clients y AppToolbar no comparten la misma parte del estado y no puedo crear una nueva acción en el reductor.

Entonces, ¿cómo se supone que debo mostrar un error global? Gracias

ACTUALIZACIÓN 1:

Olvidé mencionar que uso este devstack

ACTUALIZACIÓN 2: Marqué la respuesta de Eric como correcta, pero tengo que decir que la solución que estoy usando en este es más como una combinación de la respuesta de Eric y Dan ... Solo tienes que encontrar lo que mejor se adapte a ti en tu código ... .

Dusan Plavak
fuente
2
Está obteniendo votos cercanos y probablemente se deba a que no está proporcionando una gran cantidad de código de ejemplo. Su pregunta y las respuestas que obtenga serán más útiles para otras personas si el problema se describe con mayor claridad.
acjay
Tengo que estar de acuerdo con @acjay en que esta pregunta carece de contexto. He respondido a continuación (con ejemplos de código) con una solución general, pero su pregunta podría necesitar un poco de refinamiento. Parece que puede tener algunos problemas distintos. 1) Manejo de acciones / errores asincrónicos. 2) Dividir el estado de manera apropiada en su árbol de estado de redux. 3) Obtener los datos que necesitan sus componentes.
Erik Aybar
@ErikTheDeveloper gracias, tu respuesta se ve muy bien. Pero tienes razón, me olvido de mencionar el contexto. Edité mi pregunta, estoy usando este devstack y parece que su respuesta no es aplicable allí como está ...
Dusan Plavak

Respuestas:

116

Si desea tener el concepto de "errores globales", puede crear un errorsreductor, que puede escuchar las acciones addError, removeError, etc ... Luego, puede conectarse a su árbol de estado de Redux en state.errorsy mostrarlos donde sea apropiado.

Hay varias formas de abordar esto, pero la idea general es que los errores / mensajes globales merecerían su propio reductor para vivir completamente separados de <Clients />/ <AppToolbar />. Por supuesto, si cualquiera de estos componentes necesita acceso errors, puede pasarlos errorscomo un accesorio donde sea necesario.

Actualización: ejemplo de código

Aquí hay un ejemplo de cómo se vería si pasara los "errores globales" errorsa su nivel superior <App />y lo renderizara condicionalmente (si hay errores presentes). Usando react-redux'sconnect para conectar su <App />componente a algunos datos.

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

Y en lo que respecta al creador de la acción, enviaría ( redux-thunk ) el error de éxito de acuerdo con la respuesta

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

Si bien su reductor podría simplemente administrar una serie de errores, agregando / eliminando entradas de manera adecuada.

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
fuente
1
Erik, tengo algo similar a lo que ha sugerido aquí, pero sorprendentemente nunca logro que se catchllamen funciones si someHttpClient.get('/resources')o las fetch('/resources')que uso en mi devolución de código 500 Server Error. ¿Tiene algún pensamiento en la parte superior de su cabeza donde podría estar cometiendo un error? Esencialmente, lo que hago es fetchenviar una solicitud que termina con mi routesen el que llamo a un método en mi mongoosemodelo para hacer algo muy simple, como agregar un texto o eliminar un texto de la base de datos.
Kevin Ghaboosi
2
Oye, vine aquí desde una búsqueda en Google. Solo quería agradecerles por un gran ejemplo. He estado luchando con los mismos problemas, y esto es brillante. Por supuesto, la solución es integrar errores en la tienda. Por qué no pensé en eso ... salud
Spock
2
¿Cómo se ejecuta una función cuando se produce un error? por ejemplo, necesito mostrar un brindis / alerta en la interfaz de usuario, no generar un componente de alerta actualizando los accesorios del componente principal
Gianfranco P.
111

La respuesta de Erik es correcta, pero me gustaría agregar que no tiene que disparar acciones separadas para agregar errores. Un enfoque alternativo es tener un reductor que maneje cualquier acción con un errorcampo . Este es un asunto de elección y convención personal.

Por ejemplo, del ejemplo de Reduxreal-world que tiene manejo de errores:

// 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
fuente
¿Significa que en cada solicitud de éxito debemos pasar el tipo RESET_ERROR_MESSAGE al reductor errorMessage?
Dimi Mikadze
2
@DimitriMikadze no, no lo hace. Esta función es simplemente reductora para el estado de errores. Si pasa RESET_ERROR_MESSAGE, borrará todos los mensajes de error. Si no pasa y no hay un campo de error, simplemente devuelve el estado sin cambios, por lo que si hubo algunos errores de acciones anteriores, todavía estarán allí después de la acción exitosa ...
Dusan Plavak
Prefiero este enfoque porque permite una respuesta en línea más natural a medida que el consumidor adjunta la errorcarga útil de la acción. ¡Gracias Dan!
Mike Perrenoud
1
No puedo entender bien cómo funciona esto. Aparte del ejemplo del mundo real, ¿tiene algún documento / video aislado que explique esto? Es un requisito fundamental de la mayoría de los proyectos, y he encontrado poca documentación sobre el tema fácil de entender. Gracias.
Matt Saunders
6
@MattSaunders Mientras trataba de entenderlo, me encontré con un curso de Redux del mismo Dan (el respondedor, que en realidad es el creador de Redux), con una sección sobre Visualización de mensajes de error que junto con estas respuestas y el ejemplo del mundo real lo llevaron a casa para yo. Buena suerte.
Agustín Lado
2

El enfoque que estoy tomando actualmente para algunos errores específicos (validación de entrada del usuario) es hacer que mis sub-reductores lancen una excepción, la capturen en mi reductor raíz y la adjunten al objeto de acción. Luego tengo un redux-saga que inspecciona los objetos de acción en busca de un error y actualiza el árbol de estado con datos de error en ese caso.

Entonces:

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

Y luego agregar el error al árbol de estado es como lo describe Erik.

Lo uso con bastante moderación, pero evita que tenga que duplicar la lógica que pertenece legítimamente al reductor (para que pueda protegerse de un estado no válido).

Gavin
fuente
1

escriba Middleware personalizado para manejar todos los errores relacionados con la API. En este caso, su código será más limpio.

   failure/ error actin type ACTION_ERROR

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

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

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

       }
}
Khalid Azam
fuente
1
También hace que sea más difícil depurar en mi humilde opinión
chrisjlee
2
No necesita middleware para esto, puede escribir exactamente lo mismo ifen un reductor
Juan Campa
Si hay más de 50 api, debe escribir en todas partes. En su lugar, puede escribir middleware personalizado para verificar el error.
Shrawan
0

lo que hago es centralizar todo el manejo de errores en el efecto por efecto

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

Puede utilizar el cliente HTTP axios. Ya ha implementado la función Interceptores. Puede interceptar solicitudes o respuestas antes de que se manejen o se detecten.

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ú Đỗ
fuente
Sí, pero ¿no enviará nada a redux?
Eino Mäkitalo
Este enfoque no está mal. Por lo general, store in redux es un singleton y puede importar store in axios interceptors file y usar store.dispatch () para activar cualquier acción. Este es un enfoque unitario para manejar todos los errores de API en el sistema en 1 lugar
Miércoles