Redux Toolkit (RTK) is the official, recommended way to write Redux logic. It keeps Redux’s core ideas while removing unnecessary boilerplate and enforcing best practices.

Flow: UI → Action → Reducer → Store → UI
const counterSlice = createSlice({name: "counter",initialState: { value: 0 },reducers: {increment(state) {state.value++;},},});
RTK auto-creates:
"counter/increment"increment()const store = configureStore({reducer: { counter: counterSlice.reducer },});
RTK:
dispatch(counterSlice.actions.increment());
state.value++; // looks mutable, stays immutable
Components subscribed with useSelector get updated automatically.
Dispatch thunk → pending → (API call) → fulfilled OR rejected → state update → UI re-render
// dispatch(fetchData());const slice = createSlice({name: "data",initialState: { data: null, loading: false, error: null },reducers: {},extraReducers: (builder) => {builder.addCase(fetchData.pending, (state) => {state.loading = true;state.error = null;}).addCase(fetchData.fulfilled, (state, action) => {state.loading = false;state.data = action.payload;}).addCase(fetchData.rejected, (state, action) => {state.loading = false;state.error = action.error.message || "Failed to fetch";});},});
RTK auto-dispatches:
data/fetch/pendingdata/fetch/fulfilleddata/fetch/rejectedHandled in slice extraReducers.
Redux-Saga is a Redux middleware for handling complex async side effects like API calls, delays, retries, concurrency, and cancellation.
It uses generator functions (function*) to:
yieldcall(fn, ...args)put(action)takeEverytakeLatesttakeLeadingyield all([call(fetchUser),call(fetchPosts),call(fetchComments),])
Runs effects concurrently.
// sagas.tsfunction* fetchPokemonAllWorker(): SagaIterator {try {const list: { id: number; name: string }[] =yield call(api.fetchPokemonListApi)const fullList: PokemonFull[] = yield all(list.map(p => call(fetchPokemonFullWorker, p)))yield put(fetchPokemonAllSuccess(fullList))} catch (e) {yield put(fetchPokemonAllFailure('Failed to load Pokémon'))}}function* fetchPokemonFullWorker(pokemon: { id: number; name: string }): SagaIterator<PokemonFull> {const detail: any = yield call(api.fetchPokemonDetail, pokemon.name)const [species, types, abilities]: [any, any[], any[]] = yield all([call(api.fetchByUrl, detail.species.url),all(detail.types.map((t: any) => call(api.fetchByUrl, t.type.url))),all(detail.abilities.map((a: any) =>call(api.fetchByUrl, a.ability.url))),])return {id: detail.id,name: detail.name,detail,species,types,abilities,}}export default function* pokemonSaga(): SagaIterator {yield takeLatest(fetchPokemonAll.type, fetchPokemonAllWorker)}