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.

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.
The easiest way to define the types is to derive them directly from the store:
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.dispatchReturnType<typeof store.getState> automatically captures the shape of the entire state. When a new slice is added, RootState updates accordingly — no manual updates needed.
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.
Instead of writing useSelector((state: RootState) => ...) in every component, create a typed version once:
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux"
import type { RootState, AppDispatch } from "./store"
export const useAppDispatch: () => AppDispatch = useDispatch
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelectoruseAppSelector 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.
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.
createAsyncThunk accepts three generics: the result type, the argument type, and the thunk config type:
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.
With the { state: RootState } generic, you can read state while writing a thunk:
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.
For selectors that perform expensive recomputation, use createSelector with automatic inference:
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.
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.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.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.