or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

actions-reducers.mdasync-thunks.mdcore-store.mdentity-adapters.mdindex.mdmiddleware.mdreact-integration.mdrtk-query-react.mdrtk-query.mdutilities.md

actions-reducers.mddocs/

0

# Actions & Reducers

1

2

Redux Toolkit provides utilities for creating type-safe action creators and reducers with automatic action type generation and Immer-powered immutable updates.

3

4

## Capabilities

5

6

### Create Action

7

8

Creates a type-safe action creator function with optional payload preparation.

9

10

```typescript { .api }

11

/**

12

* Creates a type-safe action creator with optional payload preparation

13

* @param type - The action type string

14

* @param prepareAction - Optional function to prepare the payload

15

* @returns Action creator function

16

*/

17

function createAction<P = void, T extends string = string>(

18

type: T

19

): PayloadActionCreator<P, T>;

20

21

function createAction<PA extends PrepareAction<any>, T extends string = string>(

22

type: T,

23

prepareAction: PA

24

): PayloadActionCreator<ReturnType<PA>['payload'], T, PA>;

25

26

interface PayloadActionCreator<P, T extends string, PA extends PrepareAction<any> = PrepareAction<any>> {

27

(payload: P): PayloadAction<P, T>;

28

type: T;

29

toString(): T;

30

match(action: AnyAction): action is PayloadAction<P, T>;

31

}

32

33

interface PayloadAction<P = void, T extends string = string, M = never, E = never> {

34

payload: P;

35

type: T;

36

meta?: M;

37

error?: E;

38

}

39

40

interface PrepareAction<P> {

41

(payload: P): { payload: P };

42

(payload: P, meta: any): { payload: P; meta: any };

43

(payload: P, meta: any, error: any): { payload: P; meta: any; error: any };

44

}

45

```

46

47

**Usage Examples:**

48

49

```typescript

50

import { createAction } from '@reduxjs/toolkit';

51

52

// Simple action with payload

53

const increment = createAction<number>('counter/increment');

54

console.log(increment(5));

55

// { type: 'counter/increment', payload: 5 }

56

57

// Action without payload

58

const reset = createAction('counter/reset');

59

console.log(reset());

60

// { type: 'counter/reset' }

61

62

// Action with prepare function

63

const addTodo = createAction('todos/add', (text: string) => ({

64

payload: {

65

text,

66

id: nanoid(),

67

createdAt: Date.now()

68

}

69

}));

70

71

console.log(addTodo('Learn Redux Toolkit'));

72

// {

73

// type: 'todos/add',

74

// payload: {

75

// text: 'Learn Redux Toolkit',

76

// id: 'generated-id',

77

// createdAt: 1640995200000

78

// }

79

// }

80

81

// Action with meta and error handling

82

const fetchUser = createAction('user/fetch', (id: string) => ({

83

payload: id,

84

meta: { requestId: nanoid() }

85

}));

86

```

87

88

### Action Creator Types

89

90

Redux Toolkit provides various action creator types for different use cases.

91

92

```typescript { .api }

93

/**

94

* Action creator that requires a payload

95

*/

96

interface ActionCreatorWithPayload<P, T extends string = string> {

97

(payload: P): PayloadAction<P, T>;

98

type: T;

99

match(action: AnyAction): action is PayloadAction<P, T>;

100

}

101

102

/**

103

* Action creator with optional payload

104

*/

105

interface ActionCreatorWithOptionalPayload<P, T extends string = string> {

106

(payload?: P): PayloadAction<P | undefined, T>;

107

type: T;

108

match(action: AnyAction): action is PayloadAction<P | undefined, T>;

109

}

110

111

/**

112

* Action creator without payload

113

*/

114

interface ActionCreatorWithoutPayload<T extends string = string> {

115

(): PayloadAction<undefined, T>;

116

type: T;

117

match(action: AnyAction): action is PayloadAction<undefined, T>;

118

}

119

120

/**

121

* Action creator with prepared payload

122

*/

123

interface ActionCreatorWithPreparedPayload<Args extends unknown[], P, T extends string = string, E = never, M = never> {

124

(...args: Args): PayloadAction<P, T, M, E>;

125

type: T;

126

match(action: AnyAction): action is PayloadAction<P, T, M, E>;

127

}

128

```

