6.3 KiB
title | description | navigation.icon |
---|---|---|
State Management | Nuxt provides powerful state management libraries and the useState composable to create a reactive and SSR-friendly shared state. | i-ph-database-duotone |
Nuxt provides the useState
composable to create a reactive and SSR-friendly shared state across components.
useState
is an SSR-friendly ref
replacement. Its value will be preserved after server-side rendering (during client-side hydration) and shared across all components using a unique key.
::callout
Because the data inside useState
will be serialized to JSON, it is important that it does not contain anything that cannot be serialized, such as classes, functions or symbols.
::
::read-more{to="/docs/api/composables/use-state"}
Read more about useState
composable.
::
Best Practices
::callout{color="amber" icon="i-ph-warning-duotone"}
Never define const state = ref()
outside of <script setup>
or setup()
function.
Such state will be shared across all users visiting your website and can lead to memory leaks!
::
::callout{color="green" icon="i-ph-check-circle-duotone"}
Instead use const useX = () => useState('x')
::
Examples
Basic Usage
In this example, we use a component-local counter state. Any other component that uses useState('counter')
shares the same reactive state.
<script setup lang="ts">
const counter = useState('counter', () => Math.round(Math.random() * 1000))
</script>
<template>
<div>
Counter: {{ counter }}
<button @click="counter++">
+
</button>
<button @click="counter--">
-
</button>
</div>
</template>
:link-example{to="/docs/examples/features/state-management"}
::callout
To globally invalidate cached state, see clearNuxtState
util.
::
Initializing State
Most of the time, you will want to initialize your state with data that resolves asynchronously. You can use the app.vue
component with the callOnce
util to do so.
<script setup lang="ts">
const websiteConfig = useState('config')
await callOnce(async () => {
websiteConfig.value = await $fetch('https://my-cms.com/api/website-config')
})
</script>
::callout
This is similar to the nuxtServerInit
action in Nuxt 2, which allows filling the initial state of your store server-side before rendering the page.
::
:read-more{to="/docs/api/utils/call-once"}
Usage with Pinia
In this example, we leverage the Pinia module to create a global store and use it across the app.
::callout
Make sure to install the Pinia module with npx nuxi@latest module add pinia
or follow the module's installation steps.
::
::code-group
export const useWebsiteStore = defineStore('websiteStore', {
state: () => ({
name: '',
description: ''
}),
actions: {
async fetch() {
const infos = await $fetch('https://api.nuxt.com/modules/pinia')
this.name = infos.name
this.description = infos.description
}
}
})
<script setup lang="ts">
const website = useWebsiteStore()
await callOnce(website.fetch)
</script>
<template>
<main>
<h1>{{ website.name }}</h1>
<p>{{ website.description }}</p>
</main>
</template>
::
Advanced Usage
::code-group
import type { Ref } from 'vue'
export const useLocale = () => {
return useState<string>('locale', () => useDefaultLocale().value)
}
export const useDefaultLocale = (fallback = 'en-US') => {
const locale = ref(fallback)
if (process.server) {
const reqLocale = useRequestHeaders()['accept-language']?.split(',')[0]
if (reqLocale) {
locale.value = reqLocale
}
} else if (process.client) {
const navLang = navigator.language
if (navLang) {
locale.value = navLang
}
}
return locale
}
export const useLocales = () => {
const locale = useLocale()
const locales = ref([
'en-US',
'en-GB',
...
'ja-JP-u-ca-japanese'
])
if (!locales.value.includes(locale.value)) {
locales.value.unshift(locale.value)
}
return locales
}
export const useLocaleDate = (date: Ref<Date> | Date, locale = useLocale()) => {
return computed(() => new Intl.DateTimeFormat(locale.value, { dateStyle: 'full' }).format(unref(date)))
}
<script setup lang="ts">
const locales = useLocales()
const locale = useLocale()
const date = useLocaleDate(new Date('2016-10-26'))
</script>
<template>
<div>
<h1>Nuxt birthday</h1>
<p>{{ date }}</p>
<label for="locale-chooser">Preview a different locale</label>
<select id="locale-chooser" v-model="locale">
<option v-for="locale of locales" :key="locale" :value="locale">
{{ locale }}
</option>
</select>
</div>
</template>
::
:link-example{to="/docs/examples/advanced/locale"}
Shared State
By using auto-imported composables we can define global type-safe states and import them across the app.
export const useCounter = () => useState<number>('counter', () => 0)
export const useColor = () => useState<string>('color', () => 'pink')
<script setup lang="ts">
const color = useColor() // Same as useState('color')
</script>
<template>
<p>Current color: {{ color }}</p>
</template>
Using third-party libraries
Nuxt used to rely on the Vuex library to provide global state management. If you are migrating from Nuxt 2, please head to the migration guide.
Nuxt is not opinionated about state management, so feel free to choose the right solution for your needs. There are multiple integrations with the most popular state management libraries, including: