or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

index.mdmiddleware.mdreact-integration.mdstore-creation.mdutilities.md

middleware.mddocs/

0

# Middleware

1

2

Extensible middleware system for adding DevTools integration, persistence, Redux-style actions, and custom behaviors to Zustand stores.

3

4

## Capabilities

5

6

### DevTools Middleware

7

8

Integration with Redux DevTools Extension for debugging and development.

9

10

```typescript { .api }

11

/**

12

* DevTools middleware for debugging with Redux DevTools Extension

13

* @param initializer - State creator function

14

* @param devtoolsOptions - DevTools configuration options

15

* @returns Enhanced state creator with DevTools integration

16

*/

17

function devtools<T, Mps extends [StoreMutatorIdentifier, unknown][] = [], Mcs extends [StoreMutatorIdentifier, unknown][] = [], U = T>(

18

initializer: StateCreator<T, [...Mps, ['zustand/devtools', never]], Mcs, U>,

19

devtoolsOptions?: DevtoolsOptions

20

): StateCreator<T, Mps, [['zustand/devtools', never], ...Mcs]>;

21

22

interface DevtoolsOptions {

23

/** Name for the store in DevTools */

24

name?: string;

25

/** Enable/disable DevTools integration */

26

enabled?: boolean;

27

/** Default action type for unnamed setState calls */

28

anonymousActionType?: string;

29

/** Store identifier for multi-store tracking */

30

store?: string;

31

}

32

33

type NamedSet<T> = (

34

partial: T | Partial<T> | ((state: T) => T | Partial<T>),

35

replace?: boolean | undefined,

36

actionType?: string | undefined,

37

actionName?: string | undefined

38

) => void;

39

```

40

41

**Usage Examples:**

42

43

```typescript

44

import { create } from "zustand";

45

import { devtools } from "zustand/middleware";

46

47

// Basic DevTools integration

48

const useStore = create()(

49

devtools(

50

(set) => ({

51

count: 0,

52

increment: () => set((state) => ({ count: state.count + 1 }), false, "increment"),

53

}),

54

{ name: "counter-store" }

55

)

56

);

57

58

// Multiple stores with DevTools

59

const useUserStore = create()(

60

devtools(

61

(set) => ({

62

users: [],

63

addUser: (user) => set((state) => ({ users: [...state.users, user] }), false, "addUser"),

64

}),

65

{ name: "user-store", store: "users" }

66

)

67

);

68

69

// Conditional DevTools (development only)

70

const useAppStore = create()(

71

devtools(

72

(set) => ({

73

theme: "light",

74

toggleTheme: () => set((state) => ({ theme: state.theme === "light" ? "dark" : "light" })),

75

}),

76

{ enabled: process.env.NODE_ENV === "development" }

77

)

78

);

79

```

80

81

### Persist Middleware

82

83

State persistence across page reloads and application restarts with configurable storage backends.

84

85

