Read More on Telerik Blogs
August 07, 2025 Web, Vue
Get A Free Trial

Nuxt makes sharing data across components a snap. Let’s look at useState, useLocalState and useCookie.

I’ve been obsessed with useState in Nuxt since the first time I saw it. Other frameworks do not seem to share data as easily (looking at you, React Context). Nuxt has many options to store state, and it definitely makes these things easy. I love it so much, I even wrote versions for other frameworks.

TL;DR

Nuxt is the easiest framework to share data across components. You can use useState, provide and inject, or persist data across cookies with useCookie. Either way, no other framework makes it this easy.

useState

DO NOT confuse Nuxt’s useState with React’s useState. React’s version is much weaker. Nuxt’s useState, on the other hand, is awesome!

How It Works

Let’s say we want to share data across components. In other frameworks, including Vue itself, you have to prop drill shared data. It is not fun. Context, on the other hand, allows you to share data by setting a “key/value” pair, and using it anywhere. Nuxt does this automatically.

useState('count', () => 0)

The first parameter is the key. In any framework with a type of Context API you must set a key to get a value. The second parameter is the fallback. The fallback is used when there is no value belonging to that key; however, it is also used to store the initial value itself.

Usage

The recommended way to use it is to create a custom hook, or a custom composable.

export function useCount() {

	return useState('count', () => 0)
	
}

This returns a reactive counter that can be used anywhere!

I know what you’re thinking. Why not just use a normal reactive variable?

export function useCount() {
	
	return ref(0)

}

This seems logical, but if you use useCount in multiple components, the state does not get shared. You will have new counters for each implementation.

// component 1

<script setup lang="ts">
const count = useCount()
</script>

<template>
  <p>Count: {{ count }}</p>
  <button
    @click="
      () => {
        count++;
      }
    "
  >
    +
  </button>
</template>

// component 2
<script setup lang="ts">
const count = useCount()
</script>

<template>
<div> {{ count }} <div>
</template>

If we click the button, we want the counter to increment everywhere.

How Does It Really Work?

I got so obsessed with the simplicity, I researched the source code.

Global vs. Local

Nuxt has a global store. You can get the values anywhere using useNuxtApp().

export function useGlobalState<T>(key: string, init?: (() => T | Ref<T>)) {

    const nuxtApp = useNuxtApp()

    const state = toRef(nuxtApp.payload.state, key)

    if (state.value === undefined && init) {

        const initialValue = init()

        state.value = initialValue
    }
    return state
}

I created a very simplified, but working, version of useState(). You get the value from the global store, make it reactive and return it. If it doesn’t exist, create it, save it, return it.

export function useCount() {

	return useGlobalState('count', () => 0)

}

This will work the exact same way as useState. Because it runs only once, it is called a singleton.

useLocalState

But what if you need a local version?

Usage

Sometimes you don’t want to save every state value to the global store. This can make it hard to do individual component testing, create portable or isolated components, and keep data encapsulated.

Provide / Inject

Inspired by Angular, you can set and get a context easily in Vue / Nuxt by providing and injecting.

provide(key, value)

inject(key, fallback_value)

Sometimes you may see the patterns:

export function setCount(initialValue: number) {
	const count = ref(initialValue)
	provide('count', count)
	return count
}

export function getCount() {
	return inject<Ref<number>>('count');
}	

Generally, you set and get the items in different components. However, I don’t see why we can’t do the same pattern as useState here as well.

Local

export function useLocalState<T>(key: string, init?: (() => T | Ref<T>)) {

    const state = inject(key, undefined)

    if (state === undefined && init) {

        const initialValue = toRef(init())

        provide(key, initialValue)

        return initialValue
    }
    return state
}

Now we can set any state locally, just like useState.

Caveat

The state must be able to talk to the other state in a line. This means it must be nested. If I create two child components, and I set the first component with the counter, the second component WILL NOT be able to communicate with the other child component. Context is shared hierarchically, not between siblings.

<Count />
<!-- Doesn't have access to count -->
<Count />

However, you can initialize the counter in any parent so that they both have access to it.

<script setup lang="ts">
useLocalState("count", () => 0);
</script>

<template>
  <Count />
  <Count />
</template>

Now you can use useLocalState('count') anywhere in the children.

useCookie

Nuxt also has a magical way to get and set cookies on the frontend just like any other signal with ref.

const count = useCookie("count");

count.value = 10;

This will save the value to 10 and create the cookie.

You can use the value just like any other reference. The beauty of this is: it will persist! You’re not only saving a global value, you’re saving it to the cookie.

Default

There are a bunch of cookie options you can set, but to set the default value, you set a function in default just like you would with useState.

const count = useCookie("count", {
  default: () => 10
});

Bonus: Under the Hood

Cookies are a complex creature, but for the sake of explanation, I thought I would create a pseudocode version from the source code. It would require something as complex as the original to work correctly, but you get the gist.

export function useCookie(key, { default: init() }) {
  
  if (server) {
	  const cookie = getServerCookie()
	  if (cookie) {
	    return getCookie(key)
	  }
	  const state = init()
	  setCookie(key, state)
	  return state
	}
	
	// browser	
	const cookie = getClientCookie()
	if (cookie) {
		return getCookie(key)
	}
	const state = init()
	setCookie(key, state)
	return toRef(state)

// !!! NOT REAL CODE, WILL NOT WORK

Saving data has never been so easy!

Demo: Vercel
Repo: GitHub


About the Author

Jonathan Gamble

Jonathan Gamble has been an avid web programmer for more than 20 years. He has been building web applications as a hobby since he was 16 years old, and he received a post-bachelor’s in Computer Science from Oregon State. His real passions are language learning and playing rock piano, but he never gets away from coding. Read more from him at https://code.build/.

 

 

Related Posts