Как выполнить обработку исключений в Redux для HTTP-запросов - PullRequest
0 голосов
/ 28 октября 2019

Я учусь реагировать с редуксом и наблюдаемым редуксом. В частности, я пытаюсь понять, как подходить к обработке ошибок. До сих пор:

  1. Мне удалось создать эпопею, наблюдаемую редуксом, которая реагирует на действие извлечения. В частности, он делает запрос ajax и отвечает успешным или неудачным действием.

    a. Если запрос ajax завершается успешно, то эпопея запускает успешное действие с данными ответа в качестве полезной нагрузки.

    b. Если запрос терпит неудачу, то эпопея инициирует действие сбоя с ошибкой в ​​качестве полезной нагрузки.

  2. Компонент проверяет наличие состояния ошибки и, если он присутствует, затем выдает егоокружающий компонент Граница ошибки.

Проблема, с которой я сталкиваюсь при таком подходе, заключается в том, что ошибка остается в состоянии сохранения. В качестве примера предположим следующую локальную среду разработки:

  1. React / Redux клиент с использованием наблюдаемых при редексах эпосов для выполнения http-запросов.
  2. Сервер API отдыха ( НЕ НАЧИНАЕТСЯ )

Когда мой клиент React / Redux делает запрос серверу Rest API, возникает ошибка сетевого подключения, запускающая действие сбоя и приводящая к сохранению состояния ошибки. Затем я START сервер API отдыха и пытаюсь сделать тот же HTTP-запрос от клиента React / Redux. Ошибка из предыдущего запроса остается в магазине. Следовательно, отображается сообщение об ошибке.

Впоследствии, как мне выполнить сброс, чтобы разрешить новый запрос без ошибок? Существуют ли рекомендуемые шаблоны для обработки ошибок с реакцией и редукцией при использовании промежуточного программного обеспечения, такого как наблюдаемое в редуксе или саге на редуксе?

Epic

import { Epic } from 'redux-observable';
import { isActionOf } from 'typesafe-actions';
import { of } from 'rxjs';
import { catchError, filter, map, switchMap } from 'rxjs/operators';

import { fetchCoursesAsync } from './actions';
import { RootAction, RootState, Services } from 'ReduxTypes';

export const fetchCoursesRequestAction: Epic<
  RootAction,
  RootAction,
  RootState,
  Services
> = (action$, state$, { courseServices }) =>
  action$.pipe(
    filter(isActionOf(fetchCoursesAsync.request)),
    switchMap(() =>
      courseServices.default.getCourses().pipe(
        map(fetchCoursesAsync.success),
        catchError((error: Error) =>
          of(fetchCoursesAsync.failure({ hasError: true, error: error })),
        ),
      ),
    ),
  );

Функциональная составляющая

import React, { useEffect } from 'react';

import { connect } from 'react-redux';
import Grid from '@material-ui/core/Grid';
import { GridSpacing } from '@material-ui/core/Grid';

import Course from '../components/Course/Course';
import { courseModels } from '../redux/features/course';
import { courseSelectors } from '../redux/features/course';
import { fetchCoursesAsync } from '../redux/features/course/actions';
import { RootState } from 'ReduxTypes';

type ErrorReport = { hasError: boolean; error?: Error };
type StateProps = {
  isLoading: boolean;
  courses: courseModels.Course[];
  error: ErrorReport;
};

/**
 * Redix state and dispatch mappings
 */
const dispatchProps = {
  fetchCourses: fetchCoursesAsync.request,
};

const mapStateToProps = (state: RootState): StateProps => ({
  isLoading: state.courses.isLoadingCourses,
  courses: courseSelectors.getReduxCourses(state.courses),
  error: courseSelectors.getReduxCoursesError(state.courses),
});

/**
 * Component property type definitions
 */
type Props = ReturnType<typeof mapStateToProps> & typeof dispatchProps;

/**
 * CourseList component
 */
const CourseList = ({
  courses = [],
  error,
  fetchCourses,
  isLoading,
}: Props): JSX.Element => {
  // fetch course action on mount
  useEffect(() => {
    fetchCourses();
  }, []);

  if (isLoading) {
    return <p>Loading...</p>;
  }

  if (error && error.hasError && error.error) {
    throw error.error; // notify surrounding Error Boundary 
  }
  return (
    <div style={{ marginTop: 20, padding: 30 }}>
      {
        <Grid container spacing={2 as GridSpacing} justify="center">
          {courses.map(element => (
            <Grid item key={element.courseID}>
              <Course course={element} />
            </Grid>
          ))}
        </Grid>
      }
    </div>
  );
};

