Skip to content

Computing Application State in 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

We've all been there before- working on an application, and suddently we need to determine what state something is in. Maybe it's whether the form has been submitted already, or the class a certain element should have. You may be tempted to set that value to a variable, and move on. What's the harm, right?

It turns out that there are a number of reasons this could be a problem. Your state could be out of date due to a change from a different function. You could cause an 'impossible state', or a state in the UI that was never intended by the developer. And at the very least, your code is more imperative, meaning that you as the developer are having to write and maintain more lines of code. It's almost like manually juggling all the values in your application - what happens if you drop one?

Luckily, Vue provides a solution for this - the computed property. With the computed property (or Composition API method), we can perform calculations like we described above by declaring them and getting a readonly, reactive ref to use in our application. Vue is able to determine when a dependency in the computed property has changed, and recalculate its result. We can then use these calculated values as if they were another variable, and use them in our template and logic with ease.

Setting up our example

Let's start with a common example: You have been tasked with building a form that accepts a name, email, and comments. We want to track the number of characters a user has entered, and allow them to submit the form. Below is an example of this form:

<template>
  <form @submit.prevent="onSubmitFormHandler">
    <label for="name">
      Name
      <input id="name" type="text" v-model="formState.name" />
    </label>
    <label for="email">
      Email
      <input id="email" type="email" v-model="formState.email" />
    </label>
    <label for="comment">
      Comments
      <textarea
        id="comment"
        v-model="formState.comment"
        @input="updateCharacterCount"
      />
      {{ characterCount }} character(s)
    </label>
    <button>Submit Feedback</button>
  </form>
</template>

<script>
import { reactive, defineComponent, ref } from "vue";
import axios from "axios";

export default defineComponent({
  setup() {
    const formState = reactive({
      name: "",
      email: "",
      comment: "",
    });

    const characterCount = ref(0);

    const updateCharacterCount = (e) => {
      characterCount.value = e.target.value.length
    };

    const onSubmitFormHandler = () => {
      axios
        .post("http://localhost:3000/api/comment", formState)
        .then((res) => console.log(res))
        .catch((err) => console.log(err));
    };

    return {
      formState,
      characterCount,
      updateCharacterCount,
      onSubmitFormHandler,
    };
  },
});
</script>

The above form provides the basic functionality that we need. But there are a few things missing:

  • We're just logging out messages when the API comes back. We should report the error or success state to the user.
  • We probably shouldn't allow users to submit the form without filling out the fields.
  • We also shouldn't allow users to submit the form while their submission is being processed. We don't want to receive duplicate form entries.

With this in mind, let's rewrite our code. The template is the same, but we'll need to track the form's state - whether it has been submitted or had an error, and whether the user can click the submit button. Sounds simple enough, so let's add some booleans - submitting, hasError, hasSuccess. That should handle the state.

We don't want the user to have to click a "Validate" button, of course - that would be frustrating. So let's use the watch Composition API method, and calculate whether to show the submit button.

Below is our updated code:

<template>
  <form @submit.prevent="onSubmitFormHandler">
    <label for="name">
      Name
      <input id="name" type="text" v-model="formState.name" />
    </label>
    <label for="email">
      Email
      <input id="email" type="email" v-model="formState.email" />
    </label>
    <label for="comment">
      Comments
      <textarea id="comment" v-model="formState.comment" @input="updateCharacterCount" />
      {{ characterCount }} character(s)
    </label>
    <button :disabled="submitting || !formReadyToSubmit">
      Submit Feedback
    </button>
  </form>
  <div>
    <template v-if="hasError">Something went wrong!</template>
    <template v-if="hasSuccess">Submitting successfully!</template>
  </div>
</template>

<script>
import { reactive, ref, watch, defineComponent } from "vue";
import axios from "axios";

export default defineComponent({
  setup() {
    const formState = reactive({
      name: "",
      email: "",
      comment: "",
    });

    const characterCount = ref(0);

    const submitting = ref(false);
    const hasError = ref(false);
    const hasSuccess = ref(false);

    const formReadyToSubmit = ref(false);

    const updateCharacterCount = (e) => {
      characterCount.value = e.target.value.length
    };

    const validateHasInput = () => {
      formReadyToSubmit.value =
        formState.name.length > 0 &&
        formState.email.length > 0 &&
        formState.comment.length > 0;
    };

    watch(
      () => ({ ...formState }),
      () => {
        validateHasInput();
      },
      {
        deep: true,
      }
    );

    const onSubmitFormHandler = () => {
      hasError.value = false;
      hasSuccess.value = false;
      submitting.value = true;

      axios
        .post("http://localhost:3000/api/comment", formState)
        .then((res) => {
          console.log(res);

          hasSuccess.value = true;
          submitting.value = false;
        })
        .catch((err) => {
          console.log(err);

          hasError.value = true;
          submitting.value = false;
        });
    };

    return {
      formState,
      onSubmitFormHandler,
      submitting,
      hasError,
      hasSuccess,
      formReadyToSubmit,
      characterCount,
      updateCharacterCount
    };
  },
});
</script>

The way this is written works, but there are a number of issues:

  • In the onSubmitFormHandler, we are manually resetting each status to where it should be.
  • In addition, the fact that we are using multiple booleans to track our form's state means that we could end up in an impossible state, where both "submitting" and "hasError" are true. That could lead to unexpected, and unpredictable, user experiences.
  • The watcher does the job of recalculating whenever the formState is changed, but we're still having the manually track this value.
  • We're still manually handling the input event on the comments field to get the character count.

Most importantly, it's going to be much harder to refactor this going forward, because it's harder to determine what is going on. Is there a relationship between "hasError" and "hasSuccess"? What is calling "validateHasInput"?

Implementing Computed Property

Let's upgrade our application using the computed Composition method now. First, we'll replace the boolean states with a single state, and use computed properties to determine what state we are in. We can also use a computed property to get the character count, and remove that extra event on the comment textarea. Finally, we'll move the validateHasInput into its own computed property.

Here's the updated form:

<template>
  <form @submit.prevent="onSubmitFormHandler">
    <label for="name">
      Name
      <input id="name" type="text" v-model="formState.name" />
    </label>
    <label for="email">
      Email
      <input id="email" type="email" v-model="formState.email" />
    </label>
    <label for="comment">
      Comments
      <textarea id="comment" v-model="formState.comment" />
      {{ characterCount }} character(s)
    </label>
    <button :disabled="!formReadyToSubmit">Submit Feedback</button>
  </form>
  <div>
    <template v-if="hasError">Something went wrong!</template>
    <template v-if="hasSuccess">Submitting successfully!</template>
  </div>
</template>

<script>
import { reactive, ref, computed, defineComponent } from "vue";
import axios from "axios";

const Status = {
  IDLE: "IDLE",
  SUBMITTING: "SUBMITTING",
  SUCCESS: "SUCCESS",
  ERROR: "ERROR",
};

export default defineComponent({
  setup() {
    const formState = reactive({
      name: "",
      email: "",
      comment: "",
    });
    const characterCount = computed(() => formState.comment.length);

    const status = ref(Status.IDLE);

    const submitting = computed(() => status === Status.SUBMITTING);
    const hasError = computed(() => status === Status.ERROR);
    const hasSuccess = computed(() => status === Status.SUCCESS);

    const formReadyToSubmit = computed(
      () =>
        !submitting.value &&
        formState.name.length > 0 &&
        formState.email.length > 0 &&
        formState.comment.length > 0
    );

    const onSubmitFormHandler = () => {
      status.value = Status.SUBMITTING;

      axios
        .post("http://localhost:3000/api/comment", formState)
        .then((res) => {
          console.log(res);

          status.value = Status.SUCCESS;
        })
        .catch((err) => {
          console.log(err);

          status.value = Status.ERROR;
        });
    };

    return {
      formState,
      onSubmitFormHandler,
      submitting,
      hasError,
      hasSuccess,
      formReadyToSubmit,
      characterCount
    };
  },
});
</script>

