3.7 KiB
3.7 KiB
name, description
| name | description |
|---|---|
| state-management | useState composable and SSR-friendly state management in Nuxt |
State Management
Nuxt provides useState for SSR-friendly reactive state that persists across components.
useState
SSR-safe replacement for ref that shares state across components:
<script setup lang="ts">
// State is shared by key 'counter' across all components
const counter = useState('counter', () => 0);
</script>
<template>
<div>
Counter: {{ counter }}
<button @click="counter++">+</button>
<button @click="counter--">-</button>
</div>
</template>
Creating Shared State
Define reusable state composables:
// composables/useUser.ts
export function useUser() {
return useState<User | null>('user', () => null);
}
export function useLocale() {
return useState('locale', () => 'en');
}
<script setup lang="ts">
// Same state instance everywhere
const user = useUser();
const locale = useLocale();
</script>
Initializing State
Use callOnce to initialize state with async data:
<script setup lang="ts">
const config = useState('site-config');
await callOnce(async () => {
config.value = await $fetch('/api/config');
});
</script>
Best Practices
❌ Don't Define State Outside Setup
// ❌ Wrong - causes memory leaks and shared state across requests
export const globalState = ref({ user: null });
✅ Use useState Instead
// ✅ Correct - SSR-safe
export const useGlobalState = () => useState('global', () => ({ user: null }));
Clearing State
// Clear specific state
clearNuxtState('counter');
// Clear multiple states
clearNuxtState(['counter', 'user']);
// Clear all state (use with caution)
clearNuxtState();
With Pinia
For complex state management, use Pinia:
npx nuxi module add pinia
// stores/counter.ts
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
}),
actions: {
increment() {
this.count++;
},
},
});
// stores/user.ts (Composition API style)
export const useUserStore = defineStore('user', () => {
const user = ref<User | null>(null);
const isLoggedIn = computed(() => !!user.value);
async function login(credentials: Credentials) {
user.value = await $fetch('/api/login', {
method: 'POST',
body: credentials,
});
}
return { user, isLoggedIn, login };
});
<script setup lang="ts">
const counterStore = useCounterStore();
const userStore = useUserStore();
// Initialize store data once
await callOnce(async () => {
await userStore.fetchUser();
});
</script>
Advanced: Locale Example
// composables/useLocale.ts
export function useLocale() {
return useState('locale', () => useDefaultLocale().value);
}
export function useDefaultLocale(fallback = 'en-US') {
const locale = ref(fallback);
if (import.meta.server) {
const reqLocale = useRequestHeaders()['accept-language']?.split(',')[0];
if (reqLocale) locale.value = reqLocale;
} else if (import.meta.client) {
const navLang = navigator.language;
if (navLang) locale.value = navLang;
}
return locale;
}
State Serialization
useState values are serialized to JSON. Avoid:
- Functions
- Classes
- Symbols
- Circular references
// ❌ Won't work
useState('fn', () => () => console.log('hi'));
useState('instance', () => new MyClass());
// ✅ Works
useState('data', () => ({ name: 'John', age: 30 }));
useState('items', () => ['a', 'b', 'c']);