/**
 * Exports
 */
export default connect(
  mapStateToProps,
  dispatchProps,
)(CourseList);

1 Ответ

0 голосов
/ 29 октября 2019

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

Если клиентский компонент обнаруживает наличие ошибки, он обрабатывает ее. До сих пор я обнаружил, что если клиентский компонент выдает ошибку (инкапсулированную в состоянии ошибки), он попадает в окружающую границу ошибки. Однако при следующем посещении компонента действие извлечения, по-видимому, не вызывается в функции useEffect. Мне придется исследовать это дальше, чтобы определить, лежит ли проблема где-то внутри моего компонента ErrorBoundary ... возможно, последующий отдельный вопрос.

Редуктор

import { combineReducers } from 'redux';
import { createReducer } from 'typesafe-actions';

import { fetchCoursesAsync } from './actions';

import { Course } from './model';

/**
 * Error State
 */
type ErrorReport = Readonly<{
  hasError: boolean;
  error: undefined;
}>;
const initialErrorState: ErrorReport = {
  hasError: false,
  error: undefined,
};

const isLoadingCourses = createReducer(false as boolean)
  .handleAction([fetchCoursesAsync.request], () => true)
  .handleAction(
    [fetchCoursesAsync.success, fetchCoursesAsync.failure],
    () => false,
  );

const courses = createReducer([] as Course[]).handleAction(
  fetchCoursesAsync.success,
  (state, action) => action.payload,
);

const error = createReducer(initialErrorState)
  .handleAction(
    [fetchCoursesAsync.request, fetchCoursesAsync.success],
    (state): ErrorReport => ({
      hasError: false,
      error: undefined,
    }),
  )
  .handleAction(
    [fetchCoursesAsync.failure],
    (state, action): ErrorReport => ({
      hasError: action.payload.hasError,
      error: action.payload.error,
    }),
  );

const coursesReducer = combineReducers({
  isLoadingCourses,
  courses,
  error,
});

export default coursesReducer;
export type CoursesState = ReturnType<typeof coursesReducer>;

CourseList

import React, { useEffect } from 'react';

import { connect } from 'react-redux';
import Grid from '@material-ui/core/Grid';
import { GridSpacing } from '@material-ui/core/Grid';

import Course from '../components/Course/Course';

import { courseModels } from '../redux/features/course';
import { courseSelectors } from '../redux/features/course';
import { fetchCoursesAsync } from '../redux/features/course/actions';
import { RootState } from 'ReduxTypes';

type ErrorReport = { hasError: boolean; error?: Error };
type StateProps = {
  isLoading: boolean;
  courses: courseModels.Course[];
  error: ErrorReport;
};

/**
 * Redux dispatch and state mappings
 */
const dispatchProps = {
  fetchCourses: fetchCoursesAsync.request,
};

const mapStateToProps = (state: RootState): StateProps => ({
  isLoading: state.courses.isLoadingCourses,
  courses: courseSelectors.getReduxCourses(state.courses),
  error: courseSelectors.getReduxCoursesError(state.courses),
});

/**
 * Component property type definitions
 */
type Props = ReturnType<typeof mapStateToProps> & typeof dispatchProps;

/**
 * CourseList component
 */
const CourseList = ({
  courses = [],
  error,
  fetchCourses,
  isLoading,
}: Props): JSX.Element => {
  // fetch course action on mount
  useEffect(() => {
    console.log('COURSELIST FETCHING COURSES');
    fetchCourses();
  }, [fetchCourses]);

  if (isLoading) {
    return <p>Loading...</p>;
  }

  if (error && error.hasError && error.error) {
    // throw error.error;
    // if throw the error from state then encapsulating error boundary catches and displays
    // however subsequent request actions are not processed by redux-observable action stream
    // needs further investigation and if necessary a subsequent question posted....

    // if render error inside component then redux-observable stream continues
    // to process subsequent actions.
    return <p>{JSON.stringify(error.error, null, 2)}</p>;
  }

  return (
    <div style={{ marginTop: 20, padding: 30 }}>
      {
        <Grid container spacing={2 as GridSpacing} justify="center">
          {courses.map(element => (
            <Grid item key={element.courseID}>
              <Course course={element} />
            </Grid>
          ))}
        </Grid>
      }
    </div>
  );
};

/**
 * Exports
 */
export default connect(
  mapStateToProps,
  dispatchProps,
)(CourseList);

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...