We have now refactored to use the computed property. What does that give us?

  • The boolean values (submitting, hasError, hasSuccess) are no longer being set imperatively. They are being calculated based off of a status variable. If we were using something like Typescript, we could force the type of status to be a certain subset, but for now, we are using an object with a few states - including IDLE.
  • The character count is now being calculated off of the length of the comment string, rather than checking the length from the emitted event. This means that if we change the value of formState.comment programmatically, our character count is up to date without us having to change anything.
  • In the onSubmitFormHandler, we set which status our form is in as we go. We aren't having to set each status individually, which means we don't have the possibility of impossible states.

Using Getters in Vuex

Our application is in a much better state now, but there is still room to improve. One way we can better encapsulate our logic is by utilizing Vuex for managing the state of the form submission. Luckily, we can implement our computed logic in Vuex pretty easily with getters. In Vuex, getters fill the same role as computed properties in single-file components.

Let's move our API logic out of the component, and into Vuex:

import { createStore } from "vuex";
import axios from 'axios';

export const Status = {
  IDLE: "IDLE",
  SUBMITTING: "SUBMITTING",
  SUCCESS: "SUCCESS",
  ERROR: "ERROR",
};

export default createStore({
  state: {
    status: Status.IDLE
  },
  getters: {
    getStatus: state => state.status,
    submitting: state => state.status === Status.SUBMITTING,
    hasError: state => state.status === Status.ERROR,
    hasSuccess: state => state.status === Status.SUCCESS
  },
  mutations: {
    'SET_STATUS': (state, status) => state.status = status
  },
  actions: {
    submitForm: ({ commit }, payload) => {
      commit('SET_STATUS', Status.SUBMITTING);

      axios
        .post("http://localhost:3000/api/comment", payload)
        .then((res) => {
          console.log(res);

          commit('SET_STATUS', Status.SUCCESS);
        })
        .catch((err) => {
          console.log(err);

          commit('SET_STATUS', Status.ERROR);
        });
    }
  }
});

In Vuex, we create a store, which is then plugged into our app. This store contains our state (the submission status), a single mutation to handle updating the state, and an action for submitting the form. We then have our four computed properties, which match the three we had previously as well as getting the raw state.

By using Vuex, the state of our form submission is disconnected from the template. This can be useful when building out larger applcations, so that your components remain focused on the user experience, and the logic is handled within your global state management. Here's what our updated component's logic looks like:

import { reactive, computed, defineComponent } from "vue";
import { useStore } from "vuex";

export default defineComponent({
  setup() {
    const store = useStore();
    const formState = reactive({
      name: "",
      email: "",
      comment: "",
    });
    const characterCount = computed(() => formState.comment.length);

    const submitting = computed(() => store.getters.submitting);
    const hasError = computed(() => store.getters.hasError);
    const hasSuccess = computed(() => store.getters.hasSuccess);

    const formReadyToSubmit = computed(
      () =>
        !submitting.value &&
        formState.name.length > 0 &&
        formState.email.length > 0 &&
        formState.comment.length > 0
    );

    const onSubmitFormHandler = () => {
      store.dispatch("submitForm", formState);
    };

    return {
      formState,
      onSubmitFormHandler,
      submitting,
      hasError,
      hasSuccess,
      formReadyToSubmit,
      characterCount,
    };
  },
});

In our component, we are now importing useStore in order to access our Vuex store. The component then makes an API call via a dispatch, which calls our action. Neat!

Using Get/Set with Computed Properties

One more nice feature of computed properties is how they interact with ES5 getters and setters. If you aren't aware, with ES5 you can add a function to an object that is either a get() or a set(val). This function is not invoked like a normal function, but instead, whenever the value is read or assigned to. For example:

