Search code examples
reactjstypescriptaxioscreate-react-appredux-toolkit

Redux toolkit and createAsyncThunk not working with axios instance


I have a react/typescript project. I'm using redux to manage state.

//store.ts
import { configureStore, combineReducers } from '@reduxjs/toolkit';
import { userReducer } from './user';

const RootReducer = combineReducers({
  user: userReducer
})

export const store = configureStore({
  reducer: RootReducer,
  devTools: true,
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

I also have a userSlice.ts file:

import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
import { signIn, signUp } from './../../services/api';
import type { UserCredential, UserInitialState } from './types';

const initialState: UserInitialState = {
  accessToken: null,
  status: 'init',
  user: {
    id: '',
    email: '',
    firstName: null,
    lastName: null,
    isAuthorized: false,
    imported: false,

    teamId: '',
    role: '',
    linkedinLink: null,
    isReceivingNotifications: false,
    avatarKey: null,
    loggedInAt: '',
    createdAt: '',
    updatedAt: '',
    deletedAt: null,
  },
};

const signUpRequest = createAsyncThunk('user/signUpRequest', async (data: UserCredential) => {
  const response = await signUp(data);
  return response.data;
});

const signInRequest = createAsyncThunk('user/signInRequest', async (data: UserCredential) => {
  const response = await signIn(data);
  return response.data;
});

const userSlice = createSlice({
  name: 'user',
  initialState,
  reducers: {
    logOut() {
      return initialState;
    },
  },
  extraReducers: (builder) => {
    builder.addCase(signInRequest.pending, (state, action) => {
      state.status = 'pending';
    });
    builder.addCase(signInRequest.fulfilled, (state, action) => {
      const { user, accessToken } = action.payload;
      return { accessToken, ...user };
    });
  },
});

export const userSliceActions = {...userSlice.actions};
export default userSlice.reducer;

In this slice I use two functions from the API (signIn, signUp)

These functions are described in the next file:

//api.ts
import { UserCredential } from '../store/user/types';
import { AxiosPromise } from 'axios';
import ApiClient from './ApiClient';

export const signUp = (data: UserCredential): AxiosPromise => {
  return ApiClient.post('v1/auth/sign_up', { data });
};

export const signIn = (data: UserCredential): AxiosPromise => {
  return ApiClient.post('v1/auth/sign_in', { data });
};

Above i used simple axios instance from ApiClient.ts file

import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
import { store } from '../store/store';
const baseUrl = 'https://************/api';// valid url

class ApiClient {
  private api: AxiosInstance;

  constructor() {
    this.api = axios.create({
      baseURL: baseUrl,
      headers: {
        'Content-Type': 'application/json',
      },
    });

    this.api.interceptors.request.use((config) => {
      const token = store.getState().user.accessToken;
      if (token !== null) {
        config.headers.Authorization = `Bearer ${token}`;
      }
      return config;
    });
  }

  post(url: string, { data }: AxiosRequestConfig) {
    return this.api.post(url, data);
  }

  get(url: string) {
    return this.api.get(url);
  }

  put(url: string, { data }: AxiosRequestConfig) {
    return this.api.put(url, data);
  }

  delete(url: string) {
    return this.api.delete(url);
  }
}

export default new ApiClient();

It is worth noting that I try to get the token, if it exists, then write it to the header on every request.

As a result, my store is not initialising (it is empty). In the console I have errors:

  1. No reducer provided for key "user"
  2. Store does not have a valid reducer. Make sure the argument passed to combineReducers is an object whose values are reducers.

Project structure:

Project structure screenshot

NOTE 1: If I remove the API call inside the userSlice.ts, the errors disappear and the store is working. In this form, everything starts, but I need API calls inside the userSlice...

import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
import type { UserCredential, UserInitialState } from './types';

const initialState: UserInitialState = {
  accessToken: null,
  status: 'init',
  user: {
    id: '',
    email: '',
    firstName: null,
    lastName: null,
    isAuthorized: false,
    imported: false,
    teamId: '',
    role: '',
    linkedinLink: null,
    isReceivingNotifications: false,
    avatarKey: null,
    loggedInAt: '',
    createdAt: '',
    updatedAt: '',
    deletedAt: null,
  },
};

const userSlice = createSlice({
  name: 'user',
  initialState,
  reducers: {
    logOut() {
      return initialState;
    },
  },
});

export const userSliceActions = { ...userSlice.actions };
export default userSlice.reducer;

Solution

  • I'm stupid! Redux docs does not recommend import store in non-component files. I quote "You should avoid importing the store directly into other codebase files. While it may work in some cases, that often ends up causing circular import dependency errors." This is my case, i imported store to ApiClient.ts file, wooops. Following the documentation, I used the injectStore function. I share the link https://redux.js.org/faq/code-structure#how-can-i-use-the-redux-store-in-non-component-files