Skip to content

Provide/Inject API With Vue 3

This article was written over 18 months ago and may contain information that is out of date. Some content may be relevant but please refer to the relevant official documentation or available resources for the latest information.

Introduction

One of the most difficult problems to solve when building single-page applications is state management. With component-based frameworks like Vue, this is typically solved in one of two ways:

  1. State is managed by components, with data being passed to child components as props. The parent component then listens for events and performs actions on the state accordingly.
  2. State is managed with a global state management library (Vuex, Redux, etc). Global state is then injected into the desired components, and those components triggers actions in the state management library (such as API requests or data updates). This can provide a layer of separation between logic and templating which is useful, and helps with passing data between parts of the application.

Vue offers an interesting middleground between these two approaches with the Provide/Inject API. This API is similar to React's Context API, in that it allows a component to provide some sort of data to any component beneath it in the component tree. For example, a parent component could provide a piece of data (say, the user's username), and then a grandchild component could inject that value into itself.

Provide/Inject gives developers a way to share data between parent and child components while avoiding prop drilling (passing a prop from one component to the next in a chain). This can make your code more readable, and reduce the complexity of your props for any components that don't rely on this data.

In this article, we will explore a basic example using the Provide/Inject API, building up a new application with a user dashboard to update their name and email.

Getting Started - Using Props

We'll first set up our example application using the standard approach to passing data between components - props. Below is our homepage and a navigation page.

<!-- App.vue -->
<template>
  <div class="w-2/3 pt-6 m-auto">
    <Nav :name="state.name" />
  </div>
</template>

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

export default defineComponent({
  setup() {
    const state = reactive({
      name: "Bob Day",
      email: "bob@martianmovers.com",
    });

    return { state };
  },
  components: {
    Nav,
  },
});
</script>
<!-- Nav.vue -->
<template>
  <nav class="flex">
    <div class="flex-grow">
      <a class="px-4 hover:underline" href="#">Home</a>
      <a class="px-4 hover:underline" href="#">About</a>
      <a class="px-4 hover:underline" href="#">My Account</a>
    </div>
    <div class="flex-shrink">
      Hello, {{ name }}!
    </div>
  </nav>
</template>

<script lang="ts">
import { defineComponent } from 'vue'

export default defineComponent({
  props: {
    name: {
      type: String,
      default: "User"
    }
  },
  setup(props) {
    return {
      ...props
    }
  },
})
</script>

This is a very straightforward parent/child structure. Our parent component has a reactive object (state) with a user's name and email. The username is passed into the Nav component as a prop, and then displayed. This works well because we only have two components, but what if there were other layout components between the root and the navigation component?

We can use Provide/Inject to send this data from the parent to the navigation component. In our parent component, we will provide the data we want available (the username), and then inject that data into the Nav component.

Provide/Inject with Composition API

Let's start with App.vue, and explore how to use provide. Below is our rewritten root component:

<!-- App.vue -->
<template>
  <div class="w-2/3 pt-6 m-auto">
    <Nav />
  </div>
</template>

<script lang="ts">
import { computed, defineComponent, provide, reactive } from "vue";
import Nav from "./components/Nav.vue";

export default defineComponent({
  setup() {
    const state = reactive({
      name: "Bob Day",
      email: "bob@martianmovers.com",
    });

    provide('username', computed(() => state.name));

    return { state };
  },
  components: {
    Nav,
  },
});
</script>

With Vue 3, we have access to a Composition API method provide. This function takes a key and a value. The key is how the provided value will be accessed in other components. In this example. we are passing a computed property with the user's username as the value.

Why are we passing a computed property? By default, the provided value is not reactive. If we just wrote provide('username', state.name), any component that injected this value would only have the initial state of the username. If it were to change in the future, the name would be out of sync with the root component. If we wanted to provide the entire state, we could write this instead:

provide('state', state);

That's because we're using a reactive object for our state. Alternatively, if the username was a ref, we could also use that in the same way.

Keep in mind that any value could be passed as an argument to provide, including functions. This will come up later in our example, but it's important to think about when you're wanting to use this API.

Let's look our our navigation component now, using inject to get the value of state.name.

<!-- Nav.vue -->
<template>
  <nav class="flex">
    <div class="flex-grow">
      <a class="px-4 hover:underline" href="#">Home</a>
      <a class="px-4 hover:underline" href="#">About</a>
      <a class="px-4 hover:underline" href="#">My Account</a>
    </div>
    <div class="flex-shrink">
      Hello, {{ name }}!
    </div>
  </nav>
</template>

<script lang="ts">
import { defineComponent, inject } from 'vue'
export default defineComponent({
  setup() {
    const name = inject('username');

    return {
      name
    }
  },
})
</script>

Similar to what we did in App.vue, we used a Composition API method called inject to get the value. inject takes the key we used when providing the data, and then returns the value as a variable. Since we provided a computed property, inject returns a computed property as well.

inject has two other arguments as well:

  1. defaultValue: This is the value that should be returned in the event a provided value is not found with that key.
  2. treatDefaultAsFactory: As I noted above, any value (including functions) can be provided as a value to inject. In the event that a function is what you are providing, you don't want it to be invoked by mistake. But what if you're providing an object? Unless it is returned from a function, you could end up with duplicate objects that have the same reference (this is also why data and props recommend returning objects from a function rather than setting them directly). When using a function as the default value, this argument tells inject whether the default is the function itself, or the value returned by the function.

The two below examples return the same default, a string:

// With a default value
const name = inject('usernafme', 'Bob Day');

// With a default factory
const name = inject('usernafme', () => 'Bob Day', true);

In our component example, the username is being injected and returned from setup, making it available in the template. We can then use that variable as we normally would, but without having to worry about props. Nice! This could save us a lot of time and effort with prop drilling.

Providing Reactivity

In the last section, we discussed reactivity and how the argument in provide needs to be a reactive object if we want data to stay in sync. Let's build out our user dashboard so they can update their name and email. In our App.vue, we're going to add a single line to our setup method:

provide('userDetails', state);

This will provide the entire state object (a reactive object) to whatever component wants to inject it. Now, let's build out a dashboard to work with that data:

<!-- MyProfile.vue -->
<template>
  <div class="flex flex-col">
    <h2 class="block m-auto text-2xl">My Profile</h2>
    <hr />
    <label class="py-1 flex">
      <span class="w-24">Username: </span>
      <input class="shadow p-1 bg-gray-100 w-64" v-model="userDetails.name" />
    </label>
    <label class="py-1 flex">
      <span class="w-24">Email:</span>
      <input
        class="shadow p-1 bg-gray-100 w-64"
        type="email"
        v-model="userDetails.email"
      />
    </label>
  </div>
</template>

<script lang="ts">
import { defineComponent, inject } from "vue";