```typescript { .api }

86

/**

87

* Persistence middleware for storing state in various storage backends

88

* @param initializer - State creator function

89

* @param options - Persistence configuration options

90

* @returns Enhanced state creator with persistence

91

*/

92

function persist<T, Mps extends [StoreMutatorIdentifier, unknown][] = [], Mcs extends [StoreMutatorIdentifier, unknown][] = [], U = T>(

93

initializer: StateCreator<T, [...Mps, ['zustand/persist', unknown]], Mcs>,

94

options: PersistOptions<T, U>

95

): StateCreator<T, Mps, [['zustand/persist', U], ...Mcs]>;

96

97

interface PersistOptions<S, PersistedState = S, PersistReturn = unknown> {

98

/** Storage key name */

99

name: string;

100

/** Storage implementation (defaults to localStorage) */

101

storage?: PersistStorage<PersistedState, PersistReturn>;

102

/** Function to select which parts of state to persist */

103

partialize?: (state: S) => PersistedState;

104

/** Version number for migrations */

105

version?: number;

106

/** Migration function for version changes */

107

migrate?: (persistedState: unknown, version: number) => PersistedState | Promise<PersistedState>;

108

/** Custom merge function for rehydration */

109

merge?: (persistedState: unknown, currentState: S) => S;

110

/** Skip automatic hydration on store creation */

111

skipHydration?: boolean;

112

/** Callback when rehydration starts */

113

onRehydrateStorage?: (state: S) => ((state?: S, error?: unknown) => void) | void;

114

}

115

116

/**

117

* Creates JSON storage adapter for various storage backends

118

* @param getStorage - Function returning storage implementation

119

* @param options - JSON storage options

120

* @returns PersistStorage instance

121

*/

122

function createJSONStorage<S, R = unknown>(

123

getStorage: () => StateStorage<R>,

124

options?: JsonStorageOptions

125

): PersistStorage<S, unknown> | undefined;

126

127

interface JsonStorageOptions {

128

/**

129

* Function to transform values during JSON parsing (deserialization)

130

* Called for each key-value pair when retrieving state from storage

131

*/

132

reviver?: (key: string, value: unknown) => unknown;

133

134

/**

135

* Function to transform values during JSON serialization

136

* Called for each key-value pair when saving state to storage

137

*/

138

replacer?: (key: string, value: unknown) => unknown;

139

}

140

141

interface StateStorage<R = unknown> {

142

getItem: (name: string) => string | null | Promise<string | null>;

143

setItem: (name: string, value: string) => R;

144

removeItem: (name: string) => R;

145

}

146

```

147

148

**Usage Examples:**

149

150

```typescript

151

import { create } from "zustand";

152

import { persist, createJSONStorage } from "zustand/middleware";

153

154

// Basic localStorage persistence

155

const useCounterStore = create()(

156

persist(

157

(set) => ({

158

count: 0,

159

increment: () => set((state) => ({ count: state.count + 1 })),

160

}),

161

{ name: "counter-storage" }

162

)

163

);

164

165

// Partial state persistence

166

const useAppStore = create()(

167

persist(

168

(set) => ({

169

user: null,

170

preferences: { theme: "light", lang: "en" },

171

tempData: "not-persisted",

172

login: (user) => set({ user }),

173

updatePreferences: (prefs) => set((state) => ({

174

preferences: { ...state.preferences, ...prefs }

175

})),

176

}),

177

{

178

name: "app-storage",

179

partialize: (state) => ({ user: state.user, preferences: state.preferences }),

180

}

181

)

182

);

183

184

// Custom storage backend (sessionStorage)

185

const useSessionStore = create()(

186

persist(

187

(set) => ({

188

sessionData: {},

189

updateSession: (data) => set({ sessionData: data }),

190

}),

191

{

192

name: "session-storage",

193

storage: createJSONStorage(() => sessionStorage),

194

}

195

)

196

);

197

198

// With version migration

199

const useVersionedStore = create()(

200

persist(

201

(set) => ({

202

settings: { version: 2 },

203

updateSettings: (settings) => set({ settings }),

204

}),

205

{

206

name: "versioned-storage",

207

version: 2,

208

migrate: (persistedState, version) => {

209

if (version === 1) {

210

// Migrate from version 1 to 2

211

return { settings: { ...persistedState, version: 2 } };

212

}

213

return persistedState;

214

},

215

}

216

)

217

);

218

```

219

220

### Subscribe with Selector Middleware

221

222

Granular subscriptions to specific state slices with custom equality functions.

223

224

```typescript { .api }

225

/**

226

* Enhances store with selector-based subscriptions

227

* @param initializer - State creator function

228

* @returns Enhanced state creator with selective subscriptions

229

*/

230

function subscribeWithSelector<T, Mps extends [StoreMutatorIdentifier, unknown][] = [], Mcs extends [StoreMutatorIdentifier, unknown][] = []>(

231

initializer: StateCreator<T, [...Mps, ['zustand/subscribeWithSelector', never]], Mcs>

232

): StateCreator<T, Mps, [['zustand/subscribeWithSelector', never], ...Mcs]>;

233

```