console.log(this.name) // Getter

this.name = "Tim"; // Setter

With computed properties, we can leverage this system to build additional functionality. Let's say that you want to add a reset function to the form. One potential way to do that could be like this:

const currentStatus = computed({
  get: () => store.getters.getStatus,
  set: (val) => store.commit("SET_STATUS", val),
});

const resetFormHandler = () => {
  formState.name = "";
  formState.email = "";
  formState.comment = "";

  currentStatus.value = Status.IDLE;
};

With the Composition API, if you pass an object in as the first parameter (rather than a function), you can use the get and set keys to make your own custom getter and setter. This way, rather than making the function call directly to store.commit, we can simply set the currentStatus to the status we want.

This ability to set to computed properties is especially useful when using v-model on a custom component. Below is an example where this can help to model a custom input.

<template>
  <input v-model="inputValue">
</template>

<script>
export default {
  emits: [ 'update:modelValue' ],
  props: {
    modelValue: {
      type: String,
      default: ''
    }
  },
  computed: {
    inputValue: {
      get() {
        return this.modelValue;
      },
      set(val) {
        this.$emit('update:modelValue', val);
      }
    }
  }
}
</script>

By leveraging getters and setters in your computed properties, you can cut down on the amount of code you need to write in order to perform bindings between components and Vuex. You can also add additional logic, such as validation or data cleanup. Just make sure that you aren't overburdening your setters- if they start to get large, you might need to use a separate function anyway.

Conclusion

Computed properties can help ensure that your applications are easy to maintain and understandable. By leveraging a computed property, rather than manually assigning values, you allow the framework to do the heavy lifting for you. Computed properties are great for a number of complex tasks, such as:

  • Form status
  • API calls
  • Formulaic calculations (temperature conversion, weight conversion)
  • State-based CSS classes
  • Determining which state to render your component in.

Keep in mind - computed properties should not cause side effects. If you are writing a computed property that needs to alter your state, it should probably be a watcher.

Take a look at your own applications, and see where using a computed property could be beneficial. Have fun!

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

Nuxt DevTools v1.0: Redefining the Developer Experience Beyond Conventional Tools cover image

Nuxt DevTools v1.0: Redefining the Developer Experience Beyond Conventional Tools

