0
# State Management
1
2
Reactive state management with server-side hydration, cookie management, and error handling. Nuxt provides powerful composables for managing application state that works seamlessly between server and client.
3
4
## Capabilities
5
6
### Reactive State
7
8
Create reactive shared state across components with automatic SSR hydration.
9
10
```typescript { .api }
11
/**
12
* Create reactive shared state across components
13
* @param key - Optional key for the state (auto-generated if not provided)
14
* @param init - Optional initial value factory function
15
* @returns Reactive reference to the state
16
*/
17
function useState<T>(key?: string, init?: () => T): Ref<T>;
18
19
/**
20
* Clear stored state by key(s) or filter function
21
* @param keys - Keys to clear or filter function
22
*/
23
function clearNuxtState(keys?: string | string[] | ((key: string) => boolean)): void;
24
```
25
26
**Usage Examples:**
27
28
```typescript
29
// Basic state
30
const counter = useState("counter", () => 0);
31
32
function increment() {
33
counter.value++;
34
}
35
36
// Typed state
37
interface User {
38
id: number;
39
name: string;
40
email: string;
41
}
42
43
const user = useState<User>("user", () => ({
44
id: 0,
45
name: "",
46
email: ""
47
}));
48
49
// Auto-generated key
50
const theme = useState(() => "light");
51
52
// Computed state
53
const doubleCounter = computed(() => counter.value * 2);
54
55
// Complex state management
56
const todos = useState<Todo[]>("todos", () => []);
57
58
function addTodo(todo: Omit<Todo, "id">) {
59
todos.value.push({
60
...todo,
61
id: Date.now()
62
});
63
}
64
65
function removeTodo(id: number) {
66
const index = todos.value.findIndex(todo => todo.id === id);
67
if (index > -1) {
68
todos.value.splice(index, 1);
69
}
70
}
71
72
// Clear state
73
function clearTodos() {
74
clearNuxtState("todos");
75
}
76
77
// Clear multiple states
78
clearNuxtState(["counter", "user"]);
79
80
// Clear with filter
81
clearNuxtState((key) => key.startsWith("temp-"));
82
```
83
84
### Cookie Management
85
86
Manage cookies with reactive updates and SSR support.
87
88
```typescript { .api }
89
/**
90
* Manage cookies with reactive updates and SSR support
91
* @param name - Cookie name
92
* @param opts - Cookie configuration options
93
* @returns Reactive reference to cookie value
94
*/
95
function useCookie<T = string>(name: string, opts?: CookieOptions<T>): CookieRef<T>;
96
97
/**
98
* Refresh a cookie value from the latest request
99
* @param name - Cookie name to refresh
100
*/
101
function refreshCookie(name: string): void;
102
103
interface CookieOptions<T = string> {
104
/** Default value factory */
105
default?: () => T | Ref<T>;
106
/** Deserialize cookie value */
107
decode?: (value: string) => T;
108
/** Serialize cookie value */
109
encode?: (value: T) => string;
110
/** Cookie domain */
111
domain?: string;
112
/** Cookie expiration date */
113
expires?: Date;
114
/** HTTP only flag */
115
httpOnly?: boolean;
116
/** Maximum age in seconds */
117
maxAge?: number;
118
/** Cookie path */
119
path?: string;
120
/** SameSite attribute */
121
sameSite?: boolean | "lax" | "strict" | "none";
122
/** Secure flag */
123
secure?: boolean;
124
/** Read-only cookie */
125
readonly?: boolean;
126
/** Watch for changes */
127
watch?: boolean;
128
}
129
130
interface CookieRef<T> extends Ref<T> {
131
/** Cookie value */
132
value: T;
133
}
134
```
135
136
**Usage Examples:**
137
138
```typescript
139
// Basic cookie
140
const token = useCookie("token");
141
142
// Set cookie value
143
token.value = "abc123";
144
145
// Typed cookie with default
146
const theme = useCookie<"light" | "dark">("theme", {
147
default: () => "light"
148
});
149
150
// Object cookie with serialization
151
const userPrefs = useCookie<UserPrefs>("user-prefs", {
152
default: () => ({ lang: "en", notifications: true }),
153
encode: (value) => JSON.stringify(value),
154
decode: (value) => JSON.parse(value)
155
});
156
157
// Secure cookie configuration
158
const sessionId = useCookie("session-id", {
159
httpOnly: true,
160
secure: true,
161
sameSite: "strict",
162
maxAge: 60 * 60 * 24 * 7 // 7 days
163
});
164
165
// Read-only cookie
166
const csrfToken = useCookie("csrf-token", {
167
readonly: true
168
});
169
170
// Cookie with custom domain and path
171
const subdomain = useCookie("subdomain-pref", {
172
domain: ".example.com",
173
path: "/app"
174
});
175
176
// Refresh cookie from server
177
refreshCookie("session-id");
178
179
// Watch cookie changes
180
watch(theme, (newTheme) => {
181
document.documentElement.setAttribute("data-theme", newTheme);
182
});
183
```
184
185
### Authentication State Example
186
187
```typescript
188
// composables/useAuth.ts
189
interface User {
190
id: number;
191
name: string;
192
email: string;
193
role: string;
194
}
195
196
export const useAuth = () => {
197
const user = useState<User | null>("auth.user", () => null);
198
const token = useCookie("auth-token", {
199
httpOnly: false, // Allow client access
200
secure: true,
201
sameSite: "strict",
202
maxAge: 60 * 60 * 24 * 30 // 30 days
203
});
204
205
const isLoggedIn = computed(() => !!user.value);
206
207
const login = async (credentials: LoginCredentials) => {
208
try {
209
const response = await $fetch("/api/auth/login", {
210
method: "POST",
211
body: credentials
212
});
213
214
user.value = response.user;
215
token.value = response.token;
216
217
await navigateTo("/dashboard");
218
} catch (error) {
219
throw createError({
220
statusCode: 401,
221
statusMessage: "Invalid credentials"
222
});
223
}
224
};
225
226
const logout = async () => {
227
user.value = null;
228
token.value = null;
229
230
await $fetch("/api/auth/logout", {
231
method: "POST"
232
});
233
234
await navigateTo("/login");
235
};
236
237
const refresh = async () => {
238
if (!token.value) return;
239
240
try {
241
const response = await $fetch("/api/auth/me", {
242
headers: {
243
Authorization: `Bearer ${token.value}`
244
}
245
});
246
247
user.value = response.user;
248
} catch (error) {
249
// Token invalid, clear auth state
250
user.value = null;
251
token.value = null;
252
}
253
};
254
255
return {
256
user: readonly(user),
257
token: readonly(token),
258
isLoggedIn,
259
login,
260
logout,
261
refresh
262
};
263
};
264
```
265
266
### Error Handling
267
268
Comprehensive error handling with user-friendly error states.
269
270
```typescript { .api }
271
/**
272
* Get the current error state
273
* @returns Reactive reference to current error
274
*/
275
function useError(): Ref<NuxtError | null>;
276
277
/**
278
* Create a new Nuxt error object
279
* @param err - Error message or partial error object
280
* @returns Formatted Nuxt error
281
*/
282
function createError<DataT = any>(err: string | Partial<NuxtError<DataT>>): NuxtError<DataT>;
283
284
/**
285
* Show an error and trigger error handling
286
* @param error - Error to display
287
* @returns The error object
288
*/
289
function showError<DataT = any>(error: string | Partial<NuxtError<DataT>>): NuxtError<DataT>;
290
291
/**
292
* Clear the current error state
293
* @param options - Optional redirect options
294
* @returns Promise resolving when error is cleared
295
*/
296
function clearError(options?: { redirect?: string }): Promise<void>;
297
298
/**
299
* Check if an error is a Nuxt error
300
* @param error - Error to check
301
* @returns Type predicate
302
*/
303
function isNuxtError<DataT = any>(error: unknown): error is NuxtError<DataT>;
304
305
interface NuxtError<DataT = any> {
306
/** HTTP status code */
307
statusCode: number;
308
/** HTTP status message */
309
statusMessage?: string;
310
/** Error message */
311
message: string;
312
/** Stack trace */
313
stack?: string;
314
/** Additional error data */
315
data?: DataT;
316
/** Error cause */
317
cause?: unknown;
318
}
319
```
320
321
**Usage Examples:**
322
323
```typescript
324
// Get current error
325
const error = useError();
326
327
// Create and show errors
328
function handleApiError(response: Response) {
329
if (response.status === 404) {
330
throw createError({
331
statusCode: 404,
332
statusMessage: "Not Found",
333
message: "The requested resource was not found"
334
});
335
}
336
337
if (response.status >= 500) {
338
throw createError({
339
statusCode: 500,
340
statusMessage: "Server Error",
341
message: "An internal server error occurred"
342
});
343
}
344
}
345
346
// Show error manually
347
function showNotFoundError() {
348
showError({
349
statusCode: 404,
350
statusMessage: "Page Not Found"
351
});
352
}
353
354
// Clear error and redirect
355
async function goHome() {
356
await clearError({ redirect: "/" });
357
}
358
359
// Error checking
360
try {
361
await someAsyncOperation();
362
} catch (err) {
363
if (isNuxtError(err)) {
364
console.log("Nuxt error:", err.statusCode, err.message);
365
} else {
366
console.log("Unknown error:", err);
367
}
368
}
369
370
// Global error handling
371
const error = useError();
372
373
watch(error, (newError) => {
374
if (newError) {
375
// Log error to monitoring service
376
console.error("Application error:", newError);
377
378
// Show user-friendly message
379
if (newError.statusCode >= 500) {
380
// Server error - show generic message
381
showToast("Something went wrong. Please try again later.");
382
} else if (newError.statusCode === 401) {
383
// Unauthorized - redirect to login
384
navigateTo("/login");
385
}
386
}
387
});
388
```
389
390
### Shopping Cart Example
391
392
```typescript
393
// composables/useCart.ts
394
interface CartItem {
395
id: number;
396
name: string;
397
price: number;
398
quantity: number;
399
image?: string;
400
}
401
402
export const useCart = () => {
403
const items = useState<CartItem[]>("cart.items", () => []);
404
405
// Persist cart in cookie
406
const cartCookie = useCookie<CartItem[]>("cart", {
407
default: () => [],
408
encode: (value) => JSON.stringify(value),
409
decode: (value) => JSON.parse(value),
410
maxAge: 60 * 60 * 24 * 30 // 30 days
411
});
412
413
// Sync state with cookie
414
watch(items, (newItems) => {
415
cartCookie.value = newItems;
416
}, { deep: true });
417
418
// Initialize from cookie
419
if (cartCookie.value.length > 0) {
420
items.value = cartCookie.value;
421
}
422
423
const totalItems = computed(() =>
424
items.value.reduce((sum, item) => sum + item.quantity, 0)
425
);
426
427
const totalPrice = computed(() =>
428
items.value.reduce((sum, item) => sum + (item.price * item.quantity), 0)
429
);
430
431
const isEmpty = computed(() => items.value.length === 0);
432
433
const addItem = (product: Omit<CartItem, "quantity">) => {
434
const existingItem = items.value.find(item => item.id === product.id);
435
436
if (existingItem) {
437
existingItem.quantity++;
438
} else {
439
items.value.push({ ...product, quantity: 1 });
440
}
441
};
442
443
const removeItem = (id: number) => {
444
const index = items.value.findIndex(item => item.id === id);
445
if (index > -1) {
446
items.value.splice(index, 1);
447
}
448
};
449
450
const updateQuantity = (id: number, quantity: number) => {
451
const item = items.value.find(item => item.id === id);
452
if (item) {
453
if (quantity <= 0) {
454
removeItem(id);
455
} else {
456
item.quantity = quantity;
457
}
458
}
459
};
460
461
const clearCart = () => {
462
items.value = [];
463
cartCookie.value = [];
464
};
465
466
return {
467
items: readonly(items),
468
totalItems,
469
totalPrice,
470
isEmpty,
471
addItem,
472
removeItem,
473
updateQuantity,
474
clearCart
475
};
476
};
477
```
478
479
## Advanced State Patterns
480
481
```typescript
482
// Composable factory pattern
483
function createStore<T>(key: string, initialValue: T) {
484
const state = useState<T>(key, () => initialValue);
485
486
const reset = () => {
487
state.value = initialValue;
488
};
489
490
const clear = () => {
491
clearNuxtState(key);
492
};
493
494
return {
495
state,
496
reset,
497
clear
498
};
499
}
500
501
// Usage
502
const { state: userSettings, reset: resetSettings } = createStore("settings", {
503
theme: "light" as const,
504
language: "en",
505
notifications: true
506
});
507
508
// Shared state with validation
509
function useValidatedState<T>(
510
key: string,
511
initialValue: T,
512
validator: (value: T) => boolean
513
) {
514
const state = useState<T>(key, () => initialValue);
515
516
const setValue = (newValue: T) => {
517
if (validator(newValue)) {
518
state.value = newValue;
519
} else {
520
throw createError({
521
statusCode: 400,
522
statusMessage: "Invalid state value"
523
});
524
}
525
};
526
527
return {
528
value: readonly(state),
529
setValue
530
};
531
}
532
```
533
534
## Types
535
536
```typescript { .api }
537
type MaybeRef<T> = T | Ref<T>;
538
539
interface LoginCredentials {
540
email: string;
541
password: string;
542
}
543
544
interface UserPrefs {
545
lang: string;
546
notifications: boolean;
547
theme?: "light" | "dark";
548
}
549
550
interface Todo {
551
id: number;
552
text: string;
553
completed: boolean;
554
createdAt: Date;
555
}
556
```