234

235

**Usage Examples:**

236

237

```typescript

238

import { create } from "zustand";

239

import { subscribeWithSelector } from "zustand/middleware";

240

241

const useStore = create()(

242

subscribeWithSelector((set) => ({

243

count: 0,

244

user: { name: "John", age: 30 },

245

increment: () => set((state) => ({ count: state.count + 1 })),

246

updateUser: (updates) => set((state) => ({

247

user: { ...state.user, ...updates }

248

})),

249

}))

250

);

251

252

// Subscribe to specific state slice

253

const unsubscribe = useStore.subscribe(

254

(state) => state.count,

255

(count, prevCount) => {

256

console.log("Count changed:", prevCount, "->", count);

257

}

258

);

259

260

// Subscribe with custom equality function

261

const unsubscribeUser = useStore.subscribe(

262

(state) => state.user,

263

(user, prevUser) => {

264

console.log("User changed:", prevUser, "->", user);

265

},

266

{

267

equalityFn: (prev, curr) => prev.name === curr.name && prev.age === curr.age,

268

fireImmediately: true,

269

}

270

);

271

272

// Subscribe to derived state

273

const unsubscribeAdult = useStore.subscribe(

274

(state) => state.user.age >= 18,

275

(isAdult) => {

276

console.log("Adult status:", isAdult);

277

}

278

);

279

```

280

281

### Redux Middleware

282

283

Redux-style action/reducer pattern for familiar state management.

284

285

```typescript { .api }

286

/**

287

* Redux-style middleware with action dispatch

288

* @param reducer - Reducer function handling state transitions

289

* @param initialState - Initial state value

290

* @returns State creator with dispatch capability

291

*/

292

function redux<T, A>(

293

reducer: (state: T, action: A) => T,

294

initialState: T

295

): StateCreator<T & { dispatch: (action: A) => A }, [], [], T & { dispatch: (action: A) => A }>;

296

```

297

298

**Usage Examples:**

299

300

```typescript

301

import { create } from "zustand";

302

import { redux } from "zustand/middleware";

303

304

type CounterState = { count: number };

305

type CounterAction =

306

| { type: "INCREMENT" }

307

| { type: "DECREMENT" }

308

| { type: "SET"; payload: number };

309

310

const counterReducer = (state: CounterState, action: CounterAction): CounterState => {

311

switch (action.type) {

312

case "INCREMENT":

313

return { count: state.count + 1 };

314

case "DECREMENT":

315

return { count: state.count - 1 };

316

case "SET":

317

return { count: action.payload };

318

default:

319

return state;

320

}

321

};

322

323

const useCounterStore = create()(

324

redux(counterReducer, { count: 0 })

325

);

326

327

// Usage

328

function Counter() {

329

const { count, dispatch } = useCounterStore();

330

331

return (

332

<div>

333

<p>Count: {count}</p>

334

<button onClick={() => dispatch({ type: "INCREMENT" })}>+</button>

335

<button onClick={() => dispatch({ type: "DECREMENT" })}>-</button>

336

<button onClick={() => dispatch({ type: "SET", payload: 0 })}>Reset</button>

337

</div>

338

);

339

}

340

```

341

342

### Combine Middleware

343

344

Utility for combining initial state with state creator functions.

345

346

```typescript { .api }

347

/**

348

* Combines initial state with state creator for cleaner type inference

349

* @param initialState - Initial state object

350

* @param create - State creator function for actions

351

* @returns Combined state creator

352

*/

353

function combine<T extends object, U extends object, Mps extends [StoreMutatorIdentifier, unknown][] = [], Mcs extends [StoreMutatorIdentifier, unknown][] = []>(

354

initialState: T,

355

create: StateCreator<T, Mps, Mcs, U>

356

): StateCreator<Write<T, U>, Mps, Mcs>;

357

```

