3

I'm trying to set up my test environment that includes React Testing Library, Redux Toolkit, RTK Query and TypeScript but I've run into a problem I can't resolve.

The main issue I can't figure out is how to generate the AppDispatch type as explained in the documentation here when supplying a preloadedState.

Going by the RTK docs and using configureStore() directly is no problem, as the store is being created inside the module:

const store = configureStore({
  reducer: {
    home: homeReducer,
    [homeApi.reducerPath]: homeApi.reducer,
  },
  middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(
    homeApi.middleware,
  ),
});

export type RootState = ReturnType<typeof store.reducer>;
export type AppDispatch = typeof store.dispatch; // not a problem as store exists here

export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

export default store;

However, doing it this way makes it difficult to pass a preloadedState value to configureStore() and keep the generated types. If I extract the reducer and use combineReducers() I can generate RootState from there, but I still can't figure out how to export AppDispatch as store.dispatch doesn't exist until the store is created, and in order to pass preloadedState I need to delay the creation of the store.

export const rootInitialState: Pick<RootState, 'home'> = {
  home: homeInitialState,
};

const reducer = combineReducers({
  home: homeReducer,
  [homeApi.reducerPath]: homeApi.reducer,
});

export type RootState = ReturnType<typeof reducer>;

// this is the problem as store is created externally to this module,
// so how can I extract this type now?
export type AppDispatch = typeof store.dispatch; 

export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

export const createStore = (preloadedState: Partial<RootState>) => configureStore({
  reducer,
  preloadedState,
  middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(
    homeApi.middleware,
  ),
});

Can this actually be done? At this point I'm totally stumped so if anyone can either offer a solution or put me out of my misery, I'd be hugely appreciative either way.

1 Answer 1

6

It's straightforward, you just need to change the ordering of the declarations in the file and add one more level of inspection.

The key is that while we don't have a real store yet, we do have createStore, which returns the new store instance. TS can figure out "the type of the value returned by this function", so we can use that instead:

export const createStore = (preloadedState: Partial<RootState>) => configureStore({
  reducer,
  preloadedState,
  middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(
    homeApi.middleware,
  ),
});

// 1) Take the type of `createStore`
// 2) Figure out the return type of that function, which is a store
// 3) Look up the type of the `dispatch` field in that returned store type
export type AppDispatch = ReturnType<typeof createStore>['dispatch'];
Sign up to request clarification or add additional context in comments.

1 Comment

Amazing, thank you so much that's exactly what I've been looking for!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.