🔥 (#166) Design Patterns and Proxies

Hey all!

I just finished adding some new design patterns and recording a bunch of new videos for Clean Components Toolkit.

I'll be launching this update on June 5th, in two weeks, with a big discount.

Of course, if you already own it, you'll see the update soon.

I'm adding in three new design patterns, bringing the total up to 21. I've also recorded a bunch of extra "bonus" content.

Enjoy your tips for this week!

— Michael

🔥 Proxy Basics

Proxies are one of the strangest but most interesting parts of Javascript.

It's a fancy wrapper that lets us create lightweight reactivity systems like in Vue, and so much more.

Defining a proxy is simple. We just need to create a handler object, and then use it on an object:

const handler = {
get(target, prop, receiver) {
return 'proxied!';
},
};
const someObj = {
hello: 'world',
};
const proxy = new Proxy(someObj, handler);
console.log(proxy.hello) // proxied!

It lets us intercept property accesses with the get "trap", so we could force any object to use our own logging method:

const handler = {
get(target, prop, receiver) {
return () => {
if (typeof target[prop] !== "function") return;
// Force the method to use our own logging method
const consoleLog = console.log;
console.log = (msg) => {
consoleLog(`[${prop}] ${msg}`);
};
target[prop]();
console.log = consoleLog;
};
},
};
const someObj = {
hello() {
console.log('world');
}
}
const proxy = new Proxy(someObj, handler);
proxy.hello() // [hello] world

We can also intercept when a property is set, prototypes are accessed, and many more things.

You can find a complete list on MDN:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy

🔥 Easily Mock API Routes in Nuxt

If you've ever written unit tests, you'll have needed to mock out API endpoints that are used in your components or stores.

With @nuxt/test-utils this is really simple, because you get the registerEndpoint utility method:

import { registerEndpoint } from '@nuxt/test-utils/runtime';
import userTestData from './userTestData.json';
registerEndpoint('/users/', () => userTestData);
// ...tests

You can mock any server route (API endpoint), including external endpoints if you need.

🔥 Nuxt Plugin Dependencies

When writing plugins for Nuxt, you can specify dependencies:

export default defineNuxtPlugin({
name: 'my-sick-plugin-that-will-change-the-world',
dependsOn: ['another-plugin']
async setup (nuxtApp) {
// The setup is only run once `another-plugin`
// has been initialized
}
})

But why do we need this?

Normally, plugins are initialized sequentially — based on the order they are in the filesystem:

plugins/
- 01.firstPlugin.ts // Use numbers to force
// non-alphabetical order
- 02.anotherPlugin.ts
- thirdPlugin.ts

But we can also have them loaded in parallel, which speeds things up if they don’t depend on each other:

export default defineNuxtPlugin({
name: 'my-parallel-plugin',
parallel: true,
async setup (nuxtApp) {
// Runs completely independently of all other plugins
}
})

However, sometimes we have other plugins that depend on these parallel plugins. By using the dependsOn key, we can let Nuxt know which plugins we need to wait for, even if they’re being run in parallel:

export default defineNuxtPlugin({
name: 'my-sick-plugin-that-will-change-the-world',
dependsOn: ['my-parallel-plugin']
async setup (nuxtApp) {
// Will wait for `my-parallel-plugin` to
// finish before initializing
}
})

Although useful, you don’t actually need this feature (probably). Pooya Parsa has said this:

I wouldn't personally use this kind of hard dependency graph in plugins. Hooks are much more flexible in terms of dependency definition and pretty sure every situation is solvable with correct patterns. Saying I see it as mainly an "escape hatch" for authors looks good addition considering historically it was always a requested feature.

🎧 Vue.js Amsterdam Recap (DejaVue #008)

Alex and I recap the Amsterdam conference that happened back in February, now that the talks are up online.

I wasn't able to make it, so he fills me in on all the details, and we talk about:

You can also watch or listen on any major platform.

📜 How do you structure a Vue project?

Marco, a subscriber, once asked me this very question.

My quick response was: keep it flat and simple, and when things start to get messy, slowly add in folders.

An even better response: Markus wrote a great article on this, and he goes into much more detail and provides some more specific advice.

Check it out here: Vue Project Directory Structure

đź’¬ The Best Learners

"The best learners are the people who push through the discomfort of being objectively bad at something." — Tommy Collison

đź§  Spaced-repetition: Handle Client-side Errors in Nuxt

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.

Proper error handling is a crucial part of any production app, even if it isn’t the most exciting thing.

Nuxt 3 comes with the NuxtErrorBoundary component which makes handling client-side errors a breeze:

<NuxtErrorBoundary>
<!-- Put components in here -->
</NuxtErrorBoundary>

Use the error named slot and the error object to show an error message to the user:

<template #error="{ error }">
<div>
<p>Oops, it looks like the video player broke :/</p>
<p>{{ error.message }}</p>
</div>
</template>

If we pass the error object to another method, we can more easily manipulate it in order to resolve our error:

<NuxtErrorBoundary>
<ImageViewer />
<template #error="{ error }">
<div>
<p>Oops, the image viewer isn't working properly!</p>
<p>{{ error.message }}</p>
<!-- Pass the error to another method to recover -->
<p>
<button @click="recoverFromError(error)">
Try again
</button>
</p>
</div>
</template>
</NuxtErrorBoundary>

Use a recoverFromError function to set the value of the error ref to null and re-render the default slot:

const recoverFromError = (error) => {
// Try to resolve the error here
error.value = null;
}

In some cases, a more drastic action might be needed, like navigating to a safe page using the navigateTo utility:

const recoverFromError = async (error) => {
// Navigate away first, otherwise we'll get stuck in a loop
await navigateTo('/');
error.value = null;
};

You can place NuxtErrorBoundary components around distinct chunks of functionality, like each widget in an admin dashboard, to contain and handle errors in specific ways.

This is better than simply having a generic error message that’s shown no matter what the error is. By isolating errors with NuxtErrorBoundary we can show more useful error messages and provide better ways of recovering!

Learn more from the docs: https://nuxt.com/docs/api/components/nuxt-error-boundary



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