358

359

**Usage Examples:**

360

361

```typescript

362

import { create } from "zustand";

363

import { combine } from "zustand/middleware";

364

365

// Without combine - verbose typing

366

interface BearState {

367

bears: number;

368

increase: (by: number) => void;

369

}

370

371

const useBearStore1 = create<BearState>((set) => ({

372

bears: 0,

373

increase: (by) => set((state) => ({ bears: state.bears + by })),

374

}));

375

376

// With combine - automatic type inference

377

const useBearStore2 = create()(

378

combine(

379

{ bears: 0 }, // initial state

380

(set) => ({ // actions

381

increase: (by: number) => set((state) => ({ bears: state.bears + by })),

382

})

383

)

384

);

385

386

// Complex example with nested state

387

const useAppStore = create()(

388

combine(

389

{

390

user: null as User | null,

391

settings: { theme: "light", notifications: true },

392

isLoading: false,

393

},

394

(set, get) => ({

395

login: async (credentials: LoginCredentials) => {

396

set({ isLoading: true });

397

const user = await api.login(credentials);

398

set({ user, isLoading: false });

399

},

400

logout: () => set({ user: null }),

401

updateSettings: (newSettings: Partial<Settings>) =>

402

set((state) => ({

403

settings: { ...state.settings, ...newSettings },

404

})),

405

})

406

)

407

);

408

```

409

410

### Immer Middleware

411

412

Immutable state updates using Immer's draft mechanism for mutative syntax.

413

414

```typescript { .api }

415

/**

416

* Immer middleware for mutative state updates

417

* @param initializer - State creator function with Immer support

418

* @returns Enhanced state creator with draft-based mutations

419

*/

420

function immer<T, Mps extends [StoreMutatorIdentifier, unknown][] = [], Mcs extends [StoreMutatorIdentifier, unknown][] = []>(

421

initializer: StateCreator<T, [...Mps, ['zustand/immer', never]], Mcs>

422

): StateCreator<T, Mps, [['zustand/immer', never], ...Mcs]>;

423

```

424

425

**Usage Examples:**

426

427

```typescript

428

import { create } from "zustand";

429

import { immer } from "zustand/middleware/immer";

430

431

const useStore = create()(

432

immer((set) => ({

433

todos: [] as Todo[],

434

user: { name: "", preferences: { theme: "light" } },

435

436

addTodo: (text: string) =>

437

set((state) => {

438

state.todos.push({ id: Date.now(), text, completed: false });

439

}),

440

441

toggleTodo: (id: number) =>

442

set((state) => {

443

const todo = state.todos.find((t) => t.id === id);

444

if (todo) {

445

todo.completed = !todo.completed;

446

}

447

}),

448

449

updateUserPreference: (key: string, value: any) =>

450

set((state) => {

451

state.user.preferences[key] = value;

452

}),

453

454

clearCompleted: () =>

455

set((state) => {

456

state.todos = state.todos.filter((todo) => !todo.completed);

457

}),

458

}))

459

);

460

```

461

462

### Middleware Composition

463

464

Multiple middleware can be composed together, with inner middleware executing first.

465

466

```typescript

467

import { create } from "zustand";

468

import { devtools, persist, subscribeWithSelector } from "zustand/middleware";

469

import { immer } from "zustand/middleware/immer";

470

471

const useStore = create()(

472

devtools(

473

subscribeWithSelector(

474

persist(

475

immer((set) => ({

476

count: 0,

477

history: [] as number[],

478

increment: () =>

479

set((state) => {

480

state.history.push(state.count);

481

state.count += 1;

482

}),

483

})),

484

{ name: "counter-storage" }

485

)

486

),

487

{ name: "counter-devtools" }

488

)

489

);

490

491

// Order matters: immer -> persist -> subscribeWithSelector -> devtools

492

```