Search code examples
reactjsreduxreact-reduxviteredux-toolkit

Why would the error "Cannot access 'Slice' before initialization" occur when adding an asyncThunk to a Redux RTK slice?


I encounter an error which I had hard times to isolate the source.

the web app is built in ReactJs / Vite and RTK.

I created a simple RTK slice.

When I add an asyncThunk to my slice the app crash and I get the following error:

Uncaught ReferenceError: Cannot access 'tagsSlice' before initialization

I've done that one thousand times.

Adding a regular action reducers is fine.

I'm not able to understand why.

The only thing new compared to my previous project is Vite.

The error is thrown from the store.ts, here's the file :

import { configureStore, ThunkAction, Action } from '@reduxjs/toolkit';
import thunk from 'redux-thunk';
import appTimeSpanSlice from './slices/appTimeSpanSlice';
import authenticationSlice from './slices/authenticationSlice';
import uiBehaviorHandlerSlice from './slices/uiBehaviorHandlerSlice';
import intervalMiddleware from './middlewares/intervalMiddleware';
import pollingMiddleware from './middlewares/pollingMiddleware';
import tagsSlice from './slices/tagsSlice'


export const store = configureStore({
  reducer: {
    tag: tagsSlice, // <--- ERROR IS THROWN HERE
    appTime: appTimeSpanSlice,
    authentication: authenticationSlice,
    ui: uiBehaviorHandlerSlice,
  },
  middleware: [thunk, intervalMiddleware, pollingMiddleware],
});

export type RootState = ReturnType<typeof store.getState>
export type AppThunk<ReturnType = void> = ThunkAction<
  ReturnType,
  RootState,
  unknown,
  Action<string>
>;

Here's my slice :

import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
import { tagApi } from 'services/tag/tagApi'



 export const createTag = createAsyncThunk(
   'tags/createTags',
   async (body, thunkAPI) => {
     const response = await tagApi.createTag(body);
     return response.data;
   }
 );

enum Status {
  IDLE = 'idle',
  PENDING = "pending",
  FULFILLED = "fulfilled",
  REJECTED = "rejected"
}

interface TagsState {
  tags: [];
  fetchTagsStatus: Status;
  createTagStatus: Status;
}

const initialState = {
  tags: [],
  fetchTagsStatus: Status.IDLE,
  createTagStatus: Status.IDLE
} as TagsState


const tagsSlice = createSlice({
  name: 'tags',
  initialState,
  reducers: {
   testAction: () => { console.log("Hello")}
  },
  extraReducers: (builder) => {
    builder
     .addCase(createTag.pending, state => {
       state.createTagStatus = Status.PENDING;
     })
     .addCase(createTag.fulfilled, (state, { payload }) => {
       state.createTagStatus = Status.FULFILLED;
     })
     .addCase(createTag.rejected, (state, { error }) => {
       state.createTagStatus = Status.REJECTED;
     })
   },
})

const { actions, reducer } = tagsSlice;
export const { testAction } = actions;
export default reducer;

To sum up, in order to reproduce the issue, 2 conditions should be met:

  • the slice should contain an asyncThunk
  • a component should import the asyncThunk

TROUBLESHOOTING STEPS:

  • If there is NO asyncThunk in the slice but the testAction action is imported: no error
  • If there is an asyncThunk in the slice but the component does not import anything in the file : no error
  • If there is an asyncThunk in the slice and only the testAction action is imported: error

What would potentially cause this behavior ?


Solution

  • The issue was that I was trying to access the store outside a React component.

    Here's what I had:

    import api from 'services/api';
    import { selectStart, selectEnd } from 'store/slices/appTimeSpanSlice';
    import { store } from 'store/store';
    
    export const tagApi = {
      fetchAllTags: async () => {
        const start = selectStart(store.getState()); <<< not allowed
        const end = selectEnd(store.getState()); <<< not allowed
        const url = `/api/tags?start=${start}&end=${end}`;
        return await api.get(url);
      },
      createTag: async (body: any) => {
        const url = `/api/tags`;
        return await api.post(url, body);
      }
    };
    
    

    Here's how I fixed it:

    import api from 'services/api';
    import { selectStart, selectEnd } from 'store/slices/appTimeSpanSlice';
    
    
    export const tagApi = {
      fetchAllTags: async (start, end) => {
        const url = `/api/tags?start=${start}&end=${end}`;
        return await api.get(url);
      },
      createTag: async (body: any) => {
        const url = `/api/tags`;
        return await api.post(url, body);
      }
    };