1. Redux Toolkit Setup
Redux Toolkit simplifies Redux setup and includes built-in support for TypeScript.
Installation
npm install @reduxjs/toolkit react-redux
npm install --save-dev @types/react-redux
Creating a Slice
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
interface CounterState {
value: number;
}
const initialState: CounterState = { value: 0 };
const counterSlice = createSlice({
name: "counter",
initialState,
reducers: {
increment(state) {
state.value += 1;
},
decrement(state) {
state.value -= 1;
},
incrementByAmount(state, action: PayloadAction<number>) {
state.value += action.payload;
}
}
});
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;
Redux Toolkit automatically infers types for reducers and actions.
2. Store Typing
TypeScript ensures type safety for the Redux store and dispatch.
Creating the Store
import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "./counterSlice";
export const store = configureStore({
reducer: {
counter: counterReducer
}
});
// Infer RootState and AppDispatch
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
Using in Components
import { useSelector, useDispatch } from "react-redux";
import type { RootState, AppDispatch } from "./store";
const CounterComponent = () => {
const count = useSelector((state: RootState) => state.counter.value);
const dispatch: AppDispatch = useDispatch();
return (
<div>
<p>Count: {count}</p>
<button onClick={() => dispatch(increment())}>Increment</button>
</div>
);
};
Typing the store ensures safe state access and dispatch in components.
3. Async Action Typing
Redux Toolkit provides createAsyncThunk for asynchronous actions with built-in TypeScript support.
Example
import { createAsyncThunk } from "@reduxjs/toolkit";
interface User {
id: number;
name: string;
}
export const fetchUser = createAsyncThunk<User, number>(
"users/fetchById",
async (userId: number) => {
const response = await fetch(`https://api.example.com/users/${userId}`);
return (await response.json()) as User;
}
);
Handling Async Actions in Slice
const userSlice = createSlice({
name: "user",
initialState: { user: null as User | null, status: "idle" },
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchUser.pending, (state) => { state.status = "loading"; })
.addCase(fetchUser.fulfilled, (state, action: PayloadAction<User>) => {
state.status = "succeeded";
state.user = action.payload;
})
.addCase(fetchUser.rejected, (state) => { state.status = "failed"; });
}
});
Typing async actions ensures correct return types and payload types, reducing runtime errors in asynchronous state management.
Conclusion
Using Redux Toolkit with TypeScript provides a type-safe and scalable approach to state management in React applications. Proper store typing, typed slices, and asynchronous action typing enhance maintainability and prevent common runtime errors.