I have a circular dependecy issue inside redux due to shared actions between slices.
In my authActions.js, I have an authFetch function that check and refresh the auth if needed while fetching. This function use the auth reducer but need to be used in asyncThunkActions not linked with authSlice.
The problem is that I have to use store.dispatch() in my authFetch function and the import of store in the authActions file create a circular depedency because store also import slice files.
authActions.js
import { store } from "@/redux/store"; // Import your Redux store
import { createAsyncThunk } from "@reduxjs/toolkit";
import Cookies from "js-cookie";
import AppError from "@/utils/AppError";
// async Thunks
export const logIn = createAsyncThunk(
"login",
async (form, { rejectWithValue }) => {
try {
const response = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/users/login/`,
{
credentials: "include",
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(form),
}
);
console.log(response);
const data = await response.json();
if (!response.ok) {
return rejectWithValue(data);
} else {
return data;
}
} catch (err) {
console.log(err);
return rejectWithValue(err);
}
}
);
export const logOut = createAsyncThunk(
"logOut",
async (_, { rejectWithValue }) => {
try {
const response = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/users/logOut`,
{
method: "DELETE",
credentials: "include",
}
);
const data = await response.json();
Cookies.remove("token");
if (!response.ok) {
return rejectWithValue(data);
}
return data;
} catch (err) {
return rejectWithValue(err);
}
}
);
export const getAuth = createAsyncThunk(
"getAuth",
async (_, { rejectWithValue }) => {
try {
const response = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/users/refreshToken/`,
{
credentials: "include",
method: "GET",
}
);
const data = await response.json();
if (!response.ok) {
return rejectWithValue(data);
}
return data;
} catch (err) {
console.log(err);
return rejectWithValue(err);
}
}
);
function authToken() {
return store.getState().auth.data.token;
}
function addAuthTokenToFetchOptions(fetchOptions) {
const fetchOptionsWithAuth = {
...fetchOptions,
headers: {
...(fetchOptions.headers || {}),
Authorization: `Bearer ${authToken()}`,
},
};
return fetchOptionsWithAuth;
}
export const authFetch = async (url, fetchOptions) => {
fetchOptions = addAuthTokenToFetchOptions(fetchOptions);
const response = await fetch(url, fetchOptions);
if (response.ok) {
const data = await response.json();
return data;
} else {
//if response.status 401,403 => invalid access token, try to generate one with the refresh token cookie
if ([401, 403].includes(response.status)) {
const refreshTokenResponse = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/users/refreshToken`,
{
method: "GET",
credentials: "include",
}
);
//if new access Token is successfully generated => dispatch it in the auth reducers and retry the initial query
if (refreshTokenResponse.ok) {
const credentialsData = await refreshTokenResponse.json();
store.dispatch(setCredentials(credentialsData));
fetchOptions = addAuthTokenToFetchOptions(fetchOptions);
const retryResponse = await fetch(url, fetchOptions);
if (retryResponse.ok) {
data = retryResponse.json();
return data;
} else {
throw new AppError(data);
}
} else {
//if invalid refreshToken inside cookie or no token cookie => user logout
if ([401, 403].includes(refreshTokenResponse.status)) {
store.dispatch(logOut());
}
throw new AppError(data);
}
} else {
throw new AppError(data);
}
}
};
example of use of authFetch function in restaurantActions.js
{createAsyncThunk } from "@reduxjs/toolkit";
import { authFetch } from "../auth/authActions";
export const postRestaurantSettings = createAsyncThunk(
"postRestaurantSettings",
async (payload) => {
try {
const data = await authFetch(
`${process.env.NEXT_PUBLIC_API_URL}/restaurants/updateRestaurantSettings`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
}
);
return data;
} catch (error) {
console.error(error);
}
}
);
store.js
import { persistStore, persistReducer } from "redux-persist";
import { combineReducers, configureStore } from "@reduxjs/toolkit";
import createWebStorage from "redux-persist/lib/storage/createWebStorage";
import restaurant from "@/redux/restaurant/restaurantSlice";
import cart from "@/redux/cart/cartSlice";
import auth from "@/redux/auth/authSlice";
//workaround (redux-persist failed to create sync storage. falling back to noop storage when you import storage from redux-persist/lib/storage) because you cannot create the local storage in Node.js.
const createNoopStorage = () => {
return {
getItem(_key) {
return Promise.resolve(null);
},
setItem(_key, value) {
return Promise.resolve(value);
},
removeItem(_key) {
return Promise.resolve();
},
};
};
const storage =
typeof window !== "undefined"
? createWebStorage("local")
: createNoopStorage();
const reducers = combineReducers({ restaurant, cart, auth });
const persistConfig = { key: "root", storage, blacklist: ["auth"] };
export const store = configureStore({
reducer: persistReducer(persistConfig, reducers),
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({ serializableCheck: false }),
});
export const persistor = persistStore(store);
Circular dependency Error
ReferenceError: Cannot access '__WEBPACK_DEFAULT_EXPORT__' before initialization
I Know that the redux thunk api could resolve the issue but I don't want to add setup complexity in my project
What can I do ?
Thanks in advance !
I found a workaround to avoid importing store, maybe not the best solution but works:
getState and dispatch can be passed to the authFetch function, like this:
and when pass dispatch from the asyncThunkFunction to the authFetch function: