Skip to content

Improve User Experience in Vue 3 with Suspense

Introduction

When building applications that leverage the internet, it's important to remember that not every user will have the same connection speed that developers have. A developer working on a local machine isn't going to have the same experience as an end user in a coffee shop, or even at home. It is important to remember that some users may think that parts of your application are broken, simply because the internet connection isn't fast enough!

While we can't control internet speeds, we can plan accordingly and prevent users from experiencing a broken application. Luckily, Vue 3 provides a new way to handle situations like this, called Suspense. "Suspense" is a new built-in component in Vue that we can wrap around another component needing to perform an asynchronous action before it can render. The implementation of Suspense in Vue is very similar to React Suspense. If the component inside <Suspense></Suspense> has an async setup() method, then a fallback is presented to the user until it is completed.

An example of the below code can be found on CodeSandbox.

<Suspense> Component

Let's explore a basic example using Suspense. We will build a basic application that utilizes the Pokemon API to fetch a list of berries and display them in a dropdown. To start, we have two files, App.vue and Berries.vue:

Berries.vue

<template>
  <h1 class="text-3xl pb-2">Select a Berry</h1>
  <select v-model="selectedBerry" class="px-4 py-2 w-40 shadow">
    <option value="" disabled>Select...</option>
    <option
      v-for="berry in berries.results"
      :key="berry.url"
      :value="berry.url"
    >
      {{ berry.name }}
    </option>
  </select>
</template>

<script lang="ts">
import { defineComponent, ref } from "vue";
import useBerries from "../hooks/useBerries";

export default defineComponent({
  name: "Berries",
  async setup() {
    const selectedBerry = ref("");
    const berries = await useBerries();

    return { berries, selectedBerry };
  },
});
</script>

App.vue

<template>
  <Suspense>
    <Berries />

    <template #fallback>
      <span class="text-3xl">Picking berries...</span>
    </template>
  </Suspense>
</template>

<script lang="ts">
import { defineComponent } from "vue";
import Berries from "./components/Berries.vue";

export default defineComponent({
  name: "App",
  components: {
    Berries,
  },
});
</script>

Let's take a look at these two files.

In Berries.vue, we have an async setup() method which returns two values: selectedBerry and berries. In this example, berries is created by the custom function useBerries, but under the hood, it's a regular HTTP request to the Pokemon API for a list of berries. The exact implementation of the API request is not important to our example. These values are then referenced in the template as normal. If any of this looks confusing, I would recommend checking out this article on using "ref" and "reactive" or this article explaining the Composition API in general.

In App.vue, we import Berries.vue and register it as normal. In the template, however, we use the <Suspense> compontent. Remember it's built into Vue 3, so there's no need to register it anywhere. Within Suspense, we have the Berries component and a template with the name fallback. Until the setup method in Berries.vue returns its promise, the content within the fallback will be displayed. Once setup has returned, the default template will be rendered (in this case, the Berries component).

Let's take this a step further, and add some functionality to our app. When a user selects a berry from the dropdown, we want to request the provided URL and display the flavor of the berry. To do this, we'll add another component, BerryDetails.vue, and use Suspense within Berries.vue.

Below are the changes:

BerryDetails.vue

<template>This berry is {{ berryFlavor.flavor.name }}.</template>

<script lang="ts">
import { defineComponent } from "vue";
import useBerryFlavor from "../hooks/useBerryFlavor";

export default defineComponent({
  name: "BerryDetails",
  props: {
    url: {
      type: String,
      required: true,
    },
  },
  async setup(props) {
    const berryFlavor = await useBerryFlavor(props.url);

    return {
      berryFlavor,
    };
  },
});
</script>

Berries.vue

<template>
  <h1 class="text-3xl pb-2">Select a Berry</h1>
  <select v-model="selectedBerry" class="px-4 py-2 w-40 shadow">
    <option value="" disabled>Select...</option>
    <option
      v-for="berry in berries.results"
      :key="berry.url"
      :value="berry.url"
    >
      {{ berry.name }}
    </option>
  </select>
  <!-- Add section to display BerryDetails -->
  <div class="w-3/5 m-auto p-5">
    <Suspense v-if="selectedBerry">
      <BerryDetails :url="selectedBerry" />

      <template #fallback> Fetching berry details... </template>
    </Suspense>
  </div>

</template>

<script lang="ts">
import { defineComponent, ref } from "vue";
import useBerries from "../hooks/useBerries";
import BerryDetails from "./BerryDetails.vue";

export default defineComponent({
  name: "Berries",
  async setup() {
    const selectedBerry = ref("");
    const berries = await useBerries();

    return { berries, selectedBerry };
  },
  components: {
    BerryDetails,
  },
});
</script>

BerryDetails.vue is very straightforward - it displays a string with the flavor of the berry. It also accepts a prop of url, which is a string. This URL is passed into a custom method, useBerryFlavor (again, the implementation of making an API request is not important to our example).

Berries.vue is updated to include a Suspense block, which looks very similar to the one in App.vue. The only difference here is that we are waiting for selectedBerry to be set to a value before rendering, which makes sense; if we don't have a selected berry, we don't want to make an API request.

Error Handling

Great! Our application is up and running, and any slowness will be represented to the user so they know that the application is working. Right? Well, almost. What if one of these API requests throws an error? We need to communicate this to the user, rather than leaving the application suspended forever. In this case, Vue 3 provides us with a hook called onErrorCaptured, which we can use to capture the error and update the display. Let's take a look at our updated Berries.vue component:

Berries.vue

<template>
  <h1 class="text-3xl pb-2">Select a Berry</h1>
  <select v-model="selectedBerry" class="px-4 py-2 w-40 shadow">
    <option value="" disabled>Select...</option>
    <option
      v-for="berry in berries.results"
      :key="berry.url"
      :value="berry.url"
    >
      {{ berry.name }}
    </option>
  </select>
  <div class="w-3/5 m-auto p-5">
    <!-- Added error handling block -->
    <div v-if="error">Oh, snap! The berries are all gone!</div>
    <Suspense v-else-if="selectedBerry">
      <template #default>
        <BerryDetails :url="selectedBerry" :key="selectedBerry" />
      </template>
      <template #fallback> Fetching berry details... </template>
    </Suspense>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref, onErrorCaptured, watch } from "vue";
import useBerries from "../hooks/useBerries";
import BerryDetails from "./BerryDetails.vue";

export default defineComponent({
  name: "Berries",
  async setup() {
    const selectedBerry = ref("");
    // Added error ref
    const error = ref();

    // Added onErrorCaptured lifecycle hook
    onErrorCaptured((e) => {
      error.value = e;
      return true;
    });

    // Reset error when selectedBerry is updated
    watch(selectedBerry, () => (error.value = null));

    const berries = await useBerries();

    return { berries, selectedBerry, error };
  },
  components: {
    BerryDetails,
  },
});
</script>

We did three things in this component:

  1. We added a variable named error, which is a ref.
  2. We added the lifecycle hook onErrorCaptured, which will trigger whenever an error is captured from a child component. In this case, if the API request fails, we will catch the error, and update our display accordingly.
  3. We added a watch method on selectedBerry to reset the error whenever a new berry is selected.

Alternatively, this could be handled with a try/catch in BerryDetails.vue. In that case, the child component would need to handle the error, rather than let it bubble up to the parent, which is suspending render.

Conclusion

Suspense provides a built-in method to handle use cases that involve fetching data from an API, or performing some other asynchronous action. Rather than having to write custom logic, Vue 3 provides developers with the tools to build user-friendly applications and experiences. Fetching or loading data is a common task for single-page applications, and it is important that users are informed that something is happening behind the scenes. Next time you find yourself fetching data, consider whether the Suspense API is a good fit for the interface you are building.

An example of the code presented above can be found on CodeSandbox.

This Dot Labs is a development consultancy that is trusted by top industry companies, including Stripe, Xero, Wikimedia, Docusign, and Twilio. This Dot takes a hands-on approach by providing tailored development strategies to help you approach your most pressing challenges with clarity and confidence. Whether it's bridging the gap between business and technology or modernizing legacy systems, you’ll find a breadth of experience and knowledge you need. Check out how This Dot Labs can empower your tech journey.

You might also like

Understanding Vue's Reactive Data cover image

Understanding Vue's Reactive Data

Introduction Web development has always been about creating dynamic experiences. One of the biggest challenges developers face is managing how data changes over time and reflecting these changes in the UI promptly and accurately. This is where Vue.js, one of the most popular JavaScript frameworks, excels with its powerful reactive data system. In this article, we dig into the heart of Vue's reactivity system. We unravel how it perfectly syncs your application UI with the underlying data state, allowing for a seamless user experience. Whether new to Vue or looking to deepen your understanding, this guide will provide a clear and concise overview of Vue's reactivity, empowering you to build more efficient and responsive Vue 3 applications. So, let’s kick off and embark on this journey to decode Vue's reactive data system. What is Vue's Reactive Data? What does it mean for data to be ”'reactive”? In essence, when data is reactive, it means that every time the data changes, all parts of the UI that rely on this data automatically update to reflect these changes. This ensures that the user is always looking at the most current state of the application. At its core, Vue's Reactive Data is like a superpower for your application data. Think of it like a mirror - whatever changes you make in your data, the user interface (UI) reflects these changes instantly, like a mirror reflecting your image. This automatic update feature is what we refer to as “reactivity”. To visualize this concept, let's use an example of a simple Vue application displaying a message on the screen: `javascript import { createApp, reactive } from 'vue'; const app = createApp({ setup() { const state = reactive({ message: 'Hello Vue!' }); return { state }; } }); app.mount('#app'); ` In this application, 'message' is a piece of data that says 'Hello Vue!'. Let's say you change this message to 'Goodbye Vue!' later in your code, like when a button is clicked. `javascript state.message = 'Goodbye Vue!'; ` With Vue's reactivity, when you change your data, the UI automatically updates to 'Goodbye Vue!' instead of 'Hello Vue!'. You don't have to write extra code to make this update happen - Vue's Reactive Data system takes care of it. How does it work? Let's keep the mirror example going. Vue's Reactive Data is the mirror that reflects your data changes in the UI. But how does this mirror know when and what to reflect? That's where Vue's underlying mechanism comes into play. Vue has a behind-the-scenes mechanism that helps it stay alerted to any changes in your data. When you create a reactive data object, Vue doesn't just leave it as it is. Instead, it sends this data object through a transformation process and wraps it up in a Proxy. Proxy objects are powerful and can detect when a property is changed, updated, or deleted. Let's use our previous example: `javascript import { createApp, reactive } from 'vue'; const app = createApp({ setup() { const state = reactive({ message: 'Hello Vue!' }); return { state }; } }); app.mount('#app'); ` Consider our “message” data as a book in a library. Vue places this book (our data) within a special book cover (the Proxy). This book cover is unique - it's embedded with a tracking device that notifies Vue every time someone reads the book (accesses the data) or annotates a page (changes the data). In our example, the reactive function creates a Proxy object that wraps around our state object. When you change the 'message': `javascript state.message = 'Goodbye Vue!'; ` The Proxy notices this (like a built-in alarm going off) and alerts Vue that something has changed. Vue then updates the UI to reflect this change. Let’s look deeper into what Vue is doing for us and how it transforms our object into a Proxy object. You don't have to worry about creating or managing the Proxy; Vue handles everything. `javascript const state = reactive({ message: 'Hello Vue!' }); // What vue is doing behind the scenes: function reactive(obj) { return new Proxy(obj, { // target = state and key = message get(target, key) { track(target, key) return target[key] }, set(target, key, value) { target[key] = value // Here Vue will trigger its reactivity system to update the DOM. trigger(target, key) } }) } ` In the example above, we encapsulate our object, in this case, “state”, converting it into a Proxy object. Note that within the second argument of the Proxy, we have two methods: a getter and a setter. The getter method is straightforward: it merely returns the value, which in this instance is “state.message” equating to 'Hello Vue!' Meanwhile, the setter method comes into play when a new value is assigned, as in the case of “state.message = ‘Hey young padawan!’”. Here, “value” becomes our new 'Hey young padawan!', prompting the property to update. This action, in turn, triggers the reactivity system, which subsequently updates the DOM. Venturing Further into the Depths If you have been paying attention to our examples above, you might have noticed that inside the Proxy` method, we call the functions `track` and `trigger` to run our reactivity. Let’s try to understand a bit more about them. You see, Vue 3 reactivity data is more about Proxy objects. Let’s create a new example: `vue import { reactive, watch, computed, effect } from "vue"; const state = reactive({ showSword: false, message: "Hey young padawn!", }); function changeMessage() { state.message = "It's dangerous to go alone! Take this."; } effect(() => { if (state.message === "It's dangerous to go alone! Take this.") { state.showSword = true; } }); {{ state.message }} Click! ` In this example, when you click on the button, the message's value changes. This change triggers the effect function to run, as it's actively listening for any changes in its dependencies__. How does the effect` property know when to be called? Vue 3 has three main functions to run our reactivity: effect`, `track`, and `trigger`. The effect` function is like our supervisor. It steps in and takes action when our data changes – similar to our effect method, we will dive in more later. Next, we have the track` function. It notes down all the important data we need to keep an eye on. In our case, this data would be `state.message`. Lastly, we've got the trigger` function. This one is like our alarm bell. It alerts the `effect` function whenever our important data (the stuff `track` is keeping an eye on) changes. In this way, trigger`, `track`, and `effect` work together to keep our Vue application reacting smoothly to changes in data. Let’s go back to them: `javascript function reactive(obj) { return new Proxy(obj, { get(target, key) { // target = state & key = message track(target, key) // keep an eye for this return target[key] }, set(target, key, value) { target[key] = value trigger(target, key) // trigger the effects! } }) } ` Tracking (Dependency Collection) Tracking is the process of registering dependencies between reactive objects and the effects that depend on them. When a reactive property is read, it's "tracked" as a dependency of the current running effect. When we execute track()`, we essentially store our effects in a Set object. But what exactly is an "effect"? If we revisit our previous example, we see that the effect method must be run whenever any property changes. This action — running the effect method in response to property changes — is what we refer to as an "Effect"! (computed property, watcher, etc.) > Note: We'll outline a basic, high-level overview of what might happen under the hood. Please note that the actual implementation is more complex and optimized, but this should give you an idea of how it works. Let’s see how it works! In our example, we have the following reactive object: `javascript const state = reactive({ showSword: false, message: "Hey young padawn!", }); // which is transformed under the hood to: function reactive(obj) { return new Proxy(obj, { get(target, key) { // target = state | key = message track(target, key) // keep an eye for this return target[key] }, set(target, key, value) { target[key] = value trigger(target, key) // trigger the effects! } }) } ` We need a way to reference the reactive object with its effects. For that, we use a WeakMap. Which type is going to look something like this: `typescript WeakMap>> ` We are using a WeakMap to set our object state as the target (or key). In the Vue code, they call this object `targetMap`. Within this targetMap` object, our value is an object named `depMap` of Map type. Here, the keys represent our properties (in our case, that would be `message` and `showSword`), and the values correspond to their effects – remember, they are stored in a Set that in Vue 3 we refer to as `dep`. Huh… It might seem a bit complex, right? Let's make it more straightforward with a visual example: With the above explained, let’s see what this Track` method kind of looks like and how it uses this `targetMap`. This method essentially is doing something like this: `javascript let activeEffect; // we will see more of this later function track(target, key) { if (activeEffect) { // depsMap` maps targets to their keys and dependent effects let depsMap = targetMap.get(target); // If we don't have a depsMap for this target in our targetMap`, create one. if (!depsMap) { depsMap = new Map(); targetMap.set(target, depsMap); } let dep = depsMap.get(key); if (!dep) { // If we don't have a set of effects for this key in our depsMap`, create one. dep = new Set(); depsMap.set(key, dep); } // Add the current effect as a dependency dep.add(activeEffect); } } ` At this point, you have to be wondering, how does Vue 3 know what activeEffect` should run? Vue 3 keeps track of the currently running effect by using a global variable. When an effect is executed, Vue temporarily stores a reference to it in this global variable, allowing the track` function to access the currently running effect and associate it with the accessed reactive property. This global variable is called inside Vue as `activeEffect`. Vue 3 knows which effect is assigned to this global variable by wrapping the effects functions in a method that invokes the effect whenever a dependency changes. And yes, you guessed, that method is our effect` method. `javascript effect(() => { if (state.message === "It's dangerous to go alone! Take this.") { state.showSword = true; } }); ` This method behind the scenes is doing something similar to this: `javascript function effect(update) { //the function we are passing in const effectMethod = () => { // Assign the effect as our activeEffect` activeEffect = effectMethod // Runs the actual method, also triggering the get` trap inside our proxy update(); // Clean the activeEffect after our Effect has finished activeEffect = null } effectMethod() } ` The handling of activeEffect` within Vue's reactivity system is a dance of careful timing, scoping, and context preservation. Let’s go step by step on how this is working all together. When we run our `Effect` method for the first time, we call the `get` trap of the Proxy. `javascript function effect(update) const effectMethod = () => { // Storing our active effect activeEffect = effectMethod // Running the effect update() ... } ... } effect(() => { // we call the the get` trap when getting our `state.message` if (state.message === "It's dangerous to go alone! Take this.") { state.showSword = true; } }); ` When running the get` trap, we have our `activeEffect` so we can store it as a dependency. `javascript function reactive(obj) { return new Proxy(obj, { // Gets called when our effect runs get(target, key) { track(target, key) // Saves the effect return target[key] }, // ... (other handlers) }) } function track(target, key) { if (activeEffect) { //... rest of the code // Add the current effect as a dependency dep.add(activeEffect); } } ` This coordination ensures that when a reactive property is accessed within an effect, the track function knows which effect is responsible for that access. Trigger Method Our last method makes this Reactive system to be complete. The trigger` method looks up the dependencies for the given target and key and re-runs all dependent effects. `javascript function trigger(target, key) { const depsMap = targetMap.get(target); if (!depsMap) return; // no dependencies, no effects, no need to do anything const dep = depsMap.get(key); if (!dep) return; // no dependencies for this key, no need to do anything // all dependent effects to be re-run dep.forEach(effect => { effect() }); } ` Conclusion Diving into Vue 3's reactivity system has been like unlocking a hidden superpower in my web development toolkit, and honestly, I've had a blast learning about it. From the rudimentary elements of reactive data and instantaneous UI updates to the intricate details involving Proxies, track and trigger functions, and effects, Vue 3's reactivity is an impressively robust framework for building dynamic and responsive applications. In our journey through Vue 3's reactivity, we've uncovered how this framework ensures real-time and precise updates to the UI. We've delved into the use of Proxies to intercept and monitor variable changes and dissected the roles of track and trigger functions, along with the 'effect' method, in facilitating seamless UI updates. Along the way, we've also discovered how Vue ingeniously manages data dependencies through sophisticated data structures like WeakMaps and Sets, offering us a glimpse into its efficient approach to change detection and UI rendering. Whether you're just starting with Vue 3 or an experienced developer looking to level up, understanding this reactivity system is a game-changer. It doesn't just streamline the development process; it enables you to create more interactive, scalable, and maintainable applications. I love Vue 3, and mastering its reactivity system has been enlightening and fun. Thanks for reading, and as always, happy coding!...

