new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

Redux Toolkit · API · all subjects

createreducer

34 notes, read out of this brain and free to use. Each one was extracted from a source and is re-checked against its exam.

createReducer builder.addAsyncThunk method

The builder.addAsyncThunk method adds handlers for async thunk actions, allowing you to handle pending, fulfilled, and rejected states of async operations.

createReducer overview and purpose

createReducer is a utility that simplifies creating Redux reducer functions. It uses Immer internally to allow writing reducers with direct state mutations instead of immutable operations. It supports mapping specific action types to case reducer functions using a builder callback notation, similar to a switch statement but with better TypeScript support.

createReducer basic example with builder

import { createAction, createReducer } from '@reduxjs/toolkit' interface CounterState { value: number } const increment = createAction('counter/increment') const decrement = createAction('counter/decrement') const incrementByAmount = createAction<number>('counter/incrementByAmount') const initialState = { value: 0 } satisfies CounterState as CounterState const counterReducer = createReducer(initialState, (builder) => { builder .addCase(increment, (state, action) => { state.value++ }) .addCase(decrement, (state, action) => { state.value-- }) .addCase(incrementByAmount, (state, action) => { state.value += action.payload }) }) This example shows how to use createReducer with the builder callback notation to handle multiple action types.

createReducer returns reducer with getInitialState method

The reducer returned by createReducer has a getInitialState function attached to it that returns the initial state when called. This is useful for tests or usage with React's useReducer hook.

createReducer getInitialState example

const counterReducer = createReducer(0, (builder) => { builder .addCase('increment', (state, action) => state + action.payload) .addCase('decrement', (state, action) => state - action.payload) }) console.log(counterReducer.getInitialState()) // 0 This example demonstrates calling getInitialState to retrieve the initial state.

createReducer uses Immer for direct state mutation

createReducer uses Immer to allow writing reducers as if directly mutating state. The reducer receives a proxy state that translates mutations into equivalent copy operations, simplifying immutable update logic without losing immutability guarantees.

createReducer builder.addCase method

The builder.addCase method maps a specific action type to a case reducer function. When that action type is dispatched, the corresponding case reducer will be executed.

createReducer builder.addMatcher method

The builder.addMatcher method accepts a predicate function and a case reducer. The case reducer will execute for any action where the predicate returns true.

createReducer builder.addDefaultCase method

The builder.addDefaultCase method registers a case reducer that executes if no other case or matcher reducers handled the action.

createReducer mutation and return pitfall

When using createReducer with Immer, you must either mutate the state argument or return a new state, but not both. If a case reducer both mutates the state and returns a value, an exception will be thrown.

createReducer mutation and return pitfall example

import { createAction, createReducer } from '@reduxjs/toolkit' interface Todo { text: string completed: boolean } const toggleTodo = createAction<number>('todos/toggle') const todosReducer = createReducer([] as Todo[], (builder) => { builder.addCase(toggleTodo, (state, action) => { const index = action.payload const todo = state[index] // This case reducer both mutates the passed-in state... todo.completed = !todo.completed // ... and returns a new value. This will throw an // exception. In this example, the easiest fix is // to remove the `return` statement. return [...state.slice(0, index), todo, ...state.slice(index + 1)] }) }) This example shows an incorrect pattern that will throw an exception.

createReducer multiple case reducer execution order

For any dispatched action, the execution order is: 1) If there is an exact match for the action type, the corresponding case reducer executes first; 2) Any matchers that return true execute in the order they were defined; 3) If a default case reducer is provided and no case or matcher reducers ran, the default case reducer executes; 4) If no case or matcher reducers ran, the original existing state value is returned unchanged. The executing reducers form a pipeline where each receives the output of the previous reducer.

createReducer multiple matcher execution example

import { createReducer } from '@reduxjs/toolkit' const reducer = createReducer(0, (builder) => { builder .addCase('increment', (state) => state + 1) .addMatcher( (action) => action.type.startsWith('i'), (state) => state * 5, ) .addMatcher( (action) => action.type.endsWith('t'), (state) => state + 2, ) }) console.log(reducer(0, { type: 'increment' })) // Returns 7, as the 'increment' case and both matchers all ran in sequence: // - case 'increment": 0 => 1 // - matcher starts with 'i': 1 => 5 // - matcher ends with 't': 5 => 7 This example demonstrates how multiple matchers execute in sequence with state piped through each.

createReducer with current utility for logging

import { createSlice, current } from '@reduxjs/toolkit' const slice = createSlice({ name: 'todos', initialState: [{ id: 1, title: 'Example todo' }], reducers: { addTodo: (state, action) => { console.log('before', current(state)) state.push(action.payload) console.log('after', current(state)) }, }, }) The current utility creates a plain copy of the Immer Draft state value for console logging, making it readable in browsers that display Proxies in a difficult-to-read format.