In the ever-evolving world of web development, Nuxt.js has taken a monumental leap with the launch of Nuxt DevTools v1.0. More than just a set of tools, it's a game-changer—a faithful companion for developers. This groundbreaking release, available for all Nuxt projects and being defaulted from Nuxt v3.8 onwards, marks the beginning of a new era in developer tools. It's designed to simplify our development journey, offering unparalleled transparency, performance, and ease of use. Join me as we explore how Nuxt DevTools v1.0 is set to revolutionize our workflow, making development faster and more efficient than ever. What makes Nuxt DevTools so unique? Alright, let's start delving into the features that make this tool so amazing and unique. There are a lot, so buckle up! In-App DevTools The first thing that caught my attention is that breaking away from traditional browser extensions, Nuxt DevTools v1.0 is seamlessly integrated within your Nuxt app. This ensures universal compatibility across browsers and devices, offering a more stable and consistent development experience. This setup also means the tools are readily available in the app, making your work more efficient. It's a smart move from the usual browser extensions, making it a notable highlight. To use it you just need to press Shift + Option + D (macOS) or Shift + Alt + D (Windows): With simple keystrokes, the Nuxt DevTools v1.0 springs to life directly within your app, ready for action. This integration eliminates the need to toggle between windows or panels, keeping your workflow streamlined and focused. The tools are not only easily accessible but also intelligently designed to enhance your productivity. Pages, Components, and Componsables View The Pages, Components, and Composables View in Nuxt DevTools v1.0 are a clear roadmap for your app. They help you understand how your app is built by simply showing its structure. It's like having a map that makes sense of your app's layout, making the complex parts of your code easier to understand. This is really helpful for new developers learning about the app and experienced developers working on big projects. Pages View lists all your app's pages, making it easier to move around and see how your site is structured. What's impressive is the live update capability. As you explore the DevTools, you can see the changes happening in real-time, giving you instant feedback on your app's behavior. Components View is like a detailed map of all the parts (components) your app uses, showing you how they connect and depend on each other. This helps you keep everything organized, especially in big projects. You can inspect components, change layouts, see their references, and filter them. By showcasing all the auto-imported composables, Nuxt DevTools provides a clear overview of the composables in use, including their source files. This feature brings much-needed clarity to managing composables within large projects. You can also see short descriptions and documentation links in some of them. Together, these features give you a clear picture of your app's layout and workings, simplifying navigation and management. Modules and Static Assets Management This aspect of the DevTools revolutionizes module management. It displays all registered modules, documentation, and repository links, making it easy to discover and install new modules from the community! This makes managing and expanding your app's capabilities more straightforward than ever. On the other hand, handling static assets like images and videos becomes a breeze. The tool allows you to preview and integrate these assets effortlessly within the DevTools environment. These features significantly enhance the ease and efficiency of managing your app's dynamic and static elements. The Runtime Config and Payload Editor The Runtime Config and Payload Editor in Nuxt DevTools make working with your app's settings and data straightforward. The Runtime Config lets you play with different configuration settings in real time, like adjusting settings on the fly and seeing the effects immediately. This is great for fine-tuning your app without guesswork. The Payload Editor is all about managing the data your app handles, especially data passed from server to client. It's like having a direct view and control over the data your app uses and displays. This tool is handy for seeing how changes in data impact your app, making it easier to understand and debug data-related issues. Open Graph Preview The Open Graph Preview in Nuxt DevTools is a feature I find incredibly handy and a real time-saver. It lets you see how your app will appear when shared on social media platforms. This tool is crucial for SEO and social media presence, as it previews the Open Graph tags (like images and descriptions) used when your app is shared. No more deploying first to check if everything looks right – you can now tweak and get instant feedback within the DevTools. This feature not only streamlines the process of optimizing for social media but also ensures your app makes the best possible first impression online. Timeline The Timeline feature in Nuxt DevTools is another standout tool. It lets you track when and how each part of your app (like composables) is called. This is different from typical performance tools because it focuses on the high-level aspects of your app, like navigation events and composable calls, giving you a more practical view of your app's operation. It's particularly useful for understanding the sequence and impact of events and actions in your app, making it easier to spot issues and optimize performance. This timeline view brings a new level of clarity to monitoring your app's behavior in real-time. Production Build Analyzer The Production Build Analyzer feature in Nuxt DevTools v1.0 is like a health check for your app. It looks at your app's final build and shows you how to make it better and faster. Think of it as a doctor for your app, pointing out areas that need improvement and helping you optimize performance. API Playground The API Playground in Nuxt DevTools v1.0 is like a sandbox where you can play and experiment with your app's APIs. It's a space where you can easily test and try out different things without affecting your main app. This makes it a great tool for trying out new ideas or checking how changes might work. Some other cool features Another amazing aspect of Nuxt DevTools is the embedded full-featured VS Code. It's like having your favorite code editor inside the DevTools, with all its powerful features and extensions. It's incredibly convenient for making quick edits or tweaks to your code. Then there's the Component Inspector. Think of it as your code's detective tool. It lets you easily pinpoint and understand which parts of your code are behind specific elements on your page. This makes identifying and editing components a breeze. And remember customization! Nuxt DevTools lets you tweak its UI to suit your style. This means you can set up the tools just how you like them, making your development environment more comfortable and tailored to your preferences. Conclusion In summary, Nuxt DevTools v1.0 marks a revolutionary step in web development, offering a comprehensive suite of features that elevate the entire development process. Features like live updates, easy navigation, and a user-friendly interface enrich the development experience. Each tool within Nuxt DevTools v1.0 is thoughtfully designed to simplify and enhance how developers build and manage their applications. In essence, Nuxt DevTools v1.0 is more than just a toolkit; it's a transformative companion for developers seeking to build high-quality web applications more efficiently and effectively. It represents the future of web development tools, setting new standards in developer experience and productivity....

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!...

