Как лучше всего справиться с ошибкой выборки в React redux?


105

У меня есть один редуктор для клиентов, другой для AppToolbar и некоторые другие ...

Теперь допустим, что я создал действие выборки для удаления клиента, и если это не удается, у меня есть код в редукторе клиентов, который должен кое-что делать, но также я хочу отобразить некоторую глобальную ошибку в AppToolbar.

Но редукторы Clients и AppToolbar не используют одну и ту же часть состояния, и я не могу создать новое действие в редукторе.

Так как же мне показать глобальную ошибку? Спасибо

ОБНОВЛЕНИЕ 1:

Я забыл упомянуть, что использую este devstack

ОБНОВЛЕНИЕ 2: Я отметил ответ Эрика как правильный, но я должен сказать, что решение, которое я использую, больше похоже на комбинацию ответов Эрика и Дэна ... Вам просто нужно найти то, что вам больше всего подходит в вашем коде .. .


2
Вы получаете близкие голоса, и это, вероятно, потому, что вы не предоставляете много примеров кода. Ваш вопрос и полученные ответы будут более полезны для других, если проблема будет изложена более четко.
— acjay

Я должен согласиться с @acjay, что этот вопрос отсутствует в контексте. Я ответил ниже (с примерами кода) с общим решением, но ваш вопрос можно уточнить. Похоже, у вас может быть несколько отдельных проблем. 1) Обработка асинхронных действий / ошибок. 2) Соответствующее разделение состояния в вашем дереве состояний redux. 3) Получение ваших компонентов данных, которые им нужны.
— Эрик Айбар

@ErikTheDeveloper спасибо, ваш ответ выглядит великолепно. Но вы правы, я забыл указать контекст. Я отредактировал свой вопрос, я использую este devstack, и похоже, что ваш ответ там неприменим, так как он есть ...
— Душан Плавак

Ответы:


116

Если вы хотите иметь концепцию «глобальных ошибок», вы можете создать errorsредуктор, который может прослушивать действия addError, removeError и т. Д. Затем вы можете подключиться к своему дереву состояний Redux state.errorsи отображать их там, где это необходимо.

Есть несколько способов подойти к этому, но общая идея состоит в том, что глобальные ошибки / сообщения заслуживают того, чтобы их собственный редуктор существовал полностью отдельно от <Clients />/ <AppToolbar />. Конечно, если любому из этих компонентов нужен доступ, errorsвы можете передать errorsего в качестве опоры везде, где это необходимо.

Обновление: пример кода

Вот один пример того, как это могло бы выглядеть, если бы вы передали "глобальные ошибки" errorsна свой верхний уровень <App />и условно отрендерили их (если есть ошибки). Использование response-reduxconnect для подключения вашего <App />компонента к некоторым данным.

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

И что касается создателя действия, он отправит ( redux-thunk ) успешную неудачу в соответствии с ответом

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

Хотя ваш редуктор может просто управлять массивом ошибок, соответствующим образом добавляя / удаляя записи.

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
Эрик, у меня есть нечто похожее на то, что вы предложили здесь, но, что удивительно, мне никогда не удается вызвать catchфункции, если someHttpClient.get('/resources')или fetch('/resources')которые я использую в своем возврате кода 500 Server Error. Есть ли у вас какие-то мысли, в которых я мог бы ошибиться? По сути, я fetchотправляю запрос, который заканчивается моим, в routesкотором я вызываю метод своей mongooseмодели для выполнения чего-то очень простого, например, добавления текста или удаления текста из БД.
— Кевин Габуси

2
Привет, я пришел сюда из поиска в Google - просто хотел поблагодарить вас за отличный пример ... Я боролся с теми же проблемами, и это великолепно. Конечно, решение - интегрировать ошибки в магазин. Почему я не думаю, что ... приветствий
— Спок

2
Как выполнить функцию при возникновении ошибки? например, мне нужно показать тост / предупреждение в пользовательском интерфейсе, а не отображать компонент Alert, обновив реквизиты родительского компонента
— Gianfranco P.

111

Ответ Эрика правильный, но я хотел бы добавить, что вам не нужно запускать отдельные действия для добавления ошибок. Альтернативный подход - иметь редуктор, который обрабатывает любое действие с errorполем . Это вопрос личного выбора и условности.

Например, из примера Redux, вreal-world котором есть обработка ошибок:

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

Означает ли это, что при каждом успешном запросе мы должны передавать тип RESET_ERROR_MESSAGE редуктору errorMessage?
— Dimi Mikadze

2
@DimitriMikadze нет, это не так. Эта функция просто сокращает состояние ошибок. Если вы передадите RESET_ERROR_MESSAGE, все сообщения об ошибках будут удалены. Если вы не пройдете и нет поля ошибки, он просто вернет неизмененное состояние, поэтому, если были некоторые ошибки из предыдущих действий, они все равно будут там после успешного действия ....
— Душан Плавак

Я предпочитаю этот подход, потому что он обеспечивает более естественный встроенный ответ, когда потребитель присоединяет полезные данные errorк действию. Спасибо, Дэн!
— Майк Перрено

1
Я не могу понять, как это работает. Помимо реального примера, есть ли у вас какие-либо отдельные документы / видео, объясняющие это? Это довольно основное требование большинства проектов, и я нашел немного понятную документацию по этому вопросу. Спасибо.
— Мэтт Сондерс

6
@MattSaunders Пытаясь понять это, я наткнулся на курс Redux самого Дэна (ответчик, который на самом деле является создателем Redux) с разделом о отображении сообщений об ошибках, который вместе с этими ответами и реальным примером привел его домой для меня. Удачи.
— Агустин Ладо

2

Подход, который я сейчас использую для нескольких конкретных ошибок (проверка пользовательского ввода), заключается в том, чтобы мои суб-редукторы генерировали исключение, перехватывали его в моем корневом редукторе и прикрепляли к объекту действия. Затем у меня есть redux-saga, которая проверяет объекты действий на наличие ошибок и в этом случае обновляет дерево состояний с данными об ошибках.

Так:

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

А затем добавление ошибки в дерево состояний, как описывает Эрик.

Я использую его довольно экономно, но он избавляет меня от необходимости дублировать логику, которая законно принадлежит редуктору (чтобы он мог защитить себя от недопустимого состояния).


1

напишите собственное промежуточное ПО для обработки всех ошибок, связанных с api. В этом случае ваш код будет чище.

   failure/ error actin type ACTION_ERROR

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

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

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

       }
}

1
Также затрудняет отладку ИМХО
— chrisjlee

2
Для этого вам не нужно промежуточное ПО, вы можете написать то же самое ifв редукторе
— Хуан Кампа,

Если API более 50, то вам нужно писать везде. Вместо этого вы можете написать собственное промежуточное ПО для проверки ошибки.
— Shrawan

0

что я делаю, так это централизую всю обработку ошибок в эффекте для каждого эффекта

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

Вы можете использовать HTTP-клиент axios. Он уже реализовал функцию перехватчиков. Вы можете перехватывать запросы или ответы до того, как они будут обработаны к тому времени или пойманы.

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


Да, но вы ничего не отправляете на redux?
— Эйно Мякитало

Это неплохой подход. Обычно хранилище в redux является одиночным, и вы можете импортировать хранилище в файл перехватчиков axios и использовать store.dispatch () для запуска любых действий. Это единый подход для обработки всех ошибок API в системе в
— одном
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.