129

130

### Create Reducer

131

132

Creates a reducer function using Immer for immutable updates with a builder callback pattern.

133

134

```typescript { .api }

135

/**

136

* Creates a reducer using Immer for immutable updates

137

* @param initialState - Initial state value or factory function

138

* @param builderCallback - Function that receives builder for adding cases

139

* @param actionMatchers - Additional action matchers (deprecated, use builder)

140

* @param defaultCaseReducer - Default case reducer (deprecated, use builder)

141

* @returns Reducer with getInitialState method

142

*/

143

function createReducer<S>(

144

initialState: S | (() => S),

145

builderCallback: (builder: ActionReducerMapBuilder<S>) => void

146

): ReducerWithInitialState<S>;

147

148

interface ReducerWithInitialState<S> extends Reducer<S> {

149

getInitialState(): S;

150

}

151

152

interface ActionReducerMapBuilder<State> {

153

/** Add a case reducer for a specific action type */

154

addCase<ActionCreator extends TypedActionCreator<string>>(

155

actionCreator: ActionCreator,

156

reducer: CaseReducer<State, ReturnType<ActionCreator>>

157

): ActionReducerMapBuilder<State>;

158

159

/** Add a case reducer for a specific action type string */

160

addCase<Type extends string, A extends Action<Type>>(

161

type: Type,

162

reducer: CaseReducer<State, A>

163

): ActionReducerMapBuilder<State>;

164

165

/** Add a matcher for actions */

166

addMatcher<A extends AnyAction>(

167

matcher: ActionMatcher<A>,

168

reducer: CaseReducer<State, A>

169

): ActionReducerMapBuilder<State>;

170

171

/** Add a default case reducer */

172

addDefaultCase(reducer: CaseReducer<State, AnyAction>): ActionReducerMapBuilder<State>;

173

}

174

175

type CaseReducer<S = any, A extends Action = AnyAction> = (state: Draft<S>, action: A) => S | void | Draft<S>;

176

```

177

178

**Usage Examples:**

179

180

```typescript

181

import { createReducer, createAction } from '@reduxjs/toolkit';

182

183

interface CounterState {

184

value: number;

185

}

186

187

const increment = createAction<number>('increment');

188

const decrement = createAction<number>('decrement');

189

const reset = createAction('reset');

190

191

const counterReducer = createReducer(

192

{ value: 0 } as CounterState,

193

(builder) => {

194

builder

195

.addCase(increment, (state, action) => {

196

// Immer allows "mutating" the draft state

197

state.value += action.payload;

198

})

199

.addCase(decrement, (state, action) => {

200

state.value -= action.payload;

201

})

202

.addCase(reset, (state) => {

203

state.value = 0;

204

})

205

.addMatcher(

206

(action): action is PayloadAction<number> =>

207

typeof action.payload === 'number',

208

(state, action) => {

209

// Handle all actions with number payloads

210

}

211

)

212

.addDefaultCase((state, action) => {

213

// Handle any other actions

214

console.log('Unhandled action:', action.type);

215

});

216

}

217

);

218

219

// Alternative: return new state instead of mutating

220

const todoReducer = createReducer([], (builder) => {

221

builder.addCase(addTodo, (state, action) => {

222

// Can return new state instead of mutating

223

return [...state, action.payload];

224

});

225

});

226

```

227

228

### Create Slice

229

230

Automatically generates action creators and a reducer from a single configuration object.

231

232

