🔥 (#159) Writable computed refs, two script blocks, and Nuxt's powerful built-in storage

Hey there!

I hope you're enjoying spring and all your Vue development.

Don't forget to check out the latest Deja Vue podcast episode here.

Also, I've got a bunch of tips for you this week.

Enjoy!

— Michael

null

🔥 Directly accessing parent components (and why)

Props down, events up. That's how your components should communicate — most of the time.

But in rare cases, that just doesn't work.

If you need direct access to the parent component, you should just use provide/inject to pass down the relevant value or method:

import { provide } from 'vue';
const someMethodInTheParent = () => {};
provide('method', someMethodInTheParent)

Then, inject it into the child component:

import { inject } from 'vue';
const method = inject('method');
method();

In Vue 2, you can also use the instance property $parent:

// Tight coupling like this is usually a bad idea
this.$parent.methodOnParentComponent();

This is simpler, but leads to higher coupling and will more easily break your application if you ever refactor.

You can also get direct access to the application root, the very top-most component in the tree, by using $root. Vue 2 also has $children, but these were taken out for Vue 3 (please don't use this one).

When would these be useful?

There are a few different scenarios I can think of. Usually, when you want to abstract some behaviour and have it work "magically" behind the scenes.

You don't want to use props and events to connect up a component in those cases. Instead, you use provide/inject, $parent, or $root, to automatically connect the components and make things happen.

(This is similar to the Compound Component pattern)

But it's hard to come up with an example where this is the best solution. Using provide/inject is almost always the better choice.

🔥 Writable Computed Refs

Computed refs are cool and all, but did you know you can create writable computed refs?

const firstName = ref('');
const lastName = ref('');
const fullName = computed({
get: () => `${firstName.value} ${lastName.value}`,
set: (val) => {
const split = val.split(' '); // ['Michael', 'Thiessen']
firstName.value = split[0]; // 'Michael'
lastName.value = split[1]; // 'Thiessen'
}
});
fullName.value = 'Michael Thiessen';
console.log(lastName.value); // 'Thiessen'

🔥 Using two script blocks

The <script setup> sugar in Vue 3 is a really nice feature, but did you know you can use it and a regular <script> block?

<script setup>
// Composition API
import { ref } from 'vue';
console.log('Setting up new component instance');
const count = ref(0);
</script>
<script>
// ...and the options API too!
export default {
name: 'DoubleScript',
};
</script>

This works because the <script setup> block is compiled into the component's setup() function.

There are a few reasons why you might want to do this:

  • Use the options API — not everything has an equivalent in the composition API, like inheritAttrs. For these you can also use defineOptions.
  • Run setup code one time — because setup() is run for every component, if you have code that should only be executed once, you can't include it in <script setup>. You can put it inside the regular <script> block, though.
  • Named exports — sometimes, it's nice to export multiple things from one file, but you can only do that with the regular <script> block.

Check out the docs for more info

📜 I wrote my own Vue.js

A story that most of us can relate to. When do you use an existing solution or write it from scratch yourself?

Check it out here: I wrote my own Vue.js

đź’¬ Two Types of Languages

"There are only two kinds of languages: the ones people complain about and the ones nobody uses." — Bjarne Stroustrup

đź§  Spaced-repetition: Nuxt's Powerful Built-In Storage

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.

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



p.s. I also have four products/courses: Clean Components Toolkit, Vue Tips Collection 2, Mastering Nuxt 3, and Reusable Components