Как лучше всего справиться с ошибкой выборки в 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);
  });

Phú Đỗ
источник
Да, но вы ничего не отправляете на redux?
Эйно Мякитало
Это неплохой подход. Обычно хранилище в redux является одиночным, и вы можете импортировать хранилище в файл перехватчиков axios и использовать store.dispatch () для запуска любых действий. Это единый подход для обработки всех ошибок API в системе в
одном