The Complete Redux Toolkit Flow: Slice → Thunk → Reducer → UI
Redux-Toolkit:
The Complete Redux Toolkit Flow: Slice → Thunk → Reducer → UI

Redux-Toolkit:
Redux tookit is a library of react to manage complex state management in react application.
slice:
A slice is a single bundle that contains:
-
State
-
Reducers
-
Actions
for one feature of your app.
Redux Toolkit uses slices to keep code clean, organized, and simple.
store:
The store is where all your app’s state lives.
import { configureStore } from "@reduxjs/toolkit";
const store = configureStore({
reducer: {
notes: notesReducer,
user: userReducer,
},
});
/*
It holds the entire state
You access it using useSelector
You update it using dispatch
*/
Provider:
// main.tsx:
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App.tsx";
import { Provider } from "react-redux";
import { store } from "./app/store.ts";
createRoot(document.getElementById("root")!).render(
<StrictMode>
<Provider store={store}>
<App />
</Provider>
</StrictMode>
);
// Provider is used to provide a store state in your entire app.
createSlice:
createSlice in Redux Toolkit is used to make Redux code simpler and shorter.
<! — OR →
createSlice helps you create Redux state, actions, and reducers all in one place.
const userSlice = createSlice({
name: "user",
initialState,
reducers: {
clearError(state) {
state.error = null;
}},
});
export const { clearError } = userSlice.actions;
export default userSlice.reducer;
// reducers only work for synchronous tasks
Reducer:
A reducer is a function that updates the state based on an action.
extraReducer:
extraReducers lets a slice respond to actions that it did NOT create itself or made outside the slice. And it work asynchronous.
Why do we need extraReducers?
Because:
1. createAsyncThunk creates actions automatically.
2. Those actions are not inside your slice's reducers.
So we use extraReducers to handle them.
How thunk works with extraReducers:
When you create a thunk, Redux Toolkit automatically creates 3 actions:
Action | Meaning
pending | API call started
fulfilled | API call succeeded
rejected | API call failed
Thunk:
A thunk or createAsyncThunk is used to handle asynchronous work in Redux, like:
1. API calls
2. fetching data
3. waiting for something before updating the state
Thunk = a middleman between async work and reducers
export const SignUpUserThunk = createAsyncThunk(
"auth/signup",
async (
{ firstName, lastName, email, password }: signup,
{ rejectWithValue },
) => {
try {
const response = await api.post("/auth/signup", {
firstName,
lastName,
email,
password,
});
toast.success(response.data.message, {
duration: 2000,
position: "top-center",
});
return response.data;
} catch (error) {
const err = error as AxiosError<{ message: string }>;
const message = err.response?.data?.message || "Failed to Fetch details";
toast.error(message || "Invalid credentials");
return rejectWithValue(message);
}
},
);
// features/user.slice.ts
extraReducers: (builder) => {
builder
// Signup
.addCase(SignUpUserThunk.pending, (state) => {
state.loading.signupLoading = true;
state.error = null;
})
.addCase(SignUpUserThunk.fulfilled, (state, action) => {
state.loading.signupLoading = false;
state.user = action.payload;
state.isAuthenticated = true;
state.error = null;
})
.addCase(SignUpUserThunk.rejected, (state, action) => {
state.loading.signupLoading = false;
state.error = (action.payload as string) || "Login failed. Try again.";
})
}
useDispatch() || useAppDispatch()
useDispatch is a React hook used to send actions to Redux.
const History = () => {
const dispatch = useAppDispatch();
useEffect(() => {
dispatch(getInvoiceHistoryThunk());
}, []);
};
useSelector || useAppSelector
useSelector is a React hook that lets your component read data (state) from the Redux store.
const { invoiceHistory, loading } = useAppSelector((state) => state.invoice);
why we use useAppSelector and useAppDispatch instead of useSelector and useDispatch?
We use useAppSelector and useAppDispatch for TypeScript safety, better autocomplete, and fewer bugs.
// useSelector:
// The problem with useSelector & useDispatch (default ones)
useSelector((state) => state.invoice);
-
TypeScript does NOT know what state looks like
state is typed as any
No autocomplete
Easy to make mistakes
// useDispatch
const dispatch = useDispatch();
dispatch(getInvoiceHistoryThunk());
-
TypeScript does not know:
what actions exist
that thunks are allowed
Solution: Typed hooks (useAppSelector, useAppDispatch)
You create typed versions of these hooks once, then reuse them everywhere.
app/hook.ts:
import { useDispatch, useSelector } from "react-redux";
import type { RootState, AppDispatch } from "./store";
// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch = useDispatch.withTypes<AppDispatch>();
export const useAppSelector = useSelector.withTypes<RootState>();
Flow of data in redux toolkit:
Component mounts
↓
dispatch(getInvoiceHistoryThunk())
↓
Thunk starts → pending action -> `in slice of pending state.`
↓
API call happens
↓
Thunk finishes → fulfilled OR rejected
↓
extraReducers updates Redux state
↓
Component re-renders with new data
Upper is the flow of redux-toolkit with thunk , now I also show you the code to fully understand.
// component mounts:
const dispatch = useAppDispatch();
const { invoiceHistory, loading } = useAppSelector((state) => state.invoice);
useEffect(() => {
dispatch(getInvoiceHistoryThunk());
}, []);
// Thunk starts
export const getInvoiceHistoryThunk = createAsyncThunk(
"invoice/history",
async (_, { rejectWithValue }) => {}
// pending action
addCase(getInvoiceHistoryThunk.pending, (state) => {
state.loading.getInvoiceLoading = true;
state.error.getInvoiceError = null;
});
// Api call
const response = await api.get("invoice/my-invoice");
// in success case
return response.data;
// Extra Reducer update the state || fulfilled actions handled in slice.
addCase(getInvoiceHistoryThunk.fulfilled, (state, action) => {
state.loading.getInvoiceLoading = false;
state.invoiceHistory = action.payload;
console.log(action.payload, "invoice-history-slice");
});
// component re-render || Ui Automatically updates
because of this line:
const { invoiceHistory, loading } = useAppSelector(…)
Redux Toolkit is not about memorizing APIs — it’s about understanding the flow.
메타데이터
- post_id
- 7fce9b969138
- slug
- the-complete-redux-toolkit-flow-slice-thunk-reducer-ui-7fce9b969138
- url
- https://medium.com/@aliairf92/the-complete-redux-toolkit-flow-slice-thunk-reducer-ui-7fce9b969138
- canonical_url
- https://medium.com/@aliairf92/the-complete-redux-toolkit-flow-slice-thunk-reducer-ui-7fce9b969138
- author_url
- https://medium.com/@aliairf92
- status
- ok
- fetched_at
- 2026-07-17 02:44:42