← Back to list

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

Redux-Toolkit:

Ali · 2026-05-04 09:44 · 0 claps · 3.5 min read
#redux-toolkit #react-redux-toolkit #understanding-redux-toolk #redux-toolkit-flow
Open on Medium ↗
Wiki topics: 🌐 · Web Development

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:

  1. State

  2. Reducers

  3. 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