TC39 - How Changes are Made to JavaScript cover image

TC39 - How Changes are Made to JavaScript

Introduction The JavaScript ecosystem is constantly changing. As developers, we are very familiar with the ever-shifting landscape of frameworks, libraries, and tooling required to write our applications. In addition, there are other runtimes for Javascript beyond the browser, including Node, Deno, Cloudflare Workers, with more being released all the time. All of this - the tooling, the frameworks, the runtimes, even the language - are based on standards developed by a group of individuals and companies know as TC39. TC39 (Technical Committee 39) is a committee organized by Ecma International, a nonprofit standards organization for information and communication systems. In 1996, Netscape (the original creators of JavaScript) began meeting with Ecma to discuss standardizing the language. The first standard edition of JavaScript (called ECMAScript) was adopted in 1997, with further releases of the standard happening since then. The JavaScript we use today is an implementation of these standards, and each runtime of JavaScript works to implement them for use by developers. This standardization across runtimes was not always a guarantee, however. For a long time, the Node project tended to go its own way, implementing Node-specific APIs and methods of accomplishing development work. Many within Node originally felt that TC39 was forcing their standards on the Node project, despite Node havings its own needs and solutions. There are a number of examples where Node went one way, and the JavaScript standards went the other - Promises and imports are two good examples. However, the Node steering committee today is much more open to adopting standards, any many of its members participate in discussions with TC39 regarding new features and changes to JavaScript. This is in part because developers want the same language and APIs in both the browser and their Node environments, but also, because there are other runtimes to consider when developing JavaScript code. This standardization has brought about a number of changes to the language and the JS ecosystem, as more voices are coming together to work on new solution to existing problems. What does TC39 do? As I mentioned, TC39 is a committee focused on developing and ensuring the JavaScript standard. From their website, "Ecma International's TC39 is a group of JavaScript developers, implementers, academics, and more, collaborating with the community to maintain and evolve the definition of JavaScript." The committee takes proposals from the community, and determines which are going to be worked on to be implemented in the JavaScript standard. A number of major companies are directly involved with TC39, with members representing Microsoft, Google, Apple, Intel, Mozilla, eBay, and more. Some are connected to universities, while others participate as individuals. In addition to voting members, many people participate in discussions regarding the various proposals that have been submitted. While the committee itself only meets every two months, these discussions on the proposals and specifications are taking place publicly, and anyone can participate in the conversation. Proposals are hosted on GitHub, and so discussions are as simple as creating an issue or pull request. A TC39 Discourse page is another way for the JavaScript community to discuss any current proposals or new ideas that haven't been formalized yet. When the committee votes to approve a new standard, this change is then implemented in the runtime authors (such as Google's V8). But how does a new standard get added to JavaScript? The Stages of Proposal There are 5 stages to adding a new standard to JavaScript, starting at Stage 0. Each of these stages has different requirements for completion. There is no time limit on moving a proposal from one stage to the next, and no guarantee that a given proposal will be completed. TC39's website hosts a process document that explains in detail what a given stage means, and how a proposal advances to the next stage. Let's walk through the stages, and look at some of the proposals currently at each stage. Stage 0 The first stage for any proposal is stage 0. This stage is the first step in adding a feature to JavaScript. Anyone can make a proposal. You don't have to be a member of TC39. A detailed document outlines the process for submitting a new proposal into stage 0. The pain purpose of this stage is to start a conversation and begin formalizing the proposal in order for future work to be done with it. The first thing that needs to be done when a proposal is stage 0 is to find a champion. A champion is someone from TC39 who will take the lead on moving a proposal forward. In addition, work will need to go into the documentation for the proposal, such as an outline of the problem that is being addressed and a high-level API design. Once these requirements are met, the committee can vote to move the proposal to Stage 1. An interesting Stage 0 proposal is to add a deprecated global or directive to the language, so that it's easier to alert a developer when a given API has been deprecated. Example: ` Stage 1 The purpose of Stage 1 is to make the case for changing the JavaScript standard, describing the proposed solution, and any potential problems that it could cause or could be impacted by. The main goal of the committee for a Stage 1 proposal is to devote time to examining the problem, and ensuring the proposal resolves it. Typically, browser/runtimes won't make any changes to implement a Stage 1 proposal, because the API could still change pretty drastically. However, polyfills or demos may be created in order to get additional feedback on a given API. These features should not be considered production ready. Once the initial spec has been developed, the committee can vote to move the proposal to Stage 2. The pipeline operator is a great example of a Stage 1 proposal. Its goal is to add a pipeline operator (|>) to JavaScript, in order to pipe function returns or values from one function to the next. There has been some discussion around how it should pass arguments into the second function ` Another Stage 1 proposal is the compartments proposal, which helps resolve a number of issues regarding global scope of a JS file or application. Check it out! Stage 2 When a proposal reaches Stage 2, the committee is focused on writing a precise syntax using formal language. This still doesn't mean that a feature is going to make it to JavaScript, but some experimental implementations will start appearing. This process to create a defined syntax could take from months to a year, with some proposals sitting in Stage 2 for much longer than that. However, when a feature leaves Stage 2, it typically means that the proposal will eventually make it to the final spec. Changes may still happen, but typically only limited changes will happen once a proposal moves out of Stage 2. There are a number of interesting proposals in Stage 2 at the moment, including decorators and iterator helpers. Often, proposals may get stalled in Stage 2. Decorators are a good example of that. According to the TC39 proposals repository, Decorators haven't been presented since September 2020, and were originally discussed back in 2018. Sometimes, the problem being solved has multiple solutions, or there are multiple competing solutions that could be adopted. Other times, the problem turns out to be less urgent or important than previously thought. While it can be frustrating to have a proposal stall out, it's important to remember that any change to JavaScript is permanent - no standardized feature in JavaScript is removed from the spec. Better to move slowly than to end up with half-finished APIs that don't actually solve anything. Stage 3 Stage 3 is the final stage for changes to be made to the specification. Spec compliant implementations will start to roll out, typically behind feature flags, in order to get developers to start using the feature and provide feedback. Changes are still possible, but they are expected to be limited in nature. The new Temporal object is a Stage 3 proposal that's pretty exciting for the JS ecosystem. Temporal will act as an upgrade from the Date object and support additional feature such as time zones. A prototype polyfill can be found on NPM, although keep in mind that it doesn't create a global Temporal object like the finished spec would do. And again, remember that this is still a proposal, and should not be treated as production ready. Another great example of a Stage 3 proposal is Realms, which provides a way to create distinct global environments. Stage 4 When a proposal reaches Stage 4, it is considered complete and ready for implementation by the different runtime vendors. Browsers will start to ship the feature, and other runtimes like Node and Deno will also work to include it in upcoming versions. A features is ready for Stage 4 when it passes all the agreed upon tests, and there has been sufficient testing by developers to ensure that the API is sound. Once a feature is in Stage 4, its spec is not intended to be changed. This is to ensure that the web platform is stable into the future - it's important to not break the web with changes to JavaScript. Two good examples of recent Stage 4 proposals are nullish coalescing and Promise.any. These features have been released into major browsers, and are available to be used today in modern JavaScript applications. Conclusion It's pretty amazing that the JavaScript language is developed in the open like this, for all interested parties to add a voice to the discussion. Not every standard or programming language is developed like this. However, this level of openness can also be difficult, especially if a specific feature gets stalled or a proposed API ends up not being accepted. If you submit your own proposal to TC39, remember that you are trying to solve a specific problem, not simply create a feature in JavaScript. Your proposal may be adjusted or replaced as other voices are added to the discussion. Also, keep in mind that it could take a long time for a proposal to make it into the language, if ever (looking at you, decorators). Also, while I've highlighted mostly good things about this process, it's also possible for a single member to hold back a feature from advancing into the next stage. This can be frustrating, but as noted above, it's important for JavaScript to be developed methodically. Having multiple standards or multiple interpretations of those standards wouldn't benefit anyone, after all. At the end of the day, remember that TC39 is made up of indivduals who are invested in the JavaScript ecosystem, and want to work together with developers to improve the language. They have a lot of context and understanding for how features are implemented that developers may not have. Proposals that don't make it into the language may not make it for valid reasons. Does any of this interest you? Do you want to contribute to the discussion? You can find ways to participate on TC39's website, including links to their Github and Discourse....

