🔥 (215) Nuxt data fetching re-written: reactive keys, shared refs, and more

Read this on my blog

Just a few days ago Nuxt 3.17 dropped, and it comes with a massive data fetching improvement.

So I've taken a bit of extra time with this one to give you some bonus tips based on these new features.

You'll also get your regularly scheduled Vue tips, too.

This one's got a lot in it, so enjoy!

— Michael

🔥 Reactive Keys with useAsyncData

You can make the keys in useAsyncData (and urls in useFetch) reactive:

// Getter (some function)  const { data } = useAsyncData(    () => `key-${index + 1}`,    () => $fetch('https://some-url')  )    // Ref  const myRefKey = ref('my-key')  const { data } = useAsyncData(myRefKey, () => $fetch('https://some-url'))    // Computed  const myComputedKey = computed(() => `my-key-${someOtherRef.value}`)  const { data } = useAsyncData(myComputedKey, () => $fetch('https://some-url'))

This gives you more flexibility and prevents overwriting data in our payload like if we had to use a static key:

// Always overwritten when data changes  const { data } = useAsyncData('my-ref', () => $fetch('https://some-url'))

🔥 Deduped Watch Calls with useAsyncData

Now, if there are two calls to useAsyncData for the same key that watch the same source, it will only trigger a single request:

// Component A  const { data: character } = await useAsyncData(    'sw-character',    () => $fetch(`https://swapi.py4e.com/api/people/${selectedId.value}/`),    {      watch: [selectedId],    }  )    // Component B  const { data: character } = await useAsyncData(    'sw-character',    () => $fetch(`https://swapi.py4e.com/api/people/${selectedId.value}/`),    {      watch: [selectedId],    }  )

🔥 Shared Refs across data fetching composables in Nuxt

Before, each time you used useAsyncData or useFetch, you'd get back an entirely new ref object.

Now, these data fetching composables return the exact same objects, saving tons of memory and improving performance.

For example, if data is updated or mutated like this, it affects all components that are using this key:

const { data: character } = await useAsyncData('sw-character', () =>    $fetch(`https://swapi.py4e.com/api/people/${selectedId.value}/`)  )    function mutateData() {    character.value = {      name: 'Michael Thiessen',      height: 180,      mass: 70,      hair_color: 'blonde',      skin_color: 'fair',      eye_color: 'blue',      birth_year: '1993',      gender: 'male',    }  }

🔥 Granular Caching with getCachedData

In Nuxt 3.17 we get a new context object passed in to getCachedData, so we have more information on why a fetch is happening:

