Learn Redux - TypeScript & Typing the Store
Episode 7 of 23

Learn Redux - TypeScript & Typing the Store

This episode completes the Redux project with TypeScript: defining RootState and AppDispatch from the store, creating the typed hooks useAppSelector and useAppDispatch, giving generics to createAsyncThunk, and using createSelector with full type inference.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

So far we've been writing TypeScript without leveraging much of its type safety. Episode 7 fixes that: you'll understand how to type the store so TypeScript protects your code — preventing state name typos, ensuring dispatched actions are valid, and giving you autocomplete in the editor.

Correct typing isn't a formality. On large teams, type errors caught at compile time save hours of debugging. And because Redux Toolkit is built TypeScript-first, its type support is complete from day one.

RootState and AppDispatch

Deriving Types from the Store

The easiest way to define the types is to derive them directly from the store:

JSapp/store.ts with typing
import { configureStore } from "@reduxjs/toolkit"
import counterReducer from "../features/counter/counterSlice"
 
export const store = configureStore({
  reducer: {
    counter: counterReducer,
  },
})
 
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch

ReturnType<typeof store.getState> automatically captures the shape of the entire state. When a new slice is added, RootState updates accordingly — no manual updates needed.

When These Types Are Used

  • RootState is used as the type of selector arguments.
  • AppDispatch is used as the type of the dispatch argument.

These two types are what make useSelector and useDispatch type-safe.

Typed Hooks: useAppSelector and useAppDispatch

Creating Typed Hooks

Instead of writing useSelector((state: RootState) => ...) in every component, create a typed version once:

JSapp/hooks.ts
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux"
import type { RootState, AppDispatch } from "./store"
 
export const useAppDispatch: () => AppDispatch = useDispatch
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector

useAppSelector and useAppDispatch now know the shape of your application's state and dispatch. From here on, all components use these typed hooks — not the ones directly from react-redux.

Using Typed Hooks in Components

JSComponent with typed hooks
const count = useAppSelector((state) => state.counter.count)
const dispatch = useAppDispatch()

With useAppSelector, the selector parameter is automatically typed as RootState — the editor will warn you if you type a wrong field name. Consistently using typed hooks is a habit officially recommended by the Redux documentation.

Typing Slices and Thunks

Generic createAsyncThunk

createAsyncThunk accepts three generics: the result type, the argument type, and the thunk config type:

JSThunk with full generics
import { createAsyncThunk } from "@reduxjs/toolkit"
import type { RootState } from "../../app/store"
 
interface Post {
  id: number
  title: string
}
 
export const fetchPosts = createAsyncThunk<
  Post[],
  void,
  { state: RootState }
>("posts/fetchPosts", async (_, { getState }) => {
  const res = await fetch("/api/posts")
  const data: Post[] = await res.json()
  return data
})

createAsyncThunk<Post[], void, { state: RootState }> means: the returned result is typed Post[], the thunk accepts no argument (void), and getState is typed RootState. state: RootState lets you read other state inside the payloadCreator.

Using Other State Inside a Thunk

With the { state: RootState } generic, you can read state while writing a thunk:

JSReading state in the payloadCreator
export const fetchPosts = createAsyncThunk<Post[], void, { state: RootState }>(
  "posts/fetchPosts",
  async (_, { getState }) => {
    const { auth } = getState()
    const res = await fetch("/api/posts", {
      headers: { Authorization: `Bearer ${auth.token}` },
    })
    return res.json()
  },
)

getState() now returns the typed RootState, so auth.token is verified by TypeScript.

Typed Selectors with createSelector

For selectors that perform expensive recomputation, use createSelector with automatic inference:

JSTyped createSelector
import { createSelector } from "@reduxjs/toolkit"
 
const selectItems = (state: RootState) => state.posts.items
const selectFilter = (state: RootState) => state.posts.filter
 
export const selectVisiblePosts = createSelector(
  [selectItems, selectFilter],
  (items, filter) => items.filter((p) => p.title.includes(filter)),
)

createSelector derives the result type from its input selectors, so selectVisiblePosts is typed immediately without manual declarations. We'll cover its memoization details and performance in episode 13.

Conclusion

Episode 7 equips your project with proper TypeScript: types are derived from the store, hooks are typed once and used everywhere, thunks get generics, and selectors take advantage of inference.

Key takeaways:

  • RootState and AppDispatch are derived from the store with ReturnType and typeof.
  • Create useAppSelector and useAppDispatch once in app/hooks.ts.
  • createAsyncThunk<Result, Arg, { state: RootState }> for typed thunks.
  • getState() in a thunk returns RootState when the generic is set.
  • createSelector derives result types automatically.
  • Consistently using typed hooks is the officially recommended habit.

In the next episode, episode 8, we enter the world of RTK Query — createApi with fetchBaseQuery, defining query endpoints, using useGetPostsQuery in components, and understanding automatic caching plus request deduplication that saves network traffic.