RTK: Есть ли более "изящный" вариант реализации обработки ошибок в extraReducers?

Имеется некий authSlice, в котором я планирую создать extraReducers для 8 actions. Предположим, пока что есть асинхронный action по загрузке пользователя с сервера:

export const loadUser = createAsyncThunk(
  "user/loadUser",
  async (_, thunkAPI) => {
    try {
      const response = await AuthService.load_user();
      return response.data;
    } catch (e) {
      return thunkAPI.rejectWithValue("Не удалось загрузить пользователя");
    }
  }
);

Я создаю для него следующие extraReducers:

export const authSlice = createSlice({
  name: "auth",
  initialState,
  reducers: {},
  extraReducers: {
    [loadUser.fulfilled.type]: (state, action: PayloadAction<IUser>) => {
      state.isLoading = false;
      state.error = "";
      state.user = action.payload;
      state.isAuthenticated = true;
    },
    [loadUser.pending.type]: (state) => {
      state.isLoading = true;
    },
    [loadUser.rejected.type]: (state, action: PayloadAction<string>) => {
      state.isLoading = false;
      state.error = action.payload;
    },
  },
});

[loadUser.fulfilled.type] обрабатывает успешный запрос; [loadUser.rejected.type] обрабатывает запрос в котором произошла ошибка; [loadUser.pending.type] обновляет isLoading из состояния в момент начала отправки запроса.

Я хочу создать 8 различных actions нужных для работы с авторизацией. Для каждого случая прописывать [.rejected.type] и [.pending.type] мне кажется слишком глупо и громоздко, по сути делать они всегда будут одно и то же. Так вот вопрос, можно ли как то обобщить отлов ошибок и состояние "загрузки"?


Ответы (1 шт):

Автор решения: almost-wizard

Итак, если кто-то еще столкнется с данной проблемой, я нашел решение.

Для начала я создал 2 стрелочных функции:

const isPendingAction = (action) => action.type.endsWith("/pending");
const isRejectedAction = (action) => action.type.endsWith("/rejected");

Они принимают в себя action и определяют его статус. Далее, в authSlice я добавил следующий код:

export const authSlice = createSlice({
  name: "auth",
  initialState,
  reducers: {},
  extraReducers: (builder) => {
    builder.addCase(
      loadUser.fulfilled,
      (state, action: PayloadAction<IUser>) => {
        state.isLoading = false;
        state.error = "";
        state.user = action.payload;
        state.isAuthenticated = true;
      }
    );
    builder.addMatcher(
      isPendingAction,
      (state) => {
        state.isLoading = true;
      }
    );
    builder.addMatcher(
      isRejectedAction,
      (state, action: PayloadAction<string>) => {
        state.isLoading = false;
        state.error = action.payload;
      }
    );
  },
});

builder.addCase() создает extraReducer для переданного action'а. builder.addMatcher() позволяет осуществлять проверку входящей операции с помощью собственных фильтров.

Получается, что

builder.addMatcher(
  isPendingAction,
  (state) => {
    state.isLoading = true;
  }
);

будет отлавливать все actions в authSlice, которые находятся на этапе "загрузки", а

builder.addMatcher(
  isRejectedAction,
  (state, action: PayloadAction<string>) => {
    state.isLoading = false;
    state.error = action.payload;
  }
);

будет отлавливать все actions в которых произошла ошибка. Теперь для добавления обработчиков новых actions достаточно добавлять один builder.addCase(actionName.fulfilled, (state, action) => {...}), при этом обработка ошибок будет происходить автоматически для всех actions, также как и обработка состояния загрузки.

Вот и ответ на мой вопрос =)

Решение нашел здесь

→ Ссылка