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:
Project structure:
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;
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