Hey!
So many things to talk about today.
VueJS Amsterdam is streaming right now and I'm sad I couldn't make it there this year!
My Twitter feed is full of FOMO-inducing posts and it looks like a great time.
Like I say every year: next year I'll be there!
This week I'm doing the final polish and final push on getting the Reusable Components update out for next week (March 5th).
I was running into some issues deploying an update for my course platform so I can support multiple courses. It ends up touching the entire checkout and authentication flow, so it's a big change and a risky one too.
While it worked fine on my machine it wasn't cooperating in production. But I shouldn't have assumed it would — I've encountered problems like this before.
It wasn't what I planned to do this week, but that's the nature of things.
Regardless, I'm having fun with this and I'm so glad I still get to write code each week, even though my job is "creating content".
It's also my birthday today! 🎉
(and no, if I was born a day later it would've been March 1st)
Today I am the oldest that I've ever been.
— Michael
null
Render functions provide a powerful and flexible way to programmatically define the output of components.
This approach is particularly useful when you need more control over the rendering process than templates offer.
Consider a scenario where you're building a custom If...Else
component that conditionally renders content based on a given condition (we're just doing this for fun, okay?)
Here's a simplified example of how you might use a render function in an If
component:
export default {props: ['val'],setup(props, { slots }) {return () => {const children = [];if (props.val && slots.true) {children.push(slots.true());} else if (!props.val && slots.false) {children.push(slots.false());}return children;};},};
In this example, the If
component takes a val
prop to determine which slot to render — true
or false
. The render function checks props.val
and dynamically decides which slot to render based on its value.
Render functions are especially useful when:
While render functions offer great power and flexibility, they come at the cost of reduced readability and simplicity compared to template syntax.
So it's generally recommended to use them only when necessary, keeping the balance between functionality and maintainability.
Here’s a nuance to nesting reactive objects that’s tripped me up.
Nesting a ref
in a reactive array keeps everything reactive as expected:
const arr = reactive([]);arr.push(ref('hello'));arr.push(ref('world'));setTimeout(() => (arr[0].value = 'nothing'), 1000);
But putting this ref
inside a non-reactive object breaks this reactivity:
const arr = reactive([]);arr.push({text: ref('hello'),});arr.push({text: ref('world'),});setTimeout(() => (arr[0].value = 'nothing'), 1000);
I gave a hint in that last sentence — this is because it’s being wrapped in a non-reactive object. The trail of reactivity goes cold once we hit this object, but only because we’re accessing the text
property through the non-reactive object.
If we instead access the ref
directly, we’re able to trigger a reactive update as expected:
const arr = reactive([]);const one = ref('hello');const two = ref('world');arr.push({text: one,});arr.push({text: two,});// This triggers the update correctlysetTimeout(() => (one.value = 'nothing'), 1000);
Of course, this isn’t about refs in particular. All we need is to keep the reactivity alive, which we can also achieve using reactive
.
Nitro, the server that Nuxt 3 uses, comes with a very powerful key-value storage system:
const storage = useStorage();// Save a valueawait storage.setItem('some:key', value);// Retrieve a valueconst 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
Dynamic IDs are useful for testing, but can get tricky with SSR.
In this article we'll make a directive to do this easily, in both vanilla Vue and in Nuxt.
Check it out here: SSR Safe Dynamic IDs in Vue
"Give someone a program, you frustrate them for a day; teach them how to program, you frustrate them for a lifetime." — David Leinweber
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.
If you ever need to debug what’s happening inside of a template, you can just throw in a function:
<template><div v-for="i in 4" :key="i">{{ log(i) }}{{ i + 1 }}</div></template>
Vue will execute anything within the curly braces as Javascript, so this function is called normally.
It can be whatever you want. Set it to console.log
if you just want to log out some values:
const log = console.log;
Or add in a debugger
statement so you can step through the code one line at a time and inspect the variables more closely:
const log = (val) => {debugger;};
If we want global access to a debugging utility, we can use the globalProperties
field on our app config:
app.config.globalProperties.$log = console.log;
Now, we can use this $log
method in whatever component we want:
p.s. I also have four products/courses: Clean Components Toolkit, Vue Tips Collection 2, Mastering Nuxt 3, and Reusable Components