getCachedData: (key, nuxtApp, context) => {    if (context.cause === 'refresh:manual') {      // Always refetch data when `refresh` is called      return null    }      return (      nuxtApp.payload.data[key] ||      nuxtApp.static.data[key]    )  },

Currently, there are four different reasons a fetch could be triggered:

  • Initial fetch — initial
  • Triggered from a watcher — watch
  • From calling refresh — refresh:manual
  • Refresh from a hook — refresh:hook

This one is disabled by default because it is a breaking change, unless you have v4 compatibility turned on. You can also turn it on through the experimental config flag:

export default defineNuxtConfig({    experimental: {      granularCachedData: true,    },  })

🔥 From Options to Composition — The Easy Way

You can use reactive to make the switch from the Options API a little easier:

// Options API  export default {    data() {      username: 'Michael',      access: 'superuser',      favouriteColour: 'blue',    },  	methods: {      updateUsername(username) {        this.username = username;      },    }  };

We can get this working using the Composition API by copying and pasting everything over using reactive:

// Composition API  setup() {    // Copy from data()    const state = reactive({      username: 'Michael',      access: 'superuser',      favouriteColour: 'blue',    });      // Copy from methods    updateUsername(username) {      state.username = username;    }    	// Use toRefs so we can access values directly  	return {      updateUsername,      ...toRefs(state),    }  }

We also need to make sure we change thisstate when accessing reactive values, and remove it entirely if we need to access updateUsername.

Now that it's working, it's much easier to continue refactoring using ref if you want to — or just stick with reactive.

🔥 Nuxt's Powerful Built-In Storage

Nitro, the server that Nuxt 3 uses, comes with a very powerful key-value storage system:

const storage = useStorage()    // Save a value  await storage.setItem('some:key', value)    // Retrieve a value  const item = await storage.getItem('some:key')

It's not a replacement for a robust database, but it's perfect for temporary data or a caching layer.

One great application of this "session storage" is using it during an OAuth flow.

In the first step of the flow, we receive a state and a codeVerifier. In the second step, we receive a code along with the state again, which let's us use the codeVerifier to verify that the code is authentic.

We need to store the codeVerifier in between these steps, but only for a few minutes — perfect for Nitro's storage!

The first step in the /oauth endpoint we store the codeVerifier:

// ~/server/api/oauth    // ...  const storage = useStorage()  const key = `verifier:${state}`  await storage.setItem(key, codeVerifier)  // ...

Then we retrieve it during the second step in the /callback endpoint:

// ~/server/api/callback    // ...  const storage = useStorage()  const key = `verifier:${state}`  const codeVerifier = await storage.getItem(key)  // ...

A simple and easy solution, with no need to add a new table to our database and deal with an extra migration.

This just scratches the surface. Learn more about the unstorage package that powers this: https://github.com/unjs/unstorage

🔥 Reusing Code and Knowledge

Don't Repeat Yourself — an acronym that many know but many don't correctly understand.

DRY isn't actually about code, it's about the knowledge and decisions that are contained in the code. Too often we are just pattern matching on syntax, and that leads us to bad abstractions that should never exist.

Here are some ways we can fix that:

  • Don't Repeat Yourself (DRY) — Use components and composables to create reusable views and logic. Doing this well is an entire topic all on it's own, which is why I created a whole course on it.
  • Optimize for Change — Most of our time is spent modifying existing code, so it pays to make it easy. If code is new or likely to change, don't worry about abstracting it into a new component yet — duplication is welcome here.
  • Syntax vs. Knowledge — When removing duplication or "drying up" your code, make sure you're encapsulating knowledge and not syntax. Just because code looks the same doesn't mean it is the same.

🎙️ Podcasts you may have missed

No new podcast this week, but here are some great recent episodes from the past:

📜 How to Use Error Handling to Create Rock-Solid Nuxt Apps

Error handling is so important to building robust applications.

In this article, we'll go over how to use error handling (all of it!) to create rock-solid Nuxt apps.

Check it out here: How to Use Error Handling to Create Rock-Solid Nuxt Apps

📜 12 Design Patterns in Vue

Design patterns are incredibly useful in writing code that works well over the long run. They let us use proven solutions to problems that basically every single app has.

But there isn't a lot written about how design patterns apply specifically to Vue.

In this article, I cover 12 design patterns that I think are crucial to writing great Vue apps.

Check it out here: 12 Design Patterns in Vue

📅 Upcoming Events

Here are some upcoming events you might be interested in. Let me know if I've missed any!

VueConf US 2025 — (May 13, 2025 to May 15, 2025)

VueConf US 2025 is a great Vue conference, this year held in Tampa from May 13–15, with two days of talks and a day of workshops. Unfortunately, I am no longer able to make it to the conference this year. I hope everyone attending has an amazing time and I look forward to joining in the future!

Check it out here

MadVue 2025 — (May 29, 2025)

It's time to get together in Madrid. Join for a full day of talks, activities, and networking with the Vue.js community and ecosystem.

Check it out here

💬 Working

"The greatest performance improvement of all is when a system goes from not-working to working." — John Ousterhout

🧠 Spaced-repetition: Watching Arrays and Objects

The best way to commit something to long-term memory is to periodically review it, gradually increasing the time between reviews 👨‍🔬 Actually remembering these tips is much more useful than just a quick distraction, so here's a tip from a couple weeks ago to jog your memory.

The trickiest part of using a watcher is that sometimes it doesn't seem to trigger correctly.

Usually, this is because you're trying to watch an Array or an Object but didn't set deep to true:

watch(    colours,    () => {      console.log('The list of colours has changed!')    },    {      deep: true,    }  )

When using the Options API it would look like this:

export default {    name: 'ColourChange',    props: {      colours: {        type: Array,        required: true,      },    },    watch: {      // Use the object syntax instead of just a method      colours: {        // This will let Vue know to look inside the array        deep: true,          // We have to move our method to a handler field        handler()          console.log('The list of colours has changed!');        }      }    }  }

🔗 Want more Vue and Nuxt links?

Michael Hoffman curates a fantastic weekly newsletter with the best Vue and Nuxt links.

Sign up for it here.

p.s. I also have a bunch of products/courses:

Unsubscribe

评论

此博客中的热门博文

Weekly Vue News #141 - Share Styling Using Wrapper Components

Scripting News: Tuesday, October 8, 2024

Scripting News: Tuesday, August 20, 2024