Циклическая зависимость перехватчика запросов RTK

#reactjs #redux #redux-toolkit #rtk-query

#reactjs #сокращение #redux-инструментарий #rtk-запрос

Вопрос:

Пытаюсь найти наилучший способ добиться следующего:

  1. API через createApi
  2. Фрагмент аутентификации через createSlice
  3. Очистите состояние аутентификации при повторном использовании, когда API получит 401.

Нет проблем с первыми 2! Но проблема заключается в том, что мне нужно добавить перехватчик в API (недопустимая аутентификация — очистить локальное состояние аутентификации):

 // api.ts
import authSlice from './authSlice';
const baseQueryWithReauth: BaseQueryFn<string | FetchArgs, unknown, FetchBaseQueryError> = async (
  args,
  api,
  extraOptions
) => {
  const result = await baseQuery(args, api, extraOptions)

  if (result.error?.status === 401) {
    api.dispatch(authSlice.actions.clearAuth())
  }

  return result
}

export const api = createApi({
  reducerPath: API_REDUCER_KEY,
  baseQuery: baseQueryWithReauth,
  endpoints: () => ({}),
})
 
 // authService.ts
import { User } from '../../models/User'
import { api } from '../api'

export const API_REDUCER_KEY = 'api'

interface AuthResponse {
  user: User
  jwt: string
}

interface LoginData {
  email: string
  password: string
}

export const authApi = api.injectEndpoints({
  endpoints: (builder) => ({
    login: builder.mutation<AuthResponse, LoginData>({
      query: (body) => {
        return {
          url: 'login',
          method: 'POST',
          body,
        }
      },
    }),
  }),
})

export const { useLoginMutation } = authApi
 

Это вызывает циклическую зависимость — поскольку фрагменту аутентификации необходимо вызвать API для функций аутентификации (вход, выход и т. Д.).:

 // authSlice.ts
import { api } from './api';
...
export const loginLocal = createAsyncThunk<
  Pick<AuthState, 'user' | 'accessToken'>,
  {
    email: string
    password: string
  }
>('auth/login', async ({ email, password }, { dispatch }) => {
  const response = await dispatch(
    authApi.endpoints.login.initiate({
      email,
      password,
    })
  )

  if ('error' in response) {
    if ('status' in response.error) {
      throw new Error(response.error.data as string)
    }
    throw new Error(response.error.message)
  }

  const { data } = response

  const { jwt: accessToken, user } = data

  return { user, accessToken }
})
 

Циклическая зависимость выглядит следующим образом: authSlice -> AuthService -> api -> authSlice

Есть ли способ обойти это — или есть лучший / другой шаблон, который я могу использовать?

Комментарии:

1. разве это не решит проблему, если вы перейдете baseQueryWithReauth в отдельный файл? чем вам не нужно импортировать authSlice в свой api.ts

2. Кроме того, почему loginLocal записывается как a createAsyncThunk ? Это похоже на то, что также вошло бы в фрагмент API.

3. @TheWuif — я считаю, что это все равно будет циклическая зависимость, только с дополнительным звеном в цепочке: authSlice -> AuthService -> api -> baseQueryWithReauth -> authSlice

4. @markerikson — Я также применяю некоторую асинхронную логику в этой функции, которая не имеет отношения к вопросу 🙂

Ответ №1:

Простым решением было просто перенести действие «clearAuth» в его собственный файл — согласно https://redux-toolkit.js.org/usage/usage-guide#exporting-and-using-slices:

Обычно для этого требуется извлечь общий код в отдельный общий файл, который оба модуля могут импортировать и использовать. В этом случае вы можете определить некоторые общие типы действий в отдельном файле с помощью createAction, импортировать создателей этих действий в каждый файл среза и обрабатывать их с помощью аргумента extraReducers .

 // clearAuthAction.ts
import { createAction } from '@reduxjs/toolkit'

export const CLEAR_AUTH = 'auth/clearAuth'

export const clearAuth = createAction<void>(CLEAR_AUTH)
 

Затем используйте конструктор в authSlice:

 // authSlice.ts
builder.addCase(CLEAR_AUTH, (state) => {
  state.user = null
  state.accessToken = null
})
 

И отправка clearAuth в API:

 // api.ts
const baseQueryWithReauth: BaseQueryFn<string | FetchArgs, unknown, FetchBaseQueryError> = async (
  args,
  api,
  extraOptions
) => {
  const result = await baseQuery(args, api, extraOptions)

  if (result.error?.status === 401 || result.error?.status === 403) {
    api.dispatch(clearAuth())
  }

  return result
}