Skip to content
UniCircle
HardConceptualState management

Redux middleware and async thunks

Question REACT-RDX-002

Reducers must be pure, so where do API calls go in Redux? Explain middleware, and show how a thunk handles an asynchronous request with loading and error states.

Show the explanation

Model answer. Reducers cannot fetch data, use timers or read random values. Those side effects go in middleware, which sits between dispatch and the reducer:

text
dispatch(x) → middleware 1 → middleware 2 → reducer

A middleware can log actions, report errors, or intercept special values that are not plain objects.

Redux Thunk (included in Redux Toolkit) lets you dispatch a function. The middleware calls it with dispatch and getState, so it can do async work and dispatch plain actions as things progress. createAsyncThunk generates the pending, fulfilled and rejected actions for you:

JavaScript
export const fetchColleges = createAsyncThunk('colleges/fetch', async (state, { rejectWithValue }) => {
  const res = await fetch(`/api/colleges?state=${state}`);
  if (!res.ok) return rejectWithValue(`HTTP ${res.status}`);
  return res.json();
});

const collegesSlice = createSlice({
  name: 'colleges',
  initialState: { list: [], status: 'idle', error: null },
  reducers: {},
  extraReducers: b => {
    b.addCase(fetchColleges.pending,   s => { s.status = 'loading'; s.error = null; })
     .addCase(fetchColleges.fulfilled, (s, a) => { s.status = 'done'; s.list = a.payload; })
     .addCase(fetchColleges.rejected,  (s, a) => { s.status = 'failed'; s.error = a.payload ?? a.error.message; });
  },
});

The UI reads status and shows a spinner, the list, or the error. Alternatives for complex flows are redux-saga (generators) and listener middleware; for plain fetching and caching, RTK Query removes most of this boilerplate.

Original UniCircle question. Last reviewed 22 Oct 2026.