Improving the Performance of Vue 3 Applications Using v-memo and KeepAlive cover image

Improving the Performance of Vue 3 Applications Using v-memo and KeepAlive

Introduction When building a Vue application, you will encounter performance issues as your app grows, causing it to run slower than it should. Most performance problems in web apps arise from performing tasks repeatedly, even when there is no need to. For example, if you had a list of thousands of items, and had to re-render all items when a single thing changed, you'd quickly encounter performance problems. The ideal solution would be to update only what changes. For the most part, VueJS does this well, and Vue 3 came with many performance improvements out of the box. However, in more complex applications, we may need more fine-grained control over what gets re-rendered and what does not. In this article, we are going to look at how we can use v-memo`, and KeepAlive as solutions to performance problems in Vue applications. Two leading solutions to performance problems in Vue JS Memoization Memoization is like a superpower that helps your app remember things without having to constantly redo the same calculations, or processes over and over again. This allows the applications to perform faster and more efficiently, which is the ultimate goal for any web developer. Think about it; when you were a kid, your teachers always taught you to memorize things to save time. The same is valid for web applications. With memoization, they can remember the results of a calculation or process, so they don't have to waste time recalculating it every time they need the same result. For example, imagine you have a website that requires users to enter their birthdates to access certain content. If a user enters their birthdate for the first time, the application will calculate their age. With memoization, the next time the user comes to the website and enters their birthdate, the application can use the previously computed result instead of recalculating it from scratch. That's the beauty of memoization! It saves time and resources, and gives the user a faster and smoother experience. Try memoization if you want your web application to perform at its best. You'll be amazed at how much of a difference it makes! To take advantage of this in Vue, we'll be using the v-memo directive. More on this shortly. Caching Caching works by storing a copy of frequently used data and resources so that the application can access them quickly without having to go through the entire process of rendering and fetching the data from scratch. Think of it like this. When you visit a website for the first time, the browser has to fetch all the resources needed to display the page. But if you see the same website again, the browser can use the cached data, resulting in a faster and smoother experience for the user. We can take advantage of caching in our Vue applications using KeepAlive. Using v-memo V-memo was added in Vue 3.2 and, as far as I am aware, will not be back-ported to Vue 2. You can use v-memo by passing the directive to the element/component whose dependencies you want to memoize. In this case, this would be an element/component that would be computationally expensive to re-render each time a dependency changed. Here's an example of how you could use the v-memo directive: `html ` Note that it accepts an array of dependency values. And if every value in the array is the same as the last render, updates for the entire sub-tree will be skipped. The dependency values are those that's changes you'd like to be checked and memoized anytime they change. You can learn more about how v-memo works from the official Vue JS documentation. In this case, if the value of dependencyA or dependencyB changed, we would re-render the children of the div. However, if neither dependencyA nor dependencyB changed, we would skip the re-rendering process. This means that any computationally intensive tasks that were needed to occur in the re-render would not be triggered, and as a result, our application would perform better. Using KeepAlive Another alternative solution to performance issues is caching, where the KeepAlive option comes in. KeepAlive is a built-in vue component that allows us to cache component instances when dynamically switching between multiple components conditionally. Using KeepAlive in Vue 3 is straightforward. You can wrap the components you want to cache inside a KeepAlive component. Here's a sample code: `html import { ref, onMounted } from 'vue' import ComponentA from './ComponentA.vue' import ComponentB from './ComponentB.vue' const currentComponent = ref(ComponentA) onMounted(() => { setInterval(() => { currentComponent.value = currentComponent.value === ComponentA ? ComponentB : ComponentA }, 1000) }) ` In this example, we use the KeepAlive component to conditionally cache the ComponentA and ComponentB` instances, which are dynamically switched using the currentComponent reactive property. With the KeepAlive component, we can maintain the state of these components even when they are not active, which can lead to improved performance. Conclusion In this article, we talked about how we can improve the performance of our VueJS applications using memoization and caching. In the case of memoization, we can use the v-memo VueJS directive that was introduced in Vue 3.2. Alternatively, when we want to cache component instances, we can use the built-in Vue KeepAlive component. By utilizing both v-memo and KeepAlive, you can optimize the performance of your Vue 3 applications, resulting in faster and smoother user experiences. That being said, if you are looking to start a new Vue JS project and need help with how to structure your project, feel free to check out our Vue JS starter.dev GitHub showcases, which showcase (no kidding) a mini-GitHub clone application built using Vue in different ways (e.g., using Nuxt, Quasar], Vite, etc.). Alternatively, if you are looking to start a new project without worrying about all the config required, you can check out the [Vue JS starter kit instead. Thanks for checking this out!...