export default defineComponent({
  setup() {
    const userDetails = inject("userDetails");

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

In this component, we inject the entire userDetails provided value, and return it to the template. We can then use v-model to bind directly to the injected values. With this in place, it all works as expected! Any changes made to the username field would properly update in the navigation as well.

However, there's a small catch to doing things this way. Per the Vue 3 documentation, "When using reactive provide / inject values, it is recommended to keep any mutations to reactive properties inside of the provider whenever possible." The reason for this is that allowing any child component to mutate a value could lead to confusion about where a particular mutation is happening. The more disciplined our codebase is about mutating state, the more stable and predictable it will be.

Rather than directly using v-model on our reactive state, let's provide a couple functions that will do the updating for us. First, we'll update App.vue to handle the new providers:

<!-- App.vue -->
<template>
  <div class="w-2/3 pt-6 m-auto">
    <Nav />
    <main class="py-6">
      <MyProfile />
    </main>
  </div>
</template>

<script lang="ts">
import { computed, defineComponent, provide, reactive } from "vue";
import Nav from "./components/Nav.vue";
import MyProfile from "./components/MyProfile.vue";

export default defineComponent({
  setup() {
    const state = reactive({
      name: "Bob Day",
      email: "bob@martianmovers.com",
    });

    const updateUsername = (name) => {
      state.name = name;
    };

    const updateEmail = (email) => {
      state.email = email;
    };

    provide(
      "username",
      computed(() => state.name)
    );
    provide("userDetails", state);
    provide("updateUsername", updateUsername);
    provide("updateEmail", updateEmail);

    return { state };
  },
  components: {
    Nav,
    MyProfile,
  },
});
</script>

We have added two functions - updateUsername and updateEmail. These functions are nearly identical, just updating the value on our state object that they are associated to. We then provide these two functions using the provide method, so that they are available to children components.

Remember above when we discussed that any value could be provided? This is why treatDefaultAsFactory is important. Here, we are providing two functions that don't return anything. If inject by default invoked the function and returns its value, we would get undefined is not a function errors in our child component. In this case, we're really wanting a function to be injected into our component, so defaulting treatDefaultAsFactory to false is excellent.

Here's the updated code for MyProfile.vue:

<!-- MyProfile.vue -->
<template>
  <div class="flex flex-col">
    <h2 class="block m-auto text-2xl">My Profile</h2>
    <hr />
    <label class="py-1 flex">
      <span class="w-24">Username: </span>
      <input class="shadow p-1 bg-gray-100 w-64" v-model="username" />
    </label>
    <label class="py-1 flex">
      <span class="w-24">Email:</span>
      <input class="shadow p-1 bg-gray-100 w-64" type="email" v-model="email" />
    </label>
  </div>
</template>

<script>
import { defineComponent, inject, computed } from "vue";

export default defineComponent({
  setup() {
    const userDetails = inject("userDetails");
    const updateUsername = inject("updateUsername");
    const updateEmail = inject("updateEmail");

    const username = computed({
      get: () => userDetails.name,
      set: updateUsername,
    });

    const email = computed({
      get: () => userDetails.email,
      set: updateEmail,
    });

    return {
      username,
      email,
    };
  },
});
</script>

Rather than binding directly on userDetails, we created two computed properties, each with a getter and setter. We can then bind to the computed properties, since they will return the desired value (username or email) and trigger the update methods we injected. Now our reactivity is fully controlled by the root component, App.vue, rather than the child.

Global State Management

I mentioned above that Provide/Inject gives us a middle ground between global state and component state. With the introduction of the Composition API, however, there's no reason we can't use Provide/Inject as our global management. Let's take everything we've written so far and extract it to a separate file:

import { computed, inject, provide, reactive } from "vue";

export const initStore = () => {
  // State
  const state = reactive({
    name: "Bob Day",
    email: "bob@martianmovers.com",
  });

  // Getters
  const getUsername = computed(() => state.name);
  const getEmail = computed(() => state.email);

  // Actions
  const updateUsername = (name) => {
    state.name = name;
  };
  const updateEmail = (email) => {
    state.email = email;
  };

  provide("getUsername", getUsername);
  provide("getEmail", getEmail);
  provide("updateUsername", updateUsername);
  provide("updateEmail", updateEmail);
};

export const useStore = () => ({
  getUsername: inject("getUsername"),
  getEmail: inject("getEmail"),
  updateUsername: inject("updateUsername"),
  updateEmail: inject("updateEmail"),
});

In this file, we have two functions - initStore and useStore. initStore creates our reactive object, getters for both the username and email, and methods to perform updates, then provides each of those values. These three groups (state, computed, and methods) maps very nicely to how Vuex works (state, getters, and actions).

The second method, useStore, simply returns an object with the injected values. This lets us use the store we've created from a single location, so if we change the key used in provide, we can also update it in the inject. This ensures we aren't duplicating our inject calls, and we only have one file to check if something goes wrong.

Our App.vue file is now a lot simpler:

import { defineComponent } from "vue";
import { initStore } from "./store/store";
import Nav from "./components/Nav.vue";
import MyProfile from "./components/MyProfile.vue";

export default defineComponent({
  setup() {
    initStore();
  },
  components: {
    Nav,
    MyProfile,
  },
});

Since we don't need the store values in our root component, we can safely call initStore to generate the store, and provide its values to our child components. Then, in MyProfile.vue, we can do the following:

import { defineComponent, computed } from "vue";
import { useStore } from "../store/store";

export default defineComponent({
  setup() {
    const store = useStore();

    const username = computed({
      get: () => store.getUsername.value,
      set: store.updateUsername,
    });

    const email = computed({
      get: () => store.getEmail.value,
      set: store.updateEmail,
    });

    return {
      username,
      email,
    };
  },
});

Because useStore injects the values for us, we have access to the username, password, and their update methods. This is one of the ways that the Composition API can help keep our code clean, and our logic bundled by feature rather than functionality.

If this concept interests you, there's a library for Vue 2 and 3 called Pinia that takes this approach to the next level. Pinia provides you a typesafe, easy to maintain global store. Check it out!

Conclusion

Using Provide/Inject can help remove some of the complexity of passing data between parent and child components. Keep in mind that provided values do not have the same checks as props, such as required or type, so they are inherently less safe to use. There is no guarantee that the value you want to inject is present in the component tree, nor do you know for certain what shape that value is in.

Also, up until recently, the Vue documentation included, "provide and inject are primarily provided for advanced plugin / component library use cases. It is NOT recommended to use them in generic application code." This has since been removed, and libraries like Pinia show the power of using this API in application code. I would still recommend being careful when choosing to implement a feature using Provide and Inject. That said, have fun and try it out!

Here's a link to a Stackblitz example of the final form of the appliation we worked through above.

Until next time!

This Dot is a consultancy dedicated to guiding companies through their modernization and digital transformation journeys. Specializing in replatforming, modernizing, and launching new initiatives, we stand out by taking true ownership of your engineering projects.

We love helping teams with projects that have missed their deadlines or helping keep your strategic digital initiatives on course. Check out our case studies and our clients that trust us with their engineering.

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: ` 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. ` 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: ` 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': ` 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. ` 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: ` 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: ` 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: ` 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: ` 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: ` 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. ` This method behind the scenes is doing something similar to this: ` 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. ` When running the get trap, we have our activeEffect so we can store it as a dependency. ` 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. ` 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!...

Understanding Vue.js's <Suspense> and Async Components cover image

Understanding Vue.js's <Suspense> and Async Components

In this blog post, we will delve into how and async components work, their benefits, and practical implementation strategies to make your Vue.js applications more efficient and user-friendly. Without further ado, let’s get started! Suspense Let's kick off by explaining what Suspense components are. They are a new component that helps manage how your application handles components that need to await for some async resource to resolve, like fetching data from a server, waiting for images to load, or any other task that might take some time to complete before they can be properly rendered. Imagine you're building a web page that needs to load data from a server, and you have 2 components that fetch the data you need as they will show different things. Typically, you might see a loading spinner or a skeleton while the data is being fetched. Suspense components make it easier to handle these scenarios. Instead of manually managing loading states and error messages for each component that needs to fetch data, Suspense components let you wrap all these components together. Inside this wrapper, you can define: 1. What to show while the data is loading (like a loading spinner). 2. The actual content that should be displayed once the data is successfully fetched. This way, Vue Suspense simplifies the process of handling asynchronous operations (like data fetching) and improves the user (and the developer) experience by providing a more seamless and integrated way to show loading states and handle errors. There are two types of async dependencies that can wait on: - Components with an async setup() hook. This includes components using with top-level await expressions. *Note: These can only be used within a component.* - Async Components. Async components Vue's asynchronous components are like a smart loading system for your web app. Imagine your app as a big puzzle. Normally, you'd put together all the pieces at once, which can take time. But what if some pieces aren't needed right away? Asynchronous components help with this. Here's how they work: - Load Only What's Needed: Just like only picking up puzzle pieces you need right now, asynchronous components let your app load only the parts that are immediately necessary. Other parts can be loaded later, as needed. - Faster Start: Your app starts up faster because it doesn't have to load everything at once. It's like quickly starting with the border of a puzzle and filling in the rest later. - Save Resources: It uses your web resources (like internet data) more wisely, only grabbing what’s essential when it's essential. In short, asynchronous components make your app quicker to start and more efficient, improving the overall experience for your users. Example: ` Combining Async Components and Suspense Let's explore how combining asynchronous components with Vue's Suspense feature can enhance your application. When asynchronous components are used with Vue's Suspense, they form a powerful combination. The key point is that async components are "suspensable" by default. This means they can be easily integrated with Suspense to improve how your app handles loading and rendering components. When used together, you can do the following things: - Centralized Loading and Error Handling: With Suspense, you don't have to handle loading and error states individually for each async component. Instead, you can define a single loading indicator or error message within the Suspense component. This unified approach simplifies your code and ensures consistency across different parts of your app. - Flexible and Clean Code Structure: By combining async components with Suspense, your code becomes more organized and easier to maintain. An asynchronous component has the flexibility to operate independently of Suspense's oversight. By setting suspensible: false in its options, the component takes charge of its own loading behavior. This means that instead of relying on Suspense to manage when it appears, the component itself dictates its loading state and presentation. This option is particularly useful for components that have specific loading logic or visuals they need to maintain, separate from the broader Suspense-driven loading strategy in the application. In practice, this combo allows you to create a user interface that feels responsive and cohesive. Users see a well-timed loading indicator while the necessary components are being fetched, and if something goes wrong, a single, well-crafted error message is displayed. It's like ensuring that the entire puzzle is either revealed in its completed form or not at all rather than showing disjointed parts at different times. How it works When a component inside the boundary is waiting for something asynchronous, shows fallback content. This fallback content can be anything you choose, such as a loading spinner or a message indicating that data is being loaded. Example Usage Let’s use a simple example: In the visual example provided, imagine we have two Vue components: one showcasing a selected Pokémon, Eevee, and a carousel showcasing a variety of other Pokémon. Both components are designed to fetch data asynchronously. Without , while the data is being fetched, we would typically see two separate loading indicators: one for the Eevee Pokemon that is selected and another for the carousel. This can make the page look disjointed and be a less-than-ideal user experience. We could display a single, cohesive loading indicator by wrapping both components inside a boundary. This unified loading state would persist until all the data for both components—the single Pokémon display and the carousel—has been fetched and is ready to be rendered. Here's how you might structure the code for such a scenario: ` Here, is the component that's performing asynchronous operations. While loading, the text 'Loading...' is displayed to the user. Great! But what about when things don't go as planned and an error occurs? Currently, Vue's doesn't directly handle errors within its boundary. However, there's a neat workaround. You can use the onErrorCaptured() hook in the parent component of to catch and manage errors. Here's how it works: ` If we run this code, and let’s say that we had an error selecting our Pokemon, this is how it is going to display to the user: The error message is specifically tied to the component where the issue occurred, ensuring that it's the only part of your application that shows an error notification. Meanwhile, the rest of your components will continue to operate and display as intended, maintaining the overall user experience without widespread disruption. This targeted error handling keeps the application's functionality intact while indicating where the problem lies. Conclusion stands out as a formidable feature in Vue.js, transforming the management of asynchronous operations into a more streamlined and user-centric process. It not only elevates the user experience by ensuring smoother interactions during data loading phases but also enhances code maintainability and application performance. I hope you found this blog post enlightening and that it adds value to your Vue.js projects. As always, happy coding and continue to explore the vast possibilities Vue.js offers to make your applications more efficient and engaging!...

Building Web Components with Vue 3.2 cover image

Building Web Components with Vue 3.2

Introduction Have you ever worked across multiple projects, and wanted a set of custom components you could just leverage across all of them? Whether for a job or just for side projects, having a suite of components you can reach for is an excellent way to get going faster in a new or existing project. But what if not all of your projects are using the same UI framework? Or, what if you have one that isn't using any JavaScript framework at all, and is completely server-rendered? As a Vue developer, ideally we would like to just use our framework of choice to build complex user interfaces. But sometimes we find ourselves in the above situation, working with another JavaScript framework such as React or Angular, or using a backend rendering system like Rails or Laravel. How can we build a reusable UI across these various frontend options? In Vue 3.2, we now have a solution to this problem: Web Components, powered by Vue! Web Components According to MDN, "Web Components is a suite of different technologies allowing you to create reusable custom elements — with their functionality encapsulated away from the rest of your code — and utilize them in your web apps." Consider a few existing elements in HTML, such as select or video. These interactive elements contain their own basic styling (typically provided by the browser), some internal logic, and a way to listen to events. Web Components allow developers to build their own elements, and reference them in their HTML - no framework required. Here's a very basic web component example of a component that would display the current time. ` Once a custom Web Component has been defined, they can be rendered as part of the DOM, just like any standard HTML element. We can use this element in our HTML like this: ` We can also use custom attributes with these elements, allowing us to pass data into them (similar to props in Vue). Note that objects cannot be passed in as attributes, because that is a JavaScript concept, not an HTML feature. ` While we could write this logic pretty easily in a script tag using vanilla JavaScript, utilizing Web Components gives us the ability to encapsulate specific logic and functionality within the component, thus keeping our code more organized and understandable. This is the same reason we utilize component frameworks like Vue and React. Also, as we discussed earlier, Web Components are flexible in that they can be used without a JS framework, but are also compatible with modern frameworks (React and Vue both other support for using Web Components). Vue-Powered Web Components Vue 3.2 includes built-in support for defining custom Web Components while utilzing the Vue API. In this way, we get the best of both worlds - custom, reusable components across frameworks/interfaces, plus the excellent API of Vue. Let's take our example of getting the current time, and translate that into a Vue component. We will be using , which is the recommended way to write Vue single-file components today. To start, let's create our new file, CurrentTime.ce.vue (ce in this case stands for custom element). ` Great, our component is doing exactly what we were doing before. Next, we need to import this into our main Javascript somewhere, and define it as a custom element. ` What did we do here? 1. First, we import Vue's defineCustomElement function, which converts a Vue component into a custom element. 2. We then import our Vue SFC, and pass it into defineCustomElement, generating the constructor required for the web components APIs. 3. Then, we define the custom element in the DOM, supplying it with the tag that we want to use (current-time) and the constructor it should use to render. With that, our Vue web component can now be rendered in our app! And since web components work in all modern frameworks (as well as non-JS frameworks like Ruby on Rails or Laravel), we can now build out a suite of web components for our application using Vue, and then utlize them in any frontend we want. Here's a basic example using vanilla JS, and the default Vite template: ` You can see a working example of this on Stackblitz. More Features Creating a basic Vue component isn't always what you want to do, though. What happens if we need to utilize props or events? Fortunately, Vue has us covered here as well. Let's explore some of the basic functions we'd expect from our custom Vue components. Props The first thing we want to do is pass in props to our web component. Using our component, we want to be able to set the time zone. For this case, we can use props as we normally would for an HTML element. In our HTML template, let's change our code to the following: ` If you save your file now, you will probably get an error in your console like this one: ` This is because our prop isn't defined in the component yet. Let's take care of that now. Go back to your Vue component, and make the following changes: ` In our Vue component, we are now defining props (using Vue 3.2's defineProps helper, which we do not need to import), then using the timeZone prop to translate the date into the correct time zone string. Nice! Save your files, and our app should work again as expected, but this time, it will display the date in a different time zone. Feel free to play around with it a bit, trying out some different time zones. By default, Vue will translate props into their defined types. Since HTML only allows strings to be passed in as attributes, Vue is handling the translation to different types for us. Events From the docs: "Events emitted via this.$emit or setup emit are dispatched as native CustomEvents on the custom element. Additional event arguments (payload) will be exposed as an array on the CustomEvent object as its details property." Let's add a basic emit from our component that will trigger a console.log. In our Vue component, update our script block to the following: ` The main change we're making here is to add defineEmits (also available without import, similar to defineProps) in order to define what events this component makes. We then begin to use this in our setInterval step, to emit the new date as an event. In our main.js, we'll now add the event listener to our web component. ` With this, whenever the first component emits a datechange event, we will be able to listen for it, and act accordingly. Nice! Slots Slots are used exactly as expected within Vue, including named slots. Scoped slots, as they are an advanced feature within a full Vue application, are not supported. Also, when utilizing named slots in your application, you will need to use the native slot syntax, rather than Vue's specific slot syntax. Let's give this a try now. In you Vue component, change your template to the following: ` For this example, we are using a standard slot (we'll get back to named slots later). Now, in your HTML, add some text in between the tags: ` If you save and reload your page, you should now see that your text (or whatever content you want) is correctly passing into your web component. Now, let's do the same thing using named slots. Change your Vue component to have a named slot (`, for example), and your HTML like this: ` The result should be the same! And now we can use named slots in our web components. Styles One of the great parts about web components is that they can utilize a shadow root, an encapsulated portion of the DOM that contains their own styling information. This feature is available to our Vue web components as well. When you name your file with the .ce.vue extension, it defaults to having inline styles. This is perfect if you want to use your components as a library in an application. Provide/Inject Provide and inject also work as expected within Vue web components. One thing to keep in mind, however, is that they only can pass data to other Vue web components. From the docs, "a Vue-defined custom element won't be able to inject properties provided by a non-custom-element Vue component." Conclusion Vue 3.2 provides the ability to write custom web components using Vue's familiar syntax, and the flexibility of the Composition API. Keep in mind, however, that this is not the recommended approach to writing Vue applications, or application development in general. The documentation goes to great lengths to explain the differences between web components and Vue-specific components, and why the Vue team feels their approach is preferable for web development. However, web components are still an amazing technology for building cross-framework applications. Plenty of tools exist in the web development ecosystem focused purely on web components, such as Lit or Ionic. While this may not be the recommended approach to building applications with Vue, it can provide an encapsulated way to get certain features developed and functional across teams or projects. Regardless of your stance on web components, I highly encourage you to check out this new feature of Vue and experiment with it yourself. You could even try mounting your component in React or Svelte, and see how easy it is to work with across JavaScript frameworks. Most important of all, remember that the development ecosystem is always improving and growing, and it's up to you to be ready to grow with it. StackBlitz Demo Play around with the StackBlitz demo below and here's an example of a Vue web component I am utilizing in a side project I'm working on. Have fun!...

Vercel BotID: The Invisible Bot Protection You Needed cover image

Vercel BotID: The Invisible Bot Protection You Needed

Nowadays, bots do not act like “bots”. They can execute JavaScript, solve CAPTCHAs, and navigate as real users. Traditional defenses often fail to meet expectations or frustrate genuine users. That’s why Vercel created BotID, an invisible CAPTCHA that has real-time protections against sophisticated bots that help you protect your critical endpoints. In this blog post, we will explore why you should care about this new tool, how to set it up, its use cases, and some key considerations to take into account. We will be using Next.js for our examples, but please note that this tool is not tied to this framework alone; the only requirement is that your app is deployed and running on Vercel. Why Should You Care? Think about these scenarios: - Checkout flows are overwhelmed by scalpers - Signup forms inundated with fake registrations - API endpoints draining resources with malicious requests They all impact you and your users in a negative way. For example, when bots flood your checkout page, real customers are unable to complete their purchases, resulting in your business losing money and damaging customer trust. Fake signups clutter the app, slowing things down and making user data unreliable. When someone deliberately overloads your app’s API, it can crash or become unusable, making users angry and creating a significant issue for you, the owner. BotID automatically detects and filters bots attempting to perform any of the above actions without interfering with real users. How does it work? A lightweight first-party script quickly gathers a high set of browser & environment signals (this takes ~30ms, really fast so no worry about performance issues), packages them into an opaque token, and sends that token with protected requests via the rewritten challenge/proxy path + header; Vercel’s edge scores it, attaches a verdict, and checkBotId() function simply reads that verdict so your code can allow or block. We will see how this is implemented in a second! But first, let’s get started. Getting Started in Minutes 1. Install the SDK: ` 1. Configure redirects Wrap your next.config.ts with BotID’s helper. This sets up the right rewrites so BotID can do its job (and not get blocked by ad blockers, extensions, etc.): ` 2. Integrate the client on public-facing pages (where BotID runs checks): Declare which routes are protected so BotID can attach special headers when a real user triggers those routes. We need to create instrumentation-client.ts (place it in the root of your application or inside a src folder) and initialize BotID once: ` instrumentation-client.ts runs before the app hydrates, so it’s a perfect place for a global setup! If we have an inferior Next.js version than 15.3, then we would need to use a different approach. We need to render the React component inside the pages or layouts you want to protect, specifying the protected routes: ` 3. Verify requests on your server or API: ` - NOTE: checkBotId() will fail if the route wasn’t listed on the client, because the client is what attaches the special headers that let the edge classify the request! You’re all set - your routes are now protected! In development, checkBotId() function will always return isBot = false so you can build without friction. To disable this, you can override the options for development: ` What happens on a failed check? In our example above, if the check failed, we return a 403, but it is mostly up to you what to do in this case; the most common approaches for this scenario are: - Hard block with a 403 for obviously automated traffic (just what we did in the example above) - Soft fail (generic error/“try again”) when you want to be cautious. - Step-up (require login, email verification, or other business logic). Remember, although rare, false positives can occur, so it’s up to you to determine how you want to balance your fail strategy between security, UX, telemetry, and attacker behavior. checkBotId() So far, we have seen how to use the property isBot from checkBotId(), but there are a few more properties that you can leverage from it. There are: isHuman (boolean): true when BotID classifies the request as a real human session (i.e., a clear “pass”). BotID is designed to return an unambiguous yes/no, so you can gate actions easily. isBot (boolean): We already saw this one. It will be true when the request is classified as automated traffic. isVerifiedBot (boolean): Here comes a less obvious property. Vercel maintains and continuously updates a comprehensive directory of known legitimate bots from across the internet. This directory is regularly updated to include new legitimate services as they emerge. This could be helpful for allowlists or custom logic per bot. We will see an example in a sec. verifiedBotName? (string): The name for the specific verified bot (e.g., “claude-user”). verifiedBotCategory? (string): The type of the verified bot (e.g., “webhook”, “advertising”, “ai_assistant”). bypassed (boolean): it is true if the request skipped BotID check due to a configured Firewall bypass (custom or system). You could use this flag to avoid taking bot-based actions when you’ve explicitly bypassed protection. Handling Verified Bots - NOTE: Handling verified bots is available in botid@1.5.0 and above. It might be the case that you don’t want to block some verified bots because they are not causing damage to you or your users, as it can sometimes be the case for AI-related bots that fetch your site to give information to a user. We can use the properties related to verified bots from checkBotId() to handle these scenarios: ` Choosing your BotID mode When leveraging BotID, you can choose between 2 modes: - Basic Mode: Instant session-based protection, available for all Vercel plans. - Deep Analysis Mode: Enhanced Kasada-powered detection, only available for Pro and Enterprise plan users. Using this mode, you will leverage a more advanced detection and will block the hardest to catch bots To specify the mode you want, you must do so in both the client and the server. This is important because if either of the two does not match, the verification will fail! ` Conclusion Stop chasing bots - let BotID handle them for you! Bots are and will get smarter and more sophisticated. BotID gives you a simple way to push back without slowing your customers down. It is simple to install, customize, and use. Stronger protection equals fewer headaches. Add BotID, ship with confidence, and let the bots trample into a wall without knowing what’s going on....

Let's innovate together!

We're ready to be your trusted technical partners in your digital innovation journey.

Whether it's modernization or custom software solutions, our team of experts can guide you through best practices and how to build scalable, performant software that lasts.

Prefer email? hi@thisdot.co