```typescript { .api }

233

/**

234

* Automatically generates action creators and reducer from configuration

235

* @param options - Slice configuration options

236

* @returns Slice object with actions, reducer, and utilities

237

*/

238

function createSlice<

239

State,

240

CaseReducers extends SliceCaseReducers<State>,

241

Name extends string = string,

242

ReducerPath extends string = Name,

243

Selectors extends SliceSelectors<State> = {}

244

>(options: CreateSliceOptions<State, CaseReducers, Name, ReducerPath, Selectors>): Slice<State, CaseReducers, Name, ReducerPath, Selectors>;

245

246

interface CreateSliceOptions<State, CR extends SliceCaseReducers<State>, Name extends string, ReducerPath extends string, Selectors extends SliceSelectors<State>> {

247

/** Slice name used to generate action types */

248

name: Name;

249

/** Path in the store where this slice's state will be located */

250

reducerPath?: ReducerPath;

251

/** Initial state value or factory function */

252

initialState: State | (() => State);

253

/** Case reducer functions or creator functions */

254

reducers: ValidateSliceCaseReducers<State, CR>;

255

/** Builder callback for additional action types */

256

extraReducers?: (builder: ActionReducerMapBuilder<NoInfer<State>>) => void;

257

/** Selector functions */

258

selectors?: Selectors;

259

}

260

261

interface Slice<State = any, CaseReducers = {}, Name extends string = string, ReducerPath extends string = Name, Selectors = {}> {

262

/** The slice name */

263

name: Name;

264

/** The reducer path in the store */

265

reducerPath: ReducerPath;

266

/** The combined reducer function */

267

reducer: Reducer<State>;

268

/** Generated action creators */

269

actions: CaseReducerActions<CaseReducers, Name>;

270

/** Individual case reducer functions */

271

caseReducers: CaseReducers;

272

/** Returns the initial state */

273

getInitialState(): State;

274

/** Creates selectors bound to the slice state */

275

getSelectors(): Selectors & SliceSelectors<State>;

276

/** Creates selectors with custom state selector */

277

getSelectors<RootState>(selectState: (state: RootState) => State): Selectors & SliceSelectors<State>;

278

/** Selectors with default slice state selector */

279

selectors: Selectors & SliceSelectors<State>;

280

/** Selector to get the slice state */

281

selectSlice(state: { [K in ReducerPath]: State }): State;

282

/** Inject into a combined reducer */

283

injectInto(injectable: WithSlice<Slice<State, CaseReducers, Name, ReducerPath, Selectors>>, config?: { reducerPath?: any }): any;

284

}

285

```

286

287

**Usage Examples:**

288

289

```typescript

290

import { createSlice, PayloadAction } from '@reduxjs/toolkit';

291

292

interface CounterState {

293

value: number;

294

status: 'idle' | 'loading' | 'succeeded' | 'failed';

295

}

296

297

const counterSlice = createSlice({

298

name: 'counter',

299

initialState: { value: 0, status: 'idle' } as CounterState,

300

reducers: {

301

// Standard reducer with payload

302

incrementByAmount: (state, action: PayloadAction<number>) => {

303

state.value += action.payload;

304

},

305

306

// Reducer without payload

307

increment: (state) => {

308

state.value += 1;

309

},

310

311

// Reducer with prepare callback

312

incrementByAmountWithId: {

313

reducer: (state, action: PayloadAction<{ amount: number; id: string }>) => {

314

state.value += action.payload.amount;

315

},

316

prepare: (amount: number) => ({

317

payload: { amount, id: nanoid() }

318

})

319

},

320

321

// Reset to initial state

322

reset: () => ({ value: 0, status: 'idle' } as CounterState)

323

},

324

325

// Handle external actions

326

extraReducers: (builder) => {

327

builder

328

.addCase(fetchUserById.pending, (state) => {

329

state.status = 'loading';

330

})

331

.addCase(fetchUserById.fulfilled, (state) => {

332

state.status = 'succeeded';

333

})

334

.addCase(fetchUserById.rejected, (state) => {

335

state.status = 'failed';

336

});

337

},

338

339

// Selectors

340

selectors: {

341

selectValue: (state) => state.value,

342

selectStatus: (state) => state.status,

343

selectIsLoading: (state) => state.status === 'loading'

344

}

345

});

346

347

// Export actions (automatically generated)

348

export const { increment, incrementByAmount, incrementByAmountWithId, reset } = counterSlice.actions;

349

350

// Export selectors

351

export const { selectValue, selectStatus, selectIsLoading } = counterSlice.selectors;

352

353

// Export reducer

354

export default counterSlice.reducer;

355

356

// Usage with typed selectors

357

const selectCounterValue = counterSlice.selectors.selectValue;

358

const value = selectCounterValue(rootState); // Automatically typed

359

360

// Create selectors with custom state selector

361

const counterSelectors = counterSlice.getSelectors((state: RootState) => state.counter);

362

const currentValue = counterSelectors.selectValue(rootState);

363

```