Styling Vue Single-File Components cover image

Styling Vue Single-File Components

Introduction If you have any experience with writing Vue single-file components, you have probably spent some time writing CSS within your component. Single-File Components allow developers to group code together in more logical ways, rather than breaking up components by language utilized (HTML, CSS, or JavaScript). Being able to group component styles directly next to the HTML that it applies to is one of the major benefits of Vue, including the ability to scope CSS to the component so that it doesn't affect other parts of the UI. However, there are a number of features to Vue's CSS integration that you may not be familar with, such as applying styles directly to slotted elements, or the newest features available in Vue 3.2. Let's explore some of these other ways of styling Vue single-file components, and how they can benefit your applications. Scoped Styles Let's start with the most common usage of CSS in Vue: scoped styles. One of the difficulties on writing modern applications is that our CSS files begin to grow larger and larger, until nobody really knows where certain styles are used or what a given change might affect. This can lead to copying certain CSS selectors, and simply duplicating them for each component. There are other solutions for this (such as BEM or utility classes), but when working with a component-based framework like Vue, it makes a lot of sense to group CSS classes within the component. Scoped styles allows us to write CSS that only applies to the component we are working in. Here's an example from the Vue docs: `html .example { color: red; } hi ` With this, the example` class will only ever apply within this component. This is achieved by added a unique data attribute to all elements within the component, so the normal CSS cascade is still applied. External styles can still impact the design of this component, but its scoped styles cannot leak out to other components. Deep Styles This leads to an interesting problem. If our component's styles are scoped, what about children components? By default, they would not receive any styling from our scoped styles. However, Vue provides a way to do that. Let's look at an example below. `html Card Title Lorum ipsum dolor sit amet header :deep(.card-title) { font-weight: bold; } section { padding 2rem; } Title ` By using the :deep()` pseudo class, we are able to tell Vue that this particular class (`.card-title`) should not be scoped. Because the special ID is still applied to the root element (`header`), the style is still scoped, but it is available for any child component beneath it. Slotted Styles A problem I've run into in many situations is that I have a component being injected with slots, but I cannot control the styling of it like I want. Vue provides a solution for this as well with slotted styles. Let's review the above example, but this time we'll add a slotted style to our Title.vue component. `html Card Title Lorum ipsum dolor sit amet header :deep(.card-title) { font-weight: bold; } section { padding 2rem; } Title :slotted(h1) { font-size: 3rem; } ` Here, we added the :slotted` pseudo class, so that any slotted `h1` tags have the correct style applied to them. This may be a contrived example, but consider needing to have different header styles for each header tag (or equivalent CSS class). The Title.vue component can manage all of these styles, rather than relying on the consumer of these components to pass in the correct class or styling. Global Styles Of course, sometimes you need to apply styles globally, even within a scoped component. Vue provides us with two different ways to handle this: the :global` pseudo selector and multiple style blocks. `:global` Within a scoped style block, if you only need to provide one class as a global value, you can use the :global` pseudo selector to note that the style should not be scoped. From the Vue docs: `html :global(.red) { color: red; } ` Multiple style blocks There is also nothing stopping you from having multiple style blocks within your Vue component. Simply create another ` tag, and put your global styles in there. `html / global styles */ / local styles */ ` Style Modules If you're coming from React, you may be more familiar with CSS modules, where you import a CSS file and access its classes as a JavaScript object. The same can be done within Vue by using ` instead of ``. Here's an example from the Vue docs: `html This should be red .red { color: red; } ` This can be particularly nice to work with, so that you aren't passing strings around in your classes (which are prone to errors and typos). Vue also allows you to rename what the object is, so that you don't need to access them with $style` in your template if you don't want to. Dynamic CSS Values The latest feature in Vue is state-driven dynamic CSS values. There is a trend in modern CSS to use custom properties as a way to dynamically update the value of some CSS property. This can allow our CSS to be more flexible, and interact nicely with our other application code. Let's look at an example component that renders a progress bar: `html Progress {{ progress }}% import { watch } from 'vue' const props = defineProps({ progress: { type: Number, required: true } }) watch(props.progress, (value) => document .documentElement .style .setProperty('--complete-percentage', value + '%'), { immediate: true }) .progress-bar { background-color: #ccc; border-radius: 13px; padding: 3px; } .progress-bar > div { background-color: #000; width: var(--complete-percentage); height: 8px; border-radius: 10px; transition-property: width; transition-duration: 150ms; } ` This component takes in a number (progress`), then both displays that number and updates a CSS custom property with the value. As the progress changes, the CSS property is continually updated to stay in sync with the JavaScript value. In Vue 3.2, however, we are provided with a special CSS function that does this whole thing for us! Take a look at the updated code: `html Progress {{ progress }}% const props = defineProps({ progress: { type: Number, required: true } }) .progress-bar { background-color: #ccc; border-radius: 13px; padding: 3px; } .progress-bar > div { background-color: #000; width: v-bind(props.progress); height: 8px; border-radius: 10px; transition-property: width; transition-duration: 150ms; } ` By using v-bind(props.progress)`, we have elimited the need for our custom watcher, and it's clear that our CSS is being kept in sync with the value of `props.progress`. Under the hood, Vue is doing the same thing for us with a custom property, but it's so much nicer than having to write it ourselves. Conclusion CSS is a complicated language in practice, and mixing it with JavaScript makes things even more complex. Vue provides developers with the tools to handle CSS in a reliable and predictable way, which encourages building in a component-based architecture. Next time you're running into trouble with CSS in Vue, see if one of these techniques can be useful to you!...

Testing a Fastify app with the NodeJS test runner cover image

Testing a Fastify app with the NodeJS test runner

Introduction Node.js has shipped a built-in test runner for a couple of major versions. Since its release I haven’t heard much about it so I decided to try it out on a simple Fastify API server application that I was working on. It turns out, it’s pretty good! It’s also really nice to start testing a node application without dealing with the hassle of installing some additional dependencies and managing more configurations. Since it’s got my stamp of approval, why not write a post about it? In this post, we will hit the highlights of the testing API and write some basic but real-life tests for an API server. This server will be built with Fastify, a plugin-centric API framework. They have some good documentation on testing that should make this pretty easy. We’ll also add a SQL driver for the plugin we will test. Setup Let's set up our simple API server by creating a new project, adding our dependencies, and creating some files. Ensure you’re running node v20 or greater (Test runner is a stable API as of the 20 major releases) Overview `index.js` - node entry that initializes our Fastify app and listens for incoming http requests on port 3001 `app.js` - this file exports a function that creates and returns our Fastify application instance `sql-plugin.js` - a Fastify plugin that sets up and connects to a SQL driver and makes it available on our app instance Application Code A simple first test For our first test we will just test our servers index route. If you recall from the app.js` code above, our index route returns a 501 response for “not implemented”. In this test, we're using the createApp` function to create a new instance of our Fastify app, and then using the `inject` method from the Fastify API to make a request to the `/` route. We import our test utilities directly from the node. Notice we can pass async functions to our test to use async/await. Node’s assert API has been around for a long time, this is what we are using to make our test assertions. To run this test, we can use the following command: By default the Node.js test runner uses the TAP reporter. You can configure it using other reporters or even create your own custom reporters for it to use. Testing our SQL plugin Next, let's take a look at how to test our Fastify Postgres plugin. This one is a bit more involved and gives us an opportunity to use more of the test runner features. In this example, we are using a feature called Subtests. This simply means when nested tests inside of a top-level test. In our top-level test call, we get a test parameter t` that we call methods on in our nested test structure. In this example, we use `t.beforeEach` to create a new Fastify app instance for each test, and call the `test` method to register our nested tests. Along with `beforeEach` the other methods you might expect are also available: `afterEach`, `before`, `after`. Since we don’t want to connect to our Postgres database in our tests, we are using the available Mocking API to mock out the client. This was the API that I was most excited to see included in the Node Test Runner. After the basics, you almost always need to mock some functions, methods, or libraries in your tests. After trying this feature, it works easily and as expected, I was confident that I could get pretty far testing with the new Node.js core API’s. Since my plugin only uses the end method of the Postgres driver, it’s the only method I provide a mock function for. Our second test confirms that it gets called when our Fastify server is shutting down. Additional features A lot of other features that are common in other popular testing frameworks are also available. Test styles and methods Along with our basic test` based tests we used for our Fastify plugins - `test` also includes `skip`, `todo`, and `only` methods. They are for what you would expect based on the names, skipping or only running certain tests, and work-in-progress tests. If you prefer, you also have the option of using the describe` → `it` test syntax. They both come with the same methods as `test` and I think it really comes down to a matter of personal preference. Test coverage This might be the deal breaker for some since this feature is still experimental. As popular as test coverage reporting is, I expect this API to be finalized and become stable in an upcoming version. Since this isn’t something that’s being shipped for the end user though, I say go for it. What’s the worst that could happen really? Other CLI flags —watch` - https://nodejs.org/dist/latest-v20.x/docs/api/cli.html#--watch —test-name-pattern` - https://nodejs.org/dist/latest-v20.x/docs/api/cli.html#--test-name-pattern TypeScript support You can use a loader like you would for a regular node application to execute TypeScript files. Some popular examples are tsx` and `ts-node`. In practice, I found that this currently doesn’t work well since the test runner only looks for JS file types. After digging in I found that they added support to locate your test files via a glob string but it won’t be available until the next major version release. Conclusion The built-in test runner is a lot more comprehensive than I expected it to be. I was able to easily write some real-world tests for my application. If you don’t mind some of the features like coverage reporting being experimental, you can get pretty far without installing any additional dependencies. The biggest deal breaker on many projects at this point, in my opinion, is the lack of straightforward TypeScript support. This is the test command that I ended up with in my application: I’ll be honest, I stole this from a GitHub issue thread and I don’t know exactly how it works (but it does). If TypeScript is a requirement, maybe stick with Jest or Vitest for now 🙂...