ReactJS tutorial

Open ReactJS lesson pages →

ReactJS Redux Toolkit

1

Study notes

Organize slices and manage server data with RTK Query.

Read the explanation, run the worked example, then complete the matching Examples and Assignments tabs. Your original materials remain available alongside these notes.

Understand the idea

createSlice generates action creators and a reducer from named transitions. action.payload carries the data sent by a caller. configureStore connects the reducer to the store; Provider exposes that store to React. A route component can select and dispatch just like any other descendant of Provider.

RTK Query manages remote data through an API slice. A query reads data; a mutation changes it. Register both its reducer and middleware. Generated hooks expose loading and error states. Tags connect mutations to cached queries so a successful change can trigger a fresh read. Do not copy the same fetched list into another slice without a specific need.

User action ↓ Dispatch ↓ Reducer → new state ↓ Selector → UI
Trace the values through each step before changing the code.

Run the example

Use a separate React practice project with a Node.js version supported by Vite. Create it once with these commands, then use its development URL. Do not paste this into the Edupoly website’s own source files.

npm create vite@latest react-practice -- --template react
cd react-practice
npm install
npm run dev
npm install @reduxjs/toolkit react-redux

Save the first file as src/products-api.js and the second as src/App.jsx. This lesson needs the API contract described below; it does not include a backend.

src/products-api.js

// src/products-api.js — requires your own JSON API at /api/products
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
export const productsApi = createApi({
  reducerPath: 'productsApi', baseQuery: fetchBaseQuery({baseUrl: '/api/'}), tagTypes: ['Products'],
  endpoints: build => ({
    products: build.query({query: () => 'products', providesTags: ['Products']}),
    addProduct: build.mutation({query: body => ({url: 'products', method: 'POST', body}), invalidatesTags: ['Products']}),
    updateProduct: build.mutation({query: ({id, ...body}) => ({url: `products/${encodeURIComponent(id)}`, method: 'PATCH', body}), invalidatesTags: ['Products']}),
    deleteProduct: build.mutation({query: id => ({url: `products/${encodeURIComponent(id)}`, method: 'DELETE'}), invalidatesTags: ['Products']})
  })
});
export const {useProductsQuery, useAddProductMutation, useUpdateProductMutation, useDeleteProductMutation} = productsApi;

Expected result

With the documented API connected, the list shows loading, then products; a successful add invalidates the list and requests fresh data. Without an API, the error message is expected.

src/App.jsx

// src/App.jsx — alongside src/products-api.js
import { configureStore } from '@reduxjs/toolkit';
import { Provider } from 'react-redux';
import { useState } from 'react';
import { productsApi, useProductsQuery, useAddProductMutation } from './products-api';
const store = configureStore({reducer: {[productsApi.reducerPath]: productsApi.reducer}, middleware: getDefault => getDefault().concat(productsApi.middleware)});
function Products() {
  const {data = [], isLoading, isError} = useProductsQuery();
  const [addProduct, {isLoading: saving}] = useAddProductMutation();
  const [name, setName] = useState(''); const [error, setError] = useState('');
  async function submit(e) {
    e.preventDefault(); if (!name.trim()) return;
    setError('');
    try { await addProduct({name: name.trim()}).unwrap(); setName(''); }
    catch { setError('Could not save the product. Try again.'); }
  }
  return <main><form onSubmit={submit}><label>Product <input value={name} onChange={e => setName(e.target.value)} /></label><button disabled={saving}>Add</button></form>
    {error && <p role="alert">{error}</p>}{isLoading ? <p>Loading…</p> : isError ? <p role="alert">Could not load products. Check your API.</p> : <ul>{data.map(p => <li key={p.id}>{p.name}</li>)}</ul>}
  </main>;
}
export default function App() { return <Provider store={store}><Products /></Provider>; }

Apply this to your assignments

Complete the basic slice and payload exercises using the Redux cart example. Then connect the API slice below to your own backend: GET returns an array of {id,name}, POST accepts {name}, PATCH accepts partial fields, DELETE removes a record. Add PUT as a full replacement operation according to your backend contract. Test failed writes and verify the list refreshes only after successful changes.

Your existing examples

  • Redux with Routing
  • Redux Toolkit basic slice, actions, reducer
  • Redux Toolkit Slice with action payload
  • Redux Toolkit Query based API call for GET Request
  • Redux Toolkit Query Mutation for POST PUT DELETE PATCH requests

Open the Examples tab to run or inspect these supplied projects. Their original links are preserved.

This topic has no separate assignment record yet; use the practice tasks above and the existing example projects.

Common mistakes

RTK Query middleware is required. Call mutationTrigger(values).unwrap() to catch request errors. Never put backend secrets in the browser bundle.

Check your understanding

  1. Predict the output before running the example, then compare it with the result panel.
  2. Explain which value each expression reads and which action changes it.
  3. Try the boundary cases listed in the practice task and record what you observe.
  4. Rebuild one matching assignment without copying, then compare behavior with the original example.

Reference

Official documentation for this topic · Vite setup requirements