createReducer uses Immer automatically

Redux Toolkit's createReducer API uses Immer internally automatically, so it is safe to write code that mutates state inside case reducer functions passed to createReducer.

Immer requires mutation or return, not both

In any given case reducer, Immer expects that you will either mutate the existing state or construct a new state value and return it, but not both in the same function. Mutating state in an arrow function with an implicit return breaks this rule and causes an error, because statements may return a value and Immer sees both the attempted mutation and the returned value and doesn't know which to use as the result.

Immer arrow function mutation gotchas

When using arrow functions with Immer, mutating state with an implicit return causes an error. Solutions include using the void keyword to skip having a return value, or using curly braces to give the arrow function a body with no return value. Example: (state, action) => void state.push(action.payload) or (state, action) => { state.push(action.payload) }.

State must be an object or array for Immer tracking

For Immer to track mutations, the state must be a JavaScript object or array. A slice's state can be a primitive like a string or boolean, but since primitives cannot be mutated, you can only return a new value.

Object.assign for multiple field mutations

As an alternative to assigning individual fields, you can use Object.assign to mutate multiple fields at once in Immer-powered reducers, since Object.assign always mutates the first object given to it. Example: Object.assign(state, { a, b, c, d }).

Cannot replace state with direct assignment

A common mistake is trying to assign state = someValue directly. This will not work because it only points the local state variable to a different reference, which neither mutates the existing state object/array in memory nor returns an entirely new value, so Immer does not make any actual changes. To replace the entire state, you must return the new value directly.

current function for debugging Immer state

Redux Toolkit re-exports Immer's current function, which extracts a copy of the wrapped Proxy data. Use this function in reducers when you need to log or inspect work-in-progress state, as browsers display logged Proxy instances in a format that is hard to read. Example: console.log(current(state)).

Immer mutating logic only works inside Immer

The mutating logic only works correctly when wrapped inside Immer. Otherwise, that code will really mutate the data and cause bugs.

Immer does not wrap newly inserted objects

Immer will not wrap objects that are newly inserted into the state. Most of the time this should not matter, but there may be occasions when you want to insert a value and then make further updates to it.

Immer cannot track updates to extracted primitive values

If you pull out a primitive value from a nested object into its own variable and try to update it, Immer has nothing to wrap and cannot track any updates. You must mutate the nested object itself, not extracted primitive values.

Immer does not auto-create nested objects or arrays

Immer does not automatically create nested objects or arrays for you. You must create them yourself. If you unconditionally try to insert into a nested array without checking for its existence first, the logic will crash when the array does not exist.

Nested data with Immer

Nested objects and arrays in Immer are wrapped in Proxies and drafted, and it is safe to pull out a nested value into its own variable and then mutate it. This applies only to objects and arrays, not to primitive values extracted from them.

Immutable updates can be combined with mutations in Immer

It is possible to use immutable updates to do part of the work and then save the results via a mutation. For example, you can construct a new array immutably and then mutate the state to save the new array.

createReducer builder form required in RTK 2.0

The object syntax for createReducer has been removed in Redux Toolkit 2.0. You must use the builder callback form instead. For example, instead of createReducer(initialState, { [todoAdded]: (state, action) => {} }), use createReducer(initialState, (builder) => { builder.addCase(todoAdded, (state, action) => {}) }).

createReducer with case reducers

createReducer accepts an initial state and a builder callback. Inside the callback, you use builder.addCase() to define how to handle specific action types. Each case in the builder becomes a key-value pair where the key is the action type and the value is the reducer function.

createReducer uses Immer for mutation-like updates

createReducer uses the Immer library internally, allowing you to write code that appears to mutate state directly (like state.push() or state.completed = true), but actually applies updates immutably. This makes it effectively impossible to accidentally mutate state in a reducer.

createReducer allows both mutating and returning updates

Within createReducer case handlers, you can either write code that appears to mutate the draft state (thanks to Immer), or return an immutably-updated value. You cannot mix both approaches in the same handler.

createReducer example converting switch statement

Example: converting a switch-based reducer to createReducer: `const todosReducer = createReducer([], (builder) => { builder.addCase('ADD_TODO', (state, action) => { state.push(action.payload) }).addCase('TOGGLE_TODO', (state, action) => { const todo = state[action.payload.index]; todo.completed = !todo.completed }).addCase('REMOVE_TODO', (state, action) => { return state.filter((todo, i) => i !== action.payload.index) }) })`

createReducer builder parameter for type-safe reducer objects

The second parameter to createReducer is a callback receiving an ActionReducerMapBuilder instance. Use builder.addCase() to add cases with proper type inference for state and action.

builder.addMatcher with type predicate function

Use a type predicate function as the first argument to builder.addMatcher to enable TypeScript inference. The type predicate should return action is PayloadAction<T>, allowing the reducer argument to infer the correct action type.

Give your agent this brain