or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

app-lifecycle.mdconfiguration.mdcore.mddata-fetching.mdhead.mdindex.mdmodule-dev.mdnavigation.mdperformance.mdssr.mdstate.md

state.mddocs/

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

```