364

365

### Case Reducer Types

366

367

```typescript { .api }

368

/**

369

* Map of case reducer functions for a slice

370

*/

371

type SliceCaseReducers<State> = {

372

[K: string]:

373

| CaseReducer<State, PayloadAction<any>>

374

| CaseReducerWithPrepare<State, PayloadAction<any, string, any, any>>;

375

};

376

377

/**

378

* Case reducer with prepare method

379

*/

380

interface CaseReducerWithPrepare<State, Action extends PayloadAction> {

381

reducer: CaseReducer<State, Action>;

382

prepare: PrepareAction<Action['payload']>;

383

}

384

385

/**

386

* Generated action creators from slice case reducers

387

*/

388

type CaseReducerActions<CaseReducers, SliceName extends string> = {

389

[Type in keyof CaseReducers]: CaseReducers[Type] extends { prepare: any }

390

? ActionCreatorForCaseReducerWithPrepare<CaseReducers[Type], SliceName>

391

: ActionCreatorForCaseReducer<CaseReducers[Type], SliceName>;

392

};

393

394

/**

395

* Validation helper for slice case reducers

396

*/

397

type ValidateSliceCaseReducers<S, ACR extends SliceCaseReducers<S>> = ACR & {

398

[T in keyof ACR]: ACR[T] extends {

399

reducer(s: S, action?: infer A): any;

400

}

401

? {

402

prepare(...a: never[]): Omit<A, 'type'>;

403

}

404

: {};

405

};

406

```

407

408

### Action Utilities

409

410

Redux Toolkit provides utilities for working with actions and action creators.

411

412

```typescript { .api }

413

/**

414

* Type guard to check if a value is an RTK action creator

415

* @param action - Value to check

416

* @returns True if the value is an action creator

417

*/

418

function isActionCreator(action: any): action is (...args: any[]) => AnyAction;

419

420

/**

421

* Checks if an action follows the Flux Standard Action format

422

* @param action - Action to check

423

* @returns True if action is FSA compliant

424

*/

425

function isFSA(action: unknown): action is {

426

type: string;

427

payload?: any;

428

error?: boolean;

429

meta?: any;

430

};

431

432

/** Alias for isFSA */

433

function isFluxStandardAction(action: unknown): action is {

434

type: string;

435

payload?: any;

436

error?: boolean;

437

meta?: any;

438

};

439

```

440

441

**Usage Examples:**

442

443

```typescript

444

import { isActionCreator, isFSA, createAction } from '@reduxjs/toolkit';

445

446

const increment = createAction('increment');

447

const plainAction = { type: 'PLAIN_ACTION' };

448

449

console.log(isActionCreator(increment)); // true

450

console.log(isActionCreator(plainAction)); // false

451

452

console.log(isFSA(increment(5))); // true

453

console.log(isFSA({ type: 'TEST', invalid: true })); // false

454

```

455

456

## Advanced Patterns

457

458

### Builder Callback Pattern

459

460