The simplicity of deploying an MCP server on Vercel cover image

The simplicity of deploying an MCP server on Vercel

The current Model Context Protocol (MCP) spec is shifting developers toward lightweight, stateless servers that serve as tool providers for LLM agents. These MCP servers communicate over HTTP, with OAuth handled clientside. Vercel’s infrastructure makes it easy to iterate quickly and ship agentic AI tools without overhead. Example of Lightweight MCP Server Design At This Dot Labs, we built an MCP server that leverages the DocuSign Navigator API. The tools, like `get_agreements`, make a request to the DocuSign API to fetch data and then respond in an LLM-friendly way. ` Before the MCP can request anything, it needs to guide the client on how to kick off OAuth. This involves providing some MCP spec metadata API endpoints that include necessary information about where to obtain authorization tokens and what resources it can access. By understanding these details, the client can seamlessly initiate the OAuth process, ensuring secure and efficient data access. The Oauth flow begins when the user's LLM client makes a request without a valid auth token. In this case they’ll get a 401 response from our server with a WWW-Authenticate header, and then the client will leverage the metadata we exposed to discover the authorization server. Next, the OAuth flow kicks off directly with Docusign as directed by the metadata. Once the client has the token, it passes it in the Authorization header for tool requests to the API. ` This minimal set of API routes enables me to fetch Docusign Navigator data using natural language in my agent chat interface. Deployment Options I deployed this MCP server two different ways: as a Fastify backend and then by Vercel functions. Seeing how simple my Fastify MCP server was, and not really having a plan for deployment yet, I was eager to rewrite it for Vercel. The case for Vercel: * My own familiarity with Next.js API deployment * Fit for architecture * The extremely simple deployment process * Deploy previews (the eternal Vercel customer conversion feature, IMO) Previews of unfamiliar territory Did you know that the MCP spec doesn’t “just work” for use as ChatGPT tooling? Neither did I, and I had to experiment to prove out requirements that I was unfamiliar with. Part of moving fast for me was just deploying Vercel previews right out of the CLI so I could test my API as a Connector in ChatGPT. This was a great workflow for me, and invaluable for the team in code review. Stuff I’m Not Worried About Vercel’s mcp-handler package made setup effortless by abstracting away some of the complexity of implementing the MCP server. It gives you a drop-in way to define tools, setup https-streaming, and handle Oauth. By building on Vercel’s ecosystem, I can focus entirely on shipping my product without worrying about deployment, scaling, or server management. Everything just works. ` A Brief Case for MCP on Next.js Building an API without Next.js on Vercel is straightforward. Though, I’d be happy deploying this as a Next.js app, with the frontend features serving as the documentation, or the tools being a part of your website's agentic capabilities. Overall, this lowers the barrier to building any MCP you want for yourself, and I think that’s cool. Conclusion I'll avoid quoting Vercel documentation in this post. AI tooling is a critical component of this natural language UI, and we just want to ship. I declare Vercel is excellent for stateless MCP servers served over http....

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