```typescript

461

// Using builder callback for complex reducer logic

462

const complexSlice = createSlice({

463

name: 'complex',

464

initialState: { items: [], status: 'idle' },

465

reducers: {

466

itemAdded: (state, action) => {

467

state.items.push(action.payload);

468

}

469

},

470

extraReducers: (builder) => {

471

// Handle async thunk actions

472

builder

473

.addCase(fetchItems.pending, (state) => {

474

state.status = 'loading';

475

})

476

.addCase(fetchItems.fulfilled, (state, action) => {

477

state.status = 'succeeded';

478

state.items = action.payload;

479

})

480

// Handle multiple action types with matcher

481

.addMatcher(

482

(action) => action.type.endsWith('/pending'),

483

(state) => {

484

state.status = 'loading';

485

}

486

)

487

// Default handler

488

.addDefaultCase((state, action) => {

489

console.log('Unhandled action:', action);

490

});

491

}

492

});

493

```

494

495

### Build Create Slice

496

497

Advanced slice creation with custom reducer creators like async thunks.

498

499

```typescript { .api }

500

/**

501

* Creates a custom createSlice function with additional reducer creators

502

* @param config - Configuration with custom creators

503

* @returns Custom createSlice function

504

*/

505

function buildCreateSlice(config?: {

506

creators?: {

507

asyncThunk?: typeof asyncThunkCreator;

508

};

509

}): typeof createSlice;

510

511

/**

512

* Async thunk creator symbol for buildCreateSlice

513

* Used to define async reducers directly in slice definitions

514

*/

515

const asyncThunkCreator: {

516

[Symbol.for('rtk-slice-createasyncthunk')]: typeof createAsyncThunk;

517

};

518

519

/**

520

* Enum defining different types of reducers

521

*/

522

enum ReducerType {

523

reducer = 'reducer',

524

reducerWithPrepare = 'reducerWithPrepare',

525

asyncThunk = 'asyncThunk',

526

}

527

```

528

529

**Usage Examples:**

530

531

```typescript

532

import { buildCreateSlice, asyncThunkCreator } from '@reduxjs/toolkit';

533

534

// Create custom slice builder with async thunk support

535

const createSliceWithAsyncThunks = buildCreateSlice({

536

creators: { asyncThunk: asyncThunkCreator }

537

});

538

539

// Use it to create slices with async reducers

540

const userSlice = createSliceWithAsyncThunks({

541

name: 'user',

542

initialState: { data: null, loading: false },

543

reducers: (create) => ({

544

// Regular reducer

545

clearUser: create.reducer((state) => {

546

state.data = null;

547

}),

548

// Async thunk reducer defined inline

549

fetchUser: create.asyncThunk(

550

async (userId: string) => {

551

const response = await fetch(`/api/users/${userId}`);

552

return response.json();

553

},

554

{

555

pending: (state) => {

556

state.loading = true;

557

},

558

fulfilled: (state, action) => {

559

state.loading = false;

560

state.data = action.payload;

561

},

562

rejected: (state) => {

563

state.loading = false;

564

}

565

}

566

)

567

})

568

});

569

```

570

571

### Slice Injection

572

573

```typescript

574

// Dynamically inject slices into combined reducers

575

import { combineSlices } from '@reduxjs/toolkit';

576

577

const rootReducer = combineSlices(counterSlice, todosSlice);

578

579

// Later, inject a new slice

580

const withAuthSlice = authSlice.injectInto(rootReducer);

581

```

582

583

### Custom Prepare Functions

584

585

```typescript

586

const todosSlice = createSlice({

587

name: 'todos',

588

initialState: [],

589

reducers: {

590

todoAdded: {

591

reducer: (state, action) => {

592

state.push(action.payload);

593

},

594

prepare: (text: string) => {

595

return {

596

payload: {

597

id: nanoid(),

598

text,

599

completed: false,

600

createdAt: Date.now()

601

},

602

meta: {

603

timestamp: Date.now()

604

}

605

};

606

}

607

}

608

}

609

});

610

```