<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~files/atom-premium.xsl"?>
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:feedpress="https://feed.press/xmlns" xmlns:media="http://search.yahoo.com/mrss/" xmlns:podcast="https://podcastindex.org/namespace/1.0">
  <feedpress:locale>en</feedpress:locale>
  <link rel="hub" href="https://feedpress.superfeedr.com/"/>
  <logo>https://static.feedpress.com/logo/telerik-blogs-web-vue-61850e6cb2eca.jpg</logo>
  <title type="text">Telerik Blogs | Web | Vue</title>
  <subtitle type="text">The official blog of Progress Telerik - expert articles and tutorials for developers.</subtitle>
  <id>uuid:33e6abd0-8e8f-4a11-bb6a-1f5a845368d8;id=185</id>
  <updated>2026-04-07T16:18:32Z</updated>
  <link rel="alternate" href="https://www.telerik.com/"/>
  <link rel="self" type="application/atom+xml" href="https://feeds.telerik.com/blogs/web-vue"/>
  <entry>
    <id>urn:uuid:65c6b712-ad6d-4054-9c95-70203ccef9b9</id>
    <title type="text">Vue Basics: A Comprehensive Guide to Vue 3 Directives</title>
    <summary type="text">New to Vue 3? This guide breaks down everything you need to know about directives, from dynamic bindings to conditional rendering and custom behaviors, with examples you can use right away.</summary>
    <published>2026-02-04T17:14:41Z</published>
    <updated>2026-04-07T16:18:32Z</updated>
    <author>
      <name>David Adeneye Abiodun </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/23057/17270008/vue-basics-comprehensive-guide-vue-3-directives"/>
    <content type="text"><![CDATA[<p><span class="featured">New to Vue 3? This guide breaks down everything you need to know about directives, from dynamic bindings to conditional rendering and custom behaviors, with examples you can use right away.</span></p><p>When first working with Vue, you&rsquo;ll quickly notice how efficiently you can build dynamic applications, thanks in large part to one of its core features: directives. These <code>v-</code> prefixed attributes drive Vue&rsquo;s way of syncing the DOM with your data, handling conditional rendering, binding and event listening seamlessly.</p><p>Directives define how Vue extends HTML with dynamic behaviors, forming the backbone of its reactivity. Understanding how directives observe and react to data changes is key to mastering Vue&rsquo;s reactive system and creating dynamic interfaces.</p><p>In this guide, we&rsquo;ll break down how Vue 3 directives work, explore the core built-in directives you&rsquo;ll use every day, learn how to create them and look at some best practices to keep your templates clean and efficient. Whether you&rsquo;re just starting out or brushing up on your Vue fundamentals, this post will give you a solid, practical understanding of how directives fit into Vue&rsquo;s reactive world.</p><h2 id="what-are-directives-in-vue-">What Are Directives in Vue ?</h2><p>At their core, directives are special HTML attributes that start with <code>v-</code> and apply reactive behavior to a DOM element.</p><p>Think of them as Vue&rsquo;s way of giving HTML a &ldquo;brain.&rdquo; Instead of writing manual DOM logic, you describe what you want to happen, and Vue handles the how.</p><p>Here&rsquo;s a simple example:</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">v-if</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>isVisible<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Hello Vue!<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>
</code></pre><p>When <code>isVisible</code> is <code>true</code>, Vue renders the paragraph. When it&rsquo;s <code>false</code>, Vue removes it from the DOM. No manual toggling, no query selectors, no manual updates. Just reactive behavior, declaratively defined.</p><h3 id="the-anatomy-of-a-directive">The Anatomy of a Directive</h3><p>A directive looks like this:</p><pre><code>v-directiveName:argument.modifier="expression"
</code></pre><p>Let&rsquo;s break that down:</p><ul><li>Directive name: E.g, <code>v-if</code>, <code>v-bind</code>, <code>v-model</code></li><li>Argument (optional): Targets something specific, like <code>v-bind:href</code></li><li>Modifier (optional): Tweaks behavior, like <code>.prevent</code> or <code>.once</code></li><li>Expression (optional): The data or logic it depends on</li></ul><p>Here&rsquo;s a quick example showing all parts in action:</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name"><span class="token namespace">v-bind:</span>href</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>url<span class="token punctuation">"</span></span> <span class="token attr-name">@click.prevent</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>trackClick<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Visit<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">&gt;</span></span>
</code></pre><p>Here:</p><ul><li><code>v-bind</code> binds the <code>href</code> attributes to your component&rsquo;s <code>url</code>.</li><li><code>@click.prevent</code> is shorthand for <code>v-on:click.prevent</code>, meaning &ldquo;listen for a click and prevent the default browser behavior.&rdquo;</li></ul><p>Vue directives combine clean syntax with smart reactivity, and once you get used to them, you&rsquo;ll rarely want to go back to manual DOM manipulation.</p><h2 id="built-in-directives-you’ll-use-every-day">Built-in Directives You&rsquo;ll Use Every Day</h2><p>Vue ships with a handful of built-in directives that cover almost every common DOM scenario. Let&rsquo;s go through the most important ones.</p><h3 id="v-bind-dynamic-attribute-binding">v-bind: Dynamic Attribute Binding</h3><p><code>v-bind</code> connects your templates to your component&rsquo;s reactive data. You can bind any attribute&mdash;<code>src</code>, <code>href</code>, <code>class</code>, <code>style</code> and more to a reactive value.</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name"><span class="token namespace">v-bind:</span>src</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>imageUrl<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
</code></pre><p>That&rsquo;s the long form. The shorthand is much more common:</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">:src</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>imageUrl<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
</code></pre><p>You can even bind multiple attributes using an object:</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">v-bind</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>{ id: someId, class: someClass }<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
</code></pre><h3 id="v-model-two-way-data-binding">v-model: Two-Way Data Binding</h3><p><code>v-model</code> is one of Vue&rsquo;s most magical features. It keeps your form inputs in sync with component data&mdash;automatically.</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">v-model</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>message<span class="token punctuation">"</span></span> <span class="token attr-name">placeholder</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Type something...<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span>{{ message }}<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>
</code></pre><p>Every time you type, the data updates. Every time the data changes, the input updates. No event listeners, no manual syncing. Just pure reactivity.</p><p>You can even use modifiers like <code>.lazy</code>, <code>.number</code> and <code>.trim</code>:</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">v-model.lazy</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>username<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
</code></pre><p>This tells Vue to update the data after the input loses focus, rather than on every keystroke.</p><h3 id="v-if-v-else-if-v-else-conditional-rendering">v-if, v-else-if, v-else: Conditional Rendering</h3><p>These control whether an element appears in the DOM based on a condition.</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">v-if</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>user<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Welcome back, {{ user.name }}!<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">v-else</span><span class="token punctuation">&gt;</span></span>Login to continue<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>
</code></pre><p>When the condition is <code>false</code>, the element is literally removed from the DOM, not just hidden. That&rsquo;s great for performance when toggling expensive UI sections.</p><h3 id="v-show-toggle-visibility">v-show: Toggle Visibility</h3><p><code>v-show</code> is similar to <code>v-if</code>, but instead of removing elements from the DOM, it simply toggles its <code>display</code> style:</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">v-show</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>isVisible<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>This element is still in the DOM<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>
</code></pre><p>Use <code>v-show</code> when you need frequent toggling and don&rsquo;t want to destroy/recreate elements each time.</p><p>Quick rule of thumb:</p><ul><li>Use <code>v-if</code> when toggling rarely (it&rsquo;s more expensive to re-create elements).</li><li>Use <code>v-show</code> when toggling frequently (it&rsquo;s faster to show/hide).</li></ul><h3 id="v-for-list-rendering">v-for: List Rendering</h3><p><code>v-for</code> lets you loop through arrays (or objects) to render lists.</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ul</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span> <span class="token attr-name">v-for</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>(todo, index) in todos<span class="token punctuation">"</span></span> <span class="token attr-name">:key</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>index<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
    {{ todo.text }}
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">&gt;</span></span>
</code></pre><p>Always remember to include a unique <code>:key</code>. It helps Vue efficiently track changes and re-render only what&rsquo;s necessary.</p><h3 id="v-on-event-handling">v-on: Event Handling</h3><p>This one handles DOM events. It&rsquo;s used to listen for and react to user actions.</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ul</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span> <span class="token attr-name">v-for</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>(todo, index) in todos<span class="token punctuation">"</span></span> <span class="token attr-name">:key</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>index<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
    {{ todo.text }}
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">&gt;</span></span>
</code></pre><p>Or, with shorthand:</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">@click</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>count++<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Clicked {{ count }} times<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
</code></pre><p>You can chain event modifiers to control behavior:</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>form</span> <span class="token attr-name">@submit.prevent</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>submitForm<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">v-model</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>name<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>form</span><span class="token punctuation">&gt;</span></span>
</code></pre><p>The <code>.prevent</code> modifier cancels the default browser action, just like <code>event.preventDefault()</code> would.</p><h3 id="v-html-rendering-raw-html">v-html: Rendering Raw HTML</h3><p>This directive lets you render HTML content directly into the DOM.</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">v-html</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>rawHtml<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
</code></pre><p>Use it carefully, because if <code>rawHtml</code> contains user-generated content, it could expose you to XSS attacks. For most cases, stick with <code>{{ }}</code> interpolation instead.</p><h2 id="modifiers-and-arguments-in-action">Modifiers and Arguments in Action</h2><p>Modifiers and arguments are what make directives flexible.</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">v-model.trim</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>username<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">@click.once</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>handleClick<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Click me once<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
</code></pre><ul><li><code>.trim</code> cleans whitespace automatically.</li><li><code>.once</code> means the event only fires once.</li></ul><p>These small touches make Vue&rsquo;s syntax incredibly expressive and readable.</p><h2 id="creating-custom-directives">Creating Custom Directives</h2><p>Sometimes, the built-ins don&rsquo;t cover what you need. Maybe you need to auto-focus an input, trigger animations on scroll or integrate with a third-party library. That&rsquo;s when custom directives come in.</p><p>Here&rsquo;s a simple example:</p><pre class=" language-js"><code class="prism  language-js">app<span class="token punctuation">.</span><span class="token function">directive</span><span class="token punctuation">(</span><span class="token string">'focus'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span>
  <span class="token function">mounted</span><span class="token punctuation">(</span>el<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    el<span class="token punctuation">.</span><span class="token function">focus</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
</code></pre><p>Usage:</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">v-focus</span> <span class="token punctuation">/&gt;</span></span>
</code></pre><p>Boom&mdash;instant auto-focus whenever the element mounts.</p><p>Custom directives have their own lifecycle hooks:</p><ul><li><code>created</code></li><li><code>beforeMount</code></li><li><code>mounted</code></li><li><code>beforeUpdate</code></li><li><code>updated</code></li><li><code>beforeUnmount</code></li><li><code>unmounted</code></li></ul><p>Each hook gives you access to the element and the binding data, so you can respond to changes however you like.</p><h2 id="best-practices-for-using-directives">Best Practices for Using Directives</h2><p>Here are a few best practices for using directives:</p><ul><li>Keep logic inside directives simple. For complex logic, use computed properties or watchers.</li><li>Avoid stacking too many directives on one element&mdash;it becomes harder to read.</li><li>Use <code>v-if</code> and <code>v-show</code> appropriately based on how often the element toggles.</li><li>Create custom directives only when a pattern truly repeats across components.</li></ul><p>Think of directives as glue between Vue&rsquo;s reactivity and the DOM, not as replacements for component logic.</p><h2 id="common-pitfalls-to-avoid">Common Pitfalls to Avoid</h2><p>Here are some common pitfalls to avoid when using directives:</p><ul><li>Using <code>v-html</code> with untrusted content (security risk)</li><li>Forgetting to add a <code>key</code> in <code>v-for</code> loops</li><li>Combining <code>v-if</code> and <code>v-for</code> on the same element (always separate them)</li><li>Overcomplicating templates with too much logic inside directives.</li></ul><p>Directives are powerful, but they&rsquo;re meant to stay declarative. The heavy lifting should still live in your component logic.</p><h2 id="putting-it-all-together—a-mini-example">Putting It All Together&mdash;A Mini Example</h2><p>It&rsquo;s one thing to read about directives, but seeing them in action ties everything together.</p><p>Below is a small Vue Todo app that shows how multiple directives work in harmony&mdash;from conditional rendering to event handling and even custom directives.</p><p>You can try this example live in <a href="https://codesandbox.io/" target="_blank">CodeSandbox</a> or <a href="https://stackblitz.com/" target="_blank">StackBlitz</a>, or drop it into your local Vue project to see it run.</p><p>You can try this example live in CodeSandbox or StackBlitz, or drop it into your local Vue project to see it run.</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>template</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>todo-app<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h2</span><span class="token punctuation">&gt;</span></span>My Todo List<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h2</span><span class="token punctuation">&gt;</span></span>

    <span class="token comment">&lt;!-- Input field with v-model and a custom v-focus directive --&gt;</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span>
      <span class="token attr-name">v-model.trim</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>newTodo<span class="token punctuation">"</span></span>
      <span class="token attr-name">v-focus</span>
      <span class="token attr-name">placeholder</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Add a new todo<span class="token punctuation">"</span></span>
      <span class="token attr-name">@keyup.enter</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>addTodo<span class="token punctuation">"</span></span>
    <span class="token punctuation">/&gt;</span></span>

    <span class="token comment">&lt;!-- Conditional rendering using v-if / v-else --&gt;</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">v-if</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>todos.length === 0<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>No todos yet. Add one above <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ul</span> <span class="token attr-name">v-else</span><span class="token punctuation">&gt;</span></span>
      <span class="token comment">&lt;!-- Loop through todos using v-for --&gt;</span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span>
        <span class="token attr-name">v-for</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>(todo, index) in todos<span class="token punctuation">"</span></span>
        <span class="token attr-name">:key</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>index<span class="token punctuation">"</span></span>
        <span class="token attr-name">:class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>{ done: todo.done }<span class="token punctuation">"</span></span>
      <span class="token punctuation">&gt;</span></span>
        <span class="token comment">&lt;!-- Toggling state with v-on --&gt;</span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">@click</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>toggleDone(todo)<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
          {{ todo.text }}
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span>

        <span class="token comment">&lt;!-- Delete button --&gt;</span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">@click.prevent</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>removeTodo(index)<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>x<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">&gt;</span></span>

    <span class="token comment">&lt;!-- Show summary using v-show --&gt;</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">v-show</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>todos.length &gt; 0<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
      {{ remaining }} remaining
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>template</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">setup</span><span class="token punctuation">&gt;</span></span><span class="token script language-javascript">
<span class="token keyword">import</span> <span class="token punctuation">{</span> ref<span class="token punctuation">,</span> computed <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'vue'</span>

<span class="token comment">// Custom directive: auto-focus input on mount</span>
<span class="token keyword">const</span> vFocus <span class="token operator">=</span> <span class="token punctuation">{</span>
  <span class="token function">mounted</span><span class="token punctuation">(</span>el<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    el<span class="token punctuation">.</span><span class="token function">focus</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span>

<span class="token keyword">const</span> todos <span class="token operator">=</span> <span class="token function">ref</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> newTodo <span class="token operator">=</span> <span class="token function">ref</span><span class="token punctuation">(</span><span class="token string">''</span><span class="token punctuation">)</span>

<span class="token keyword">const</span> <span class="token function-variable function">addTodo</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span>newTodo<span class="token punctuation">.</span>value<span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    todos<span class="token punctuation">.</span>value<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token punctuation">{</span> text<span class="token punctuation">:</span> newTodo<span class="token punctuation">.</span>value<span class="token punctuation">,</span> done<span class="token punctuation">:</span> <span class="token boolean">false</span> <span class="token punctuation">}</span><span class="token punctuation">)</span>
    newTodo<span class="token punctuation">.</span>value <span class="token operator">=</span> <span class="token string">''</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token keyword">const</span> <span class="token function-variable function">removeTodo</span> <span class="token operator">=</span> <span class="token punctuation">(</span>index<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  todos<span class="token punctuation">.</span>value<span class="token punctuation">.</span><span class="token function">splice</span><span class="token punctuation">(</span>index<span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>

<span class="token keyword">const</span> <span class="token function-variable function">toggleDone</span> <span class="token operator">=</span> <span class="token punctuation">(</span>todo<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  todo<span class="token punctuation">.</span>done <span class="token operator">=</span> <span class="token operator">!</span>todo<span class="token punctuation">.</span>done
<span class="token punctuation">}</span>

<span class="token keyword">const</span> remaining <span class="token operator">=</span> <span class="token function">computed</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> todos<span class="token punctuation">.</span>value<span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span><span class="token punctuation">(</span>t<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token operator">!</span>t<span class="token punctuation">.</span>done<span class="token punctuation">)</span><span class="token punctuation">.</span>length<span class="token punctuation">)</span>
</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span> <span class="token attr-name">scoped</span><span class="token punctuation">&gt;</span></span><span class="token style language-css">
<span class="token selector"><span class="token class">.todo-app</span> </span><span class="token punctuation">{</span>
  <span class="token property">max-width</span><span class="token punctuation">:</span> <span class="token number">400</span>px<span class="token punctuation">;</span>
  <span class="token property">margin</span><span class="token punctuation">:</span> <span class="token number">2</span>rem auto<span class="token punctuation">;</span>
  <span class="token property">text-align</span><span class="token punctuation">:</span> left<span class="token punctuation">;</span>
  <span class="token property">font-family</span><span class="token punctuation">:</span> system-ui, sans-serif<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token selector"><span class="token class">.done</span> </span><span class="token punctuation">{</span>
  <span class="token property">text-decoration</span><span class="token punctuation">:</span> line-through<span class="token punctuation">;</span>
  <span class="token property">color</span><span class="token punctuation">:</span> gray<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token selector">button </span><span class="token punctuation">{</span>
  <span class="token property">margin-left</span><span class="token punctuation">:</span> <span class="token number">0.5</span>rem<span class="token punctuation">;</span>
  <span class="token property">background</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span>
  <span class="token property">border</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span>
  <span class="token property">color</span><span class="token punctuation">:</span> crimson<span class="token punctuation">;</span>
  <span class="token property">cursor</span><span class="token punctuation">:</span> pointer<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token selector">button<span class="token pseudo-class">:hover</span> </span><span class="token punctuation">{</span>
  <span class="token property">color</span><span class="token punctuation">:</span> red<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">&gt;</span></span>
</code></pre><h3 id="what’s-happening-here">What&rsquo;s Happening Here</h3><p>This small example brings together almost every directive we&rsquo;ve talked about:</p><ul><li><code>v-model</code> keeps the input field and data in sync.</li><li><code>v-focus</code> (a custom directive) automatically focuses the input when mounted.</li><li><code>v-if</code> / <code>v-else</code> handle conditional rendering for the &ldquo;todo yet&rdquo; message.</li><li><code>v-for</code> loops through the list of todos and binds a unique <code>:key</code>.</li><li><code>@click</code> (shorthand for <code>v-on:click</code>) handles toggling and removing items.</li><li><code>v-show</code> conditionally shows the &ldquo;remaining&rdquo; count without removing it from the DOM.</li></ul><p>This little component shows how directives make your templates reactive, declarative and readable. No manual DOM code, no boilerplate event logic, just Vue doing what it does best.</p><h2 id="wrapping-it-up">Wrapping It Up</h2><p>Directives are one of those features that make Vue feel effortless once you understand them. Instead of micromanaging the DOM, you describe what you want to happen, and Vue handles the rest. From dynamic bindings to conditional rendering and custom behaviors, a directive is the bridge between your data and your UI.</p><p>If you&rsquo;ve followed along up to this point, you&rsquo;ve already touched nearly every directive you&rsquo;ll use in day-to-day Vue work, and even built your first custom one. That&rsquo;s a solid step toward thinking declaratively and writing cleaner, more maintainable Vue components.</p><p>In our previous post, we went a level deeper and looked at <a href="https://www.telerik.com/blogs/vue-basics-mastering-vue-lifecycle-hooks" target="_blank">Vue&rsquo;s lifecycle hooks</a>, the what happens inside a component. Understanding the lifecycle will help you know exactly when to run logic, register listeners or clean things up as your components mount and unmount.</p><p>​If you&rsquo;ve been enjoying this series, stick around, we&rsquo;re just getting started. Follow along for more <a href="https://www.telerik.com/blogs/tag/vue-basics" target="_blank">Vue Basics</a>&nbsp;guides where we&rsquo;ll continue breaking down the framework piece by piece, in a way that feels practical and developer-friendly.</p><img src="https://feeds.telerik.com/link/23057/17270008.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:c8d7bc17-d937-4dfc-88a4-c61863477c1c</id>
    <title type="text">Vue Basics: Ultimate Guide to Vue 3 Reactivity</title>
    <summary type="text">A hands-on guide to understanding Vue 3’s reactivity system. Learn how ref, reactive, computed and watch work together to keep your UI automatically in sync with your data. Includes practical examples, gotchas and best practices for mastering reactivity in Vue.</summary>
    <published>2026-01-27T17:43:48Z</published>
    <updated>2026-04-07T16:18:32Z</updated>
    <author>
      <name>David Adeneye Abiodun </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/23057/17264612/vue-basics-ultimate-guide-vue-3-reactivity"/>
    <content type="text"><![CDATA[<p><span class="featured">A hands-on guide to understanding Vue 3&rsquo;s reactivity system. Learn how <code>ref</code>, <code>reactive</code>, <code>computed</code> and <code>watch</code> work together to keep your UI automatically in sync with your data. Includes practical examples, gotchas and best practices for mastering reactivity in Vue.</span></p><p>If you&rsquo;ve ever wondered why Vue feels so effortless, why your UI just knows when to update, it all boils down to one thing: <strong>reactivity</strong>. Vue&rsquo;s reactivity system is the magic behind its simplicity. It turns plain JavaScript data into a living, self-updating state that automatically keeps your UI in sync with your logic.</p><p>In this guide, we&rsquo;ll go beyond &ldquo;it just works&rdquo; and unpack how Vue 3&rsquo;s reactivity actually functions, what makes it so intuitive, what&rsquo;s happening under the hood and how you can use it effectively in your own components.</p><h2 id="what-reactivity-means-and-why-it’s-awesome">What Reactivity Means (and Why It&rsquo;s Awesome)</h2><p>Before Vue (and frameworks like it), updating the UI meant manually syncing your data and the DOM. Change a value? You&rsquo;d have to find the element, update the text and keep track of what&rsquo;s what.</p><p>Vue&rsquo;s reactivity flips that process on its head. Now, your data becomes the <strong>single source of truth</strong>, and Vue automatically keeps your UI in sync with it. Change the data, and the DOM reacts. That&rsquo;s the essence of reactivity.</p><p>Here&rsquo;s a quick taste:</p><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">import</span> <span class="token punctuation">{</span> reactive <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'vue'</span>
<span class="token keyword">const</span> state <span class="token operator">=</span> <span class="token function">reactive</span><span class="token punctuation">(</span><span class="token punctuation">{</span> count<span class="token punctuation">:</span> <span class="token number">0</span> <span class="token punctuation">}</span><span class="token punctuation">)</span>

<span class="token keyword">function</span> <span class="token function">increment</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  state<span class="token punctuation">.</span>count<span class="token operator">++</span>
  <span class="token comment">// The DOM updates automatically</span>
<span class="token punctuation">}</span>
</code></pre><p>For example, after you make <code>state</code> reactive, you no longer need to manually update the DOM; Vue handles this using dependency tracking.</p><p>Behind the scenes, Vue 3&rsquo;s reactivity system is powered by modern <strong>JavaScript Proxies</strong>. When you make an object reactive, Vue wraps it in a proxy that &ldquo;intercepts&rdquo; reads and writes:</p><ul><li>When you <strong>read </strong>a property, Vue tracks that dependency.</li><li>When you <strong>change </strong>it, Vue knows exactly which parts of the DOM depend on it and updates them efficiently.<br />Think of it as a super-smart observer pattern baked directly into your data layer.</li></ul><h2 id="the-core-apis-you’ll-use">The Core APIs You&rsquo;ll Use</h2><p>Let&rsquo;s walk through the main tools Vue provides to make your data reactive and useful.</p><h3 id="reactive-make-objects-reactive">reactive(): Make Objects Reactive</h3><p>Use <code>reactive()</code> when working with objects, arrays, or anything with multiple properties.</p><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">import</span> <span class="token punctuation">{</span> reactive <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'vue'</span>

<span class="token keyword">const</span> user <span class="token operator">=</span> <span class="token function">reactive</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  name<span class="token punctuation">:</span> <span class="token string">'David'</span><span class="token punctuation">,</span>
  age<span class="token punctuation">:</span> <span class="token number">25</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>

user<span class="token punctuation">.</span>age<span class="token operator">++</span> <span class="token comment">// reactivity in action</span>
</code></pre><p>Every nested property inside <code>reactive()</code> is tracked automatically, giving you deep reactivity out of the box.</p><h3 id="ref-reactivity-for-primitives">ref(): Reactivity for Primitives</h3><p>Objects work great with <code>reactive</code>, but what about primitives like numbers, strings or booleans?</p><p>That&rsquo;s where <code>ref()</code> comes in:</p><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">import</span> <span class="token punctuation">{</span> ref <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'vue'</span>

<span class="token keyword">const</span> count <span class="token operator">=</span> <span class="token function">ref</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span>

<span class="token keyword">function</span> <span class="token function">increment</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  count<span class="token punctuation">.</span>value<span class="token operator">++</span>
<span class="token punctuation">}</span>
</code></pre><p>Notice <code>value</code>? That&rsquo;s how you access the actual value inside a ref. Vue adds this layer because primitive values (like numbers or strings) can&rsquo;t be wrapped in proxies directly.</p><p>The cool part: inside templates, Vue automatically unwraps refs, so you can use <code>{{ count }} instead of</code>{{ count.value }}` in your template.</p><p>Rule of thumb:</p><ul><li>Use <code>ref()</code> for single values (primitives).</li><li>Use <code>reactive</code> for objects and collections.</li></ul><h3 id="computed-derived-read-only-state">computed(): Derived, Read-Only State</h3><p>Sometimes, your data depends on other data. Instead of manually updating values, let Vue handle it with <code>computed()</code>.</p><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">import</span> <span class="token punctuation">{</span> ref<span class="token punctuation">,</span> computed <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'vue'</span>

<span class="token keyword">const</span> price <span class="token operator">=</span> <span class="token function">ref</span><span class="token punctuation">(</span><span class="token number">100</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> quantity <span class="token operator">=</span> <span class="token function">ref</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span>

<span class="token keyword">const</span> total <span class="token operator">=</span> <span class="token function">computed</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> price<span class="token punctuation">.</span>value <span class="token operator">*</span> quantity<span class="token punctuation">.</span>value<span class="token punctuation">)</span>
</code></pre><p><code>computed</code> properties are cached and only rerun when their dependencies change&mdash;perfect for anything derived from existing state.</p><h3 id="watch-and-watcheffect-responding-to-changes">watch() and watchEffect(): Responding to Changes</h3><p><code>watch()</code> is your go-to for running side effects when reactive data changes&mdash;for example, saving data to localStorage or triggering an API call.</p><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">import</span> <span class="token punctuation">{</span> ref<span class="token punctuation">,</span> watch <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'vue'</span>

<span class="token keyword">const</span> username <span class="token operator">=</span> <span class="token function">ref</span><span class="token punctuation">(</span><span class="token string">''</span><span class="token punctuation">)</span>

<span class="token function">watch</span><span class="token punctuation">(</span>username<span class="token punctuation">,</span> <span class="token punctuation">(</span>newValue<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  localStorage<span class="token punctuation">.</span><span class="token function">setItem</span><span class="token punctuation">(</span><span class="token string">'username'</span><span class="token punctuation">,</span> newValue<span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
</code></pre><p><code>watchEffect</code> works similarly but is even more automatic. It immediately runs and tracks dependencies on its own:</p><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">import</span> <span class="token punctuation">{</span> ref<span class="token punctuation">,</span> watchEffect <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'vue'</span>

<span class="token keyword">const</span> count <span class="token operator">=</span> <span class="token function">ref</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span>

<span class="token function">watchEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token string">`Count is now </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>count<span class="token punctuation">.</span>value<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">`</span></span><span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
</code></pre><p>If you&rsquo;re not sure which one to use, start with <code>watchEffect()</code>. It&rsquo;s simpler for quick reactions to state.</p><h2 id="how-vue’s-reactivity-works-under-the-hood">How Vue&rsquo;s Reactivity Works Under the Hood</h2><p>Here&rsquo;s a high-level mental model of how Vue&rsquo;s reactivity works:</p><ol><li>You access a reactive property &rarr; Vue tracks it.</li><li>You change that property &rarr; Vue triggers updates.</li><li>Vue efficiently re-renders only what depends on it.</li></ol><p>Want to peek behind the curtain? Here&rsquo;s a simplified reactivity system you can try yourself:</p><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">let</span> value
<span class="token keyword">const</span> subscribers <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Set</span><span class="token punctuation">(</span><span class="token punctuation">)</span>

<span class="token keyword">function</span> <span class="token function">effect</span><span class="token punctuation">(</span>fn<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  subscribers<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>fn<span class="token punctuation">)</span>
  <span class="token function">fn</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>

<span class="token keyword">function</span> <span class="token keyword">set</span><span class="token punctuation">(</span>newVal<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  value <span class="token operator">=</span> newVal
  subscribers<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span>fn <span class="token operator">=&gt;</span> <span class="token function">fn</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>

<span class="token comment">// Usage</span>
<span class="token function">effect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Value is'</span><span class="token punctuation">,</span> value<span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token keyword">set</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token comment">// logs "Value is 1"</span>
<span class="token keyword">set</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token comment">// logs "Value is 2"</span>
</code></pre><p>That&rsquo;s basically what Vue does, just much more optimized and feature-rich.</p><p>Reactivity in the Composition API<br />The Composition API is built entirely on top of reactivity. Inside <code>setup()</code>, you can freely mix <code>ref()</code>, <code>reactive()</code>, <code>computed()</code> and <code>watch()</code> to manage state and logic cleanly:</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">setup</span><span class="token punctuation">&gt;</span></span><span class="token script language-javascript">
<span class="token keyword">import</span> <span class="token punctuation">{</span> reactive<span class="token punctuation">,</span> computed <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'vue'</span>

<span class="token keyword">const</span> cart <span class="token operator">=</span> <span class="token function">reactive</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  items<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>

<span class="token keyword">const</span> total <span class="token operator">=</span> <span class="token function">computed</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span>
  cart<span class="token punctuation">.</span>items<span class="token punctuation">.</span><span class="token function">reduce</span><span class="token punctuation">(</span><span class="token punctuation">(</span>sum<span class="token punctuation">,</span> item<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> sum <span class="token operator">+</span> item<span class="token punctuation">.</span>price<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span>
<span class="token punctuation">)</span>

<span class="token keyword">function</span> <span class="token function">addItem</span><span class="token punctuation">(</span>item<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  cart<span class="token punctuation">.</span>items<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>item<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span>
</code></pre><p>Everything inside <code>setup()</code> stays reactive and automatically updates your template whenever your state changes .</p><h2 id="practical-example-a-reactive-todo-list">Practical Example&mdash;A Reactive Todo List</h2><p>Here is everything in action:</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">setup</span><span class="token punctuation">&gt;</span></span><span class="token script language-javascript">
<span class="token keyword">import</span> <span class="token punctuation">{</span> reactive<span class="token punctuation">,</span> computed<span class="token punctuation">,</span> watch <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'vue'</span>

<span class="token keyword">const</span> todos <span class="token operator">=</span> <span class="token function">reactive</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  list<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
  newTodo<span class="token punctuation">:</span> <span class="token string">''</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>

<span class="token keyword">const</span> total <span class="token operator">=</span> <span class="token function">computed</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> todos<span class="token punctuation">.</span>list<span class="token punctuation">.</span>length<span class="token punctuation">)</span>

<span class="token keyword">function</span> <span class="token function">addTodo</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>todos<span class="token punctuation">.</span>newTodo<span class="token punctuation">)</span> <span class="token keyword">return</span>
  todos<span class="token punctuation">.</span>list<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>todos<span class="token punctuation">.</span>newTodo<span class="token punctuation">)</span>
  todos<span class="token punctuation">.</span>newTodo <span class="token operator">=</span> <span class="token string">''</span>
<span class="token punctuation">}</span>

<span class="token function">watch</span><span class="token punctuation">(</span>
  <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> todos<span class="token punctuation">.</span>list<span class="token punctuation">,</span>
  <span class="token punctuation">(</span>newList<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> localStorage<span class="token punctuation">.</span><span class="token function">setItem</span><span class="token punctuation">(</span><span class="token string">'todos'</span><span class="token punctuation">,</span> JSON<span class="token punctuation">.</span><span class="token function">stringify</span><span class="token punctuation">(</span>newList<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  <span class="token punctuation">{</span> deep<span class="token punctuation">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span>
<span class="token punctuation">)</span>
</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>template</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">v-model</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>todos.newTodo<span class="token punctuation">"</span></span> <span class="token attr-name">placeholder</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Add a todo<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">@click</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>addTodo<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Add<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span>Total: {{ total }}<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>

    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ul</span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span> <span class="token attr-name">v-for</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>todo in todos.list<span class="token punctuation">"</span></span> <span class="token attr-name">:key</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>todo<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>{{ todo }}<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>template</span><span class="token punctuation">&gt;</span></span>
</code></pre><p>In this example:</p><ul><li><code>reactive()</code> manages our state object</li><li><code>computed()</code> handles derived totals</li><li>And <code>watch()</code> persists the data</li></ul><p>Everything is tied together by Vue&rsquo;s reactivity system. Everything stays perfectly in sync without a single manual DOM update.</p><h2 id="gotchas-and-edge-cases">Gotchas and Edge Cases</h2><p>Even though Vue&rsquo;s reactivity feels seamless, there are a few quirks worth remembering:</p><ul><li>Destructuring breaks reactivity:</li></ul><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">const</span> <span class="token punctuation">{</span> count <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">reactive</span><span class="token punctuation">(</span><span class="token punctuation">{</span> count<span class="token punctuation">:</span> <span class="token number">0</span> <span class="token punctuation">}</span><span class="token punctuation">)</span>
count<span class="token operator">++</span> <span class="token comment">// ❌ not reactive anymore</span>
</code></pre><p>Always access properties directly from the reactive object.</p><ul><li>Ref vs. reactive confusion: You can&rsquo;t use <code>.value</code> on a reactive object, only on a ref.</li><li>Shallow reactivity: If you only need top-level tracking, <code>shallowReactive()</code> or <code>shallowRef()</code> might be better for performance-heavy objects.</li><li>Reassigning reactive objects: Reassigning a whole reactive object breaks its reference. Mutate its properties instead.</li></ul><h2 id="wrapping-up">Wrapping Up</h2><p>Understanding Vue&rsquo;s reactivity is like unlocking the framework&rsquo;s secret language. Once you understand how data flows and updates behind the scenes, everything from state management to the Composition API starts to click.</p><p>As an add-on, take a look at these best practices for working with reactivity:</p><ul><li>Prefer <code>computed</code> over <code>watch</code> for derived state.</li><li>Keep watchers side-effect-free.</li><li>Avoid unnecessary <code>.value</code> access&mdash;templates handle that for you.</li><li>Be cautious when mixing reactive and non-reactive data.</li><li>Don&rsquo;t overuse reactivity&mdash;keep it simple and purposeful.</li></ul><p>In addition, here are tips for debugging reactivity when things don&rsquo;t seem to update as expected:</p><ul><li>Use <code>isReactive()</code> or <code>isRef()</code> to check what&rsquo;s reactive.</li><li>Use <code>toRaw()</code> if you need the original, unwrapped object (for logging or external libraries).</li><li>Vue DevTools can visualize your reactivity graph&mdash;extremely helpful for debugging complex state.</li></ul><p>If you&rsquo;ve enjoyed this deep dive, stick around&mdash;this article in this Vue Basics series explores <a href="https://www.telerik.com/blogs/vue-basics-mastering-vue-lifecycle-hooks" target="_blank">how Vue&rsquo;s lifecycle hooks tie into reactive state</a> and when to tap into them for precise control over your components.</p><img src="https://feeds.telerik.com/link/23057/17264612.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:be61fd5b-ee6a-4c5f-9f05-6a0b091be7a9</id>
    <title type="text">Vue Basics: State Management in Vue</title>
    <summary type="text">Learn how to scale Vue state management with ref/reactive, props/emits, provide/inject and Pinia as your app grows.</summary>
    <published>2025-10-08T15:06:44Z</published>
    <updated>2026-04-07T16:18:32Z</updated>
    <author>
      <name>David Adeneye Abiodun </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/23057/17181510/vue-basics-state-management-vue"/>
    <content type="text"><![CDATA[<p><span class="featured">Learn how to scale Vue state management with ref/reactive, props/emits, provide/inject and Pinia as your app grows.</span></p><p>State is one of the first core concepts you&rsquo;ll encounter when building anything interactive with Vue. Whether it&rsquo;s the text in a form, the items in a cart or the logged-in user&rsquo;s profile, managing that state properly is essential for keeping your app stable, reactive and easy to scale.</p><p>Vue 3 introduced the Composition API along with a revamped reactivity system, giving developers more powerful and flexible ways to manage state than ever before. In this guide, we&rsquo;ll explore how Vue 3 handles state&mdash;starting from local state with <code class="inline-code">ref</code> and <code class="inline-code">reactive</code>, to sharing data with <code class="inline-code">props</code> and <code class="inline-code">provide/inject</code>, and finally leveling up to Pinia, Vue&rsquo;s official state management library. By the end, you&rsquo;ll have a clear roadmap for choosing the right tool for your app&rsquo;s needs.</p><h2 id="understanding-the-concept-of-state-management-in-vue">Understanding the Concept of State Management in Vue</h2><p>State is at the heart of every interactive Vue app. It&rsquo;s what makes your UI dynamic&mdash;update the state, and Vue automatically reflects those changes in the DOM.</p><p>Broadly, there are two types of state:</p><ul><li>Local state: Exists inside a single component (e.g., a <code class="inline-code">count</code> variable in a counter component)</li><li>Global state: Shared across multiple components or sections of your app (e.g., user authentication status)</li></ul><p>By default, every Vue component maintains its own reactive state, which we often refer to as local state. Let&rsquo;s start here before exploring how to share state across components and eventually manage it globally with Pinia.</p><h2 id="local-state-with-ref-and-reactive">Local State with ref and reactive</h2><p>To manage local reactive state, the Vue 3 Composition API offers two main tools: <code class="inline-code">ref</code> and <code class="inline-code">reactive</code>. If you&rsquo;re new to Vue, deciding which to use can be confusing&mdash;so let&rsquo;s break it down, building on our understanding of local state.</p><h3 id="using-ref">Using ref</h3><p>Use <code class="inline-code">ref</code> when working with primitive values (numbers, strings, booleans).</p><pre class=" language-vue"><code class="prism  language-vue">&lt;script setup&gt;
import { ref } from 'vue'

//state
const count = ref(0)

//actions
function increment() {
  count.value++
}
&lt;/script&gt;

&lt;!-- view --&gt;
&lt;template&gt;
  &lt;button @click="increment"&gt;Count is: {{ count }}&lt;/button&gt;
&lt;/template&gt;
</code></pre><p>Here,<code class="inline-code">count</code> is wrapped in a <code class="inline-code">ref</code>, and its value is accessed via <code class="inline-code">.value</code>.</p><h3 id="using-reactive">Using reactive</h3><p>Use <code class="inline-code">reactive</code> when working with objects or arrays.</p><pre class=" language-vue"><code class="prism  language-vue">&lt;script setup&gt;
import { reactive } from 'vue'

const user = reactive({
  name: ' ',
  age: 25
})
&lt;/script&gt;

&lt;template&gt;
   &lt;input v-model="user.name" placeholder="Enter your name" /&gt;
  &lt;p&gt;{{ user.name }} is {{ user.age }} years old.&lt;/p&gt;
&lt;/template&gt;
</code></pre><p>Behind the scenes, Vue wraps the object in a Proxy, so it can automatically track changes and update the DOM.</p><p>Local state is the simplest example of Vue&rsquo;s one-way data flow: the state drives the UI. But once multiple components need to share the same piece of state, managing it locally becomes difficult, and that&rsquo;s when we move beyond local state.</p><h2 id="sharing-state-between-components">Sharing State Between Components</h2><p>Local state works fine for a single component, but what if multiple components need the same data? Vue gives us several ways to share state: props/emits and provide/inject.</p><h3 id="props-and-emits">Props and Emits</h3><p>Props let a parent pass state down, while emits let children send events up.</p><p>Let&rsquo;s take a look at this simple demo below:</p><h4 id="parent-component">Parent Component</h4><p>You can pass data from parent to child via props.</p><pre class=" language-vue"><code class="prism  language-vue">&lt;!-- Parent.vue --&gt;
&lt;template&gt;
  &lt;Child :count="count" @increment="count++" /&gt;
&lt;/template&gt;

&lt;script setup&gt;
import { ref } from 'vue'
import Child from './Child.vue'

const count = ref(0)
&lt;/script&gt;
</code></pre><h4 id="child-component">Child Component</h4><p>When the child needs to update the parent&rsquo;s state, it can emit an event.</p><pre class=" language-vue"><code class="prism  language-vue">&lt;!-- Child.vue --&gt;
&lt;template&gt;
  &lt;button @click="$emit('increment')"&gt;Clicked&lt;/button&gt;
&lt;/template&gt;

&lt;script setup&gt;
defineProps(['count'])
defineEmits(['increment'])
&lt;/script&gt;
</code></pre><p>This approach works well for small apps, but if you have deeply nested components, passing props and emits around quickly becomes messy, leading to another problem known as <a target="_blank" href="https://vuejs.org/guide/components/provide-inject.html#prop-drilling">prop drilling</a>. To avoid this, Vue provides an alternative option: provide/inject.</p><h3 id="provideinject-avoiding-prop-drilling">Provide/inject: Avoiding Prop Drilling</h3><p>The <code class="inline-code">provide/inject</code> API in Vue makes it easy for a parent component to share data with its children, no matter how deeply nested they are, without having to pass props down through every intermediate layer.</p><p>Let&rsquo;s take a look at this simple code demo below:</p><h4 id="parent-component-1">Parent Component</h4><pre class=" language-vue"><code class="prism  language-vue">&lt;!-- ParentComponent.vue --&gt;
&lt;script setup&gt;
import { ref, provide } from 'vue'
import ChildComponent from './ChildComponent.vue'

   const count = ref(0)
   provide('count', count)

   const increment = () =&gt; {
      count.value++
  }
&lt;/script&gt;

&lt;template&gt;
  &lt;div&gt;
    &lt;h2&gt;Parent Count: {{ count }}&lt;/h2&gt;
    &lt;button @click="increment"&gt;Increment&lt;/button&gt;
    &lt;!-- Pass down without props --&gt;
    &lt;ChildComponent /&gt;
  &lt;/div&gt;
&lt;/template&gt;
</code></pre><p>The <code class="inline-code">provide()</code> function is used in a parent component to make data available to its descendants. It takes two arguments: an injection key and a value. The key can be either a string or a symbol, and descendant components will use that key to access the corresponding value through <code class="inline-code">inject()</code>. A single component isn&rsquo;t limited to one call; you can call <code class="inline-code">provide()</code> multiple times with different keys to share different values.</p><p>The second argument of <code class="inline-code">provide()</code> is the data you want to share, which can be of any type&mdash;primitives, objects, functions or even reactive state like <code class="inline-code">ref</code> or <code class="inline-code">reactive</code>. When you provide a reactive value, Vue doesn&rsquo;t pass a copy; it establishes a live connection, allowing descendant components that <code class="inline-code">inject()</code> it to automatically stay in sync with the provider.</p><h4 id="child-component-1">Child Component</h4><pre class=" language-vue"><code class="prism  language-vue">&lt;!-- ChildComponent.vue --&gt;
&lt;script setup&gt;
import GrandChildComponent from './GrandChildComponent.vue'
&lt;/script&gt;
&lt;template&gt;
  &lt;div&gt;
    &lt;h3&gt;I am the Child&lt;/h3&gt;
    &lt;!-- Notice: no prop passing --&gt;
    &lt;GrandChildComponent /&gt;
  &lt;/div&gt;
&lt;/template&gt;
</code></pre><p>To inject data provided by an ancestor component, make use of the <code class="inline-code">inject()</code> function in the <code class="inline-code">GrandChildComponent.vue</code>:</p><pre class=" language-vue"><code class="prism  language-vue">&lt;!-- GrandChildComponent.vue --&gt;
&lt;script setup&gt;
import { inject } from 'vue'
const count = inject('count')
&lt;/script&gt;
&lt;template&gt;
  &lt;div&gt;
    &lt;p&gt;Grandchild sees count: {{ count }}&lt;/p&gt;
  &lt;/div&gt;
&lt;/template&gt;
</code></pre><p>In the code demo above, the ParentComponent provides the reactive <code class="inline-code">count</code>, so the ChildComponent doesn&rsquo;t need to do anything&mdash;it just passes the slot/children down. Then the GrandChildComponent can directly inject <code class="inline-code">count</code> and stay reactive to parent updates</p><p>This is a basic demo of how to use the <code class="inline-code">provide/inject</code> pattern; however, if you want to learn more, this post covers it in great detail: <a target="_blank" href="https://www.telerik.com/blogs/vue-basics-exploring-vue-provide-inject-pattern">Vue Basics: Exploring Vue&rsquo;s Provide/Inject Pattern</a>.</p><p>The <code class="inline-code">provide/inject</code> pattern is perfect for avoiding prop drilling in medium-sized apps. However, as your app grows, managing dependencies this way can get complicated. For larger and complex apps, we need a dedicated state management solution that is more structured and scalable, which is where libraries like Pinia come in.</p><h2 id="state-at-scale-with-pinia">State at Scale with Pinia</h2><p>As your app grows, manually managing state across multiple components quickly turns into a headache. The patterns we covered earlier work fine for smaller projects, but once you&rsquo;re building a large-scale production application, there are many things to consider:</p><ul><li>Hot Module Replacement</li><li>Stronger conventions for team collaboration</li><li>Integrating with the Vue DevTools, including timeline, in-component inspection and time-travel debugging</li><li>Server-side rendering support</li></ul><p>You need a central store, a single source of truth that multiple components can read and write to, and that is where <a target="_blank" href="https://pinia.vuejs.org/">Pinia</a> comes in.</p><p>Pinia is the official state management library for Vue 3 and the successor to Vuex. It&rsquo;s designed to handle all the scenarios we listed above. It&rsquo;s much simpler, more intuitive and built to work seamlessly with the Composition API.</p><p>Before we dive into the code demo, let&rsquo;s make sure we&rsquo;re clear on the fundamentals. At its core, state management is about where your data lives, how you read it and how to update it. Pinia formalizes this with four concepts:</p><ul><li>State: This is your actual reactive data. Think of it as the source of truth for your app. For example, a user&rsquo;s profile details, the item in a cart or whether a modal is open.</li><li>Store: The centralized container that holds your state. Instead of scattering data across multiple components, the store centralizes it, so any components can access and update it without messy prop drilling.</li><li>Getters: These act like computed properties for your store. They let you derive or transform state values (for example, calculating the cost of items in a cart) without duplication logic in multiple places.</li><li>Actions: Functions that update your state. They&rsquo;re like methods in a Vue component, and they&rsquo;re where you keep the logic for making changes, whether it&rsquo;s incrementing a counter, adding an item to a list or fetching data from an API.</li></ul><p>Think of it this way:</p><ul><li>State is what you have</li><li>Getters are how you view it</li><li>Actions define how it changes</li><li>The store is where it all lives</li></ul><p>So let&rsquo;s demonstrate how to integrate Pinia with our counter demo app.</p><h2 id="setting-up-pinia">Setting Up Pinia</h2><p>Setting up Pinia for a Vue 3 single-page application is straightforward. If you&rsquo;re creating a new project from scratch using the Vue CLI or create-vue, the setup wizard will even ask if you want to use Pinia as your state management of choice.</p><p>To set up Pinia manually in a new project&mdash;or add it to an existing app&mdash;follow these steps:</p><p>First, install the Pinia package using either <code class="inline-code">npm</code> or <code class="inline-code">yarn</code>:</p><pre><code>npm install pinia

# or

yarn install pinia
</code></pre><p>To register Pinia in your app, open your entry file (usually <code class="inline-code">main.js</code> or <code class="inline-code">main.ts</code>) where the app is mounted, and call <code class="inline-code">app.use(pinia)</code> on your Vue application instance:</p><p><strong>main.js</strong></p><pre class=" language-js"><code class="prism  language-js"><span class="token comment">// main.js</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> createApp <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'vue'</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> createPinia <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'pinia'</span>
<span class="token keyword">import</span> App <span class="token keyword">from</span> <span class="token string">'./App.vue'</span>

<span class="token keyword">const</span> pinia <span class="token operator">=</span> <span class="token function">createPinia</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> app <span class="token operator">=</span> <span class="token function">createApp</span><span class="token punctuation">(</span>App<span class="token punctuation">)</span>

app<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span>pinia<span class="token punctuation">)</span>
app<span class="token punctuation">.</span><span class="token function">mount</span><span class="token punctuation">(</span><span class="token string">'#app'</span><span class="token punctuation">)</span>
</code></pre><p>Now we can move to creating the store for the counter demo app.</p><h3 id="creating-a-store">Creating a Store</h3><p>Here&rsquo;s a simple counter store that demonstrates all four concepts. To create a store, start by making a new file that holds its code. A good practice is to place these files inside a dedicated <code class="inline-code">stores</code> folder to keep your project organized.</p><p><strong>CounterStore.js</strong></p><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">export</span> <span class="token keyword">const</span> useCounterStore <span class="token operator">=</span> <span class="token function">defineStore</span><span class="token punctuation">(</span><span class="token string">'counter'</span><span class="token punctuation">,</span>  <span class="token punctuation">{</span>

<span class="token operator">--</span><span class="token operator">--</span>

<span class="token punctuation">}</span><span class="token punctuation">)</span>
</code></pre><p>To create a store, we use Pinia&rsquo;s <code class="inline-code">defineStore</code> method. It takes two main arguments: the first is the store&rsquo;s <code class="inline-code">id</code>, which must be unique across your application. You can name it anything you like, but for this example, <code class="inline-code">counter</code> is the most fitting since that&rsquo;s exactly what our store manages.</p><p>The second argument is an object that defines the store&rsquo;s options. Let&rsquo;s break down what we can include inside it:</p><h3 id="state">State</h3><p>The first option to define in a store is <code class="inline-code">state</code>. If you&rsquo;ve worked with Vue&rsquo;s Options API, this will feel familiar. It&rsquo;s simply a function that returns an object holding all the reactive data your store should manage. For our counter app demo, we&rsquo;ll add <code class="inline-code">count</code> property to the state:</p><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">export</span> <span class="token keyword">const</span> useCounterStore <span class="token operator">=</span> <span class="token function">defineStore</span><span class="token punctuation">(</span><span class="token string">'counter'</span><span class="token punctuation">,</span>  <span class="token punctuation">{</span>
<span class="token comment">// State &mdash; the reactive shared data</span>
  state<span class="token punctuation">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">(</span><span class="token punctuation">{</span>
     count<span class="token punctuation">:</span> <span class="token number">0</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
</code></pre><p>Then we can easily import this store in our <code class="inline-code">CounterButton.vue</code> component and make use of the <code class="inline-code">count</code> states.</p><p><strong>CounterButton.vue</strong></p><pre class=" language-vue"><code class="prism  language-vue">&lt;script setup&gt;
import { useCounterStore } from '../stores/CounterStore.js&rsquo;
const counter = useCounterStore()
&lt;/script&gt;
&lt;template&gt;
  &lt;div&gt;
  &lt;button &gt;
    Clicked {{ counter.count }} times
 &lt;/button&gt;
 
  &lt;/div&gt;
&lt;/template&gt;
</code></pre><p>In the above code example, we imported the <code class="inline-code">useCounterStore</code> and then called the method. This will return a copy of the counter store that we created earlier. The state in this store is global, meaning any updates made to it will automatically be reflected across all components that use the store.</p><h3 id="getters">Getters</h3><p>Just like Vue&rsquo;s computed properties, Pinia stores allow us to define <code class="inline-code">getters</code>. A getter is essentially a computed value that&rsquo;s derived from the store&rsquo;s state. They&rsquo;re useful when you want to transform, filter or calculate something based on the existing state without duplicating logic across components. For example, we can calculate the multiplication of the current state using the getter method:</p><p>Update your <code class="inline-code">CounterStore.js</code> with the following code:</p><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">export</span> <span class="token keyword">const</span> useCounterStore <span class="token operator">=</span> <span class="token function">defineStore</span><span class="token punctuation">(</span><span class="token string">'counter'</span><span class="token punctuation">,</span>  <span class="token punctuation">{</span>

<span class="token comment">// State &mdash; the reactive shared data</span>
  state<span class="token punctuation">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">(</span><span class="token punctuation">{</span>
     count<span class="token punctuation">:</span> <span class="token number">0</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>

<span class="token comment">// Getters &mdash; derived state</span>
  getters<span class="token punctuation">:</span> <span class="token punctuation">{</span>
    doubleCount<span class="token punctuation">:</span> <span class="token punctuation">(</span>state<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> state<span class="token punctuation">.</span>count <span class="token operator">*</span> <span class="token number">2</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>

<span class="token punctuation">}</span><span class="token punctuation">)</span>
</code></pre><p>That is it. Now we now have a <code class="inline-code">doubleCount</code> property that we can use across any component.</p><p>Create a <code class="inline-code">CounterDispay.vue</code> component to use the <code class="inline-code">doubleCount</code> property to display message to the user.</p><pre class=" language-vue"><code class="prism  language-vue">&lt;script setup&gt;
import { useCounterStore } from '../stores/CounterStore.js'
const counter = useCounterStore()
&lt;/script&gt;

&lt;template&gt;
  &lt;div&gt;
    &lt;p&gt;Current Count: {{ counter.count }}&lt;/p&gt;
    &lt;p&gt;Double Count: {{ counter.doubleCount }}&lt;/p&gt;
  &lt;/div&gt;
&lt;/template&gt;
</code></pre><p>Getters are synchronous by design. If you need to perform asynchronous work (like fetching data), use an action instead.</p><h3 id="actions">Actions</h3><p>The last option we can define in our store is actions. Think of actions as the store&rsquo;s version of component methods&mdash;they encapsulate the logic for changing state or performing tasks. Unlike getters, which are only for deriving and returning data, actions are designed to update state and handle side effects.</p><p>One major advantage of actions is that they can be asynchronous, unlike getters. This makes them ideal for tasks such as fetching data from an API, handling form submissions or performing any operation that takes time before committing the result back to the store. For example, this is a good location to create a logic to increment the state <code class="inline-code">count</code> or fetch the initial <code class="inline-code">count</code> data from your API.</p><p>Open our <code class="inline-code">CounterStore.js</code> and update with the following code:</p><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">export</span> <span class="token keyword">const</span> useCounterStore <span class="token operator">=</span> <span class="token function">defineStore</span><span class="token punctuation">(</span><span class="token string">'counter'</span><span class="token punctuation">,</span>  <span class="token punctuation">{</span>

<span class="token comment">// State &mdash; the reactive shared data</span>
  state<span class="token punctuation">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">(</span><span class="token punctuation">{</span>
     count<span class="token punctuation">:</span> <span class="token number">0</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>

<span class="token comment">// Getters &mdash; derived state</span>
  getters<span class="token punctuation">:</span> <span class="token punctuation">{</span>
    doubleCount<span class="token punctuation">:</span> <span class="token punctuation">(</span>state<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> state<span class="token punctuation">.</span>count <span class="token operator">*</span> <span class="token number">2</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>

<span class="token comment">// Actions &mdash; logic to update state</span>
actions<span class="token punctuation">:</span> <span class="token punctuation">{</span>
    <span class="token function">increment</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">this</span><span class="token punctuation">.</span>count<span class="token operator">++</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>

    <span class="token keyword">async</span> <span class="token function">fetchInitialCount</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">const</span> res <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token string">'/api/count'</span><span class="token punctuation">)</span>
      <span class="token keyword">const</span> data <span class="token operator">=</span> <span class="token keyword">await</span> res<span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
      <span class="token keyword">this</span><span class="token punctuation">.</span>count <span class="token operator">=</span> data<span class="token punctuation">.</span>value
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>

  <span class="token punctuation">}</span>

<span class="token punctuation">}</span><span class="token punctuation">)</span>
</code></pre><p>Now inside a <code class="inline-code">CounterButton.vue</code> component, you can call the action instead of directly mutating the state:</p><pre class=" language-vue"><code class="prism  language-vue">&lt;script setup&gt;
import { useCounterStore } from '../stores/CounterStore.js&rsquo;
const counter = useCounterStore()
&lt;/script&gt;

&lt;template&gt;
  &lt;button @click="counter.increment"&gt;Increment&lt;/button&gt;
  &lt;button @click="counter.fetchInitialCount"&gt;Load Initial Count&lt;/button&gt;
  &lt;p&gt;Count is: {{ counter.count }}&lt;/p&gt;
&lt;/template&gt;
</code></pre><p>From the code modification above, the <code class="inline-code">increment()</code> is a simple action that directly modifies the store&rsquo;s state, while the <code class="inline-code">fetchInitialCount()</code> demonstrates that action also handles asynchronous tasks, like timers or APIs. And since Pinia stores are reactive, once an action updates the state, all components using that store will instantly reflect the new value.</p><h2 id="wrapping-up">Wrapping Up</h2><p>State management in Vue doesn&rsquo;t have to be overwhelming. Start small with <code class="inline-code">ref</code> and <code class="inline-code">reactive</code> for local state. When components need to communicate, <code class="inline-code">props</code> and <code class="inline-code">emits</code> are a natural fit. As your app grows, <code class="inline-code">provide/inject</code> helps reduce prop drilling and keeps things organized.</p><p>But when your application requires a state that&rsquo;s shared and consistent across many components, Pinia stands out. It provides a centralized, scalable store that serves as the single source of truth for your app.</p><p>Knowing when to use each approach is the real key. You don&rsquo;t need Pinia from day one, but as your project becomes more complex, you&rsquo;ll appreciate its structure and reliability. With these options in hand, you can manage state confidently&mdash;whether you&rsquo;re building a small widget or a full-scale production app.</p><p>If you want to go beyond the basics, the official <a target="_blank" href="https://pinia.vuejs.org/">Pinia documentation</a> is the best next step. It dives into plugins, advanced patterns, devtools integration and more&mdash;all explained in a clear and practical way.</p><img src="https://feeds.telerik.com/link/23057/17181510.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:2b487508-724d-4c58-b2bb-b06100a96516</id>
    <title type="text">Vue Basics: Mastering the Vue Lifecycle Hooks</title>
    <summary type="text">Understand each stage of the Vue 3 component lifecycle using the Composition API—creation, mounting, updating and unmounting—and the corresponding hook.</summary>
    <published>2025-09-15T14:40:47Z</published>
    <updated>2026-04-07T16:18:32Z</updated>
    <author>
      <name>David Adeneye Abiodun </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/23057/17155171/vue-basics-mastering-vue-lifecycle-hooks"/>
    <content type="text"><![CDATA[<p><span class="featured">Understand each stage of the Vue 3 component lifecycle using the Composition API&mdash;creation, mounting, updating and unmounting&mdash;and the corresponding hook.</span></p><p>If you&rsquo;ve been working with Vue or are new to it, understanding how your Vue components are created, updated and eventually destroyed is very important, and one of the most powerful concepts you&rsquo;ll encounter is lifecycle hooks. These hooks give precise control over what your component does at each phase of its life, from setup and rendering to updates and cleanup.</p><p>Whether you&rsquo;re spinning up a simple component or architecting a complex application, getting a handle on lifecycle hooks will save you from hours of confusion down the road. In this guide, we&rsquo;ll walk through each stage of the Vue 3 component lifecycle using the Composition API, the modern and scalable approach to building Vue apps.</p><h2 id="prerequisites">Prerequisites</h2><ul><li>To follow along, you&rsquo;ll need <a target="_blank" href="http://node.js"></a><a target="_blank" href="http://node.js">Node.js</a> installed on your machine. If you&rsquo;re new to Vue, feel free to check out this <a target="_blank" href="https://www.telerik.com/blogs/vue-basics-getting-started-vuejs-visual-studio-code">setup guide for getting a fresh Vue project up and running</a>.</li><li>Alternatively, you can fork this <a target="_blank" href="https://codesandbox.io/p/devbox/vue-3-boilerplate-template-m5v8g4"></a><a target="_blank" href="https://codesandbox.io/p/devbox/vue-3-boilerplate-template-m5v8g4">Vue 3 CodeSandbox boilerplate</a>, which I&rsquo;ll be using throughout the tutorial.</li><li>A basic knowledge of <a target="_blank" href="http://vue.js"></a><a target="_blank" href="http://vue.js">Vue.js</a>.</li></ul><p>Cool? Let&rsquo;s dive in.</p><h2 id="what-are-vue.js-lifecycle-hooks">What Are Vue.js Lifecycle Hooks?</h2><p>When a Vue component instance is created, it follows a series of initialization steps: setting up data observation, compiling the template, mounting to the DOM, updating the DOM as data changes and unmounting the DOM. During this process, Vue invokes lifecycle hooks, allowing developers to run custom code at specific stages.</p><p>Lifecycle hooks are crucial in Vue.js because they offer insight into the framework&rsquo;s inner workings. They enable you to perform actions during different stages of the Vue component lifecycle. These stages include creation, mounting, updating and unmounting.</p><p>Understanding these stages and the hooks associated with each is key to grasping the Vue reactivity system and managing component behavior effectively.</p><h2 id="the-vue-composition-api">The Vue Composition API</h2><p>Vue components can be authored in two different <a target="_blank" href="https://vuejs.org/guide/introduction.html#api-styles">API styles</a>: Options API and the Composition API introduced in Vue 3.</p><p>The Composition API is a set of APIs that allows us to author Vue components using imported functions instead of declaring options. One of the primary advantages of Composition API is that it enables efficient logic reuse in the form of composable functions. It solves <a target="_blank" href="https://vuejs.org/guide/reusability/composables.html#vs-mixins">the drawbacks of mixins</a>, the primary logic reuse mechanism of the Options API. It helps developers organize complex logic and separate concerns into modular, testable units, making their codebases easier to maintain and scale.</p><p>Vue 3 maintains backward compatibility with Vue 2, so both versions share similar lifecycle hooks. However, when you use the Composition API, you access these hooks a bit differently. We will look at the lifecycle hooks using the Composition API style.</p><p>Moving forward, we will categorize all the various types of lifecycle hooks according to their functionality: creation, mounting, updating and unmounting hooks. We will explore each hook and showcase actions that are allowed at each stage of the instance lifecycle.</p><p>Below is the diagram for the instance lifecycle hook:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2025/2025-08/lifecycle-hook-diagram.png?sfvrsn=8a8edb00_2" alt="" /><br /><span style="font-size:11px;">Reference: <a target="_blank" href="https://vuejs.org/guide/essentials/lifecycle.html">https://vuejs.org/guide/essentials/lifecycle.html</a></span></p><p>Open your Vue starter application or use the codesandbox boilerplate to follow along. Open the <code class="inline-code">HelloWorld.vue</code> file in your <code class="inline-code">component</code> folder and edit the code with this below:</p><pre class=" language-vue"><code class="prism  language-vue">&lt;template&gt;
  &lt;h1&gt;{{ msg }}&lt;/h1&gt;

  &lt;div class="card"&gt;
     &lt;button type="button" @click="increment"&gt;Count is {{ count }}&lt;/button&gt;
 &lt;/div&gt;
&lt;/template&gt;

&lt;script &gt;
// import the lifecycle hooks from vue
import { ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from "vue";

export default {
 setup() {
   // use ref to create a reactive state
   const msg = ref("Hello Vue 3");
   const count = ref(0);

   // define a method to increment the counter
   const increment = () =&gt; {
     count.value++;
   };

   // return the state and methods to the template
   return {
     msg,
     count,
     increment
   };
 }
};
&lt;/script&gt;

&lt;style scoped&gt;
.read-the-docs {
 color: #888;
}
&lt;/style&gt;
</code></pre><p>This code above is a counter component that updates the state of the count when the button is clicked. We will use only the <code class="inline-code">script</code> section to explain the subsequent part of this article. Also, in Composition API, you need to import lifecycle hooks in your project before you can use them, just as shown in the code snippet above.</p><h2 id="creation-phase-setup-hook">Creation Phase (setup() Hook)</h2><p>The creation phase is the initial stage of a Vue component lifecycle. This phase is critical because it lays the groundwork for everything that follows in the component&rsquo;s existence. During this phase, Vue begins constructing the component instance before it is added to the DOM. At this point, Vue sets up reactivity by observing the component&rsquo;s reactive state. This phase includes the <code class="inline-code">beforeCreate</code> and <code class="inline-code">created</code> lifecycle hooks in the Options API, while in the Composition API, this is where the <code class="inline-code">setup()</code> hook is called.</p><h3 id="the-setup-hook">The setup() Hook</h3><p>The <code class="inline-code">setup()</code> hook serves as the entry point for Composition API usage in your component. It is the first lifecycle hook called in Vue, triggered immediately after the application instance is initialized. In the Composition API, the <code class="inline-code">setup()</code> hook replaces the <code class="inline-code">beforeCreate()</code> and <code class="inline-code">created()</code> hooks previously used in the Options API.</p><p>The <code class="inline-code">setup()</code> hook is ideal for tasks that should run once the component is fully set up, such as configuring event listeners or fetching data from an API.</p><p>Add the following code below within your setup hook:</p><pre class=" language-vue"><code class="prism  language-vue">alert("The creation phase has been initialized")
</code></pre><p>The code above will display this:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2025/2025-08/vue-setup-hook.gif?sfvrsn=a1ca082a_2" alt="" /></p><p>You&rsquo;ll notice the alert statement runs before the component is loaded. This occurs because the function is executed before the Vue engine creates and mounts the <code class="inline-code">app</code> component.</p><h2 id="mounting-phase-mounting-hooks">Mounting Phase (Mounting Hooks)</h2><p>The mounting phase marks the moment your Vue component renders for the first time and is inserted into the DOM, making it visible in the browser. This phase begins after the component has been fully created and reactive systems have been initialized (in the creation phase).</p><p>The associated lifecycle hooks in this phase give you precise control over what to do before and after the initial render, making them powerful tools for managing the component&rsquo;s dynamic behavior. Keep in mind that these hooks don&rsquo;t run during server-side rendering since the server doesn&rsquo;t have access to a real DOM.</p><p>In the Options API, the mounting phase includes two hooks: <code class="inline-code">beforeMount()</code> and <code class="inline-code">mounted()</code>. The equivalent hooks in Composition API are <code class="inline-code">onBeforeMount()</code> and <code class="inline-code">onMounted()</code>.</p><h3 id="onbeforemount">onBeforeMount()</h3><p>After Vue runs the <code class="inline-code">setup()</code> hook, it immediately calls the <code class="inline-code">onBeforeMount()</code> lifecycle hook. By this time, Vue has already initialized the component&rsquo;s reactive state, but it hasn&rsquo;t created the DOM yet. Since the component is about to render for the first time, you still can&rsquo;t access or manipulate the DOM&mdash;the <code class="inline-code">element</code> property (which points to the root DOM element) isn&rsquo;t available yet.</p><p>It&rsquo;s an ideal place for setup logic that needs to run before the initial render but doesn&rsquo;t depend on the DOM being available:</p><pre class=" language-vue"><code class="prism  language-vue"> onBeforeMount(() =&gt; {
   alert('onBeforeMount has been initialized')
 });
</code></pre><h3 id="onmounted">onMounted()</h3><p>After Vue calls the <code class="inline-code">onBeforeMount()</code> hook and prepares the component for rendering, it proceeds to mount the component by rendering its template and inserting the resulting DOM element into the page. Once Vue completes this process, it immediately calls the <code class="inline-code">onMounted()</code> lifecycle hook.</p><p>At this point, the component is fully available in the DOM. Now you can safely perform operations that depend on DOM access, such as interacting with DOM elements, setting up event listeners or making API calls that require the DOM to be available.</p><p>In the Composition API, <code class="inline-code">onMounted()</code> is registered inside the <code class="inline-code">setup()</code> function and is called once after the first render:</p><pre class=" language-vue"><code class="prism  language-vue"> onMounted(() =&gt; {
    // You can now access the DOM or perform other side effects
    console.log('Component is mounted and the DOM is now accessible.');
 });
</code></pre><p>Unlike <code class="inline-code">onBeforeMount()</code>, which runs before the DOM is ready, <code class="inline-code">onMounted()</code> runs after the DOM is fully constructed, making it an ideal hook for any logic that needs the component to be fully rendered and interactive in the DOM.</p><h2 id="updating-phase-updating-hooks">Updating Phase (Updating Hooks)</h2><p>The updating phase is that phase of the Vue lifecycle that is triggered whenever the component reactive data changes. Whenever a reactive value changes, Vue automatically rerenders the affected parts of the DOM using its virtual DOM diffing algorithm. These two hooks below let you tap into the update process before and after a DOM rerender occurs:</p><h3 id="onbeforeupdate">onBeforeUpdate()</h3><p>After the mounting hooks, <code class="inline-code">onBeforeUpdate()</code> is called right before a component is about to update its DOM tree due to a reactive state change. It gives you access to interact with or inspect the DOM state before Vue updates the DOM based on the new reactive values.</p><p>It&rsquo;s helpful when you want to capture the DOM state before changes, debug rendering issues by comparing old vs new state, perform cleanup or prep work before the DOM is updated etc. It&rsquo;s the equivalent of the <code class="inline-code">beforeMount()</code> hook in the Options API.</p><p>Here is the basic usage in our counter app:</p><pre class=" language-vue"><code class="prism  language-vue"> onBeforeUpdate(() =&gt; {
     // DOM is still in its previous state
     alert('onBeforeUpdate has been called');
 });

 onMounted(() =&gt; {
    console.log('onMounted hook');
    console.log(count.value);
 });
</code></pre><h3 id="onupdated">onUpdated()</h3><p>In the Composition API, the <code class="inline-code">onUpdated()</code> hook is called after the component has rerendered due to a change in its reactive state. It gives you access to the updated DOM, making it ideal for performing post-render DOM-dependent operations. It&rsquo;s advisable not to mutate components in the updated hook, as it will likely lead to an infinite update loop.</p><p>It pairs well with <code class="inline-code">onBeforeUpdate()</code> as it allows you to access the old DOM and the new DOM. For instance, this pairing is powerful for comparing before-and-after states or restoring scroll and focus behavior. It&rsquo;s the equivalent of the <code class="inline-code">updated()</code> hook in the Options API.</p><p>Pair it with the previous demo in the <code class="inline-code">onBeforeUpdate()</code> example:</p><pre class=" language-vue"><code class="prism  language-vue">  onBeforeUpdate(() =&gt; {
     // DOM is still in its previous state
     alert('onBeforeUpdate hook');
     console.log('About to update the current value:', count.value);
  });

  onUpdated(() =&gt; {
     console.log("onUpdated hook");
     console.log(count.value);
     // You can now interact with updated DOM elements here
  });
</code></pre><p>Click on the counter button and look into the console to see the flow of the lifecycle hooks:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2025/2025-08/vue-lifecycle-hook-flow.gif?sfvrsn=ec935d88_2" alt="" /></p><h2 id="unmounting-phase-unmounting-hooks">Unmounting Phase (Unmounting Hooks)</h2><p>The unmounting phase marks the final stage in a Vue component&rsquo;s lifecycle. It begins when Vue decides to remove the component from the DOM. During this phase, Vue actively cleans up the component to free memory, prevent memory leaks, and stop any reactive effects or event listeners from running after the component is no longer needed.</p><p>In the Options API, this phase includes two hooks: <code class="inline-code">beforeUnmount()</code> and <code class="inline-code">unmounted()</code>. In the Composition API, you&rsquo;ll use <code class="inline-code">onBeforeUnmount()</code> and <code class="inline-code">onUnmounted()</code> instead. The unmounting phase in Vue 3 focuses entirely on cleaning up side effects before Vue removes the component from the DOM.</p><h3 id="onbeforeunmount">onBeforeUnmount()</h3><p>In Vue 3&rsquo;s Composition API, Vue calls the <code class="inline-code">onBeforeUnmount()</code> hook right before it unmounts and removes the component from the DOM. This hook gives you a final opportunity to run cleanup logic while the component instance and its DOM elements are still fully accessible and functional.</p><pre class=" language-vue"><code class="prism  language-vue"> onBeforeUnmount(() =&gt; {
    console.log('Component is about to be unmounted.');
 });
</code></pre><p>This hook is ideal for removing event listeners, cancelling timers, deleting variables and destroying third-party tools especially if they depend on the current DOM or component state.</p><h3 id="onunmounted">onUnmounted()</h3><p>The <code class="inline-code">onUnmounted()</code> hook is called after a component has been fully removed from the DOM and its reactive effects have been cleaned up. Think of <code class="inline-code">onUnmounted()</code> as the last opportunity to clean up any side effects that do not require access to the DOM or to the component&rsquo;s internal state (since the DOM and reactive context are now gone or inactive). It is suitable for cleaning up global side effects that could cause memory leaks or unexpected behavior if left running after the component is gone.</p><pre class=" language-vue"><code class="prism  language-vue">onUnmounted(() =&gt; {
    console.log('Component has been unmounted.');
});
</code></pre><p>It is your final cleanup hook perfect for shutting down anything that continues running outside the component&rsquo;s DOM.</p><h2 id="additional-life-cycle-hooks">Additional Life Cycle Hooks</h2><p>Vue 3 introduced several additional lifecycle hooks beyond those in Vue 2, particularly in the Composition API to support more advanced use cases, improve flexibility and enhance support for features like Suspense, teleport and asynchronous setup.</p><p>Below is a breakdown of these additional lifecycle hooks and how you can use them:</p><h3 id="onrendertracked-and-onrendertriggered">onRenderTracked() and onRenderTriggered()</h3><p>These hooks help you debug your component&rsquo;s reactivity behavior during development. Vue calls <code class="inline-code">onRenderTracked()</code> during development when the component&rsquo;s render effect tracks a reactive dependency. This hook helps you debug reactivity by showing which reactive properties the component tracks while rendering.</p><p>The <code class="inline-code">onRenderTriggered()</code> hook runs during development when a reactive dependency triggers a rerender. It helps debug performance issues and shows you which reactive value caused the component to rerender. Both <code class="inline-code">onRenderTracked()</code> and <code class="inline-code">onRenderTriggered()</code> are only active in development mode, not in production builds. They are equivalent to the <code class="inline-code">renderTracked()</code> and <code class="inline-code">renderTriggered()</code> in the Option API.</p><p>Here is a syntax demo structure:</p><pre class=" language-vue"><code class="prism  language-vue">import { onRenderTracked, onRenderTriggered} from 'vue'

onRenderTracked((event) =&gt; {
   console.log('Tracked:', event)
 })

onRenderTriggered((event) =&gt; {
   console.log('Triggered by:', event)
 })
</code></pre><h3 id="onactivated-and-ondeactivated">onActivated() and onDeactivated()</h3><p>Vue calls these hooks only when the component is inside a <code class="inline-code">&lt;KeepAlive&gt;</code> wrapper. It calls <code class="inline-code">onActivated()</code> after inserting the component instance back into the DOM from the cached tree managed by <code class="inline-code">KeepAlive</code>. Use this hook to restore state, restart timers or refetch data when the component becomes active again.</p><p>The <code class="inline-code">onDeactivated()</code> hook is called after the component instance is removed from the DOM as part of a tree cached by <code class="inline-code">KeepAlive&gt;</code>. It&rsquo;s ideal for pausing timers, cancelling API polling or temporarily stopping reactive effects while the component is inactive. They are equivalent to the <code class="inline-code">activated()</code> and <code class="inline-code">deactivated()</code> in Option API.</p><h3 id="onerrorcaptured">onErrorCaptured()</h3><p>Vue calls this hook when it captures an error in the component or any of its child components. This hook works like an error boundary: It lets you handle or suppress errors from children component to prevent them from crashing the entire app. In the Options API, this hook is equivalent to <code class="inline-code">errorCaptured()</code>.</p><p>Here is a syntax demo structure:</p><pre class=" language-vue"><code class="prism  language-vue">import { onErrorCaptured } from 'vue'

 onErrorCaptured((err, instance, info) =&gt; {
   console.error('Captured error:', err)
   return false // prevent further propagation
 })
</code></pre><h3 id="onserverprefetch">onServerPrefetch()</h3><p>This hook is a special lifecycle hook used exclusively for server-side rendering (SSR) in Vue. It runs on the server before the component is rendered, allowing you to perform asynchronous data fetching ahead of time. This allows the component to be rendered with all the necessary data already available.</p><p>Here is a syntax demo structure:</p><pre class=" language-vue"><code class="prism  language-vue">import { onServerPrefetch } from 'vue'

setup() {
  onServerPrefetch(async () =&gt; {
    // Fetch your async data here
  })
}
</code></pre><p>The additional lifecycle hooks provide fine-grained control over component behavior in complex use cases such as debugging, error handling, server-side rendering and component activation/deactivation for improved performance and UX.</p><h2 id="wrapping-up">Wrapping Up</h2><p>That&rsquo;s a long read. Lifecycle hooks are your window into how Vue operates behind the scenes. Whether you&rsquo;re building a simple component or architecting complex dynamic interfaces, understanding the functionalities of these hooks will help you write cleaner, more maintainable components.</p><p>That said, the true power of lifecycle hooks lies in how well you organize, optimize and clean up your logic within them. The way you handle this will directly impact the performance and maintainability of your components.</p><p>Below are a few best practices I recommend when working with lifecycle hooks:</p><ul><li>Organize your lifecycle hooks clearly and logically: Group related hooks together and follow a consistent order within your component. Doing this makes your code easier to follow and helps you (and your teammates) understand how the component behaves at each stage. For example, if you add an event listener in <code class="inline-code">onMounted()</code>, clean it up in <code class="inline-code">onUnmounted()</code> right afterward to keep related logic easy to trace.</li><li>Avoid heavy operations inside lifecycle hooks: Avoid executing heavy tasks like large computations, complex DOM manipulations or intense loops directly within hooks such as <code class="inline-code">onMounted()</code> or <code class="inline-code">onUpdated()</code>. These operations can block the browser&rsquo;s main thread, leading to performance issues.</li><li>Always perform cleanup in <code class="inline-code">onBeforeUnmount()</code> or <code class="inline-code">onUnmounted</code>: Clean up side effects like intervals, timeouts, event listeners, subscriptions and third-party libraries when your component is unmounted.</li><li>Keep logic declarative and reactive: Instead of managing DOM or state imperatively, lean into Vue&rsquo;s reactivity system. Use <code class="inline-code">computed()</code> for derived values and <code class="inline-code">watch()</code> to react to changes. Avoid recalculating or updating state manually in lifecycle hooks when you can rely on Vue&rsquo;s built-in tools.</li></ul><p>To learn more deeply about lifecycle hooks in Option API, check out this link: <a target="_blank" href="https://vuejs.org/guide/essentials/lifecycle.html">Lifecycle Hooks with Options API</a>.</p><p>Happy coding!</p><img src="https://feeds.telerik.com/link/23057/17155171.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:f7a150a9-b1ac-484f-86c0-018b15d1f410</id>
    <title type="text">Searching Your Site Without a Database</title>
    <summary type="text">Learn how to work with fuzzy search, scoring and multiple fields and allow your Nuxt app to search static data.</summary>
    <published>2025-08-22T14:15:00Z</published>
    <updated>2026-04-07T16:18:32Z</updated>
    <author>
      <name>Jonathan Gamble </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/23057/17124627/searching-site-without-database"/>
    <content type="text"><![CDATA[<p><span class="featured">Learn how to work with fuzzy search, scoring and multiple fields and allow your Nuxt app to search static data.</span></p><p>Do you ever see documentation websites that have a search feature and it&rsquo;s super fast? Well, the novices decide to use a real database like Algolia and potentially pay for it. The pros use pure JavaScript.</p><h2 id="tldr">TL;DR</h2><p>Learn to create a Nuxt application that can search static data. Set the data as an object, then filter through the results in real time to get the desired page. This demo adds fuzzy search, uses scoring and handles multiple fields.</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2025/2025-07/search.png?sfvrsn=a2b517f1_2" alt="Search box where user has entered mary mc, and suggestions come up for Marty McFly and Enchantment Under the Sea Dance" /></p><h2 id="nuxt">Nuxt</h2><p>This demo uses Nuxt and assumes you have a basic understanding of composables, pages and layouts.</p><h3 id="layout">Layout</h3><p>Create the default layout.</p><pre class=" language-tsx"><code class="prism  language-tsx">// layouts/default.vue

&lt;template&gt;
  &lt;main class="min-h-screen bg-gray-100 text-gray-800"&gt;
    &lt;div class="flex flex-col items-center py-8"&gt;
      &lt;img src="https://www.telerik.com/bttf.webp" alt="Back to the Future man!" /&gt;
    &lt;/div&gt;
    &lt;div class="max-w-4xl mx-auto px-6"&gt;
      &lt;slot /&gt;
    &lt;/div&gt;
    &lt;nav class="bg-white border-t border-gray-200 mt-12 py-8"&gt;
      &lt;div class="max-w-4xl mx-auto px-6"&gt;
        &lt;h2 class="text-xl font-bold text-blue-700 mb-4"&gt;
          Back to the Future Archive
        &lt;/h2&gt;
        &lt;ul class="grid grid-cols-1 sm:grid-cols-2 gap-3"&gt;
          &lt;li&gt;
            &lt;NuxtLink to="/" class="hover:underline text-blue-600"&gt;
              Home
            &lt;/NuxtLink&gt;
          &lt;/li&gt;
          &lt;li&gt;
            &lt;NuxtLink
              to="/vehicles/delorean"
              class="hover:underline text-blue-600"
            &gt;
              The DeLorean Time Machine
            &lt;/NuxtLink&gt;
          &lt;/li&gt;
          &lt;li&gt;
            &lt;NuxtLink
              to="/characters/marty"
              class="hover:underline text-blue-600"
            &gt;
              Marty McFly
            &lt;/NuxtLink&gt;
          &lt;/li&gt;
          &lt;li&gt;
            &lt;NuxtLink
              to="/characters/doc-brown"
              class="hover:underline text-blue-600"
            &gt;
              Doc Emmett Brown
            &lt;/NuxtLink&gt;
          &lt;/li&gt;
          &lt;li&gt;
            &lt;NuxtLink
              to="/timeline/hill-valley"
              class="hover:underline text-blue-600"
            &gt;
              Hill Valley Timeline
            &lt;/NuxtLink&gt;
          &lt;/li&gt;
          &lt;li&gt;
            &lt;NuxtLink
              to="/tech/hoverboard"
              class="hover:underline text-blue-600"
            &gt;
              Hoverboard Technology
            &lt;/NuxtLink&gt;
          &lt;/li&gt;
          &lt;li&gt;
            &lt;NuxtLink
              to="/characters/biff"
              class="hover:underline text-blue-600"
            &gt;
              Biff Tannen's Antagonism
            &lt;/NuxtLink&gt;
          &lt;/li&gt;
          &lt;li&gt;
            &lt;NuxtLink
              to="/tech/flux-capacitor"
              class="hover:underline text-blue-600"
            &gt;
              Flux Capacitor
            &lt;/NuxtLink&gt;
          &lt;/li&gt;
          &lt;li&gt;
            &lt;NuxtLink
              to="/timeline/1985-vs-2015"
              class="hover:underline text-blue-600"
            &gt;
              1985 vs. 2015
            &lt;/NuxtLink&gt;
          &lt;/li&gt;
          &lt;li&gt;
            &lt;NuxtLink
              to="/events/enchantment-dance"
              class="hover:underline text-blue-600"
            &gt;
              Enchantment Under the Sea Dance
            &lt;/NuxtLink&gt;
          &lt;/li&gt;
          &lt;li&gt;
            &lt;NuxtLink
              to="/shop/merchandise"
              class="hover:underline text-blue-600"
            &gt;
              Back to the Future Merchandise
            &lt;/NuxtLink&gt;
          &lt;/li&gt;
        &lt;/ul&gt;
      &lt;/div&gt;
    &lt;/nav&gt;
  &lt;/main&gt;
&lt;/template&gt;
</code></pre><p>This is just some links to Back to the Future pages.</p><h2 id="create-catch-all-route">Create Catch-All Route</h2><p>I didn&rsquo;t feel like manually creating a bunch of pages, so I just generated them. In a real world app, you will probably have generated static files created in markdown. If you generate pages from a database, you would probably use the database itself to search.</p><pre class=" language-tsx"><code class="prism  language-tsx">// pages/[...slug].vue

&lt;script setup lang="ts"&gt;
import { useRoute } from "vue-router";

const route = useRoute();

const fullPath = "/" + (route.params.slug as string[]).join("/");
const page = items.find((item) =&gt; item.url === fullPath);
&lt;/script&gt;

&lt;template&gt;
  &lt;div v-if="page"&gt;
    &lt;h1&gt;{{ page.title }}&lt;/h1&gt;
    &lt;p&gt;{{ page.description }}&lt;/p&gt;
  &lt;/div&gt;
  &lt;div v-else&gt;
    &lt;h1&gt;404 - Page Not Found&lt;/h1&gt;
    &lt;p&gt;The requested page does not exist.&lt;/p&gt;
  &lt;/div&gt;
&lt;/template&gt;
</code></pre><p>Again, this is for demo purposes only.</p><h2 id="searching-the-actual-data">Searching: The Actual Data</h2><p>We want to store our data in a large object.</p><pre class=" language-tsx"><code class="prism  language-tsx">// composables/useSearch.ts

type BTTFItem = {
    title: string;
    description: string;
    url: string;
};

export const items: BTTFItem[] = [
    {
        title: "The DeLorean Time Machine",
        description: "Explore the iconic DeLorean, the time-traveling car built by Doc Brown.",
        url: "/vehicles/delorean"
    },
    {
        title: "Marty McFly",
        description: "Learn all about the skateboarding teenager who travels through time.",
        url: "/characters/marty"
    },
    {
        title: "Doc Emmett Brown",
        description: "Meet the eccentric inventor behind the time machine.",
        url: "/characters/doc-brown"
    },
    {
        title: "Hill Valley Timeline",
        description: "A deep dive into the changing history of Hill Valley across the trilogy.",
        url: "/timeline/hill-valley"
    },
    {
        title: "Hoverboard Technology",
        description: "Discover the future of personal transportation with hoverboards.",
        url: "/tech/hoverboard"
    },
    {
        title: "Biff Tannen's Antagonism",
        description: "Explore the many timelines where Biff makes life difficult for Marty.",
        url: "/characters/biff"
    },
    {
        title: "Flux Capacitor",
        description: "The core component that makes time travel possible.",
        url: "/tech/flux-capacitor"
    },
    {
        title: "1985 vs. 2015",
        description: "Compare the original 1985 to the future version envisioned in Part II.",
        url: "/timeline/1985-vs-2015"
    },
    {
        title: "Enchantment Under the Sea Dance",
        description: "The pivotal high school dance that almost erased Marty from existence.",
        url: "/events/enchantment-dance"
    },
    {
        title: "Back to the Future Merchandise",
        description: "Browse collectibles, clothes, and posters from the BTTF universe.",
        url: "/shop/merchandise"
    }
];
</code></pre><h2 id="how-search-works">How Search Works</h2><p>There is no secret to searching in Vanilla JS. We use a filter.</p><pre class=" language-tsx"><code class="prism  language-tsx">items.filter(item =&gt; item.title.includes(q));
</code></pre><p>Now, we must check for lowercase, check both title and description, deal with white space before and after, and handle signals.</p><h2 id="usesearch">useSearch</h2><p>For our first simplest version, we create a composable.</p><pre class=" language-tsx"><code class="prism  language-tsx">export function useSearch() {

    const query = ref('');
    const results = ref&lt;BTTFItem[]&gt;([]);

    function search() {
        const q = query.value.trim().toLowerCase();
        results.value = q === ''
            ? []
            : items.filter(item =&gt;
                item.title.toLowerCase().includes(q) ||
                item.description.toLowerCase().includes(q)
            );
    }
    return {
        query,
        results,
        search
    };
}
</code></pre><p>This composable searches through our <code class="inline-code">items</code> array and filters out the results.</p><p> Notice the <code class="inline-code">items</code> array is declared outside the hook itself, as we only need to declare the static data once.</p><h2 id="usage">Usage</h2><p>We need to import the the composable and use it in our search component. Make sure to use proper separation of concerns and the single responsibility principle.</p><p> Keep the functionality in the composable.</p><pre class=" language-tsx"><code class="prism  language-tsx">&lt;script setup lang="ts"&gt;
const { query, results, search } = useSearch();
&lt;/script&gt;

&lt;template&gt;

  &lt;div class="p-6 max-w-xl mx-auto relative"&gt;
    &lt;input
      type="text"
      v-model="query"
      @input="search"
      placeholder="Search Back to the Future..."
      class="w-full rounded-lg border border-gray-300 px-4 py-3 text-sm shadow-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
    /&gt;

    &lt;transition
      enter-active-class="transition duration-200 ease-out"
      enter-from-class="opacity-0"
      enter-to-class="opacity-100"
      leave-active-class="transition duration-150 ease-in"
      leave-from-class="opacity-100"
      leave-to-class="opacity-0"
    &gt;
      &lt;ul
        v-if="query &amp;&amp; results.length"
        class="absolute left-0 right-0 mt-2 z-50 bg-white border border-gray-200 rounded-lg shadow-lg max-h-80 overflow-y-auto"
      &gt;
        &lt;li
          v-for="(item, index) in results"
          :key="index"
          class="hover:bg-gray-50 border-b border-gray-100 last:border-0"
        &gt;
          &lt;NuxtLink
            :to="item.url"
            class="block px-4 py-3 text-blue-600 font-medium"
          &gt;
            {{ item.title }}
          &lt;/NuxtLink&gt;
        &lt;/li&gt;
      &lt;/ul&gt;
    &lt;/transition&gt;

    &lt;transition
      enter-active-class="transition duration-200 ease-out"
      enter-from-class="opacity-0"
      enter-to-class="opacity-100"
      leave-active-class="transition duration-150 ease-in"
      leave-from-class="opacity-100"
      leave-to-class="opacity-0"
    &gt;
      &lt;p
        v-if="query &amp;&amp; !results.length"
        class="absolute left-0 right-0 mt-2 z-50 bg-white border border-gray-200 rounded-lg shadow-lg px-4 py-3 text-center text-gray-500 italic"
      &gt;
        No results found.
      &lt;/p&gt;
    &lt;/transition&gt;
  &lt;/div&gt;
&lt;/template&gt;
</code></pre><p> Vue has some beautiful transition effects that work great with Tailwind.</p><p>For basic filtering examples:</p><ul><li><a target="_blank" href="https://www.telerik.com/kendo-vue-ui/components/grid/filtering">Kendo UI for Vue Grid Filtering</a></li><li><a target="_blank" href="https://www.telerik.com/kendo-react-ui/components/knowledge-base/grid-search">KendoReact Grid Search Bar</a></li></ul><h2 id="fuzzy-search">Fuzzy Search</h2><p>I know, I know, you want more! You want to implement a basic fuzzy search too!</p><h3 id="version-1">Version 1</h3><pre class=" language-tsx"><code class="prism  language-tsx">function fuzzyMatch(source: string, target: string): boolean {
  source = source.toLowerCase();
  target = target.toLowerCase();

  let sIndex = 0;
  for (let i = 0; i &lt; target.length; i++) {
    if (source[sIndex] === target[i]) {
      sIndex++;
    }
    if (sIndex === source.length) {
      return true;
    }
  }
  return false;
}

export function useSearch() {

  const query = ref('');
  const results = ref&lt;BTTFItem[]&gt;([]);

  function search() {
    const q = query.value.trim().toLowerCase();
    if (q === '') {
      results.value = [];
      return;
    }

    results.value = items.filter(item =&gt;
      fuzzyMatch(q, item.title) || fuzzyMatch(q, item.description)
    );
  }

  return {
    query,
    results,
    search
  };
}
</code></pre><p>The <code class="inline-code">fuzzyMatch</code> algorithm checks if all characters match in order or not.</p><h3 id="example-1">Example 1</h3><pre class=" language-tsx"><code class="prism  language-tsx">fuzzyMatch("doc", "Doc Emmett Brown") // true
</code></pre><ul><li>Matches: <code class="inline-code">d</code> &rarr; found at index 0, <code class="inline-code">o</code> &rarr; index 1, <code class="inline-code">c</code> &rarr; index 2 (all in order ✅)</li></ul><h3 id="example-2">Example 2</h3><pre class=" language-tsx"><code class="prism  language-tsx">fuzzyMatch("mcf", "Marty McFly") // true
</code></pre><ul><li>Matches: <code class="inline-code">m</code> (Marty), <code class="inline-code">c</code> (McFly), <code class="inline-code">f</code> (McFly)&mdash;all in order ✅</li></ul><h3 id="example-3">Example 3</h3><pre class=" language-tsx"><code class="prism  language-tsx">fuzzyMatch("dcm", "Doc Emmett Brown") // false
</code></pre><ul><li><code class="inline-code">d</code> matches, <code class="inline-code">c</code> matches&mdash;but there&rsquo;s no <code class="inline-code">m</code> <strong>after</strong> <code class="inline-code">c</code> in the string ❌</li></ul><p>If you want to ignore spaces, you can add:</p><pre class=" language-tsx"><code class="prism  language-tsx">source = source.replace(/\s+/g, '').toLowerCase();
target = target.replace(/\s+/g, '').toLowerCase();
</code></pre><h2 id="scoring">Scoring</h2><p>Now, let&rsquo;s score the results so we can sort the order.</p><pre class=" language-tsx"><code class="prism  language-tsx">function fuzzyScore(query: string, text: string) {

    query = query.replace(/\s+/g, '').toLowerCase();
    text = text.replace(/\s+/g, '').toLowerCase();

    let score = 0;
    let lastIndex = -1;

    for (const char of query) {
        const index = text.indexOf(char, lastIndex + 1);
        if (index === -1) return 0;
        score += 1 / (index - lastIndex);
        lastIndex = index;
    }

    return score;
}

export function useSearch() {

    const query = ref('');
    const results = ref&lt;BTTFItem[]&gt;([]);

    function search() {
        const q = query.value.trim().toLowerCase();
        if (!q) {
            results.value = [];
            return;
        }

        results.value = items
            .map(item =&gt; {
                const scoreTitle = fuzzyScore(q, item.title);
                const scoreDesc = fuzzyScore(q, item.description);
                const totalScore = scoreTitle * 2 + scoreDesc;
                return { item, score: totalScore };
            })
            .filter(entry =&gt; entry.score &gt; 0)
            .sort((a, b) =&gt; b.score - a.score)
            .map(entry =&gt; entry.item);
    }

    return {
        query,
        results,
        search
    };
}
</code></pre><p>Instead of returning a <code class="inline-code">boolean</code>, we return a <code class="inline-code">score</code> and use <code class="inline-code">.sort()</code> to sort by that score. This includes the <code class="inline-code">description</code> AND the <code class="inline-code">title</code>.</p><p>Beautiful!</p><p>This is just the beginning of what you can do, but now you have the fundamentals!</p><p>The future isn&rsquo;t written yet.</p><p><strong>Repo:</strong> <a target="_blank" href="https://github.com/jdgamble555/nuxt-static-search">GitHub</a><br /><strong>Demo:</strong> <a target="_blank" href="https://nuxt-static-search.vercel.app/">Vercel</a>
</p><aside><hr data-sf-ec-immutable="" /><div class="row"><div class="col-4 u-normal-full u-small-mb0"><h4 class="u-fs20 u-fw5 u-lh125 u-mb0">Nuxt 3 Server Components Rock</h4></div><div class="col-8"><p class="u-fs16 u-mb0">See how great <a target="_blank" href="https://www.telerik.com/blogs/nuxt-3-server-components-rock">Server Components are in Vue with Nuxt</a>. They can also be used in conjunction with client components.</p></div></div></aside><img src="https://feeds.telerik.com/link/23057/17124627.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:cff9a89a-97d6-4b38-8e39-3acf71a266b8</id>
    <title type="text">Unlocking Data Sharing in Your Nuxt App</title>
    <summary type="text">Nuxt makes sharing data across components a snap. Let’s look at useState, useLocalState and useCookie.</summary>
    <published>2025-08-07T14:10:47Z</published>
    <updated>2026-04-07T16:18:32Z</updated>
    <author>
      <name>Jonathan Gamble </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/23057/17113318/unlocking-data-sharing-nuxt-app"/>
    <content type="text"><![CDATA[<p><span class="featured">Nuxt makes sharing data across components a snap. Let&rsquo;s look at useState, useLocalState and useCookie.</span></p><p>I&rsquo;ve been obsessed with <code class="inline-code">useState</code> in Nuxt since the first time I saw it. Other frameworks do not seem to share data as easily (looking at you, React Context). Nuxt has many options to store state, and it definitely makes these things easy. I love it so much, I even wrote versions for <a target="_blank" href="https://dev.to/jdgamble555/copying-nuxts-usestate-in-qwik-and-svelte-5eo3">other frameworks</a>.</p><h2 id="tldr">TL;DR</h2><p>Nuxt is the easiest framework to share data across components. You can use <code class="inline-code">useState</code>, <code class="inline-code">provide</code> and <code class="inline-code">inject</code>, or persist data across cookies with <code class="inline-code">useCookie</code>. Either way, no other framework makes it this easy.</p><h2 id="usestate">useState</h2><p>DO NOT confuse Nuxt&rsquo;s <code class="inline-code">useState</code> with React&rsquo;s <code class="inline-code">useState</code>. React&rsquo;s version is much weaker. Nuxt&rsquo;s useState, on the other hand, is awesome!</p><h3 id="how-it-works">How It Works</h3><p>Let&rsquo;s say we want to share data across components. In other frameworks, including Vue itself, you have to prop drill shared data. It is not fun. Context, on the other hand, allows you to share data by setting a &ldquo;key/value&rdquo; pair, and using it anywhere. <code class="inline-code">Nuxt</code> does this automatically.</p><pre class=" language-tsx"><code class="prism  language-tsx">useState('count', () =&gt; 0)
</code></pre><p>The first parameter is the key. In any framework with a type of <em>Context API</em> you must set a key to get a value. The second parameter is the fallback. The fallback is used when there is no value belonging to that key; however, it is also used to store the initial value itself.</p><h3 id="usage">Usage</h3><p>The recommended way to use it is to create a custom hook, or a custom composable.</p><pre class=" language-tsx"><code class="prism  language-tsx">export function useCount() {

return useState('count', () =&gt; 0)

}
</code></pre><p>This returns a reactive counter that can be used anywhere!</p><p>I know what you&rsquo;re thinking. Why not just use a normal reactive variable?</p><pre class=" language-tsx"><code class="prism  language-tsx">export function useCount() {

return ref(0)

}
</code></pre><p>This seems logical, but if you use <code class="inline-code">useCount</code> in multiple components, the state does not get shared. You will have new counters for each implementation.</p><pre class=" language-tsx"><code class="prism  language-tsx">// component 1

&lt;script setup lang="ts"&gt;
const count = useCount()
&lt;/script&gt;

&lt;template&gt;
  &lt;p&gt;Count: {{ count }}&lt;/p&gt;
  &lt;button
    @click="
      () =&gt; {
        count++;
      }
    "
  &gt;
    +
  &lt;/button&gt;
&lt;/template&gt;

// component 2
&lt;script setup lang="ts"&gt;
const count = useCount()
&lt;/script&gt;

&lt;template&gt;
&lt;div&gt; {{ count }} &lt;div&gt;
&lt;/template&gt;
</code></pre><p>If we click the button, we want the counter to increment everywhere.</p><h3 id="how-does-it-really-work">How Does It Really Work?</h3><p>I got so obsessed with the simplicity, I researched the <a target="_blank" href="https://github.com/nuxt/nuxt/blob/a7d21ece19e69703aac5fbb1f7bf53f0dfc5751f/packages/nuxt/src/app/composables/state.ts#L15">source code</a>.</p><h3 id="global-vs.-local">Global vs. Local</h3><p>Nuxt has a global store. You can get the values anywhere using <code class="inline-code">useNuxtApp()</code>.</p><pre class=" language-tsx"><code class="prism  language-tsx">export function useGlobalState&lt;T&gt;(key: string, init?: (() =&gt; T | Ref&lt;T&gt;)) {

    const nuxtApp = useNuxtApp()

    const state = toRef(nuxtApp.payload.state, key)

    if (state.value === undefined &amp;&amp; init) {

        const initialValue = init()

        state.value = initialValue
    }
    return state
}
</code></pre><p>I created a very simplified, but working, version of <code class="inline-code">useState()</code>. You get the value from the global store, make it reactive and return it. If it doesn&rsquo;t exist, create it, save it, return it.</p><pre class=" language-tsx"><code class="prism  language-tsx">export function useCount() {

return useGlobalState('count', () =&gt; 0)

}
</code></pre><p>This will work the exact same way as <code class="inline-code">useState</code>. Because it runs only once, it is called a <em>singleton</em>.</p><h2 id="uselocalstate">useLocalState</h2><p>But what if you need a local version?</p><h3 id="usage-1">Usage</h3><p>Sometimes you don&rsquo;t want to save every state value to the global store. This can make it hard to do individual component testing, create portable or isolated components, and keep data encapsulated.</p><h3 id="provide--inject">Provide / Inject</h3><p>Inspired by Angular, you can set and get a context easily in Vue / Nuxt by providing and injecting.</p><pre class=" language-tsx"><code class="prism  language-tsx">provide(key, value)

inject(key, fallback_value)
</code></pre><p>Sometimes you may see the patterns:</p><pre class=" language-tsx"><code class="prism  language-tsx">export function setCount(initialValue: number) {
const count = ref(initialValue)
provide('count', count)
return count
}

export function getCount() {
return inject&lt;Ref&lt;number&gt;&gt;('count');
}
</code></pre><p>Generally, you set and get the items in different components. However, I don&rsquo;t see why we can&rsquo;t do the same pattern as <code class="inline-code">useState</code> here as well.</p><h3 id="local">Local</h3><pre class=" language-tsx"><code class="prism  language-tsx">export function useLocalState&lt;T&gt;(key: string, init?: (() =&gt; T | Ref&lt;T&gt;)) {

    const state = inject(key, undefined)

    if (state === undefined &amp;&amp; init) {

        const initialValue = toRef(init())

        provide(key, initialValue)

        return initialValue
    }
    return state
}
</code></pre><p>Now we can set any state locally, just like <code class="inline-code">useState</code>.</p><h3 id="caveat">Caveat</h3><p>The state must be able to talk to the other state in a line. This means it must be nested. If I create two child components, and I set the first component with the counter, the second component <em>WILL NOT</em> be able to communicate with the other child component. Context is shared hierarchically, not between siblings.</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Count</span> <span class="token punctuation">/&gt;</span></span>
<span class="token comment">&lt;!-- Doesn't have access to count --&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Count</span> <span class="token punctuation">/&gt;</span></span>
</code></pre><p>However, you can initialize the counter in any parent so that they both have access to it.</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">setup</span> <span class="token attr-name">lang</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>ts<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token script language-javascript">
<span class="token function">useLocalState</span><span class="token punctuation">(</span><span class="token string">"count"</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>template</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Count</span> <span class="token punctuation">/&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Count</span> <span class="token punctuation">/&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>template</span><span class="token punctuation">&gt;</span></span>
</code></pre><p>Now you can use <code class="inline-code">useLocalState('count')</code> anywhere in the children.</p><h2 id="usecookie">useCookie</h2><p>Nuxt also has a magical way to get and set cookies on the frontend just like any other signal with <code class="inline-code">ref</code>.</p><pre class=" language-tsx"><code class="prism  language-tsx">const count = useCookie("count");

count.value = 10;
</code></pre><p>This will save the value to <code class="inline-code">10</code> and create the cookie.</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2025/2025-07/value-10.png?sfvrsn=a97d269b_2" alt="value 10" /></p><p>You can use the value just like any other reference. The beauty of this is: it will persist! You&rsquo;re not only saving a global value, you&rsquo;re saving it to the cookie.</p><h3 id="default">Default</h3><p>There are a bunch of cookie options you can set, but to set the default value, you set a function in <code class="inline-code">default</code> just like you would with <code class="inline-code">useState</code>.</p><pre class=" language-tsx"><code class="prism  language-tsx">const count = useCookie("count", {
  default: () =&gt; 10
});
</code></pre><h3 id="bonus-under-the-hood">Bonus: Under the Hood</h3><p>Cookies are a complex creature, but for the sake of explanation, I thought I would create a pseudocode version from the <a target="_blank" href="https://github.com/nuxt/nuxt/blob/a7d21ece19e69703aac5fbb1f7bf53f0dfc5751f/packages/nuxt/src/app/composables/cookie.ts#L39">source code</a>. It would require something as complex as the original to work correctly, but you get the gist.</p><pre class=" language-tsx"><code class="prism  language-tsx">export function useCookie(key, { default: init() }) {
  
  if (server) {
  const cookie = getServerCookie()
  if (cookie) {
    return getCookie(key)
  }
  const state = init()
  setCookie(key, state)
  return state
}

// browser
const cookie = getClientCookie()
if (cookie) {
return getCookie(key)
}
const state = init()
setCookie(key, state)
return toRef(state)

// !!! NOT REAL CODE, WILL NOT WORK
</code></pre><p>Saving data has never been so easy!</p><p><strong>Demo:</strong> <a target="_blank" href="https://nuxt-state.vercel.app/">Vercel</a><br /><strong>Repo:</strong> <a target="_blank" href="https://github.com/jdgamble555/nuxt-state">GitHub</a>
</p><aside><hr data-sf-ec-immutable="" /><div class="row"><div class="col-4 u-normal-full u-small-mb0"><h4 class="u-fs20 u-fw5 u-lh125 u-mb0">Nuxt 3 Server Components Rock</h4></div><div class="col-8"><p class="u-fs16 u-mb0">See how great <a target="_blank" href="https://www.telerik.com/blogs/nuxt-3-server-components-rock">Server Components are in Vue with Nuxt</a>. They can also be used in conjunction with client components.</p></div></div></aside><img src="https://feeds.telerik.com/link/23057/17113318.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:68bc97ad-7a64-43aa-99ca-5706f4539729</id>
    <title type="text">Adding Audio Effects to Dynamic Websites</title>
    <summary type="text">When we create dynamic websites, we may need to add audio. HTML5 allows us to do this with the magic of the audio component.</summary>
    <published>2025-06-27T14:59:05Z</published>
    <updated>2026-04-07T16:18:32Z</updated>
    <author>
      <name>Jonathan Gamble </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/23057/17064890/adding-audio-effects-dynamic-websites"/>
    <content type="text"><![CDATA[<p><span class="featured">When we create dynamic websites, we may need to add audio. HTML5 allows us to do this with the magic of the <code class="inline-code">&lt;audio /&gt;</code> component.</span></p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2025/2025-05/image.png?sfvrsn=8eb835a7_2" alt="image.png" /></p><h2 id="tldr">TL;DR</h2><p>These demos use Vue and Nuxt to present audio files with default controls, auto-play and customizable actions for your own app&rsquo;s needs. The concept should work in any JavaScript framework.</p><h2 id="display-sound-with-controls">Display Sound with Controls</h2><p>To add a sound into our app, we must use the <code class="inline-code">&lt;audio /&gt;</code> element with the <code class="inline-code">src</code> pointing to our filename.</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>template</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h2</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>text-2xl my-3<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Let's go Mario!<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h2</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>audio</span> <span class="token attr-name">autoplay</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>/mario.wav<span class="token punctuation">"</span></span> <span class="token attr-name">preload</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>auto<span class="token punctuation">"</span></span> <span class="token attr-name">controls</span> <span class="token punctuation">/&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>template</span><span class="token punctuation">&gt;</span></span>
</code></pre><p>By adding <code class="inline-code">controls</code>, the browser will display the default control options.</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2025/2025-05/image-1.png?sfvrsn=f750a670_2" alt="image.png" /></p><p>You will have options to download the audio file, change the playback speed, play and pause, and modify the volume.</p><h3 id="preloading">Preloading</h3><p>We can preload the audio file by adding <code class="inline-code">preload="auto"</code> and even earlier in the header if we want.</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>link</span> <span class="token attr-name">rel</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>preload<span class="token punctuation">"</span></span> <span class="token attr-name">as</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>audio<span class="token punctuation">"</span></span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>/mario.wav<span class="token punctuation">"</span></span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>audio/wav<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
</code></pre><p>The <code class="inline-code">link</code> tag will pre-download the file as soon as the HTML is parsed in the header, but the <code class="inline-code">audio</code> tag will preload the file.</p><h3 id="multiple-sources">Multiple Sources</h3><p>If we have multiple versions of the file and we want to make sure our browser supports it, we <em>could</em> use the <code class="inline-code">source</code> tag as well, but I do not use it in our example.</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>audio</span> <span class="token attr-name">controls</span> <span class="token attr-name">preload</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>auto<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>source</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>/audio/mario.mp3<span class="token punctuation">"</span></span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>audio/mpeg<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>source</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>/audio/mario.wav<span class="token punctuation">"</span></span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>audio/wav<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
  Your browser does not support the audio element.
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>audio</span><span class="token punctuation">&gt;</span></span>
</code></pre><h3 id="autoplay">Autoplay</h3><p>If we add <code class="inline-code">autoplay</code> to our element, it will automatically play the audio file. However, this is <em>ONLY</em> after interaction to the webpage. You can see this in action by changing routes and clicking back home.</p><h2 id="laser-looping">Laser Looping</h2><p>Our laser gun example will loop the file over and over. We add the <code class="inline-code">loop</code> attribute if we want to repeat the sound. Again, this will only start playing after the page had had interaction.</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2025/2025-05/image-2.png?sfvrsn=3c4e8903_2" alt="image.png" /></p><h2 id="custom-controls">Custom Controls</h2><p>If you&rsquo;re building a game or dynamic website, you&rsquo;re going to want to customize your controls.</p><h3 id="audio-hook">Audio Hook</h3><p>We also need an audio hook to control our audio element.</p><pre class=" language-tsx"><code class="prism  language-tsx">export const useAudio = (filename: string) =&gt; {

    const audio = ref&lt;HTMLAudioElement | null&gt;(null)

    const isPlaying = ref&lt;boolean&gt;(false)
    const currentTime = ref&lt;number&gt;(0)
    const duration = ref&lt;number&gt;(0)

    const seek = () =&gt; {
        if (audio.value) audio.value.currentTime = currentTime.value
    }

    const updateTime = () =&gt; {
        if (audio.value) currentTime.value = audio.value.currentTime
    }

    const pause = () =&gt; {
        if (!audio.value) {
            return
        }
        if (audio.value.paused) {
            audio.value.play()
            return;
        }
        audio.value.pause()
    }

    onMounted(() =&gt; {

        if (!audio.value) {
            return
        }

        audio.value.src = filename
        audio.value.autoplay = true
        audio.value.ontimeupdate = updateTime
        audio.value.onplay = () =&gt; {
            isPlaying.value = true
        }
        audio.value.onpause = () =&gt; {
            isPlaying.value = false
        }
        audio.value.onloadedmetadata = () =&gt; {
            duration.value = audio.value!.duration
        }
    })

    return {
        audio,
        pause,
        isPlaying,
        currentTime,
        duration,
        seek
    }
}
</code></pre><p>Nuxt and other frameworks have libraries for this with many more features, so let&rsquo;s break down how you can create one.</p><h3 id="signals">Signals</h3><p>Anything that will require the DOM to update dynamically will need a signal.</p><pre class=" language-tsx"><code class="prism  language-tsx">const isPlaying = ref&lt;boolean&gt;(false)
const currentTime = ref&lt;number&gt;(0)
const duration = ref&lt;number&gt;(0)
</code></pre><h3 id="filename">Filename</h3><p>We set our filename by passing it to the hook.</p><pre class=" language-tsx"><code class="prism  language-tsx">audio.value.src = filename
</code></pre><p>When the component gets mounted, so does the filename.</p><h3 id="attributes">Attributes</h3><p>Our <code class="inline-code">autoplay</code> and <code class="inline-code">loop</code> attributes are equally available.</p><pre class=" language-tsx"><code class="prism  language-tsx">audio.value.autoplay = true
</code></pre><h3 id="isplaying">isPlaying</h3><p>Notice the <code class="inline-code">isPlaying</code> gets handled using the built-in <code class="inline-code">onpause</code> and <code class="inline-code">onplay</code>.</p><pre class=" language-tsx"><code class="prism  language-tsx">audio.value.onplay = () =&gt; {
    isPlaying.value = true
}
audio.value.onpause = () =&gt; {
    isPlaying.value = false
}
audio.value.onloadedmetadata = () =&gt; {
    duration.value = audio.value!.duration
}
</code></pre><h3 id="duration">Duration</h3><p>We will need to calculate the duration of the audio file as soon as it is available.</p><pre class=" language-tsx"><code class="prism  language-tsx">audio.value.onloadedmetadata = () =&gt; {
    duration.value = audio.value!.duration
}
</code></pre><h3 id="play-and-pause-button">Play and Pause Button</h3><p>We will need to toggle the play/pause correctly.</p><pre class=" language-tsx"><code class="prism  language-tsx">const pause = () =&gt; {
    if (!audio.value) {
        return
    }
    if (audio.value.paused) {
        audio.value.play()
        return
    }
    audio.value.pause()
}
</code></pre><p>If paused, we display play; if playing, we display pause.</p><p><strong>Note:</strong> We can&rsquo;t use the audio element until the reference is correctly attached to the hook with <code class="inline-code">audio.value</code>. We do nothing if not attached yet while loading.</p><h3 id="current-play-time">Current Play Time</h3><p>Instead of starting a clock when the audio starts playing, we can watch the <code class="inline-code">ontimeupdate</code> event.</p><pre class=" language-tsx"><code class="prism  language-tsx">audio.value.ontimeupdate = updateTime
</code></pre><p>We update our signal based on that time.</p><pre class=" language-tsx"><code class="prism  language-tsx">const updateTime = () =&gt; {
    if (audio.value) currentTime.value = audio.value.currentTime
}
</code></pre><h2 id="hook-usage">Hook Usage</h2><p>We call the hook inside our component.</p><pre class=" language-tsx"><code class="prism  language-tsx">&lt;script setup lang="ts"&gt;
const { audio, isPlaying, pause, currentTime, duration, seek } =
  useAudio("/basketball.wav")
&lt;/script&gt;

&lt;template&gt;
  &lt;main class="flex items-center justify-center gap-5"&gt;
    &lt;button
      type="button"
      class="p-3 border rounded-xl bg-blue-500 text-white font-bold cursor-pointer"
      v-on:click="pause"
    &gt;
      {{ isPlaying ? "Pause" : "Play" }}
    &lt;/button&gt;
    &lt;audio ref="audio" preload="auto" /&gt;
    &lt;input
      type="range"
      class="w-full accent-blue-500"
      min="0"
      :max="duration"
      step="0.1"
      v-model="currentTime"
      @input="seek"
    /&gt;
  &lt;/main&gt;
&lt;/template&gt;
</code></pre><h3 id="attach-element">Attach Element</h3><pre class=" language-tsx"><code class="prism  language-tsx">const { audio, ... } = useAudio('/filename');

...

&lt;audio ref="audio" preload="auto" /&gt;
</code></pre><h3 id="play-button">Play Button</h3><p>Since <code class="inline-code">isPlaying</code> is a signal, we can easily display the proper text.</p><pre class=" language-tsx"><code class="prism  language-tsx">{{ isPlaying ? "Pause" : "Play" }}
</code></pre><h3 id="seek-with-input">Seek with Input</h3><p>We use an range input element to seek.</p><pre class=" language-tsx"><code class="prism  language-tsx">&lt;input
  type="range"
  class="w-full accent-blue-500"
  min="0"
  :max="duration"
  step="0.1"
  v-model="currentTime"
  @input="seek"
/&gt;
</code></pre><p>We can set our <code class="inline-code">duration</code> and <code class="inline-code">currentTime</code>, and when we change it, this will update the <code class="inline-code">seek</code> function.</p><p>This is just the beginning. Now go build something and listen to your sounds!</p><p><strong>Demo:</strong> <a target="_blank" href="https://nuxt-audio.vercel.app/">Vercel</a><br /><strong>Repo:</strong> <a target="_blank" href="https://github.com/jdgamble555/nuxt-audio">GitHub</a>
</p><img src="https://feeds.telerik.com/link/23057/17064890.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:bb94dc48-4bd8-4fc6-bcae-6dcb76a7d3ff</id>
    <title type="text">A Pure SVG Circular Component</title>
    <summary type="text">Creating a dynamic SVG component is easier than ever in your framework of choice. After you master a few basic techniques, anything you can imagine can be realized.</summary>
    <published>2025-06-12T11:01:52Z</published>
    <updated>2026-04-07T16:18:32Z</updated>
    <author>
      <name>Jonathan Gamble </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/23057/17050064/pure-svg-circular-component"/>
    <content type="text"><![CDATA[<p><span class="featured">Creating a dynamic SVG component is easier than ever in your framework of choice. After you master a few basic techniques, anything you can imagine can be realized.</span></p><h2 id="tldr">TL;DR</h2><p>This article teaches you how to create an SVG progress circular component that can be used in custom components like games, character counts and charts. This example uses Vue and Nuxt, but it could be easily modified to work with any JS framework. It also uses Tailwind, but standard CSS works as well.</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2025/2025-05/svg-circular.png?sfvrsn=31a84f68_2" alt="image.png" /></p><h2 id="the-core-reusable-component">The Core Reusable Component</h2><p>We allow the user to customize the component dimensions, put an HTML object inside it and keep it reactive.</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">setup</span> <span class="token attr-name">lang</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>ts<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token script language-javascript">
<span class="token keyword">const</span> <span class="token punctuation">{</span>
  size <span class="token operator">=</span> <span class="token number">150</span><span class="token punctuation">,</span>
  width <span class="token operator">=</span> <span class="token number">20</span><span class="token punctuation">,</span>
  trailColor <span class="token operator">=</span> <span class="token string">"gray"</span><span class="token punctuation">,</span>
  strokeColor <span class="token operator">=</span> <span class="token string">"black"</span><span class="token punctuation">,</span>
  progress<span class="token punctuation">,</span>
<span class="token punctuation">}</span> <span class="token operator">=</span> defineProps<span class="token operator">&lt;</span><span class="token punctuation">{</span>
  size<span class="token operator">?</span><span class="token punctuation">:</span> number
  width<span class="token operator">?</span><span class="token punctuation">:</span> number
  trailColor<span class="token operator">?</span><span class="token punctuation">:</span> string
  strokeColor<span class="token operator">?</span><span class="token punctuation">:</span> string
  progress<span class="token punctuation">:</span> number <span class="token operator">|</span> string
<span class="token punctuation">}</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> cy <span class="token operator">=</span> size <span class="token operator">/</span> <span class="token number">2</span>
<span class="token keyword">const</span> r <span class="token operator">=</span> cy <span class="token operator">-</span> width <span class="token operator">/</span> <span class="token number">2</span>
<span class="token keyword">const</span> circumference <span class="token operator">=</span> <span class="token number">2</span> <span class="token operator">*</span> Math<span class="token punctuation">.</span>PI <span class="token operator">*</span> r

<span class="token keyword">const</span> dashOffset <span class="token operator">=</span> <span class="token function">computed</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> circumference <span class="token operator">*</span> <span class="token punctuation">(</span><span class="token number">1</span> <span class="token operator">-</span> <span class="token function">Number</span><span class="token punctuation">(</span>progress<span class="token punctuation">)</span> <span class="token operator">/</span> <span class="token number">100</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>template</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>svg</span> <span class="token attr-name">xmlns</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>http://www.w3.org/2000/svg<span class="token punctuation">"</span></span> <span class="token attr-name">:width</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>size<span class="token punctuation">"</span></span> <span class="token attr-name">:height</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>size<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>circle</span>
      <span class="token attr-name">:cx</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>cy<span class="token punctuation">"</span></span>
      <span class="token attr-name">:cy</span>
      <span class="token attr-name">:r</span>
      <span class="token attr-name">:stroke</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>trailColor<span class="token punctuation">"</span></span>
      <span class="token attr-name">fill</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>none<span class="token punctuation">"</span></span>
      <span class="token attr-name">:stroke-width</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>width<span class="token punctuation">"</span></span>
    <span class="token punctuation">/&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>circle</span>
      <span class="token attr-name">:cx</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>cy<span class="token punctuation">"</span></span>
      <span class="token attr-name">:cy</span>
      <span class="token attr-name">:r</span>
      <span class="token attr-name">fill</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>none<span class="token punctuation">"</span></span>
      <span class="token attr-name">:stroke</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>strokeColor<span class="token punctuation">"</span></span>
      <span class="token attr-name">:transform</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>`rotate(-90 ${cy} ${cy})`<span class="token punctuation">"</span></span>
      <span class="token attr-name">:stroke-dasharray</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>circumference<span class="token punctuation">"</span></span>
      <span class="token attr-name">:stroke-dashoffset</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>dashOffset<span class="token punctuation">"</span></span>
      <span class="token attr-name">:stroke-width</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>width<span class="token punctuation">"</span></span>
    <span class="token punctuation">/&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>foreignObject</span> <span class="token attr-name">:x</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>0<span class="token punctuation">"</span></span> <span class="token attr-name">:y</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>0<span class="token punctuation">"</span></span> <span class="token attr-name">:width</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>size<span class="token punctuation">"</span></span> <span class="token attr-name">:height</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>size<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>size-full flex items-center justify-center<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>slot</span> <span class="token punctuation">/&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>foreignObject</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>svg</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>template</span><span class="token punctuation">&gt;</span></span>

</code></pre><ul><li>Size &ndash; The SVG size</li><li>Width &ndash; The SVG stroke width</li><li>Trail Color &ndash; The Circle Color</li><li>Stroke Color &ndash; The progress color</li><li>Progress &ndash; The progress number (or string) between 1 and 100</li></ul><h3 id="foreign-object">Foreign Object</h3><p>The proper way to put HTML inside SVG, is to use a foreign object. To make our alignment correct, we must keep our width and height <code class="inline-code">full</code> using Tailwind or CSS, but this could be done manually outside the component.</p><h2 id="progress-selector">Progress Selector</h2><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2025/2025-05/chrome_bett7aye7r.gif?sfvrsn=fd0482e0_2" alt="chrome_bETt7AYe7R.gif" /></p><p>This component allows you to select the progress between 1 and 100 with a range selector.</p><h3 id="range-component">Range Component</h3><p>We need custom CSS to style the Range component, as this is not standard using Tailwind.</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">setup</span> <span class="token attr-name">lang</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>ts<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token script language-javascript">
<span class="token keyword">const</span> progress <span class="token operator">=</span> <span class="token function">useProgress</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>template</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>flex items-center justify-center text-center gap-5 mt-10<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>block mb-2 text-md font-medium text-gray-900<span class="token punctuation">"</span></span> <span class="token attr-name">for</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>progress<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
      Progress
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span>
      <span class="token attr-name">v-model</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>progress<span class="token punctuation">"</span></span>
      <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer<span class="token punctuation">"</span></span>
      <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>range<span class="token punctuation">"</span></span>
      <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>progress<span class="token punctuation">"</span></span>
      <span class="token attr-name">min</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>0<span class="token punctuation">"</span></span>
      <span class="token attr-name">max</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>100<span class="token punctuation">"</span></span>
      <span class="token attr-name">step</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>5<span class="token punctuation">"</span></span>
    <span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>template</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span> <span class="token attr-name">scoped</span><span class="token punctuation">&gt;</span></span><span class="token style language-css">
<span class="token selector">input<span class="token attribute">[type="range"]</span><span class="token pseudo-element">::-webkit-slider-thumb</span> </span><span class="token punctuation">{</span>
  <span class="token property">appearance</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span>
  <span class="token property">height</span><span class="token punctuation">:</span> <span class="token number">1</span>rem<span class="token punctuation">;</span>
  <span class="token property">width</span><span class="token punctuation">:</span> <span class="token number">1</span>rem<span class="token punctuation">;</span>
  <span class="token property">background-color</span><span class="token punctuation">:</span> black<span class="token punctuation">;</span>
  <span class="token property">border-radius</span><span class="token punctuation">:</span> <span class="token number">9999</span>px<span class="token punctuation">;</span>
  <span class="token property">cursor</span><span class="token punctuation">:</span> pointer<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token selector">input<span class="token attribute">[type="range"]</span><span class="token pseudo-element">::-moz-range-thumb</span> </span><span class="token punctuation">{</span>
  <span class="token property">height</span><span class="token punctuation">:</span> <span class="token number">1</span>rem<span class="token punctuation">;</span>
  <span class="token property">width</span><span class="token punctuation">:</span> <span class="token number">1</span>rem<span class="token punctuation">;</span>
  <span class="token property">background-color</span><span class="token punctuation">:</span> black<span class="token punctuation">;</span>
  <span class="token property">border-radius</span><span class="token punctuation">:</span> <span class="token number">9999</span>px<span class="token punctuation">;</span>
  <span class="token property">cursor</span><span class="token punctuation">:</span> pointer<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">&gt;</span></span>
</code></pre><h3 id="shared-progress">Shared Progress</h3><p>Nuxt has a useful feature called <code class="inline-code">useState</code>, which allows you to share state between components without prop drilling. You can share the state between the components manually using providers, injectors or context, depending on your framework.</p><pre class=" language-tsx"><code class="prism  language-tsx">**export const useProgress = () =&gt; {
    return useState('progress', () =&gt; 90);
};**
</code></pre><p>With this code, we can use <code class="inline-code">useProgress</code> in any component, and the state is shared across the component.</p><h3 id="progress-selector-1">Progress Selector</h3><p>We use the <code class="inline-code">useProgress</code> hook, and pass it to our <code class="inline-code">&lt;svg-circle /&gt;</code> component, as well as inside the slot to display the percent.</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">setup</span> <span class="token attr-name">lang</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>ts<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token script language-javascript">
<span class="token keyword">const</span> progress <span class="token operator">=</span> <span class="token function">useProgress</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>template</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>flex items-center justify-center text-center mt-10<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>svg-circle</span> <span class="token attr-name">:progress</span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h1</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>text-2xl font-bold<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>{{ progress }}%<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h1</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>svg-circle</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>range-input</span> <span class="token punctuation">/&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>template</span><span class="token punctuation">&gt;</span></span>
</code></pre><p>We display the <code class="inline-code">&lt;range-input /&gt;</code> component and the progress signal inside the SVG circle. Works like a charm.</p><p><strong>Note:</strong> We must keep the items in <code class="inline-code">flex-center</code> for proper alignment.</p><h2 id="word-counter">Word Counter</h2><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2025/2025-05/chrome_wfx80bbewm.gif?sfvrsn=64630d29_2" alt="chrome_WFX80bBEWm.gif" /></p><p>We set a character limit, compute the characters left and display the progress based on that number. With our core SVG component, this becomes extremely easy.</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">setup</span> <span class="token attr-name">lang</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>ts<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token script language-javascript">
<span class="token keyword">const</span> CHARACTER_LIMIT <span class="token operator">=</span> <span class="token number">280</span>

<span class="token keyword">const</span> content <span class="token operator">=</span> <span class="token function">ref</span><span class="token punctuation">(</span><span class="token string">""</span><span class="token punctuation">)</span>

<span class="token keyword">const</span> progress <span class="token operator">=</span> <span class="token function">computed</span><span class="token punctuation">(</span>
  <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>content<span class="token punctuation">.</span>value<span class="token punctuation">.</span>length <span class="token operator">/</span> CHARACTER_LIMIT<span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token number">100</span><span class="token punctuation">)</span> <span class="token operator">|</span> <span class="token number">0</span>
<span class="token punctuation">)</span>

<span class="token keyword">const</span> display_characters <span class="token operator">=</span> <span class="token function">computed</span><span class="token punctuation">(</span>
  <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> CHARACTER_LIMIT <span class="token operator">-</span> content<span class="token punctuation">.</span>value<span class="token punctuation">.</span>length
<span class="token punctuation">)</span>

<span class="token keyword">const</span> color <span class="token operator">=</span> <span class="token function">computed</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span>
  display_characters<span class="token punctuation">.</span>value <span class="token operator">&lt;</span> <span class="token number">0</span> <span class="token operator">?</span> <span class="token string">"#dc2626"</span> <span class="token punctuation">:</span> <span class="token string">"#1d4ed8"</span>
<span class="token punctuation">)</span>
</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>template</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>mx-10 mt-10<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span>
      <span class="token attr-name">for</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>message<span class="token punctuation">"</span></span>
      <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>block mb-2 text-sm font-medium text-gray-900 dark:text-white<span class="token punctuation">"</span></span>
      <span class="token punctuation">&gt;</span></span>Your message<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span>
    <span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>textarea</span>
      <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>message<span class="token punctuation">"</span></span>
      <span class="token attr-name">v-model</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>content<span class="token punctuation">"</span></span>
      <span class="token attr-name">rows</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>4<span class="token punctuation">"</span></span>
      <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>block p-2.5 w-full text-sm text-gray-900 bg-gray-50 rounded-lg border border-gray-300 focus:ring-blue-500 focus:border-blue-500<span class="token punctuation">"</span></span>
      <span class="token attr-name">placeholder</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Write here to see counter....<span class="token punctuation">"</span></span>
    <span class="token punctuation">/&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>flex items-center mt-5 gap-5<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>svg-circle</span>
        <span class="token attr-name">:progress</span>
        <span class="token attr-name">:width</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>5<span class="token punctuation">"</span></span>
        <span class="token attr-name">:size</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>50<span class="token punctuation">"</span></span>
        <span class="token attr-name">:stroke-color</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>color<span class="token punctuation">"</span></span>
        <span class="token attr-name">trail-color</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>#9ca3af<span class="token punctuation">"</span></span>
      <span class="token punctuation">&gt;</span></span>
        {{ display_characters }}
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>svg-circle</span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span>Characters Left<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>template</span><span class="token punctuation">&gt;</span></span>

</code></pre><p>We follow the same protocol of passing the <code class="inline-code">progress</code> to the <code class="inline-code">&lt;svg-circle /&gt;</code> component, and displaying the characters inside slot. This must be a signal as well to properly update the DOM.</p><h2 id="lingo-circle">Lingo Circle</h2><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2025/2025-05/chrome_zwkq2tp17f.gif?sfvrsn=d254e823_2" alt="chrome_Zwkq2Tp17F.gif" /></p><p>The gamified button, similar to Duolingo, is really a matter of proper styling.</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">setup</span> <span class="token attr-name">lang</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>ts<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token script language-javascript">
<span class="token keyword">const</span> progress <span class="token operator">=</span> <span class="token function">ref</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span>

<span class="token keyword">const</span> <span class="token function-variable function">changePercent</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> tmp <span class="token operator">=</span> progress<span class="token punctuation">.</span>value <span class="token operator">+</span> <span class="token number">20</span>
  progress<span class="token punctuation">.</span>value <span class="token operator">=</span> tmp <span class="token operator">&gt;</span> <span class="token number">100</span> <span class="token operator">?</span> <span class="token number">0</span> <span class="token punctuation">:</span> tmp
<span class="token punctuation">}</span>
</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>template</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>flex flex-col items-center justify-center my-10 gap-5<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h1</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>font-bold<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Lingo<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h1</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>mx-50<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>svg-circle</span>
        <span class="token attr-name">:progress</span>
        <span class="token attr-name">trail-color</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>#e5e7eb<span class="token punctuation">"</span></span>
        <span class="token attr-name">stroke-color</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>#4ade80<span class="token punctuation">"</span></span>
        <span class="token attr-name">:size</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>100<span class="token punctuation">"</span></span>
        <span class="token attr-name">:width</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>10<span class="token punctuation">"</span></span>
      <span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span>
          <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>button<span class="token punctuation">"</span></span>
          <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>inline-flex rounded-full items-center justify-center whitespace-nowrap text-sm font-bold ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 uppercase tracking-wide bg-green-500 text-primary-foreground hover:bg-green-500/90 border-green-600 active:border-b-0 h-[70px] w-[70px] border-b-8 fill-primary-foreground text-primary-foreground<span class="token punctuation">"</span></span>
          <span class="token attr-name">@click</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>changePercent<span class="token punctuation">"</span></span>
        <span class="token punctuation">&gt;</span></span>
          <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>CrownSvg</span> <span class="token punctuation">/&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>svg-circle</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>template</span><span class="token punctuation">&gt;</span></span>
</code></pre><p>With our <code class="inline-code">&lt;svg-circle /&gt;</code> customizable, this makes creating any component easy. We can calculate our progress, which must be a signal, and pass it to the circle component as usual. Instead of using a number inside of our component slot, we simply use a styled button with an <code class="inline-code">onclick</code> handle.</p><p>The rest is up to your imagination!</p><p><strong>Demo</strong>: <a target="_blank" href="https://svg-circle.vercel.app/">Vercel</a><br /><strong>Repo:</strong> <a target="_blank" href="https://github.com/jdgamble555/svg-circle">GitHub</a>
</p><hr /><blockquote><p>Even easier? Check out the Progress Kendo UI for <a target="_blank" href="https://www.telerik.com/kendo-vue-ui/components/progressbars/progressbar">Vue ProgressBar</a>, one of 110+ components professionally built with accessibility baked in.</p></blockquote><img src="https://feeds.telerik.com/link/23057/17050064.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:3d49463d-d00a-44b3-9efb-fb5a5ad28eb9</id>
    <title type="text">Vue Basics: Script Setup Sugar</title>
    <summary type="text">Vue 3 enables us to create components using script setup, reducing the amount of code to write. Here’s how to capitalize on it!</summary>
    <published>2025-05-12T11:08:20Z</published>
    <updated>2026-04-07T16:18:32Z</updated>
    <author>
      <name>Marina Mosti </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/23057/17028808/vue-basics-script-setup-sugar"/>
    <content type="text"><![CDATA[<p><span class="featured">Vue 3 enables us to create components using script setup, reducing the amount of code to write. Here&rsquo;s how to capitalize on it!</span></p><p>With Vue 3 not only comes the ability to use the composition API but also the capacity to create components using script setup to minimize the amount of code we have to write. Script setup in itself is mostly syntactical sugar, and for that reason we have a few &ldquo;magic&rdquo; functions (or compiler macros if you want to be technical) that you should familiarize yourself with if you are going to be leveraging its capabilities.</p><h2 id="defineprops">defineProps</h2><p>When creating a component with script setup, one of the first things you will probably run into is the need to create and define component properties.</p><p>In the options API, this is extremely clear as we define the props property in the object that is exported at the top level.</p><pre class=" language-vue"><code class="prism  language-vue">&lt;script&gt;
export default {
  props: {
    myProp: { type: String, required: true },
    optional: { type: Boolean, default: false } 
  }
}
&lt;/script&gt;
</code></pre><p>To be able to achieve this in script setup, we use a magic function called defineProps. The function receives either an array of strings to define property names, or the same object syntax that you are already used to with the options API.</p><p>The above example, written in script setup and leveraging defineProps is written as follows:</p><pre class=" language-vue"><code class="prism  language-vue">&lt;script setup&gt;
defineProps({
  myProp: { type: String, required: true },
  optional: { type: Boolean, default: false } 
})
&lt;/script&gt;
</code></pre><p>Note that if you want to use these props directly in your script, for example to create a computed property, you can capture the return value of the function&mdash;a reactive object.</p><pre class=" language-vue"><code class="prism  language-vue">&lt;script setup&gt;
const props = defineProps({
  myProp: { type: String, required: true },
  optional: { type: Boolean, default: false } 
})

const componentTitle = computed(() =&gt; props.optional ? 'Optional' : 'Required' )
&lt;/script&gt;
</code></pre><p>For a simplified version, we can write this using array syntax. However, note that we lose the ability to warn our developers of type errors.</p><pre class=" language-vue"><code class="prism  language-vue">&lt;script setup&gt;
defineProps(['myProp', 'optional'])
&lt;/script&gt;
defineEmits
</code></pre><h2 id="defineemits">defineEmits</h2><p>A component will usually interact with other components through its public API which consists of props and emits. We just learned above how to declare props in our script setup components, but how do we go about declaring which events our component emits?</p><p>Just as we have defineProps, Vue 3 also grants us the defineEmits function. This function takes one parameter, an array of strings or an object&mdash;just as defineProps did.</p><p>Consider the following component that emits a click event.</p><pre class=" language-vue"><code class="prism  language-vue">&lt;script setup&gt;
const emit = defineEmits(['click'])
&lt;/script&gt;

&lt;template&gt;
  &lt;button @click="emit('click')" /&gt;
&lt;/template&gt;
</code></pre><p>The component uses the defineEmits function to declare that this component will emit a click event. This helps reduce errors, as Vue will warn you if you emit an event that is not declared in the defineEmits declaration, and it allows us to also declare emit types&mdash;more on this in a second.</p><p>The defineEmits function returns another function which we name emit, and we use this function to emit the actual click event on our button input. This function can also be used internally within the script section to dynamically emit events when necessary.</p><p>If we wanted to be a bit safer, we can use object syntax (just like with our props) to declare a type of emit, and even a validator.</p><pre class=" language-vue"><code class="prism  language-vue">&lt;script setup&gt;
const props = defineProps({
  number: { type: Number, required: true }
})

const emit = defineEmits({
  click: {
    type: Number,
    validator: num =&gt; num &gt; 0
  }
})

const emitValue = () =&gt; {
  emit(props.number + 1)
}
&lt;/script&gt;

&lt;template&gt;
  &lt;button @click="emitValue" /&gt;
&lt;/template&gt;
</code></pre><p>There&rsquo;s a couple of changes here. We&rsquo;ve added a prop number to enhance our example. This allows us to add an emit value to our click event so that we can demonstrate the object syntax of defineEmits.</p><p>Notice that our click emit now declares a type of Number and even a validator that checks that the emitted value is always greater than 0. This is quite valuable and allows to catch errors in development early, as these warnings will be reported in browser console and test suites like Vitest.</p><aside><hr data-sf-ec-immutable="" /><div class="row"><div class="col-4 u-normal-full u-small-mb0"><h4 class="u-fs20 u-fw5 u-lh125 u-mb0">Vue Basics: Testing with Vitest</h4></div><div class="col-8"><p class="u-fs16 u-mb0">Testing is vital for the reliability of our applications. See the essentials of <a target="_blank" href="https://www.telerik.com https://www.telerik.com/blogs/vue-basics-testing-vitest">testing Vue applications using Vitest</a> and @vue/test-utils.</p></div></div><hr class="u-mb3" /></aside><p>Notice additionally that the emit function is now being used within the wrapper function emitValue. As described above, the function returned by defineEmits can be used to emit a value within the script block itself if needed.</p><h2 id="definemodel">defineModel</h2><p>In Vue 3 the <a target="_blank" href="https://vuejs.org/guide/components/v-model.html">v-model defaults</a> considerably changed from the way that they were declared in Vue 2. We now use modelValue as the default property for a model and update:modelValue for the default emit value of a v-model in a component. I will not go into depth on the new v-model syntax as this is out of the scope of the article. Instead, we will explore how we can leverage defineModel to quickly create several v-model-able properties within our component.</p><p>Consider the following example:</p><pre class=" language-vue"><code class="prism  language-vue">&lt;script setup&gt;
defineProps({
  modelValue: String,
  name: { type: String, default: 'Marina' }
})

defineEmits(['update:modelValue', 'update:name'])
&lt;/script&gt;
</code></pre><p>defineModel allows us to quickly declare a &ldquo;model&rdquo; for both of the props above without having to declare them additionally in our emits.</p><pre class=" language-vue"><code class="prism  language-vue">&lt;script setup&gt;
const modelValue = defineModel({ type: String })
const name = defineModel('name', { type: String, default: 'Marina' })
&lt;/script&gt;
</code></pre><p>Notice that the first defineModel call does not pass a string initially to declare the name of the model as modelValue. This is because modelValue is already the default, can we can safely skip it.</p><p>The second example creates the name property for us, and under the hood also declares the update:name emit so that the consuming component can bind to it using v-model:name syntax.</p><h2 id="defineexpose">defineExpose</h2><p>Components that are created using script setup syntax are closed by default. This means that unlike regular components, the properties that we create on the component will <em>not</em> be available to accessed using template refs or VM accessor in tests.</p><p>In order to make certain properties of our component available to be externally accessed in this manner, we must declare them as exposed with defineExpose as follows.</p><pre class=" language-vue"><code class="prism  language-vue">&lt;script setup&gt;
const privateProp = ref(123)
const publicProp = ref(234)

defineExpose({
  publicProp
})
&lt;/script&gt;
</code></pre><p>If someone where to try to access our component&rsquo;s publicProp through a $ref or through the .VM in a test, then component.publicProp would equal the reactive value of 234.</p><p>However, if someone tried to access the privateProp property, they would get undefined as that property is not publicly available.</p><p>Take note that exposing properties is to be used with extreme care and normally would be considered a code smell unless explicitly documented. Always err on the side of exposing a public API through an emit instead when possible.</p><h2 id="defineoptions">defineOptions</h2><p>In the options API, a component can be created declaring custom <a target="_blank" href="https://vuejs.org/api/component-instance.html#options">component options</a>. These options are normally declare to provide a static property that is not reactive but useful to the internals of the component.</p><p>In order to declare options in a script setup component, we must declare it using the defineOptions function.</p><pre class=" language-vue"><code class="prism  language-vue">&lt;script setup&gt;
defineOptions({
  inheritAttrs: false,
  customOptions: {
    myOption: 123
  }
})
&lt;/script&gt;
</code></pre><p>Notice that before creating our custom myOption option I&rsquo;ve chosen to add an inheritAttrs call. I&rsquo;ve done this on purpose because most of the time when you find yourself in need to declare options in a script setup component you will do it in order to control <a target="_blank" href="https://vuejs.org/api/options-misc.html#inheritattrs">attribute fallthrough</a> in your component.</p><h2 id="wrap-up">Wrap-up</h2><p>I hope this post has helped you understand these compiler macros for use in your Vue applications!</p><img src="https://feeds.telerik.com/link/23057/17028808.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:ae971878-dbb0-4054-8a3a-98901ea1a1e3</id>
    <title type="text">Vue Basics: Testing with Vitest</title>
    <summary type="text">Testing is vital for the reliability of our applications. See the essentials of testing Vue applications using Vitest and @vue/test-utils.</summary>
    <published>2025-05-05T11:10:17Z</published>
    <updated>2026-04-07T16:18:32Z</updated>
    <author>
      <name>Thomas Findlay </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/23057/17022089/vue-basics-testing-vitest"/>
    <content type="text"><![CDATA[<p><span class="featured">Testing is vital for the reliability of our applications. See the essentials of testing Vue applications using Vitest and @vue/test-utils.</span></p><p>Imagine filling in a long form on a website, finally getting to the end, and clicking the submit button while starting to celebrate the job done. However, nothing happens. You decide to open the devtools, only to see a bunch of errors popping up when pressing the submit button again.</p><p>A situation like this can be very discouraging, and users often abandon websites after such ordeals. Fortunately, as developers, there are things we can do about reducing the chances of something like this happening. One of those is testing. In this article, we will cover how to set up and run tests with Vitest in a Vue application.</p><h2 id="why-is-testing-important">Why Is Testing Important?</h2><p>An automated testing suite can be crucial for a success of an application, as it can improve application reliability, reduce the occurrence of bugs and increase the speed of delivering new features and updates to existing functionality.</p><p>With automated tests, developers can be more confident the code they wrote is bug free and won&rsquo;t break existing functionality. There are three primary types of tests that can be used for Vue applications:</p><p><strong>1. Unit Testing</strong><br />Check the behavior of small functions, composables and components in isolation. For example, a unit test could check if a Tabs component works correctly and changes the active tab on a click.</p><p><strong>2. Integration Testing</strong><br />Assert multiple components and units work together in cohesion.</p><p><strong>3. End-to-End (E2E) Testing</strong><br />Simulate a real user and test the entire functionality flow from opening the browser, visiting the URL and interacting with the website to verify the frontend works seamlessly with the server and users are able to accomplish specific tasks.</p><p>There are pros and cons to each. While unit and integration tests can run faster, as most of the time they are tested in isolation without a need for API requests and interacting with any services, end-to-end tests can provide the most confidence, as they test that the whole system from the UI to the database works as expected.</p><h2 id="whats-vitest">What&rsquo;s Vitest?</h2><p>For many years, <a target="_blank" href="https://jestjs.io/">Jest</a> was the testing framework of choice for JavaScript applications due to its robust functionality, mature community and plugin system, and compatibility with a lot of frameworks and libraries.</p><p>However, Jest can be quite slow in larger applications that might comprise hundreds or even thousands of files and tests. What&rsquo;s more, it also doesn&rsquo;t work well with modern tools such as <a target="_blank" href="https://vitejs.dev">Vite</a> that rely on ES modules, as Jest uses CommonJS modules.</p><p>That&rsquo;s where <a target="_blank" href="https://vitest.dev">Vitest</a> came into play. Vitest is a modern testing framework that is blazing fast in comparison to Jest and many other solutions. It has out-of-the-box support for ES modules, TypeScript, JSX and has Jest compatible API, which means migrating from Jest to Vitest is very straightforward.</p><p>It also has other interesting features, such as browser mode, in-source testing or type testing. You can find out more about all features in the <a target="_blank" href="https://vitest.dev/guide/features.html">documentation</a>.</p><h2 id="project-setup-with-vue-vite-and-vitest">Project Setup with Vue, Vite and Vitest</h2><p>The best way to learn to code is by practice, so let&rsquo;s set up a new Vue project with Vite. After the setup, we will create a simple Vue component and then write unit tests with Vitest to test that the component works as expected. You can also find the full code for this article in <a target="_blank" href="https://github.com/ThomasFindlay/vue-vitest-basics">this GitHub repository</a>.</p><p>To scaffold a new Vue project, open the terminal and run the following command:</p><pre class=" language-shell"><code class="prism  language-shell"># npm 7+, extra double-dash is needed:
$ npm create vite@latest vue-vitest-basics -- --template vue
</code></pre><p>This command will create a new project in the <code class="inline-code">vue-vitest-basics</code> folder. After the installation is completed, change the directory into <code class="inline-code">vue-vitest-basics</code> and install <code class="inline-code">vitest</code>, <code class="inline-code">@vue/test-utils</code> and <code class="inline-code">jsdom</code>.</p><pre class=" language-shell"><code class="prism  language-shell">$ cd vue-vitest-basics
$ npm install -D vitest @vue/test-utils jsdom
</code></pre><p>If you&rsquo;re wondering why we need to install <a target="_blank" href="https://test-utils.vuejs.org/">@vue/test-utils</a>, here&rsquo;s the reason. Vitest itself is a test runner, but it&rsquo;s framewor-agnostic. Therefore, it doesn&rsquo;t know out of the box how to handle Vue components. For that, we need a separate library. <code class="inline-code">@vue/test-utils</code> is the official testing library provided by the Vue.js team. While there are other possible solutions, such as <a target="_blank" href="https://testing-library.com/docs/vue-testing-library/intro/">Vue Testing Library</a>, in this article, we will focus on using <code class="inline-code">@vue/test-utils</code>.</p><p>Another library that we need to include is <a target="_blank" href="http://npmjs.com/jsdom">jsdom</a>. By default, Vitest runs tests in the Node environment. However, Vue components need to be mounted in a browser-like environment. Since we will run our unit tests in Node, we need to configure Vitest to simulate the browser environment. We can do so by updating the <code class="inline-code">vite.config.js</code> file and adding <code class="inline-code">test.environment</code> config.</p><p><strong>vite.config.js</strong></p><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">import</span> <span class="token punctuation">{</span> defineConfig <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"vite"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> vue <span class="token keyword">from</span> <span class="token string">"@vitejs/plugin-vue"</span><span class="token punctuation">;</span>

<span class="token comment">// https://vite.dev/config/</span>
<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token function">defineConfig</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  plugins<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token function">vue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
  test<span class="token punctuation">:</span> <span class="token punctuation">{</span>
    environment<span class="token punctuation">:</span> <span class="token string">"jsdom"</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>Finally, you can start the development environment by running the command below.</p><pre class=" language-shell"><code class="prism  language-shell">$ npm run dev
</code></pre><p>You can visit <code class="inline-code">http://localhost:5173</code> in your browser to see the starting page.</p><p>One more thing we need to do as part of the setup is to update the <code class="inline-code">package.json</code> file. Add a new <code class="inline-code">test</code> script that will run Vitest.</p><p><strong>package.json</strong></p><pre class=" language-json"><code class="prism  language-json"><span class="token string">"scripts"</span><span class="token punctuation">:</span> <span class="token punctuation">{</span>
  <span class="token comment">// other scripts </span>
  <span class="token string">"test"</span><span class="token punctuation">:</span> <span class="token string">"vitest"</span>
<span class="token punctuation">}</span>
</code></pre><p>We don&rsquo;t have any test files yet, but we will get to it in a moment. You can already run the test command in the terminal to start Vitest.</p><h2 id="building-a-simple-tabs-component">Building a Simple Tabs Component</h2><p>Let&rsquo;s create a simple <code class="inline-code">Tabs</code> component that will render tab buttons based on the props provided. The <code class="inline-code">Tabs</code> component will accept two props: <code class="inline-code">tabs</code> and <code class="inline-code">modelValue</code>. The former will be an array of strings for each tab that should be displayed, while the former will represent the currently active tab. Here&rsquo;s the code for the component.</p><p><strong>src/components/Tabs.vue</strong></p><pre class=" language-vue"><code class="prism  language-vue">&lt;script setup&gt;
const props = defineProps({
  tabs: {
    type: Array,
    default: () =&gt; [],
  },
  modelValue: {
    type: String,
  },
});

const emit = defineEmits(["update:modelValue"]);
&lt;/script&gt;

&lt;template&gt;
  &lt;div&gt;
    &lt;ul :class="$style.tab_list" role="tablist"&gt;
      &lt;li
        v-for="tab of tabs"
        :key="tab"
        role="presentation"
      &gt;
        &lt;button
          type="button"
          :class="[
            $style.tab_button,
            tab === props.modelValue &amp;&amp; $style.active,
          ]"
          role="tab"
          :id="`tab-${tab}`"
          :aria-selected="tab === props.modelValue"
          @click="emit('update:modelValue', tab)"
        &gt;
          {{ tab }}
        &lt;/button&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/div&gt;
&lt;/template&gt;
&lt;style module&gt;
.tab_list {
  list-style: none;
  display: flex;
  align-items: center;
  gap: 1rem;
  border-bottom: 1px solid #ccc;
  padding: 0;
}

.tab_button {
  background: none;
  border: none;
  padding: 0.5rem 1rem;
  cursor: pointer;
  color: #007bff;
}

.active {
  border-bottom: 2px solid #007bff;
  background-color: green;
  color: white;
}
&lt;/style&gt;
</code></pre><p>The <code class="inline-code">Tabs</code> component loops through the <code class="inline-code">tabs</code> array provided via props and renders a button for each tab. Whenever a tab is active, the <code class="inline-code">active</code> style is applied and the <code class="inline-code">aria-selected</code> attribute changes to <code class="inline-code">"true"</code>.</p><p>Now, we can update the <code class="inline-code">App.vue</code> component, render the <code class="inline-code">Tabs</code> component, and see what it looks like in the browser.</p><p><strong>src/App.vue</strong></p><pre class=" language-vue"><code class="prism  language-vue">&lt;script setup&gt;
import Tabs from "./components/Tabs.vue";
const tabs = ["Home", "Profile", "About", "Settings"];
const activeTab = defineModel("activeTab");
&lt;/script&gt;

&lt;template&gt;
  &lt;div&gt;
    &lt;Tabs :tabs="tabs" v-model="activeTab" /&gt;
  &lt;/div&gt;
&lt;/template&gt;
</code></pre><p>You should see in the browser four tabs for Home, Profile, About and Settings.</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2025/2025-04/tabs.png?sfvrsn=4d3a602b_2" alt="Tabs" /></p><p>Whenever a tab is clicked, its styles should change to reflect that.</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2025/2025-04/active-tab.png?sfvrsn=57a085d_2" alt="Active tab" /></p><p>Now that we have a working component, let&rsquo;s write some unit tests to verify its functionality.</p><h2 id="unit-testing-the-tabs-component-with-vitest">Unit Testing the Tabs Component with Vitest</h2><p>The <code class="inline-code">Tabs</code> component is quite simple in its functionality, but there are a few things we can test to verify it&rsquo;s working as expected:</p><ol><li>The component renders the correct number of tabs, as provided via props.</li><li>A correct tab is set as active based on the <code class="inline-code">modelValue</code> prop.</li><li>The component emits an event when a tab is clicked and updates the active tab accordingly whenever the <code class="inline-code">modelValue</code> prop changes.</li><li>No buttons are rendered if the <code class="inline-code">tabs</code> prop doesn&rsquo;t contain any items.</li></ol><p>By writing tests for the aforementioned scenarios, we will cover how to:</p><ul><li>Mount and render Vue components</li><li>Query elements and verify their contents and attributes</li><li>Fire, intercept and verify events</li><li>Update props programmatically</li></ul><p>Let&rsquo;s start by creating a new file called <strong>Tabs.spec.js</strong> in the <em>src/components</em> directory and writing the first test.</p><p><strong>src/components/Tabs.spec.js</strong></p><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">import</span> <span class="token punctuation">{</span> mount <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@vue/test-utils"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> describe<span class="token punctuation">,</span> it<span class="token punctuation">,</span> expect <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"vitest"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> Tabs <span class="token keyword">from</span> <span class="token string">"./Tabs.vue"</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> tabs <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">"Home"</span><span class="token punctuation">,</span> <span class="token string">"Profile"</span><span class="token punctuation">,</span> <span class="token string">"About"</span><span class="token punctuation">,</span> <span class="token string">"Settings"</span><span class="token punctuation">]</span><span class="token punctuation">;</span>

<span class="token function">describe</span><span class="token punctuation">(</span><span class="token string">"Tabs.vue"</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token function">it</span><span class="token punctuation">(</span><span class="token string">"renders correct number of tabs"</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> wrapper <span class="token operator">=</span> <span class="token function">mount</span><span class="token punctuation">(</span>Tabs<span class="token punctuation">,</span> <span class="token punctuation">{</span>
      props<span class="token punctuation">:</span> <span class="token punctuation">{</span>
        tabs<span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">const</span> buttons <span class="token operator">=</span> wrapper<span class="token punctuation">.</span><span class="token function">findAll</span><span class="token punctuation">(</span><span class="token string">"button"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token function">expect</span><span class="token punctuation">(</span>buttons<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toHaveLength</span><span class="token punctuation">(</span><span class="token number">4</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> <span class="token punctuation">[</span>index<span class="token punctuation">,</span> tab<span class="token punctuation">]</span> <span class="token keyword">of</span> Object<span class="token punctuation">.</span><span class="token function">entries</span><span class="token punctuation">(</span>tabs<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token function">expect</span><span class="token punctuation">(</span>buttons<span class="token punctuation">[</span>index<span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">text</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toBe</span><span class="token punctuation">(</span>tab<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>The <code class="inline-code">describe</code> function collects and groups all the tests defined inside, while <code class="inline-code">it</code> separates tests. Our first test checks if the <code class="inline-code">Tabs</code> component renders the correct number of tabs. As I mentioned before, Vitest doesn&rsquo;t know how to handle Vue component directly, so that&rsquo;s where the <code class="inline-code">mount</code> from the <code class="inline-code">@vue/test-utils</code> library comes into play. The <code class="inline-code">Tabs</code> component receives the <code class="inline-code">tabs</code> array as a prop with four items&mdash;<code class="inline-code">Home</code>, <code class="inline-code">Profile</code>, <code class="inline-code">About</code> and <code class="inline-code">Settings</code>. After the component is mounted, we find all the buttons and verify their text content matches the items in the <code class="inline-code">tabs</code> array.</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2025/2025-04/first-test-passed.png?sfvrsn=2f12856a_2" alt="First test passed" /></p><p>Now that we have the first passing test, we can add a few more. Let&rsquo;s check if the component emits an update event when a tab is clicked, and whether it updates the active tab accordingly.</p><p><strong>src/components/Tabs.spec.js</strong></p><pre class=" language-js"><code class="prism  language-js"><span class="token comment">// ...other code</span>
<span class="token function">describe</span><span class="token punctuation">(</span><span class="token string">"Tabs.vue"</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token comment">// ...other tests</span>
 
  <span class="token function">it</span><span class="token punctuation">(</span><span class="token string">"emits update:modelValue on tab click"</span><span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> wrapper <span class="token operator">=</span> <span class="token function">mount</span><span class="token punctuation">(</span>Tabs<span class="token punctuation">,</span> <span class="token punctuation">{</span>
      props<span class="token punctuation">:</span> <span class="token punctuation">{</span>
        tabs<span class="token punctuation">:</span> tabs<span class="token punctuation">,</span>
        modelValue<span class="token punctuation">:</span> tabs<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> buttons <span class="token operator">=</span> wrapper<span class="token punctuation">.</span><span class="token function">findAll</span><span class="token punctuation">(</span><span class="token string">"button"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token function">expect</span><span class="token punctuation">(</span>buttons<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">attributes</span><span class="token punctuation">(</span><span class="token string">"aria-selected"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toBe</span><span class="token punctuation">(</span><span class="token string">"true"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token function">expect</span><span class="token punctuation">(</span>buttons<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">attributes</span><span class="token punctuation">(</span><span class="token string">"aria-selected"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toBe</span><span class="token punctuation">(</span><span class="token string">"false"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">await</span> buttons<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">trigger</span><span class="token punctuation">(</span><span class="token string">"click"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token function">expect</span><span class="token punctuation">(</span>wrapper<span class="token punctuation">.</span><span class="token function">emitted</span><span class="token punctuation">(</span><span class="token string">"update:modelValue"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toBeTruthy</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token function">expect</span><span class="token punctuation">(</span>wrapper<span class="token punctuation">.</span><span class="token function">emitted</span><span class="token punctuation">(</span><span class="token string">"update:modelValue"</span><span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toEqual</span><span class="token punctuation">(</span><span class="token punctuation">[</span>tabs<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">await</span> wrapper<span class="token punctuation">.</span><span class="token function">setProps</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
      modelValue<span class="token punctuation">:</span> tabs<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token function">expect</span><span class="token punctuation">(</span>buttons<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">attributes</span><span class="token punctuation">(</span><span class="token string">"aria-selected"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toBe</span><span class="token punctuation">(</span><span class="token string">"true"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
</code></pre><p>In the new test, we set the first tab as active by passing the <code class="inline-code">"Home"</code> text to the <code class="inline-code">modelValue</code> prop. After the component is mounted, we verify that it&rsquo;s active by checking the state of the <code class="inline-code">aria-selected</code> attribute.</p><p>We are going to test whether the active tab will change on click from the first tab the second one, so we also verify the second tab is in the inactive state. Afterward, the click event is triggered on the second tab by executing the <code class="inline-code">trigger('click')</code> method on the second tab button. Note that the <code class="inline-code">trigger</code> methods <strong>must be</strong> awaited, as otherwise the test will proceed executing other lines of code before Vue is able to update the state and re-render any changes that occurred as a result of the click. This could result in race conditions and unexpected test outcomes.</p><p>After the promise returned by the <code class="inline-code">trigger</code> method is resolved, we verify the <code class="inline-code">Tabs</code> component emitted the <code class="inline-code">update:modelValue</code> event and check the argument passed to it matches the text of the second tab.</p><p>Next, we change the <code class="inline-code">modelValue</code> prop to match the second tab. The reason we do it is because the <code class="inline-code">Tabs</code> component here is tested in isolation, and the active tab state is actually controlled by the parent component that renders it.</p><p>In our tests, the <code class="inline-code">Tabs</code> component doesn&rsquo;t have any parent, and we are responsible for passing the props. That&rsquo;s why we change the <code class="inline-code">modelValue</code> prop programmatically. Similarly to the <code class="inline-code">trigger</code> method, <code class="inline-code">setProps</code> returns a promise that must be awaited.</p><p>Finally, we verify that the second button is now active by asserting the <code class="inline-code">aria-selected</code> attribute is &ldquo;true&rdquo;.</p><p>Here is the code for a few more tests that check if the <code class="inline-code">Tabs</code> component correctly handles attributes, and empty values.</p><pre class=" language-js"><code class="prism  language-js"><span class="token comment">// ...other code</span>
<span class="token function">describe</span><span class="token punctuation">(</span><span class="token string">"Tabs.vue"</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token comment">// ...other tests</span>
  
  <span class="token function">it</span><span class="token punctuation">(</span><span class="token string">"sets correct role and id attributes"</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> wrapper <span class="token operator">=</span> <span class="token function">mount</span><span class="token punctuation">(</span>Tabs<span class="token punctuation">,</span> <span class="token punctuation">{</span>
      props<span class="token punctuation">:</span> <span class="token punctuation">{</span>
        tabs<span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> button <span class="token operator">=</span> wrapper<span class="token punctuation">.</span><span class="token function">find</span><span class="token punctuation">(</span><span class="token string">"button"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token function">expect</span><span class="token punctuation">(</span>button<span class="token punctuation">.</span><span class="token function">attributes</span><span class="token punctuation">(</span><span class="token string">"role"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toBe</span><span class="token punctuation">(</span><span class="token string">"tab"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token function">expect</span><span class="token punctuation">(</span>button<span class="token punctuation">.</span><span class="token function">attributes</span><span class="token punctuation">(</span><span class="token string">"id"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toBe</span><span class="token punctuation">(</span><span class="token template-string"><span class="token string">`tab-</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>tabs<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token function">it</span><span class="token punctuation">(</span><span class="token string">"handles empty tabs array"</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> wrapper <span class="token operator">=</span> <span class="token function">mount</span><span class="token punctuation">(</span>Tabs<span class="token punctuation">,</span> <span class="token punctuation">{</span>
      props<span class="token punctuation">:</span> <span class="token punctuation">{</span>
        tabs<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> lis <span class="token operator">=</span> wrapper<span class="token punctuation">.</span><span class="token function">findAll</span><span class="token punctuation">(</span><span class="token string">"li"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token function">expect</span><span class="token punctuation">(</span>lis<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toHaveLength</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token function">it</span><span class="token punctuation">(</span><span class="token string">"does not set aria-selected if modelValue not in tabs"</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> wrapper <span class="token operator">=</span> <span class="token function">mount</span><span class="token punctuation">(</span>Tabs<span class="token punctuation">,</span> <span class="token punctuation">{</span>
      props<span class="token punctuation">:</span> <span class="token punctuation">{</span>
        tabs<span class="token punctuation">:</span> tabs<span class="token punctuation">,</span>
        modelValue<span class="token punctuation">:</span> <span class="token string">"Features"</span><span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> buttons <span class="token operator">=</span> wrapper<span class="token punctuation">.</span><span class="token function">findAll</span><span class="token punctuation">(</span><span class="token string">"button"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    buttons<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span>button <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
      <span class="token function">expect</span><span class="token punctuation">(</span>button<span class="token punctuation">.</span><span class="token function">attributes</span><span class="token punctuation">(</span><span class="token string">"aria-selected"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toBe</span><span class="token punctuation">(</span><span class="token string">"false"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token function">it</span><span class="token punctuation">(</span><span class="token string">"handles missing modelValue"</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> wrapper <span class="token operator">=</span> <span class="token function">mount</span><span class="token punctuation">(</span>Tabs<span class="token punctuation">,</span> <span class="token punctuation">{</span>
      props<span class="token punctuation">:</span> <span class="token punctuation">{</span>
        tabs<span class="token punctuation">:</span> tabs<span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> buttons <span class="token operator">=</span> wrapper<span class="token punctuation">.</span><span class="token function">findAll</span><span class="token punctuation">(</span><span class="token string">"button"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    buttons<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span>button <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
      <span class="token function">expect</span><span class="token punctuation">(</span>button<span class="token punctuation">.</span><span class="token function">attributes</span><span class="token punctuation">(</span><span class="token string">"aria-selected"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toBe</span><span class="token punctuation">(</span><span class="token string">"false"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
</code></pre><h2 id="conclusion">Conclusion</h2><p>In this article, we&rsquo;ve explored the essentials of testing Vue applications using Vitest and @vue/test-utils. By setting up a testing environment with Vite and crafting unit tests for a simple Tabs component, we&rsquo;ve demonstrated how to verify component behavior, manage props, handle events and verify reliability under various conditions.</p><p>Testing is a cornerstone of building robust and dependable Vue applications; it catches potential issues early and boosts confidence in your code. To take your testing skills further, consider experimenting with more advanced scenarios, such as testing components with slots, handling asynchronous operations or integrating with state management solutions like Pinia.</p><aside><hr data-sf-ec-immutable="" /><div class="row"><div class="col-4 u-normal-full u-small-mb0"><h4 class="u-fs20 u-fw5 u-lh125 u-mb0">How to Manage Composition API Refs in Vue 3 When Unit Testing</h4></div><div class="col-8"><p class="u-fs16 u-mb0">See how to reuse state logic in Vue with <a target="_blank" href="https://www.telerik.com/blogs/how-manage-composition-api-refs-vue-3-unit-testing">composables written with the Composition API</a>. </p></div></div></aside><img src="https://feeds.telerik.com/link/23057/17022089.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:f54300ad-32c3-4259-a574-036bcd679f57</id>
    <title type="text">Mastering Authentication in Nuxt 3 with Server-Side Rendering: A Minimalist’s Guide</title>
    <summary type="text">Get one dev’s tips for the minimalist approach to authentication in Nuxt 3.</summary>
    <published>2024-11-13T09:16:53Z</published>
    <updated>2026-04-07T16:18:32Z</updated>
    <author>
      <name>Nada Rifki </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/23057/16884948/mastering-authentication-nuxt-3-server-side-rendering-minimalist-guide"/>
    <content type="text"><![CDATA[<p><span class="featured">Get one dev&rsquo;s tips for the minimalist approach to authentication in Nuxt 3.</span></p><p>After spending a significant amount of my time working with Nuxt 3 this summer, I had to focus on designing a few authentication systems. I&rsquo;ve explored various approaches, discovering that some are far simpler and more efficient than others.</p><p>In this article, I&rsquo;ll share with you my most streamlined approach to managing authentication in Nuxt 3. &zwj;</p><h2 id="what-solutions-are-available">What Solutions Are Available?</h2><p>With the transition from Nuxt 2 to Nuxt 3, many developers might notice that familiar packages like <code class="inline-code">@nuxtjs/auth</code> are no longer viable. Fortunately for us, the Nuxt team is actively developing a new official module for Nuxt 3. ️&zwj;♀️</p><p>In the meantime, there are three main solutions currently available:</p><ol><li><p><strong>Built-in Nuxt 3 utilities</strong>: Nuxt 3&rsquo;s native utilities offer a straightforward, integrated approach to session management and authentication. This is the method I&rsquo;ll focus on in this article.</p></li><li><p><strong><a target="_blank" href="https://github.com/atinux/nuxt-auth-utils"><code class="inline-code">nuxt-auth-utils</code></a></strong>: A minimalistic module maintained by Sebastien Chopin, <code class="inline-code">nuxt-auth-utils</code> provides essential tools for session management and OAuth integration. It&rsquo;s lightweight yet powerful for many projects.</p></li><li><p><strong><a target="_blank" href="https://github.com/sidebase/nuxt-auth"><code class="inline-code">@sidebase/nuxt-auth</code></a></strong>: If you&rsquo;re looking for a more feature-rich solution, this package is a strong contender. It supports OAuth providers, email-based authentication with Magic URLs and more. Leveraging the well-established Auth.js/NextAuth.js ecosystem, it&rsquo;s ideal for more complex projects.</p></li></ol><h3 id="key-differences-between-nuxt-auth-utils-and-sidebasenuxt-auth">Key Differences Between nuxt-auth-utils and @sidebase/nuxt-auth</h3><p>While both <code class="inline-code">nuxt-auth-utils</code> and <code class="inline-code">@sidebase/nuxt-auth</code> are excellent options, they cater to different needs. <code class="inline-code">nuxt-auth-utils</code> is a more streamlined solution, focusing on core utilities for session and OAuth management. On the other hand, <code class="inline-code">@sidebase/nuxt-auth</code> offers a broader range of features, including support for multiple authentication providers and enhanced session management capabilities, making it a robust choice for complex applications. &zwj;♀️</p><p>I haven&rsquo;t had the chance to use any of these packages in production yet so I won&rsquo;t cover them in this article. I thought they were worth mentioning, though, as they might be more suitable for your project&rsquo;s requirements if the solution I&rsquo;m about to present doesn&rsquo;t meet your needs. </p><h2 id="leveraging-built-in-nuxt-3-utilities">Leveraging Built-in Nuxt 3 Utilities</h2><p>This approach is one I frequently use in various applications due to its simplicity and direct integration with Nuxt 3. This solution assumes that your backend handles authentication using tokens, such as JWT, and that these tokens are accessible via an API.</p><p>To implement this, you&rsquo;ll need three components: a simple composable function (or a Pinia store), middleware to handle SSR and an Axios plugin to attach the token to the request headers. While you can use <code class="inline-code">fetch</code> or any other HTTP client, I prefer Axios due to familiarity. </p><h3 id="step-1-create-a-composable-function">Step 1: Create a Composable Function</h3><p>Let&rsquo;s start by creating a composable function to manage authentication:</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token comment">// composable/useAuth.ts</span>
<span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">useAuth</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token comment">// Custom composable to interact with your API</span>
  <span class="token keyword">const</span> api <span class="token operator">=</span> <span class="token function">useApi</span><span class="token punctuation">(</span><span class="token punctuation">)</span>

  <span class="token comment">// Define reactive states for user and token</span>
  <span class="token keyword">const</span> user <span class="token operator">=</span> ref<span class="token operator">&lt;</span><span class="token keyword">null</span> <span class="token operator">|</span> User<span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">)</span>
  <span class="token keyword">const</span> opaqueToken <span class="token operator">=</span> useState<span class="token operator">&lt;</span><span class="token keyword">null</span> <span class="token operator">|</span> <span class="token keyword">string</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token string">'opaqueToken'</span><span class="token punctuation">)</span>

  <span class="token comment">// Function to handle login via email</span>
  <span class="token keyword">const</span> loginWithEmail <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span>params<span class="token punctuation">:</span> <span class="token punctuation">{</span>
    email<span class="token punctuation">:</span> <span class="token keyword">string</span>
    password<span class="token punctuation">:</span> <span class="token keyword">string</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">try</span> <span class="token punctuation">{</span>
      <span class="token keyword">const</span> <span class="token punctuation">{</span> data<span class="token punctuation">:</span> <span class="token punctuation">{</span> opaqueToken<span class="token punctuation">,</span> user <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">await</span> api<span class="token punctuation">.</span>auth<span class="token punctuation">.</span><span class="token function">loginWithEmail</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
        email<span class="token punctuation">:</span> params<span class="token punctuation">.</span>email<span class="token punctuation">,</span>
        password<span class="token punctuation">:</span> params<span class="token punctuation">.</span>password<span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">)</span>

      opaqueToken<span class="token punctuation">.</span>value <span class="token operator">=</span> opaqueToken
      user<span class="token punctuation">.</span>value <span class="token operator">=</span> user

      <span class="token keyword">return</span> <span class="token punctuation">{</span> user <span class="token punctuation">}</span>
    <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">error</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">return</span> Promise<span class="token punctuation">.</span><span class="token function">reject</span><span class="token punctuation">(</span>error<span class="token punctuation">)</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>

  <span class="token comment">// Function to handle login via Google by exchanging the code for an opaque token</span>
  <span class="token keyword">const</span> loginWithGoogle <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span>params<span class="token punctuation">:</span> <span class="token punctuation">{</span>
    code<span class="token punctuation">:</span> <span class="token keyword">string</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">try</span> <span class="token punctuation">{</span>
      <span class="token keyword">const</span> <span class="token punctuation">{</span> data<span class="token punctuation">:</span> <span class="token punctuation">{</span> opaqueToken<span class="token punctuation">,</span> user <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">await</span> api<span class="token punctuation">.</span>auth<span class="token punctuation">.</span><span class="token function">loginWithGoogle</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
        code<span class="token punctuation">:</span> params<span class="token punctuation">.</span>code<span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">)</span>

      opaqueToken<span class="token punctuation">.</span>value <span class="token operator">=</span> opaqueToken
      user<span class="token punctuation">.</span>value <span class="token operator">=</span> user

      <span class="token keyword">return</span> <span class="token punctuation">{</span> user <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">error</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">return</span> Promise<span class="token punctuation">.</span><span class="token function">reject</span><span class="token punctuation">(</span>error<span class="token punctuation">)</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>

  <span class="token comment">// Function to handle logout</span>
  <span class="token keyword">const</span> logout <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">try</span> <span class="token punctuation">{</span>
      <span class="token keyword">await</span> api<span class="token punctuation">.</span>auth<span class="token punctuation">.</span><span class="token function">logout</span><span class="token punctuation">(</span><span class="token punctuation">)</span>

      opaqueToken<span class="token punctuation">.</span>value <span class="token operator">=</span> <span class="token keyword">null</span>
      user<span class="token punctuation">.</span>value <span class="token operator">=</span> <span class="token keyword">null</span>
    <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">error</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">return</span> Promise<span class="token punctuation">.</span><span class="token function">reject</span><span class="token punctuation">(</span>error<span class="token punctuation">)</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>

  <span class="token comment">// Watch for changes in the opaqueToken and sync with a cookie</span>
  <span class="token function">watch</span><span class="token punctuation">(</span>opaqueToken<span class="token punctuation">,</span> <span class="token punctuation">(</span>newVal<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> cookie <span class="token operator">=</span> <span class="token function">useCookie</span><span class="token punctuation">(</span><span class="token string">'opaqueToken'</span><span class="token punctuation">)</span>

    <span class="token keyword">if</span> <span class="token punctuation">(</span>cookie<span class="token punctuation">.</span>value <span class="token operator">!==</span> newVal<span class="token punctuation">)</span>
      cookie<span class="token punctuation">.</span>value <span class="token operator">=</span> newVal
  <span class="token punctuation">}</span><span class="token punctuation">)</span>

  <span class="token keyword">return</span> <span class="token punctuation">{</span>
    opaqueToken<span class="token punctuation">,</span>
    user<span class="token punctuation">,</span>
    loginWithEmail<span class="token punctuation">,</span>
    loginWithGoogle<span class="token punctuation">,</span>
    logout<span class="token punctuation">,</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>By invoking the <code class="inline-code">loginWithEmail</code> function anywhere in your application, you&rsquo;ll have access to both the user data and the opaque token.</p><h3 id="step-2-create-a-middleware-for-ssr">Step 2: Create a Middleware for SSR</h3><p>Now let&rsquo;s create a global middleware to share the opaque token between the server and the client. As you can see, we are using the <code class="inline-code">useCookie</code> composable as a proxy to share the opaque token between the server and the client. <code class="inline-code">useState</code> should be initialized on the server during SSR so it&rsquo;s available immediately when the client hydrates.</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token comment">// middleware/opaqueToken.global.ts</span>

<span class="token comment">// Define a global middleware that will run for every route in the application</span>
<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token function">defineNuxtRouteMiddleware</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token comment">// Create or access a reactive state named 'opaqueToken'</span>
  <span class="token comment">// This state will be available throughout the application</span>
  <span class="token keyword">const</span> state <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token string">'opaqueToken'</span><span class="token punctuation">)</span>

  <span class="token comment">// Retrieve the value of the 'opaqueToken' cookie and assign it to the 'opaqueToken' state</span>
  <span class="token comment">// This ensures that the state is always in sync with the cookie value</span>
  <span class="token comment">// This synchronization is crucial for maintaining a consistent user session across server and client.</span>
  state<span class="token punctuation">.</span>value <span class="token operator">=</span> <span class="token function">useCookie</span><span class="token punctuation">(</span><span class="token string">'opaqueToken'</span><span class="token punctuation">)</span><span class="token punctuation">.</span>value
<span class="token punctuation">}</span><span class="token punctuation">)</span>
</code></pre><p>This way, the token is always available and synchronized between SSR and CSR, maintaining a consistent user session.</p><h3 id="step-3-set-up-an-axios-plugin">Step 3: Set Up an Axios Plugin</h3><p>Finally, let&rsquo;s set up an Axios plugin to automatically include the opaque token in the authorization headers for each request:</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token comment">// plugins/axios.ts</span>
<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token function">defineNuxtPlugin</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token comment">// Retrieve the 'opaqueToken' state</span>
  <span class="token keyword">const</span> opaqueToken <span class="token operator">=</span> <span class="token function">useCookie</span><span class="token punctuation">(</span><span class="token string">'opaqueToken'</span><span class="token punctuation">)</span><span class="token punctuation">.</span>value

  <span class="token comment">// Set the 'Authorization' header with the 'opaqueToken' value</span>
  axios<span class="token punctuation">.</span>interceptors<span class="token punctuation">.</span>request<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span><span class="token punctuation">(</span>config<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>opaqueToken<span class="token punctuation">.</span>value<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      config<span class="token punctuation">.</span>headers<span class="token punctuation">.</span>Authorization <span class="token operator">=</span> <span class="token template-string"><span class="token string">`Bearer </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>opaqueToken<span class="token punctuation">.</span>value<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">`</span></span>
    <span class="token punctuation">}</span>

    <span class="token keyword">return</span> config
  <span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
</code></pre><p>With this plugin, the opaque token is included in the authorization headers for each request, allowing the backend to authenticate the user.</p><h2 id="conclusion">Conclusion</h2><p>And that&rsquo;s all you need to implement a basic, yet effective, authentication system using built-in Nuxt 3 utilities! &zwj;♀️</p><p>Sometimes, the simplest solutions are the most powerful. After countless hours of trial and error, this approach has proven to be the most reliable and easy to implement. Whether you&rsquo;re building a small project or a more complex application, this method provides a solid foundation for secure and efficient authentication. </p><p>If you found this article helpful, feel free to reach out to me on Twitter <a target="_blank" href="https://twitter.com/RifkiNada">@RifkiNada</a>. For more articles and insights, check out my work at <a target="_blank" href="https://www.nadarifki.com/">www.nadarifki.com</a>. </p><p>Thank you for reading! </p><aside><hr data-sf-ec-immutable="" /><div class="row"><div class="col-4 u-normal-full u-small-mb0"><h4 class="u-fs20 u-fw5 u-lh125 u-mb0">How to Manage Composition API Refs in Vue 3 When Unit Testing</h4></div><div class="col-8"><p class="u-fs16 u-mb0">See how to reuse state logic in Vue with <a target="_blank" href="https://www.telerik.com/blogs/how-manage-composition-api-refs-vue-3-unit-testing">composables written with the Composition API</a>. </p></div></div></aside><img src="https://feeds.telerik.com/link/23057/16884948.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:bc1b2d3e-0751-44dd-aa9f-724dd84151fc</id>
    <title type="text">How to Manage Composition API Refs in Vue 3 When Unit Testing</title>
    <summary type="text">Reuse state logic in Vue with composables written with the Composition API. Read more.</summary>
    <published>2024-10-28T08:56:05Z</published>
    <updated>2026-04-07T16:18:32Z</updated>
    <author>
      <name>Marina Mosti </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/23057/16866982/how-manage-composition-api-refs-vue-3-unit-testing"/>
    <content type="text"><![CDATA[<p><span class="featured">Reuse state logic in Vue with composables written with the Composition API. Read more.</span></p><p>The Composition API for Vue opened up a very powerful and flexible way of coding Vue components. A big part of this flexibility is the ability to create composables, which are, as described by the Vue docs, &ldquo;<em><strong>a function that leverages Vue&rsquo;s Composition API to encapsulate and reuse stateful logic</strong></em>.&rdquo;</p><p>A problem that I&rsquo;ve often encountered is that the way to test the <em>use</em> of these composables within a component is not immediately obvious, and can lead to the creation of bad practices.</p><p>In this article, we will explore how to test the use of a composable within a Vue 3 component with Jest and Vue Test Utils&mdash;the concepts, however, are directly translatable to Vitest.</p><h2 id="key-concepts">Key Concepts</h2><p>Before we dive into the code, I want to establish a couple of key concepts.</p><p>We will be leveraging the use of <code class="inline-code">jest.mock</code> to mock out the whole composable when testing it, this means that we will not be directly testing the <em>internals</em> of the composable. This is particularly important when testing composables that either make API calls internally or are provided by a third-party library.</p><p>It&rsquo;s not uncommon in certain teams to avoid this in favor of deep testing the integration of a component with all of the dependencies, but this practice can be problematic as it breaks the concept of unit testing encapsulation. The bottom line is that a unit test that tests two different components or files is crossing into the territory of an integration test.</p><p>This does not in any case mean that you should not test the composable extensively, but it should be tested <em>separately</em> as a unit. As a consumer of that unit, the component that uses it should trust that the component&rsquo;s contract input and output (I/O) is what it expects it to be, and any and all refactoring of the I/O of the unit should be treated as a breaking change within the bigger context of the codebase. (Whew.)</p><p>In some cases, however, such as simple composables that, for example, provide only a subset of <code class="inline-code">computed</code> values, it may be OK to not mock the returns of the function. However, in general, I recommend mocking the contents of these files in the component that imports them&mdash;otherwise, we&rsquo;d be creating integration tests as stated previously.</p><p>Having laid out all that, let&rsquo;s jump into the example.</p><h2 id="the-code">The Code</h2><p>Let&rsquo;s assume that we have a composable that obtains data from our API&rsquo;s endpoint for a user. Within this composable, a few network calls are happening, and the data is being parsed out into a few different computed properties that are returned out of the function so that we can check user permissions and state. Additionally, we will have an <code class="inline-code">update</code> function that would mutate the user&rsquo;s configuration.</p><p>The internals of this composable are not important. The only thing we need to know as its consumer is the contract&mdash;what properties does this composable expect, and what will it return when I call it?</p><p><code class="inline-code">useUser.js</code></p><pre class=" language-javascript"><code class="prism  language-javascript">export const useUser = (userId) =&gt; {
  [...]

  return {
    update, // a function
    user, // a ref with the User object
    isEmployee // a computed Boolean
  }
}
</code></pre><p>Whenever we call the <code class="inline-code">useUser</code> composable, we know that will have to pass a <code class="inline-code">userId</code> to it and we will receive in return the three properties declared above.</p><p>Let&rsquo;s create a simplified demo component that will use our <code class="inline-code">useUser</code> composable.</p><p><code class="inline-code">UpdateUserForm.vue</code></p><pre class=" language-markup"><code class="prism  language-markup ">&lt;template&gt;
&lt;form @submit.prevent="submit"&gt;
  &lt;input data-testid="email" name="email" v-model="user.email" /&gt;
  &lt;input data-testid="employee-id" v-if="isEmployee" name="employeeID" v-model="user.employeeID" /&gt;
&lt;/form&gt;
&lt;/template&gt;

&lt;script setup&gt;
import { useUser } from './useUser'

const props = defineProps({
  userId: { type: Number, required: true }
})

const { update, user, isEmployee } = useUser(props.userId)

const submit = async () =&gt; {
  try {
    await update(user.id, {
      email: user.value.email,
      employeeID: user.value.employeeID
    })
  } catch (e) {
    // handle error
  }
}
&lt;/script&gt;
</code></pre><p>There&rsquo;s a few things happening here so let&rsquo;s take a deep look.</p><ol><li>We are creating an extremely simplified version of a form for our user where we can edit both the email and, in the case that the user is an employee, their employee ID.</li><li>Note that the employee ID input will be displayed only in the case that the computed <code class="inline-code">isEmploye</code> is true&mdash;we will have to test for this.</li><li>When the user is submitted, it will call our <code class="inline-code">submit</code> method.</li><li>We import our <code class="inline-code">useUser</code> composable and call it with the <code class="inline-code">userId</code> received by the prop <code class="inline-code">userId</code>&mdash;we will want to test this. We extract the three returnable properties out of it by leveraging destructing.</li><li>Within our submit function we call the <code class="inline-code">update</code> function with the user&rsquo;s ID as the first parameter and a second parameter we update the properties of the user <code class="inline-code">email</code> and <code class="inline-code">employeeID</code> with the updated values.</li><li>We are assuming for simplicity that it&rsquo;s OK to mutate the <code class="inline-code">user</code> object returned to us by the composable. In some cases this may not be true, but we will use this as an example later on to clarify a point.</li></ol><p>Now that we have our component, we can start writing our tests.</p><h2 id="testing-the-updateuserform-component">Testing the UpdateUserForm Component</h2><p>The first thing we&rsquo;ll want to test is that our <code class="inline-code">useUser</code> composable is getting called with the correct value. We don&rsquo;t want to be in a situation where it&rsquo;s not correctly receiving the user id as a property. For this we will have to already set up our mock, so let&rsquo;s go through the code together.</p><p><code class="inline-code">UpdateUserForm.spec.js</code></p><pre class=" language-javascript"><code class="prism  language-javascript">import UpdateUserForm from './UpdateUserForm.vue'
import { useUser } from './useUser'
import { shallowMount } from 'vue@test-utils'
import { ref } from 'vue'

jest.mock('./useUser')

describe('UpdateUserForm', () =&gt; {
  const update = jest.fn()
  const user = ref({})
  const isEmployee = ref(false)
  
  beforeEach(() =&gt; {
    jest.clearAllMocks()

    user.value = { email: 'test@test.com', employeeID: null }
    isEmployee.value = false

    useUser.mockReturnValue({
      update,
      user,
      isEmployee
    })
  })

  it('passes the userId prop to the useUser composable', () =&gt; {
    const userId = 123
    shallowMount({
      props: { userId }
    })

    expect(useUser).toHaveBeenCalledWith(123)
  })
})
</code></pre><p>There&rsquo;s a lot happening here, so let&rsquo;s go step by step.</p><ol><li>We are using <code class="inline-code">jest.mock</code> to mock out the whole <code class="inline-code">/useUser</code> file. We will control the return of the <code class="inline-code">useUser</code> function in line 16.</li><li>In a <code class="inline-code">beforeEach</code> call, we <code class="inline-code">clearAllMocks</code> on jest because we will be using <code class="inline-code">jest.fn</code> to keep track of the <code class="inline-code">update</code> function. We also use jest&rsquo;s <code class="inline-code">mockReturnValue</code> to mock the return of the <code class="inline-code">useUser</code> composable. This means that we have to provide the returns for the composable here.</li><li>The first return <code class="inline-code">update</code> is declared above as a <code class="inline-code">jest.fn</code>, we declare it above as a <code class="inline-code">const</code> so that we can easily access this in all of our tests without having to reestablish the <code class="inline-code">mockReturnValue</code> in each test that we check for it. This may seem like overkill, but the re-mocking of the return value in every test can quickly bloat the document.</li><li>We are also declaring in lines 10 and 11 <code class="inline-code">ref</code> values for our <code class="inline-code">user</code> value and a <code class="inline-code">ref</code> for the <code class="inline-code">isEmployee</code> computed, later on inside the <code class="inline-code">beforeEach</code> block we reset the values of both of these values.</li></ol><p>A couple of question may arise at this point, the first one being why are we using a <code class="inline-code">ref</code> to mock out a computed value?</p><p>The answer is for simplicity. You could absolutely set the <code class="inline-code">isEmployee</code> constant to a computed as so.</p><pre class=" language-javascript"><code class="prism  language-javascript">const isEmployee = computed(() =&gt; false)
</code></pre><p>However, this will make it a little challenging when writing tests as you will be able to easily change the return value of this property on the fly to check for different states of your component without having to re-mock the composable. This will make more sense when we write tests for it.</p><p>The second question that may arise is: why are we declaring the refs outside of the scope of all of our tests and then reseting them in the top most <code class="inline-code">beforeEach</code> block?</p><p>As you write more and more tests that use these values, you will be faced with two challenges. Either you have to make the choice of re-mocking the return values of the composable on every test or describe block to fit what you are trying to test, which can be overwhelming and error-prone, or keep them in a scope where they are accessible to all your tests.</p><p>For cleanliness, I recommend doing all of this in the highest level <code class="inline-code">describe</code> block of your test as shown here.</p><p>The second part of this is that we are choosing to reset the values of these refs <code class="inline-code">beforeEach</code> test because these refs will <em>retain</em> the value that the last test left them on as they are scoped outside of the tests themselves. We will use the top level <code class="inline-code">beforeEach</code> as a setup point for our whole test suite, so it makes sense to set the default values there. This is why the <code class="inline-code">user</code> ref is initially created with an empty object as a value, because this value is soon replaced.</p><p>Let&rsquo;s move on and write a test that covers the <code class="inline-code">v-if</code> of our employee ID <code class="inline-code">input</code>.</p><p><code class="inline-code">UpdateUserForm.spec.js</code></p><pre class=" language-javascript"><code class="prism  language-javascript">import UpdateUserForm from './UpdateUserForm.vue'
import { useUser } from './useUser'
import { shallowMount } from 'vue@test-utils'
import { ref } from 'vue'

jest.mock('./useUser')

describe('UpdateUserForm', () =&gt; {
  const update = jest.fn().mockResolvedValue(true)
  const user = ref({})
  const isEmployee = ref(false)
  
  beforeEach(() =&gt; {
    jest.clearAllMocks()

    user.value = { email: 'test@test.com', employeeID: null }
    isEmployee.value = false

    useUser.mockReturnValue({
      update,
      user,
      isEmployee
    })
  })

  it('passes the userId prop to the useUser composable', () =&gt; {
    const userId = 123
    shallowMount({
      props: { userId }
    })

    expect(useUser).toHaveBeenCalledWith(123)
  })

  // NEW BLOCK
  describe('When the user is an employee', () =&gt; {
    beforeEach(() =&gt; {
      isEmployee.value = true
    })

    it('displays the employee ID input field when the user is an employee', () =&gt; {
      const wrapper = shallowMount({
        props: { userId: 123 }
      })

      expect(wrapper.find('[data-testid="employee-id"]').exists()).toBe(true)
    })
  })

  // NEW BLOCK
  describe('When the user is not an employee', () =&gt; {
    beforeEach(() =&gt; {
      isEmployee.value = false
    })

    it('displays the employee ID input field when the user is an employee', () =&gt; {
      const wrapper = shallowMount({
        props: { userId: 123 }
      })

      expect(wrapper.find('[data-testid="employee-id"]').exists()).toBe(false)
    })
  })
})
</code></pre><p>Let&rsquo;s once again take a deep look.</p><ol><li>We have opted for creating two <code class="inline-code">describe</code> blocks that accurately describe the state of the application that we want to test for. One of them will contain the tests for when our component is handling an employee, and one for when they are not. Notice that we added <code class="inline-code">beforeEach</code> blocks on each of them to clearly set up for the tests. In each of them we set the value of the <code class="inline-code">isEmployee</code> ref to either true or false as needed. We can rest assured that this value change will only affect these tests as we have established a reset for this earlier in the top level <code class="inline-code">beforeEach</code> block.</li><li>We then added a test for each of the scenarios.</li></ol><p>At face value, this may seem like a lot of boilerplate and structure for such simple tests, and it is&mdash;but the reality is that in a production environment you will likely have quite a few more tests and complex scenarios. Having a good order and structure for your tests is always a good practice!</p><p>Let&rsquo;s move on with the final portion of the tests, checking the ability of the user to submit the information.</p><p><code class="inline-code">UpdateUserForm.spec.js</code></p><pre class=" language-javascript"><code class="prism  language-javascript">import UpdateUserForm from './UpdateUserForm.vue'
import { useUser } from './useUser'
import { shallowMount, flushPromises } from 'vue@test-utils'
import { ref } from 'vue'

jest.mock('./useUser')

describe('UpdateUserForm', () =&gt; {
  const update = jest.fn()
  const user = ref({})
  const isEmployee = ref(false)
  
  beforeEach(() =&gt; {
    jest.clearAllMocks()

    user.value = { email: 'test@test.com', employeeID: null }
    isEmployee.value = false

    useUser.mockReturnValue({
      update,
      user,
      isEmployee
    })
  })

  it('passes the userId prop to the useUser composable', () =&gt; {
    const userId = 123
    shallowMount({
      props: { userId }
    })

    expect(useUser).toHaveBeenCalledWith(123)
  })

  describe('When the user is an employee', () =&gt; {
    beforeEach(() =&gt; {
      isEmployee.value = true
    })

    it('displays the employee ID input field when the user is an employee', () =&gt; {
      const wrapper = shallowMount({
        props: { userId: 123 }
      })

      expect(wrapper.find('[data-testid="employee-id"]').exists()).toBe(true)
    })

    // NEW
    it('calls the update function when the user submits the form', () =&gt; {
      const userId = 123
      const wrapper = shallowMount({
        props: { userId }
      })

      wrapper.find('[data-testid="email"]').setValue('email@test.com')
      wrapper.find('form').trigger('submit')
      
      await flushPromises()      

      expect(update).toHaveBeenCalledWith(userId, {
        email: 'email@test.com',
        employeeID: null
      })
    })
  })

  describe('When the user is not an employee', () =&gt; {
    beforeEach(() =&gt; {
      isEmployee.value = false
    })

    it('displays the employee ID input field when the user is an employee', () =&gt; {
      const wrapper = shallowMount({
        props: { userId: 123 }
      })

      expect(wrapper.find('[data-testid="employee-id"]').exists()).toBe(false)
    })
  })
})
</code></pre><p>In line 48, we added a new test to cover the submission of the form when the user is not an employee.</p><ol><li>We find the <code class="inline-code">email</code> input and set its value to a new email and proceed to submit the form.</li><li>We need to <code class="inline-code">await flushPromises</code> as we have declared that the <code class="inline-code">update</code> function is going to return a <code class="inline-code">Promise</code> and is being awaited.</li><li>Finally we check that the <code class="inline-code">update</code> function (that was returned by the composable) is being called with the correct values.</li></ol><p>An important thing to mention that may not be immediately obvious is that the <code class="inline-code">user</code> ref is being modified by the component whenever we update the value of one of the inputs, as the <code class="inline-code">v-model</code> bindings of each of the inputs is actually pointing to the user object.</p><p>I mentioned earlier that this may not be the case in your particular component, and this is not particularly a practice that I endorse, as mutating return values that are coming from a black box such as a composable leads frequently to problems and spaghetti code. However, I wanted to illustrate the importance of the <code class="inline-code">ref</code> management we made on the first <code class="inline-code">beforeEach</code> block.</p><p>If we had skipped the setting of the default values on this block, we would be in a situation where the tests following this one would be operating with the email we are setting on this test. This would be perhaps easy to pinpoint in a component as simple as this one, but, as your application grows in complexity, these kind of errors become extremely hard to pin down as they produce false positives in your tests.</p><h2 id="wrapping-up">Wrapping Up</h2><p>Ref management and the way that we structure our unit tests needs to be a careful consideration with as much attention to detail and care as the actual component composition itself. As applications grow and become exponentially more complex, having good clean practices and management of your reactive values will save you many headaches.</p><aside><hr /><div class="row"><div class="col-4 u-normal-full u-small-mb0"><h4 class="u-fs20 u-fw5 u-lh125 u-mb0">Why Isolating Your Unit Tests Matters</h4></div><div class="col-8"><p class="u-fs16 u-mb0">Developers are already isolating their code, even when they aren&rsquo;t running unit tests, because it lets them do more in less time. <a href="https://www.telerik.com/blogs/why-isolating-your-unit-tests-matters" target="_blank">Embracing isolation when testing has those benefits and more: better code, faster development and more reliable releases.</a></p></div></div><hr class="u-mb3" /></aside><img src="https://feeds.telerik.com/link/23057/16866982.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:22ea63d3-6d28-4ac7-988d-919aaf9c7b43</id>
    <title type="text">Vue Basics: Pinia State Management</title>
    <summary type="text">For straightforward state management in Vue, let’s look at Pinia, which lets us store and share data between components and page.</summary>
    <published>2024-10-14T08:15:05Z</published>
    <updated>2026-04-07T16:18:32Z</updated>
    <author>
      <name>Marina Mosti </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/23057/16842968/vue-basics-pinia-state-management"/>
    <content type="text"><![CDATA[<p><span class="featured">For straightforward state management in Vue, let&rsquo;s look at Pinia, which lets us store and share data between components and page. </span></p><p>There will come a time within most apps where you will have two pieces of your application that you wished shared a common reactive state. A good example of this is sharing the user information between a header component and a user edit form that is deeply nested in your application.</p><h2 id="the-problem">The Problem</h2><p>Consider the following component tree for an app.</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2024/2024-08/app-component-tree.png?sfvrsn=15e60a7a_2" alt="App points to header and settings. Settings points to usersettings, points to userform" /></p><p>Say now that you need to get a <code class="inline-code">user</code> object that you fetched out of the API on the <code class="inline-code">App</code> component with both the <code class="inline-code">Header</code> and the <code class="inline-code">UserForm</code>. You certainly could start creating a prop and emits snakes and ladders down both trees, but in a more complex application where <code class="inline-code">UserForm</code> or any other component that requires access to shared reactive state this quickly can get out of hand.</p><h2 id="the-solutions">The Solution(s)</h2><p>Vue 3 comes with a couple of tools to handle such a problem. The first approach would be passing down the <code class="inline-code">user</code> object as required down through props as we mentioned before. This can get out of hand quickly as we could potentially be dealing with an <code class="inline-code">N</code> amount of components to put the prop down through, and if we later on need to add more properties to inject down the tree this can really add up.</p><p>A second approach would be to leverage <code class="inline-code">provide</code> and <code class="inline-code">inject</code>. This is beyond the scope of this article, but I can spoil it for you already and say that, while it is <em>a</em> solution, it&rsquo;s not the <em>best</em> solution. <code class="inline-code">Provide</code> and <code class="inline-code">inject</code> are extremely powerful and flexible tools, and as any tool with the combination of these attributes it comes with a lot of drawbacks, especially when working with large teams or codebases as it can very quickly become unmanageable spaghetti.</p><p>A third approach when dealing specifically with state that is coming exclusively from your API is to use asynchronous state management libraries such as <a target="_blank" href="https://tanstack.com/query/latest">Tanstack Query</a> if you have a RESTful API or <a target="_blank" href="https://apollo.vuejs.org/">Vue Apollo</a> if you are using GraphQL. They are extremely powerful tools and you should get familiar with them (in my opinion), but they are designed for use with network data and not as a store.</p><p>The solution we will explore today is <a target="_blank" href="https://pinia.vuejs.org/introduction.html">Pinia</a>, a straightforward, easy-to-use state management store for Vue 3. This store allows us to share data between components and page in a seamless way while keeping a centralized state. Let&rsquo;s take a look.</p><h2 id="setting-up-pinia">Setting Up Pinia</h2><p>Setting Pinia up for a Vue 3 SPA is very straightforward. As usual, if you are building a new application from scratch, the npm or yarn <code class="inline-code">create</code>&nbsp;Vue script will ask you if you want to use Pinia as your state management of choice.</p><p>To incorporate it manually into your application or to add it to an existing app, we need to follow a couple of steps.</p><p>First, add the package to your application with either <code class="inline-code">npm</code> or <code class="inline-code">yarn</code>.</p><pre class=" language-markup"><code class="prism  language-markup ">yarn add pinia
# or
npm install pinia
</code></pre><p>We then need to navigate to our app&rsquo;s <code class="inline-code">main.js</code> or <code class="inline-code">index.js</code> (or wherever you are mounting your app) file and use <code class="inline-code">Vue.use</code> to add it to our application.</p><p><code class="inline-code">main.js</code></p><pre class=" language-javascript"><code class="prism  language-javascript">import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'

const pinia = createPinia()
const app = createApp(App)

app.use(pinia)
app.mount('#app')
</code></pre><p>That&rsquo;s it! Now we can move on to creating our very own user store.</p><h2 id="our-first-pinia-store">Our First Pinia Store</h2><p>We are going to create a user store to continue on with our made-up application example from the tree above. To add a store, we need to create a new file that will contain the code for it, I recommend you add these files under a <code class="inline-code">stores</code> folder to keep tidied up.</p><p><code class="inline-code">UserStore.js</code></p><pre class=" language-javascript"><code class="prism  language-javascript">import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', {

})
</code></pre><p>To create a store, we use the <code class="inline-code">defineStore</code> method exposed by <code class="inline-code">pinia</code>. It takes two main params. The first one will be the <code class="inline-code">id</code> of the store; it can be anything you prefer, but it needs to be unique to your application&mdash;&ldquo;user&rdquo; makes the most sense for our example since this is what the store will be holding.</p><p>The second param is an object that holds the store&rsquo;s options, let&rsquo;s take a look at what we can add here.</p><h3 id="state">State</h3><p>The first property we will add to the options is the <code class="inline-code">state</code>, if you are familiar with Vue&rsquo;s options API this will feel right at home, as the <code class="inline-code">state</code> for a store is basically the same thing. It is a function that returns an object that will hold all the state that we want this store to keep for us. For the sake of example we will add a <code class="inline-code">user</code> property and an <code class="inline-code">isLoading</code> flag.</p><p><code class="inline-code">UserStore.js</code></p><pre class=" language-javascript"><code class="prism  language-javascript">import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', {
  state: () =&gt; {
    return {
      isLoading: false,
      user: null
    }
  }
})
</code></pre><p>As quick as that we can already import this store into our mock <code class="inline-code">UserForm.vue</code> component and make use of the <code class="inline-code">isLoading</code> and <code class="inline-code">user</code> states.</p><p><code class="inline-code">UserForm.vue</code></p><pre class=" language-markup"><code class="prism  language-markup ">&lt;template&gt;
  &lt;p v-if="store.isLoading"&gt;Loading!&lt;/p&gt;
  &lt;form&gt; 
    &lt;input :value="store.user.name" /&gt;
  &lt;/form&gt;
&lt;/template&gt;

&lt;script setup&gt;
import { useUserStore } from './stores/UserStore.js'
const store = useUserStore()
&lt;/script&gt;
</code></pre><p>In the above example, we are importing the <code class="inline-code">useUserStore</code> and then calling the method. This returns to us a copy of the user store that we just created. The state in this store is shared, so any changes we do to it will of course be shared by any other components using the store.</p><p>Notice in the template we can now use the <code class="inline-code">isLoading</code> flag to check for the state and display a friendly message, and bind the <code class="inline-code">user.name</code> property to an input for later use.</p><h3 id="getters">Getters</h3><p>Similar to <code class="inline-code">computed</code> values, we can create getters inside of our store. A straightforward example would be a getter that joins the user&rsquo;s name and last name. Let&rsquo;s give that one a shot.</p><p><code class="inline-code">UserStore.js</code></p><pre class=" language-javascript"><code class="prism  language-javascript">import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', {
  state: () =&gt; {
    return {
      isLoading: false,
      user: null
    }
  },
  getters: {
    userFullName: (state) =&gt; state.user.name + ' ' + state.user.lastName
  }
})
</code></pre><p>As simply as it would be to create a <code class="inline-code">computed</code> property we now have a <code class="inline-code">userFullName</code> getter that we can use across our components. Let&rsquo;s head to the mock <code class="inline-code">Header.vue</code> component and use it to display a message for our users.</p><p>Note that the getter does receive a param <code class="inline-code">state</code> with the store&rsquo;s current state. It will contain any and all properties of <code class="inline-code">state</code> we created beforehand plus any other getters as well. Additionally, it&rsquo;s important to keep in mind that <code class="inline-code">getters</code> can <em>not</em> be async!</p><p><code class="inline-code">Header.vue</code></p><pre class=" language-markup"><code class="prism  language-markup ">&lt;template&gt;
&lt;header&gt;
  Welcome {{ store.userFullName }}
&lt;/header&gt;
&lt;/template&gt;

&lt;script setup&gt;
import { useUserStore } from './stores/UserStore.js'
const store = useUserStore()
&lt;/script&gt;
</code></pre><p>Note that once again we import the store and call the <code class="inline-code">useUserStore</code> method to create it. This store points to the same state as the one we created before on the <code class="inline-code">UserForm.vue</code> file, so if the user changes their name it will also be reflected here.</p><p>The getter we created, <code class="inline-code">userFullName</code>, can be used directly from the store as shown in the above example.</p><h3 id="actions">Actions</h3><p>The last option we can add to our store are <code class="inline-code">actions</code>, these are the equivalent of a component&rsquo;s <code class="inline-code">methods</code>. As opposed to <code class="inline-code">getters</code>, <code class="inline-code">methods</code> can be async and can contain API calls. This is a good location to set up our logic to load the user&rsquo;s data.</p><p><code class="inline-code">UserStore.js</code></p><pre class=" language-javascript"><code class="prism  language-javascript">import { defineStore } from 'pinia'
import { getCurrentUser } from './api/user'

export const useUserStore = defineStore('user', {
  state: () =&gt; {
    return {
      isLoading: false,
      user: null
    }
  },
  getters: {
    userFullName: (state) =&gt; state.user.name + ' ' + state.user.lastName
  },
  methods: {
    async loadUser () {
      this.isLoading = true
      
      const data = await getCurrentUser()
      this.user = data      

      this.isLoading = false
    }
  }
})
</code></pre><p>We&rsquo;ve imported a mock <code class="inline-code">getCurrentUser</code> API call on the top of the file, this would most likely make a <code class="inline-code">GET</code> call to an endpoint to retrieve the user.</p><p>Then, we&rsquo;ve created a new method on our store <code class="inline-code">loadUser</code>. This method sets the <code class="inline-code">isLoading</code> flag to <code class="inline-code">true</code> so that the user knows something is happening, and then makes an async call to the <code class="inline-code">getUser</code> method. When we receive the data back, we store it into our <code class="inline-code">user</code> state and then reset the <code class="inline-code">isLoading</code> flag to <code class="inline-code">false</code>.</p><p>A couple of things that are missing from this example are error handling and double-checking to see if we already have a user in the state before making a new network request to refetch it, but that&rsquo;s outside of the scope of this article.</p><p>Now we can go back to our <code class="inline-code">UserForm.vue</code> component and call this method.</p><pre class=" language-markup"><code class="prism  language-markup ">&lt;template&gt;
  &lt;p v-if="store.isLoading"&gt;Loading!&lt;/p&gt;
  &lt;form&gt; 
    &lt;input :value="store.user.name" /&gt;
  &lt;/form&gt;
&lt;/template&gt;

&lt;script setup&gt;
import { useUserStore } from './stores/UserStore.js'
const store = useUserStore()

store.loadUser()
&lt;/script&gt;
</code></pre><p>Now when our component loads, it will call the <code class="inline-code">loadUser</code> method of our store and load the user from the API. As soon as the user is loaded, the <code class="inline-code">Header.vue</code> component will display the <code class="inline-code">userFullName</code> getter we created. Remember, the <code class="inline-code">user</code> state is <em>shared</em> between all of the components that are using this store.</p><h2 id="wrapping-up">Wrapping Up</h2><p>This merely scratches the surface of what you can do with Pinia. As with any library, I recommend taking a deep dive into their <a target="_blank" href="https://pinia.vuejs.org/getting-started.html">documentation</a> as it is very well written and will help you learn not only the fundamentals but also the more intricate parts of this amazing library.</p><p>Nevertheless, you are now armed with the basics and can start writing your very own Pinia stores!</p><img src="https://feeds.telerik.com/link/23057/16842968.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:5843ae05-2738-4119-a1d9-42059de981e1</id>
    <title type="text">Vue Basics: How to Develop Better Vue Applications with Nuxt</title>
    <summary type="text">These 10 features of Nuxt 3 can enhance your Vue application development beyond Vue alone.</summary>
    <published>2024-10-01T09:16:11Z</published>
    <updated>2026-04-07T16:18:32Z</updated>
    <author>
      <name>Nada Rifki </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/23057/16829257/vue-basics-how-develop-better-vue-applications-nuxt"/>
    <content type="text"><![CDATA[<p><span class="featured">These 10 features of Nuxt 3 can enhance your Vue application development beyond Vue alone.</span></p><p> If you&rsquo;ve read <a target="_blank" href="https://www.telerik.com/blogs/author/nada-rifki">my previous articles</a>, you know that I&rsquo;m a Vue gal and that I love Nuxt! &zwj;♀️</p><p>In a nutshell, Nuxt is a powerful framework built on top of Vue that gives your Vue apps superpowers.</p><p>In this article, I discuss 10 important features of Nuxt 3 and why you should learn more about this framework if you are familiar with Vue. It&rsquo;s another learning curve, but you will be able to enhance your Vue applications and take them to the next level. </p><h2 id="installing-nuxt">Installing Nuxt</h2><p>Creating a new Nuxt project is as easy as typing <code class="inline-code">npx nuxi@latest init</code> in your terminal. <a target="_blank" href="https://nuxt.com/docs/getting-started/installation">Head over the official installation page</a> for more details.</p><h2 id="but-first-say-hello-to-typescript">But First, Say Hello to TypeScript</h2><p>JavaScript is great, but have you ever had to deal with an error in production that you didn&rsquo;t catch in the dev or staging environment because of a type mismatch?!</p><p>Yes, yes, I have, and many web developers have had to deal with that too.  Especially when you&rsquo;re in a small team and/or when you don&rsquo;t have code reviews.  It even happens, in very rare occurrences, in big teams with a staging server, code reviews and pre-prod environments.</p><p> So do yourself, your team and your clients a favor and use TypeScript to avoid bugs and maintain consistency throughout your codebase. The good news is that Nuxt 3 was built to be a first-class TypeScript citizen. To be honest, using Nuxt 3 without TypeScript seems like a strange choice to me. One of the reasons almost everyone switched to the Composition API is because of its better integration with TypeScript.</p><aside><hr data-sf-ec-immutable="" /><div class="row"><div class="col-4 u-normal-full u-small-mb0"><h4 class="u-fs20 u-fw5 u-lh125 u-mb0">10 Quick Tips I Have Learned Using TypeScript
      </h4></div><div class="col-8"><p class="u-fs16 u-mb0">TypeScript offers everything JavaScript does, plus static typing. <a href="https://www.telerik.com/blogs/10-quick-tips-learned-using-typescript" target="_blank"> Check out these 10 tips that will help you fall in love too!</a>
 </p></div></div><hr class="u-mb3" /></aside><p>To give you one great example, you will be able to see right from your editor when you are sending something wrong to one component prop. In our projects, we usually put all of our types in a folder called types at the root. I will create a file called bases.d.ts inside with the following content.</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">type</span> BaseButtonColor <span class="token operator">=</span> <span class="token string">'blue'</span> <span class="token operator">|</span> <span class="token string">'gray'</span> <span class="token operator">|</span> <span class="token string">'green'</span>
</code></pre><p>Now, in my <code class="inline-code">BaseButton</code> component, I will use something called <code class="inline-code">PropTypes</code> (that is automatically imported in Nuxt) like this:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token operator">&lt;</span>script lang<span class="token operator">=</span><span class="token string">"ts"</span> setup<span class="token operator">&gt;</span>
<span class="token comment">// Props</span>
<span class="token keyword">const</span> props <span class="token operator">=</span> <span class="token function">defineProps</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  color<span class="token punctuation">:</span> <span class="token punctuation">{</span>
    <span class="token keyword">default</span><span class="token punctuation">:</span> <span class="token string">'gray'</span><span class="token punctuation">,</span>
    <span class="token keyword">type</span><span class="token punctuation">:</span> String <span class="token keyword">as</span> PropType<span class="token operator">&lt;</span>BaseButtonColor<span class="token operator">&gt;</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token operator">&lt;</span><span class="token operator">/</span>script<span class="token operator">&gt;</span>
</code></pre><p>If I try to use this <code class="inline-code">BaseButton</code> component somewhere and add the color red, your editor will warn you that this prop is not correct.</p><p>Of course, using TypeScript comes with many more benefits that you will discover along your journey, but believe me, once you make the switch, there is no going back. </p><h2 id="feature-1-nuxt-makes-routing-more-intuitive">Feature #1: Nuxt Makes Routing More Intuitive!</h2><p>Nuxt 3 comes with an automatic <a target="_blank" href="https://nuxt.com/docs/getting-started/routing">file-based routing system</a>, which simplifies the creation and management of your routes. As you remember, that&rsquo;s different from Vue, where you have to set your routes manually. This means that now, you don&rsquo;t have to get lost anymore in the maze that your router file becomes when your app scales up. &zwj;</p><p>In Nuxt, your file structure dictates your routes. Each file is a route. As you can see in the documentation, if you structure your files like this, for example:</p><pre class=" language-markdown"><code class="prism  language-markdown">| <span class="token title important">pages/
<span class="token punctuation">---</span></span>| <span class="token title important">about.vue
<span class="token punctuation">---</span></span>| <span class="token title important">index.vue
<span class="token punctuation">---</span></span>| <span class="token title important">posts/
<span class="token punctuation">-----</span></span>| [id].vue
</code></pre><p>This will automatically generate this router structure:</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token punctuation">{</span>
  <span class="token string">"routes"</span><span class="token punctuation">:</span> <span class="token punctuation">[</span>
    <span class="token punctuation">{</span>
      <span class="token string">"path"</span><span class="token punctuation">:</span> <span class="token string">"/about"</span><span class="token punctuation">,</span>
      <span class="token string">"component"</span><span class="token punctuation">:</span> <span class="token string">"pages/about.vue"</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">{</span>
      <span class="token string">"path"</span><span class="token punctuation">:</span> <span class="token string">"/"</span><span class="token punctuation">,</span>
      <span class="token string">"component"</span><span class="token punctuation">:</span> <span class="token string">"pages/index.vue"</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">{</span>
      <span class="token string">"path"</span><span class="token punctuation">:</span> <span class="token string">"/posts/:id"</span><span class="token punctuation">,</span>
      <span class="token string">"component"</span><span class="token punctuation">:</span> <span class="token string">"pages/posts/[id].vue"</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">]</span>
<span class="token punctuation">}</span>
</code></pre><p>Handy right!? &zwj;♀️</p><p>I&rsquo;ve never had any issues with it, so I can&rsquo;t recommend enough that you use the file system routing if you like it. It&rsquo;s, in my opinion, the best way to quickly organize your application. As you can see, with Nuxt there is one less file to deal with. &zwj;</p><h2 id="feature-2-nuxt-uses-convention-over-configuration">Feature #2: Nuxt Uses Convention over Configuration</h2><p>What does convention over configuration mean and why should we care? </p><p>Nuxt favors predefined conventions and opinionated defaults over requiring developers to explicitly configure every aspect of their app. This approach saves us a lot of time and allows us to focus on writing the unique parts of our application rather than setting up the environment. Of course, it means that you have to follow the conventions, but it&rsquo;s a small price to pay for the benefits it brings.</p><p>This is something I enjoy about Nuxt. Everything is properly organized, and you don&rsquo;t have to think a lot about the structure of your application. You just follow the framework&rsquo;s best practices. For example, Nuxt includes powerful middleware and layout systems that you can use right from the start. And if you need to create your own library of components, just use the <a target="_blank" href="https://nuxt.com/docs/getting-started/layers">Nuxt layers</a> as it provides a lot of flexibility (and believe me, it was harder in the past; <a target="_blank" href="https://www.telerik.com/blogs/vuejs-how-to-build-your-first-package-publish-it-on-npm">I even wrote an article about it</a>). &zwj;♀️</p><blockquote><p><strong>Note:</strong> You are working with a higher level of abstraction, and things can sometimes seem magical, but the good news is that with the <a href="https://devtools.nuxt.com/" target="_blank">Nuxt DevTools</a>, you can see everything that is happening under the hood. &zwj;♀️ P.S. Here is my <a href="https://www.telerik.com/blogs/vue-basics-deep-dive-nuxt-devtools" target="_blank">Get Started with Nuxt DevTools</a> piece.</p></blockquote><p>Another powerful aspect of Nuxt 3 is <a target="_blank" href="https://nuxt.com/docs/guide/going-further/runtime-config">its runtime configuration</a>. When you need to manage different environments (development, staging, production) without needing to rebuild your application, this feature allows you to maintain consistent behavior across various environments with minimal effort.</p><p>My point is that by choosing a framework that embraces this philosophy, you will enhance your development workflow, maintain cleaner code and build scalable applications with ease. &zwj;</p><h2 id="feature-3-nuxt-auto-imports-all-your-composables">Feature #3: Nuxt Auto-Imports All Your Composables</h2><p>One thing I enjoy about Nuxt 3 is that everything exported in your composable folder is available globally in your app. This is incredibly handy as it makes our developer experience more seamless (we don&rsquo;t have to make sure the right thing is imported). You also have access to native composables or the ones coming from modules like <code class="inline-code">useLocalePath</code>, <code class="inline-code">useI18n</code>, <code class="inline-code">useFetch</code>, <code class="inline-code">useAsyncData</code> and many more. Additionally, managing cookies is made easy with the <code class="inline-code">useCookies</code> composable. &zwj;♀️</p><blockquote><p><strong>Note</strong>: If you need to see all the composables that are auto-imported, use the DevTools by going to imports or composables.</p></blockquote><p>One thing I like is to have a composable <code class="inline-code">useIcons</code> where I put all my icons in one place. This way, I just have to call <code class="inline-code">getIcon('myIcon')</code>. For example, you can use <a target="_blank" href="https://iconify.design/">Iconify</a>, which is free and comes with thousands of icons from multiple packs.</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">const</span> icons <span class="token operator">=</span> <span class="token punctuation">{</span>
  check<span class="token punctuation">:</span> <span class="token string">'solar:check-circle-linear'</span><span class="token punctuation">,</span>
  chevronLeft<span class="token punctuation">:</span> <span class="token string">'solar:alt-arrow-left-linear'</span><span class="token punctuation">,</span>
  chevronRight<span class="token punctuation">:</span> <span class="token string">'solar:alt-arrow-right-linear'</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span> <span class="token keyword">as</span> <span class="token keyword">const</span>

<span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">getIcon</span><span class="token punctuation">(</span>attribute<span class="token punctuation">:</span> keyof <span class="token keyword">typeof</span> icons<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> icons<span class="token punctuation">[</span>attribute<span class="token punctuation">]</span> <span class="token operator">||</span> icons<span class="token punctuation">.</span><span class="token keyword">default</span>
<span class="token punctuation">}</span>

<span class="token keyword">export</span> <span class="token keyword">type</span> UIcon <span class="token operator">=</span> keyof <span class="token keyword">typeof</span> icons
</code></pre><h2 id="feature-4-nuxt-comes-with-different-rendering-modes-for-better-performance.">Feature #4: Nuxt Comes with Different Rendering Modes for Better Performance</h2><p>Like Next for React, one of the killer features of Nuxt has always been its server-side rendering (also known as SSR). In contrast, Vue 3 requires additional setup and configuration for SSR.</p><p>To summarize what SSR is about, it simply means that the server will fully render the HTML page before sending it to the client. This will improve SEO, the initial load performance (compared to client-side rendering) and the overall user experience. I have dedicated an entire section about why Nuxt is a better choice for SEO than using Vue, but as you can guess SSR is one of the main reasons people got attracted to Nuxt. By allowing webpages to be displayed faster, we get them indexed more effectively by search engines.</p><p>Also, over the years, Nuxt has evolved, and four additional rendering modes are now available:</p><ul><li><strong>Static Site Generation (SSG)</strong>: This means the page is pre-rendered at build time, resulting in static HTML files that enhance performance and security. You will have many HTML files ready to be served to your users that you can easily deploy on a CDN like Netlify or Vercel.</li><li><strong>Client-Side Rendering (CSR)</strong>: Like a classic Vue app, the page is entirely rendered in the browser. It may benefit highly interactive applications but results in slower initial load times.</li><li><strong>Hybrid Rendering</strong>: It combines SSR, SSG and CSR, allowing different pages or routes to use different rendering modes as needed. This is a great feature that allows you to choose the best rendering mode for each page of your application.</li><li><strong>And the last one&hellip; Edge-Side Rendering (ESR)</strong>: I&rsquo;ve never used it, but it offloads the rendering process of your page to the CDN&rsquo;s edge servers. As explained in the documentation: <em>When a request for a page is made, instead of going all the way to the original server, it&rsquo;s intercepted by the nearest edge server. This server generates the HTML for the page and sends it back to the user. This process minimizes the physical distance the data has to travel, reducing latency and loading the page faster.</em></li></ul><p>If you want to know more about the different rendering modes, I recommend you read the <a target="_blank" href="https://nuxt.com/docs/guide/concepts/rendering">Nuxt documentation</a>. &zwj;</p><h2 id="feature-5-nuxt-will-make-your-seo-life-easier.">Feature #5: Nuxt Will Make Your SEO Life Easier</h2><p>We talked about SSR, but Nuxt 3 also comes with built-in composables that greatly enhance your SEO capabilities: <a target="_blank" href="https://nuxt.com/docs/api/composables/use-seo-meta"><code class="inline-code">useSeoMeta</code></a>. You can easily set your meta tags inside your <code class="inline-code">&lt;head&gt;</code> section, titles and other SEO-related information with this composable. As Nuxt auto-imports all your composables, you can use it globally anywhere in your pages.</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token function">useSeoMeta</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  description<span class="token punctuation">:</span> <span class="token string">'My awesome description'</span><span class="token punctuation">,</span>
  title<span class="token punctuation">:</span> <span class="token string">'Welcome to my website'</span><span class="token punctuation">,</span>
  titleTemplate<span class="token punctuation">:</span> <span class="token string">'Project | %pageTitle'</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
</code></pre><p>There are over 100 parameters that you can use. <a target="_blank" href="https://github.com/harlan-zw/zhead/blob/main/packages/zhead/src/metaFlat.ts">You can see the full list here.</a> &zwj;</p><p>Additionally, there&rsquo;s a fantastic package called <a target="_blank" href="https://nuxtseo.com">Nuxt SEO</a> that you will need. It is a collection of different modules that you can add to your app to easily deal with your sitemap (<code class="inline-code">@nuxtjs/sitemap</code>), your robots.txt (<code class="inline-code">nuxt-simple-robots</code>), your schema org or even to generate dynamic OG Images.</p><blockquote><p><strong>Note</strong>: If you use the Nuxt DevTools, take a look at the SEO section to see all your missing meta tags. It&rsquo;s a great way to quickly check if everything is set correctly. &zwj;♀️</p></blockquote><h2 id="feature-6-nuxt-allows-you-to-write-your-own-api.">Feature #6: Nuxt Allows You to Write Your Own API</h2><p>Nuxt 3 is a full-stack framework. This means you can write your own API directly in Nuxt. There&rsquo;s no need to add another framework to your stack like Adonis or NestJS. Your backend logic, such as creating or managing user accounts, can be handled directly within Nuxt. ️&zwj;♀️</p><p>For detailed information, check out the <a target="_blank" href="https://nuxt.com/docs/guide/directory-structure/server">Nuxt API documentation</a>. Take a moment to read through it because it&rsquo;s really cool! </p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token comment">// server/api/hello.ts</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token function">defineEventHandler</span><span class="token punctuation">(</span><span class="token punctuation">(</span>event<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token punctuation">{</span>
    hello<span class="token punctuation">:</span> <span class="token string">'world'</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
</code></pre><p>This is a significant advantage over Vue 3, which focuses primarily on the frontend and requires additional backend configuration to achieve similar functionality.</p><blockquote><p><strong>Note</strong>: By utilizing <code class="inline-code">@sentry/vue</code>, you can catch errors in your API routes, monitor what&rsquo;s going wrong and continuously improve your app&rsquo;s performance and reliability.</p></blockquote><h2 id="feature-7-data-fetching-with-nuxt-is-easier">Feature #7: Data Fetching with Nuxt Is Easier</h2><p>Nuxt 3 simplifies data fetching with composables like <code class="inline-code">useAsyncData</code> and <code class="inline-code">useFetch</code>. These composables are designed to work seamlessly with SSR, making it easier to manage asynchronous data fetching without the hassle of additional boilerplate code. They also provide additional options for handling loading states, errors and caching, enhancing the overall user experience.</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token operator">&lt;</span>script setup lang<span class="token operator">=</span><span class="token string">"ts"</span><span class="token operator">&gt;</span>
<span class="token keyword">const</span> <span class="token punctuation">{</span> data<span class="token punctuation">,</span> status<span class="token punctuation">,</span> error<span class="token punctuation">,</span> refresh<span class="token punctuation">,</span> clear <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">useFetch</span><span class="token punctuation">(</span><span class="token string">'/api/user'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span>
  pick<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">'username'</span><span class="token punctuation">,</span> <span class="token string">'email'</span><span class="token punctuation">,</span> <span class="token string">'avatar'</span><span class="token punctuation">]</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>

<span class="token comment">// data: the result of the asynchronous function that is passed in.</span>
<span class="token comment">// refresh: a function that can be used to refresh the data returned by the handler function.</span>
<span class="token comment">// error: an error object if the data fetching failed.</span>
<span class="token comment">// status: a string indicating the status of the data request ("idle", "pending", "success", "error").</span>
<span class="token comment">// clear: a function which will set data to undefined, set error to null, set status to 'idle', and mark any currently pending requests as cancelled.</span>
<span class="token operator">&lt;</span><span class="token operator">/</span>script<span class="token operator">&gt;</span>
</code></pre><p>In contrast, Vue 3 requires you to manage data fetching manually with a library like Axios.</p><h2 id="feature-8-nuxt-has-an-i18n-module-to-create-multilingual-applications">Feature #8: Nuxt Has an I18N Module to Create Multilingual Applications</h2><p>If you know that your application will be in multiple languages, I can&rsquo;t recommend <a target="_blank" href="https://i18n.nuxtjs.org/">Nuxt I18N</a> enough to start with from the beginning. This module makes it easy to create multilingual applications, saving you a lot of time and effort in the future. &zwj;</p><p>It even allows you to use <a target="_blank" href="https://i18n.nuxtjs.org/docs/guide/per-component-translations">per-component translation</a> (something I use intensively, as GitHub Copilot can easily translate for other languages).</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token operator">&lt;</span>script setup lang<span class="token operator">=</span><span class="token string">"ts"</span><span class="token operator">&gt;</span>
<span class="token keyword">const</span> <span class="token punctuation">{</span> t <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">useI18n</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  useScope<span class="token punctuation">:</span> <span class="token string">'local'</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token operator">&lt;</span><span class="token operator">/</span>script<span class="token operator">&gt;</span>

<span class="token operator">&lt;</span>template<span class="token operator">&gt;</span>
  <span class="token operator">&lt;</span>p<span class="token operator">&gt;</span><span class="token punctuation">{</span><span class="token punctuation">{</span> <span class="token function">t</span><span class="token punctuation">(</span><span class="token string">'hello'</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token operator">&lt;</span><span class="token operator">/</span>p<span class="token operator">&gt;</span>
<span class="token operator">&lt;</span><span class="token operator">/</span>template<span class="token operator">&gt;</span>

<span class="token operator">&lt;</span>i18n lang<span class="token operator">=</span><span class="token string">"json"</span><span class="token operator">&gt;</span>
<span class="token punctuation">{</span>
  <span class="token string">"en"</span><span class="token punctuation">:</span> <span class="token punctuation">{</span>
    <span class="token string">"hello"</span><span class="token punctuation">:</span> <span class="token string">"hello world!"</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token string">"ja"</span><span class="token punctuation">:</span> <span class="token punctuation">{</span>
    <span class="token string">"hello"</span><span class="token punctuation">:</span> <span class="token string">"こんにちは、世界!"</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token operator">&lt;</span><span class="token operator">/</span>i18n<span class="token operator">&gt;</span>
</code></pre><p>Useful composables you will need all the time are: <code class="inline-code">useLocalePath</code>, <code class="inline-code">useI18n</code> and <code class="inline-code">useSwitchLocalePath</code>. &zwj;</p><h2 id="feature-9-nuxt-has-nuxt-content-a-module-to-manage-content">Feature #9: Nuxt Has Nuxt Content, a Module to Manage Content</h2><p>If you have a lot of content to write and want to use Markdown, use <a target="_blank" href="https://content.nuxt.com/">Nuxt Content</a>. It will be automatically converted to HTML, making it ideal for blogs, documentation and static content.</p><p>You will have access to composables like <a target="_blank" href="https://content.nuxt.com/composables/query-content"><code class="inline-code">queryContent</code></a> to query your content, and you can even use <a target="_blank" href="https://content.nuxt.com/usage/markdown#vue-components">your own Vue component inside your Markdown files</a>.</p><pre class=" language-markdown"><code class="prism  language-markdown">::alert{type="warning"}
The <span class="token bold"><span class="token punctuation">**</span>alert<span class="token punctuation">**</span></span> component.
::
</code></pre><p>I always use Nuxt Content for articles, privacy policies and other content needs. Additionally, consider using <a target="_blank" href="https://nuxt.studio/">Nuxt Studio</a>, a CMS for Nuxt Content, though it comes at a cost.</p><h2 id="feature-10-nuxt-comes-with-a-lot-of-modules">Feature #10: Nuxt Comes with a Lot of Modules</h2><p>I could not end this article without mentioning one big advantage of Nuxt: <a target="_blank" href="https://nuxt.com/modules">its rich ecosystem of modules</a>. There is a module for almost everything you need, from authentication to PWA, analytics and more. And what&rsquo;s best is that it is easy to add powerful features to your application with minimal configuration. &zwj;♀️</p><p>For example, it&rsquo;s easy to create a dark mode with the <code class="inline-code">@nuxtjs/color-mode</code> module or optimize all your images with the <code class="inline-code">@nuxt/image</code> module.</p><p>So don&rsquo;t reinvent the wheel, and take a look at the modules that are available. You will be surprised by how much time you can save by using them. </p><h2 id="conclusion">Conclusion</h2><p>I hope I could convince you to have a look at Nuxt. Next time you start a new project, consider using Nuxt instead of Vue if it makes more sense to you. I am sure it will make your life easier and your applications better. </p><p>You can reach me on Twitter <a target="_blank" href="https://twitter.com/RifkiNada">@RifkiNada</a>. And in case you are curious about my work or other articles, you can look at them here <a target="_blank" href="https://www.nadarifki.com/">www.nadarifki.com</a>. </p><p>Thank you for reading! </p><img src="https://feeds.telerik.com/link/23057/16829257.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:906ff35e-d38f-46b8-9cb7-00788281bc0a</id>
    <title type="text">Vue Basics: How to Build Complex Forms in Vue</title>
    <summary type="text">Using a demo project, we will walk through all the concepts and tips that will help understand how to build forms in Vue that efficiently handle complex data and user interaction.</summary>
    <published>2024-09-24T09:12:56Z</published>
    <updated>2026-04-07T16:18:32Z</updated>
    <author>
      <name>Nada Rifki </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/23057/16819177/vue-basics-how-build-complex-forms-vue"/>
    <content type="text"><![CDATA[<p><span class="featured">Using a demo project, we will walk through all the concepts and tips that will help understand how to build forms in Vue that efficiently handle complex data and user interaction.</span></p><p>☝️ Before we even dive in, let&rsquo;s first answer a simple question: What do we consider a complex form in Vue?</p><p>Well, any form that has multiple steps. Take a shopping checkout process.  Here, you&rsquo;ll need state management to store the data as the customer navigates through different views.</p><p>The same applies for nested inputs  when forms require organized sections for details like separate billing and shipping addresses. We will need to validate the values according to specific rules or use a state management system (spoiler alert: I will introduce you to our friends Zod and Pinia).</p><p> Another example is when you need to integrate with external APIs, like <a target="_blank" href="https://developers.google.com/maps/documentation/address-validation/overview">Google address validation API</a> for real-time postal address verification. As we don&rsquo;t control the response time, this comes with additional limitations we will have to take into account.</p><p>And let&rsquo;s not forget fields that dynamically adapt to user input, such as the phone number field that displays the country name/flag as the user types it in.</p><p> So if you have to deal with any of these types of forms, you are in the right place to understand how to build forms in Vue that efficiently handle complex data and user interaction. </p><h2 id="the-basics">The Basics</h2><h3 id="project-setup">Project Setup</h3><p>✨ Together we will build a form for an educational app, &zwj; where the user:</p><ol><li>Selects the subject they want to study</li><li>Chooses the platforms from where we will compose the curriculum</li><li>Fills out a checkout form</li><li>Pays for the study package</li></ol><p>&zwj; I know, this rocks! </p><p>Let&rsquo;s set up our project using Vite:</p><ol><li>Open your terminal and run <code class="inline-code">npm create vite@latest</code> in the folder of your choice.</li><li>Choose the following: Typescript + Vue Router + Pinia + Vitest + Cypress + ESLint + Prettier + Vue DevTools.</li><li>Open your project in VS Code using this command, like the cool kids do: <code class="inline-code">cd the-name-of-your-project; code</code>.</li><li>Run these commands in your VS Code built-in terminal:</li></ol><pre class=" language-bash"><code class="prism  language-bash"><span class="token function">npm</span> <span class="token function">install</span>
<span class="token function">npm</span> run <span class="token function">format</span>
<span class="token function">npm</span> run dev
</code></pre><ol start="5"><li>We will also be using Tailwind CSS. Follow these steps to get it up and running in your project:</li></ol><pre class=" language-bash"><code class="prism  language-bash"><span class="token function">npm</span> <span class="token function">install</span> -D tailwindcss postcss autoprefixer<span class="token punctuation">;</span>
npx tailwindcss init -p
</code></pre><ul><li>Then, in your <code class="inline-code">tailwind.config.js</code>:</li></ul><pre class=" language-js"><code class="prism  language-js">content<span class="token punctuation">:</span> <span class="token punctuation">[</span>
  <span class="token string">"./index.html"</span><span class="token punctuation">,</span>
  <span class="token string">"./src/**/*.{vue,js,ts}"</span><span class="token punctuation">,</span>
<span class="token punctuation">]</span><span class="token punctuation">,</span>
</code></pre><ul><li>Then, import tailwind in your <code class="inline-code">main.css</code>:</li></ul><pre class=" language-css"><code class="prism  language-css"><span class="token atrule"><span class="token rule">@tailwind</span> base<span class="token punctuation">;</span></span>
<span class="token atrule"><span class="token rule">@tailwind</span> components<span class="token punctuation">;</span></span>
<span class="token atrule"><span class="token rule">@tailwind</span> utilities<span class="token punctuation">;</span></span>
</code></pre><ol start="6"><li>Now all you have to do is clean your components and views  or you can <a target="_blank" href="https://www.dropbox.com/scl/fi/eldze955sfol70txpkeqa/safo.zip?rlkey=jodreljxhafche3dz55ifkeu8&amp;st=9394399c&amp;dl=0">download my version here</a>. (I know, I could have told you earlier, but where is the fun in that?!)</li></ol><h3 id="two-way-data-binding-v-model">Two-way Data Binding (v-model)</h3><p><code class="inline-code">v-model</code> is the glue that holds your data and UI in perfect harmony. It synchronizes in real time the state of your application with what the user interacts with. You will use this feature a lot when building forms in Vue.</p><p>For instance, if you have a username input, as soon as the user inputs their name (let&rsquo;s say: nada), you can display a &ldquo;Hello, nada!&rdquo; without having to go through the hassle of setting up event listeners to detect changes and updates your data.</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token operator">&lt;</span><span class="token operator">!</span><span class="token operator">--</span> Random example <span class="token operator">--</span><span class="token operator">&gt;</span>

<span class="token operator">&lt;</span>template<span class="token operator">&gt;</span>
  <span class="token operator">&lt;</span>div<span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span>input v<span class="token operator">-</span>model<span class="token operator">=</span><span class="token string">"userName"</span> placeholder<span class="token operator">=</span><span class="token string">"Enter your name"</span> <span class="token operator">/</span><span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span>p<span class="token operator">&gt;</span>Hello<span class="token punctuation">,</span> <span class="token punctuation">{</span><span class="token punctuation">{</span> userName <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token operator">!</span><span class="token operator">&lt;</span><span class="token operator">/</span>p<span class="token operator">&gt;</span>
  <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span>
<span class="token operator">&lt;</span><span class="token operator">/</span>template<span class="token operator">&gt;</span>

<span class="token operator">&lt;</span>script<span class="token operator">&gt;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> defineComponent<span class="token punctuation">,</span> ref <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'vue'</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token function">defineComponent</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  name<span class="token punctuation">:</span> <span class="token string">'GreetingExample'</span><span class="token punctuation">,</span>

  <span class="token function">setup</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> userName <span class="token operator">=</span> <span class="token function">ref</span><span class="token punctuation">(</span><span class="token string">''</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">return</span> <span class="token punctuation">{</span> userName <span class="token punctuation">}</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token operator">&lt;</span><span class="token operator">/</span>script<span class="token operator">&gt;</span>
</code></pre><p>Another neat feature of two-way data binding is that components and their parent components can communicate. Let&rsquo;s see how this plays in our new app:</p><ol><li>Create a <code class="inline-code">FieldInput.vue</code>component:</li></ol><pre class=" language-ts"><code class="prism  language-ts"><span class="token operator">&lt;</span>template<span class="token operator">&gt;</span>
  <span class="token operator">&lt;</span>div <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"mb-4"</span><span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span><span class="token operator">!</span><span class="token operator">--</span> Conditionally render the label <span class="token keyword">if</span> the <span class="token string">'label'</span> prop is provided <span class="token operator">--</span><span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span>label
      v<span class="token operator">-</span><span class="token keyword">if</span><span class="token operator">=</span><span class="token string">"label"</span>
      <span class="token punctuation">:</span><span class="token keyword">for</span><span class="token operator">=</span><span class="token string">"id"</span> 
      <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"mb-2 block text-lg font-semibold text-gray-700"</span>
    <span class="token operator">&gt;</span>
      <span class="token punctuation">{</span><span class="token punctuation">{</span> label <span class="token punctuation">}</span><span class="token punctuation">}</span>
    <span class="token operator">&lt;</span><span class="token operator">/</span>label<span class="token operator">&gt;</span>

    <span class="token operator">&lt;</span><span class="token operator">!</span><span class="token operator">--</span> Input field bound directly to <span class="token string">'internalValue'</span> using v<span class="token operator">-</span>model <span class="token operator">--</span><span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span>input
      <span class="token punctuation">:</span>id<span class="token operator">=</span><span class="token string">"id"</span>
      v<span class="token operator">-</span>model<span class="token operator">=</span><span class="token string">"internalValue"</span>
      <span class="token punctuation">:</span><span class="token keyword">type</span><span class="token operator">=</span><span class="token string">"type"</span>
      <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 rounded-md p-2 border"</span>
      <span class="token punctuation">:</span>placeholder<span class="token operator">=</span><span class="token string">"placeholder"</span>
    <span class="token operator">/</span><span class="token operator">&gt;</span>
  <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span>
<span class="token operator">&lt;</span><span class="token operator">/</span>template<span class="token operator">&gt;</span>

<span class="token operator">&lt;</span>script lang<span class="token operator">=</span><span class="token string">"ts"</span><span class="token operator">&gt;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> defineComponent<span class="token punctuation">,</span> computed <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'vue'</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token function">defineComponent</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
 name<span class="token punctuation">:</span> <span class="token string">'FieldInput'</span><span class="token punctuation">,</span>

  <span class="token comment">// Define the props that this component accepts</span>
  props<span class="token punctuation">:</span> <span class="token punctuation">{</span>
  id<span class="token punctuation">:</span> <span class="token punctuation">{</span>
    <span class="token keyword">type</span><span class="token punctuation">:</span> String<span class="token punctuation">,</span>
    required<span class="token punctuation">:</span> <span class="token keyword">true</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
  label<span class="token punctuation">:</span> <span class="token punctuation">{</span>
    <span class="token keyword">type</span><span class="token punctuation">:</span> String<span class="token punctuation">,</span>
    <span class="token keyword">default</span><span class="token punctuation">:</span> <span class="token string">''</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
  modelValue<span class="token punctuation">:</span> <span class="token punctuation">{</span>
    <span class="token keyword">type</span><span class="token punctuation">:</span> String<span class="token punctuation">,</span>
    required<span class="token punctuation">:</span> <span class="token keyword">true</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token keyword">type</span><span class="token punctuation">:</span> <span class="token punctuation">{</span>
    <span class="token keyword">type</span><span class="token punctuation">:</span> String<span class="token punctuation">,</span>
    <span class="token keyword">default</span><span class="token punctuation">:</span> <span class="token string">'text'</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
  placeholder<span class="token punctuation">:</span> <span class="token punctuation">{</span>
    <span class="token keyword">type</span><span class="token punctuation">:</span> String<span class="token punctuation">,</span>
    <span class="token keyword">default</span><span class="token punctuation">:</span> <span class="token string">''</span>
  <span class="token punctuation">}</span>
 <span class="token punctuation">}</span><span class="token punctuation">,</span>

  <span class="token comment">// Declare the events that this component can emit</span>
 emits<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">'update:modelValue'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>

  <span class="token function">setup</span><span class="token punctuation">(</span>props<span class="token punctuation">,</span> <span class="token punctuation">{</span> emit <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// Create a computed ref for internalValue</span>
    <span class="token comment">// The getter returns the current value of the modelValue prop</span>
    <span class="token comment">// The setter emits the 'update:modelValue' event when the value changes</span>
    <span class="token keyword">const</span> internalValue <span class="token operator">=</span> <span class="token function">computed</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
      <span class="token keyword">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token comment">// Return the current value of modelValue prop</span>
        <span class="token keyword">return</span> props<span class="token punctuation">.</span>modelValue
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token keyword">set</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token comment">// Emit the 'update:modelValue' event with the new value</span>
        <span class="token function">emit</span><span class="token punctuation">(</span><span class="token string">'update:modelValue'</span><span class="token punctuation">,</span> value<span class="token punctuation">)</span>
      <span class="token punctuation">}</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span>

    <span class="token keyword">return</span> <span class="token punctuation">{</span>
      <span class="token comment">// Return the computed ref to make it available in the template</span>
      internalValue
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token operator">&lt;</span><span class="token operator">/</span>script<span class="token operator">&gt;</span>
</code></pre><ol start="2"><li>Add your new component to the <code class="inline-code">HomeView.vue</code>:</li></ol><pre class=" language-ts"><code class="prism  language-ts"><span class="token operator">&lt;</span>template<span class="token operator">&gt;</span>
  <span class="token operator">&lt;</span>div <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"w-full min-h-screen"</span><span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span>form <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"mt-40 p-6 mx-auto md:shadow-2xl rounded-lg md:w-1/2 lg:1/3"</span><span class="token operator">&gt;</span>
      <span class="token operator">&lt;</span>FieldInput
        id<span class="token operator">=</span><span class="token string">"subject"</span>
        label<span class="token operator">=</span><span class="token string">"Subject"</span>
        v<span class="token operator">-</span>model<span class="token operator">=</span><span class="token string">"subject"</span>
        placeholder<span class="token operator">=</span><span class="token string">"Type the subject you want to learn here..."</span>
      <span class="token operator">/</span><span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span><span class="token operator">/</span>form<span class="token operator">&gt;</span>
  <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span>
<span class="token operator">&lt;</span><span class="token operator">/</span>template<span class="token operator">&gt;</span>

<span class="token operator">&lt;</span>script lang<span class="token operator">=</span><span class="token string">"ts"</span><span class="token operator">&gt;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> defineComponent<span class="token punctuation">,</span> ref<span class="token punctuation">,</span> onMounted <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'vue'</span>
<span class="token keyword">import</span> FieldInput <span class="token keyword">from</span> <span class="token string">'@/components/fields/FieldInput.vue'</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token function">defineComponent</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  name<span class="token punctuation">:</span> <span class="token string">'App'</span><span class="token punctuation">,</span>

  components<span class="token punctuation">:</span> <span class="token punctuation">{</span>
    FieldInput<span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>

  <span class="token function">setup</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> subject <span class="token operator">=</span> <span class="token function">ref</span><span class="token punctuation">(</span><span class="token string">''</span><span class="token punctuation">)</span>

    <span class="token keyword">return</span> <span class="token punctuation">{</span>
      subject
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token operator">&lt;</span><span class="token operator">/</span>script<span class="token operator">&gt;</span>
</code></pre><h2 id="multi-step-forms-nested-inputs-dynamic-fields">Multi-step Forms, Nested Inputs, Dynamic Fields</h2><h3 id="managing-fields-based-on-user-input-dynamic-fields">Managing Fields Based on User Input: Dynamic Fields</h3><p>Clean and user-friendly forms rely on dynamic fields&mdash;fields that are displayed based on the user&rsquo;s input.</p><p>In our example, we will display a card selector when the user starts inputting the subject to study.</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2024/2024-07/subject-platforms.png?sfvrsn=3fd7a83_2" alt="field card selector" /></p><ol><li>Let&rsquo;s create our <code class="inline-code">FieldCardSelector.vue</code>:</li></ol><pre class=" language-ts"><code class="prism  language-ts"><span class="token operator">&lt;</span>template<span class="token operator">&gt;</span>
  <span class="token operator">&lt;</span>div <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"mb-4"</span><span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span><span class="token operator">!</span><span class="token operator">--</span> Conditionally render the label <span class="token keyword">if</span> the <span class="token string">'label'</span> prop is provided <span class="token operator">--</span><span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span>label v<span class="token operator">-</span><span class="token keyword">if</span><span class="token operator">=</span><span class="token string">"label"</span> <span class="token punctuation">:</span><span class="token keyword">for</span><span class="token operator">=</span><span class="token string">"id"</span> <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"mb-2 block text-lg font-semibold text-gray-700"</span><span class="token operator">&gt;</span>
      <span class="token punctuation">{</span><span class="token punctuation">{</span> label <span class="token punctuation">}</span><span class="token punctuation">}</span>
    <span class="token operator">&lt;</span><span class="token operator">/</span>label<span class="token operator">&gt;</span>

    <span class="token operator">&lt;</span><span class="token operator">!</span><span class="token operator">--</span> Input field bound directly to <span class="token string">'internalValue'</span> using v<span class="token operator">-</span>model <span class="token operator">--</span><span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span>input
      <span class="token punctuation">:</span>id<span class="token operator">=</span><span class="token string">"id"</span>
      v<span class="token operator">-</span>model<span class="token operator">=</span><span class="token string">"internalValue"</span>
      <span class="token punctuation">:</span><span class="token keyword">type</span><span class="token operator">=</span><span class="token string">"type"</span>
      <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 rounded-md p-2 border"</span>
      <span class="token punctuation">:</span>placeholder<span class="token operator">=</span><span class="token string">"placeholder"</span>
    <span class="token operator">/</span><span class="token operator">&gt;</span>
  <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span>
<span class="token operator">&lt;</span><span class="token operator">/</span>template<span class="token operator">&gt;</span>

<span class="token operator">&lt;</span>script lang<span class="token operator">=</span><span class="token string">"ts"</span><span class="token operator">&gt;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> defineComponent<span class="token punctuation">,</span> computed <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'vue'</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token function">defineComponent</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  name<span class="token punctuation">:</span> <span class="token string">'FieldInput'</span><span class="token punctuation">,</span>

  <span class="token comment">// Define the props that this component accepts</span>
  props<span class="token punctuation">:</span> <span class="token punctuation">{</span>
    id<span class="token punctuation">:</span> <span class="token punctuation">{</span>
      <span class="token keyword">type</span><span class="token punctuation">:</span> String<span class="token punctuation">,</span>
      required<span class="token punctuation">:</span> <span class="token keyword">true</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    label<span class="token punctuation">:</span> <span class="token punctuation">{</span>
      <span class="token keyword">type</span><span class="token punctuation">:</span> String<span class="token punctuation">,</span>
      <span class="token keyword">default</span><span class="token punctuation">:</span> <span class="token string">''</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    modelValue<span class="token punctuation">:</span> <span class="token punctuation">{</span>
      <span class="token keyword">type</span><span class="token punctuation">:</span> String<span class="token punctuation">,</span>
      required<span class="token punctuation">:</span> <span class="token keyword">true</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token keyword">type</span><span class="token punctuation">:</span> <span class="token punctuation">{</span>
      <span class="token keyword">type</span><span class="token punctuation">:</span> String<span class="token punctuation">,</span>
      <span class="token keyword">default</span><span class="token punctuation">:</span> <span class="token string">'text'</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    placeholder<span class="token punctuation">:</span> <span class="token punctuation">{</span>
      <span class="token keyword">type</span><span class="token punctuation">:</span> String<span class="token punctuation">,</span>
      <span class="token keyword">default</span><span class="token punctuation">:</span> <span class="token string">''</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>

  <span class="token comment">// Declare the events that this component can emit</span>
  emits<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">'update:modelValue'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>

  <span class="token function">setup</span><span class="token punctuation">(</span>props<span class="token punctuation">,</span> <span class="token punctuation">{</span> emit <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// Create a computed ref for internalValue</span>
    <span class="token comment">// The getter returns the current value of the modelValue prop</span>
    <span class="token comment">// The setter emits the 'update:modelValue' event when the value changes</span>
    <span class="token keyword">const</span> internalValue <span class="token operator">=</span> <span class="token function">computed</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
      <span class="token keyword">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token comment">// Return the current value of modelValue prop</span>
        <span class="token keyword">return</span> props<span class="token punctuation">.</span>modelValue
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token keyword">set</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token comment">// Emit the 'update:modelValue' event with the new value</span>
        <span class="token function">emit</span><span class="token punctuation">(</span><span class="token string">'update:modelValue'</span><span class="token punctuation">,</span> value<span class="token punctuation">)</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span>

    <span class="token keyword">return</span> <span class="token punctuation">{</span>
      <span class="token comment">// Return the computed ref to make it available in the template</span>
      internalValue
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token operator">&lt;</span><span class="token operator">/</span>script<span class="token operator">&gt;</span>
</code></pre><ol start="2"><li>Then in our <code class="inline-code">HomeView.vue</code>, we will display it only if our user has filled in the subject input (if <code class="inline-code">subject.length &gt; 0</code>).  This is something we can do thanks to the <code class="inline-code">v-model</code> and the <code class="inline-code">computed</code> that detects changes in the value of the subject input.</li></ol><pre class=" language-ts"><code class="prism  language-ts"><span class="token operator">&lt;</span>template<span class="token operator">&gt;</span>
  <span class="token operator">&lt;</span>div <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"w-full min-h-screen"</span><span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span><span class="token operator">!</span><span class="token operator">--</span> Form to collect user inputs <span class="token operator">--</span><span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span>form <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"mt-40 p-6 mx-auto md:shadow-2xl rounded-lg md:w-1/2 lg:1/3"</span><span class="token operator">&gt;</span>
      <span class="token operator">&lt;</span><span class="token operator">!</span><span class="token operator">--</span> FieldInput component <span class="token keyword">for</span> entering the subject <span class="token operator">--</span><span class="token operator">&gt;</span>
      <span class="token operator">&lt;</span>FieldInput
        id<span class="token operator">=</span><span class="token string">"subject"</span>
        label<span class="token operator">=</span><span class="token string">"Subject"</span>
        v<span class="token operator">-</span>model<span class="token operator">=</span><span class="token string">"subject"</span>
        placeholder<span class="token operator">=</span><span class="token string">"Type the subject you want to learn here..."</span>
      <span class="token operator">/</span><span class="token operator">&gt;</span>

      <span class="token operator">&lt;</span><span class="token operator">!</span><span class="token operator">--</span> Conditionally render FieldCardSelector based on the subject input <span class="token operator">--</span><span class="token operator">&gt;</span>
      <span class="token operator">&lt;</span>FieldCardSelector
        v<span class="token operator">-</span><span class="token keyword">if</span><span class="token operator">=</span><span class="token string">"subject.length &gt; 0"</span>
        id<span class="token operator">=</span><span class="token string">"platforms"</span>
        label<span class="token operator">=</span><span class="token string">"Platforms"</span>
        v<span class="token operator">-</span>model<span class="token operator">=</span><span class="token string">"selectedOptions"</span>
        <span class="token punctuation">:</span>options<span class="token operator">=</span><span class="token string">"options"</span>
        <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"mt-6"</span>
      <span class="token operator">/</span><span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span><span class="token operator">/</span>form<span class="token operator">&gt;</span>
  <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span>
<span class="token operator">&lt;</span><span class="token operator">/</span>template<span class="token operator">&gt;</span>

<span class="token operator">&lt;</span>script lang<span class="token operator">=</span><span class="token string">"ts"</span><span class="token operator">&gt;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> defineComponent<span class="token punctuation">,</span> ref<span class="token punctuation">,</span> onMounted <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'vue'</span>
<span class="token keyword">import</span> FieldInput <span class="token keyword">from</span> <span class="token string">'@/components/fields/FieldInput.vue'</span>
<span class="token keyword">import</span> FieldCardSelector <span class="token keyword">from</span> <span class="token string">'@/components/fields/FieldCardSelector.vue'</span>

<span class="token comment">// Define the Platform interface for type-checking</span>
<span class="token keyword">interface</span> <span class="token class-name">Platform</span> <span class="token punctuation">{</span>
  id<span class="token punctuation">:</span> <span class="token keyword">number</span>
  name<span class="token punctuation">:</span> <span class="token keyword">string</span>
  icon<span class="token punctuation">:</span> <span class="token keyword">string</span>
  url<span class="token punctuation">:</span> <span class="token keyword">string</span>
<span class="token punctuation">}</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token function">defineComponent</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  name<span class="token punctuation">:</span> <span class="token string">'HomeView'</span><span class="token punctuation">,</span>
  components<span class="token punctuation">:</span> <span class="token punctuation">{</span>
    FieldInput<span class="token punctuation">,</span>
    FieldCardSelector
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token function">setup</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// Holds the subject input value</span>
    <span class="token keyword">const</span> subject <span class="token operator">=</span> <span class="token function">ref</span><span class="token punctuation">(</span><span class="token string">''</span><span class="token punctuation">)</span> 
    <span class="token comment">// Holds the selected platform options</span>
    <span class="token keyword">const</span> selectedOptions <span class="token operator">=</span> ref<span class="token operator">&lt;</span><span class="token keyword">string</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span>
    <span class="token comment">// Holds the list of platform options</span>
    <span class="token keyword">const</span> options <span class="token operator">=</span> ref<span class="token operator">&lt;</span><span class="token punctuation">{</span> value<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span> text<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span> logo<span class="token punctuation">:</span> <span class="token keyword">string</span> <span class="token punctuation">}</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span>

    <span class="token comment">// Fetch platform options when the component is mounted</span>
    <span class="token function">onMounted</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
      <span class="token comment">// Fetch platform data from a JSON file</span>
      <span class="token keyword">const</span> response <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token string">'/platforms.json'</span><span class="token punctuation">)</span> 
      <span class="token keyword">const</span> data <span class="token operator">=</span> <span class="token keyword">await</span> response<span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
      <span class="token comment">// Map the platform data to the options array</span>
      options<span class="token punctuation">.</span>value <span class="token operator">=</span> data<span class="token punctuation">.</span>platforms<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span>platform<span class="token punctuation">:</span> Platform<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">(</span><span class="token punctuation">{</span>
        value<span class="token punctuation">:</span> platform<span class="token punctuation">.</span>id<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
        text<span class="token punctuation">:</span> platform<span class="token punctuation">.</span>name<span class="token punctuation">,</span>
        logo<span class="token punctuation">:</span> platform<span class="token punctuation">.</span>icon
      <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span>

    <span class="token comment">// Return state variables and methods to be used in the template</span>
    <span class="token keyword">return</span> <span class="token punctuation">{</span>
      subject<span class="token punctuation">,</span>
      selectedOptions<span class="token punctuation">,</span>
      options<span class="token punctuation">,</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token operator">&lt;</span><span class="token operator">/</span>script<span class="token operator">&gt;</span>
</code></pre><p>The options are fetched from this <code class="inline-code">platforms.json</code>:</p><pre class=" language-json"><code class="prism  language-json"><span class="token punctuation">{</span>
  <span class="token string">"platforms"</span><span class="token punctuation">:</span> <span class="token punctuation">[</span>
    <span class="token punctuation">{</span>
      <span class="token string">"id"</span><span class="token punctuation">:</span> <span class="token number">1</span><span class="token punctuation">,</span>
      <span class="token string">"name"</span><span class="token punctuation">:</span> <span class="token string">"Udemy"</span><span class="token punctuation">,</span>
      <span class="token string">"icon"</span><span class="token punctuation">:</span> <span class="token string">"/img/udemy.png"</span><span class="token punctuation">,</span>
      <span class="token string">"url"</span><span class="token punctuation">:</span> <span class="token string">"https://www.udemy.com/"</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">{</span>
      <span class="token string">"id"</span><span class="token punctuation">:</span> <span class="token number">2</span><span class="token punctuation">,</span>
      <span class="token string">"name"</span><span class="token punctuation">:</span> <span class="token string">"YouTube"</span><span class="token punctuation">,</span>
      <span class="token string">"icon"</span><span class="token punctuation">:</span> <span class="token string">"/img/youtube.jpeg"</span><span class="token punctuation">,</span>
      <span class="token string">"url"</span><span class="token punctuation">:</span> <span class="token string">"https://www.youtube.com/"</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">]</span>
<span class="token punctuation">}</span>
</code></pre><h3 id="preserving-state-between-steps">Preserving State Between Steps</h3><p>Now, what happens when our user inputs the subject to study and platforms from which to fetch the courses?  We create a curriculum based on the stated needs and move to the checkout page.</p><ol><li>Let&rsquo;s create our <code class="inline-code">BaseButton.vue</code>:</li></ol><pre class=" language-js"><code class="prism  language-js"><span class="token operator">&lt;</span>template<span class="token operator">&gt;</span>
  <span class="token operator">&lt;</span>button
    <span class="token punctuation">:</span>type<span class="token operator">=</span><span class="token string">"type"</span>
    <span class="token punctuation">:</span><span class="token keyword">class</span><span class="token operator">=</span>"<span class="token punctuation">[</span>
      <span class="token string">'inline-flex items-center justify-center px-4 py-2 border border-transparent text-md font-semibold rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-2'</span><span class="token punctuation">,</span>
      <span class="token punctuation">{</span>
        <span class="token string">'bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500'</span><span class="token punctuation">:</span> variant <span class="token operator">===</span> <span class="token string">'primary'</span><span class="token punctuation">,</span>
        <span class="token string">'bg-gray-600 text-white hover:bg-gray-700 focus:ring-gray-500'</span><span class="token punctuation">:</span> variant <span class="token operator">===</span> <span class="token string">'secondary'</span><span class="token punctuation">,</span>
        <span class="token string">'bg-red-600 text-white hover:bg-red-700 focus:ring-red-500'</span><span class="token punctuation">:</span> variant <span class="token operator">===</span> <span class="token string">'danger'</span><span class="token punctuation">,</span>
        <span class="token string">'bg-white text-gray-700 border-gray-300 hover:bg-gray-50 focus:ring-indigo-500'</span><span class="token punctuation">:</span>
          variant <span class="token operator">===</span> <span class="token string">'outline'</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">]</span>"
    @click<span class="token operator">=</span><span class="token string">"onClick"</span>
  <span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span>slot<span class="token operator">&gt;</span><span class="token operator">&lt;</span><span class="token operator">/</span>slot<span class="token operator">&gt;</span>
  <span class="token operator">&lt;</span><span class="token operator">/</span>button<span class="token operator">&gt;</span>
<span class="token operator">&lt;</span><span class="token operator">/</span>template<span class="token operator">&gt;</span>

<span class="token operator">&lt;</span>script lang<span class="token operator">=</span><span class="token string">"ts"</span><span class="token operator">&gt;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> defineComponent <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'vue'</span>
<span class="token keyword">import</span> type <span class="token punctuation">{</span> PropType <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'vue'</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token function">defineComponent</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  name<span class="token punctuation">:</span> <span class="token string">'BaseButton'</span><span class="token punctuation">,</span>

  props<span class="token punctuation">:</span> <span class="token punctuation">{</span>
    type<span class="token punctuation">:</span> <span class="token punctuation">{</span>
      type<span class="token punctuation">:</span> String <span class="token keyword">as</span> PropType<span class="token operator">&lt;</span><span class="token string">'button'</span> <span class="token operator">|</span> <span class="token string">'submit'</span> <span class="token operator">|</span> <span class="token string">'reset'</span><span class="token operator">&gt;</span><span class="token punctuation">,</span>
      <span class="token keyword">default</span><span class="token punctuation">:</span> <span class="token string">'button'</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    variant<span class="token punctuation">:</span> <span class="token punctuation">{</span>
      type<span class="token punctuation">:</span> String <span class="token keyword">as</span> PropType<span class="token operator">&lt;</span><span class="token string">'primary'</span> <span class="token operator">|</span> <span class="token string">'secondary'</span> <span class="token operator">|</span> <span class="token string">'danger'</span> <span class="token operator">|</span> <span class="token string">'outline'</span><span class="token operator">&gt;</span><span class="token punctuation">,</span>
      <span class="token keyword">default</span><span class="token punctuation">:</span> <span class="token string">'primary'</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
  emits<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">'click'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>

  <span class="token function">setup</span><span class="token punctuation">(</span>props<span class="token punctuation">,</span> <span class="token punctuation">{</span> emit <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> <span class="token function-variable function">onClick</span> <span class="token operator">=</span> <span class="token punctuation">(</span>event<span class="token punctuation">:</span> Event<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
      <span class="token function">emit</span><span class="token punctuation">(</span><span class="token string">'click'</span><span class="token punctuation">,</span> event<span class="token punctuation">)</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">return</span> <span class="token punctuation">{</span>
      onClick
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token operator">&lt;</span><span class="token operator">/</span>script<span class="token operator">&gt;</span>
</code></pre><ol start="2"><li>Now let&rsquo;s add it to our form in <code class="inline-code">HomeView.vue</code> and use it to move to the next step where a preview of the curriculum we made is displayed:</li></ol><pre class=" language-js"><code class="prism  language-js"><span class="token operator">&lt;</span>template<span class="token operator">&gt;</span>
  <span class="token operator">&lt;</span>div <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"w-full min-h-screen"</span><span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span>form @submit<span class="token punctuation">.</span>prevent<span class="token operator">=</span><span class="token string">"onGenerateCurriculum"</span> <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"mt-40 p-6 mx-auto md:shadow-2xl rounded-lg md:w-1/2"</span><span class="token operator">&gt;</span>
      <span class="token operator">&lt;</span>FieldInput
        id<span class="token operator">=</span><span class="token string">"subject"</span>
        label<span class="token operator">=</span><span class="token string">"Subject"</span>
        v<span class="token operator">-</span>model<span class="token operator">=</span><span class="token string">"subject"</span>
        placeholder<span class="token operator">=</span><span class="token string">"Type the subject you want to learn here..."</span>
      <span class="token operator">/</span><span class="token operator">&gt;</span>

      <span class="token operator">&lt;</span>FieldCardSelector
        v<span class="token operator">-</span><span class="token keyword">if</span><span class="token operator">=</span><span class="token string">"subject.length &gt; 0"</span>
        id<span class="token operator">=</span><span class="token string">"platforms"</span>
        label<span class="token operator">=</span><span class="token string">"Platforms"</span>
        v<span class="token operator">-</span>model<span class="token operator">=</span><span class="token string">"selectedPlatforms"</span>
        <span class="token punctuation">:</span>options<span class="token operator">=</span><span class="token string">"options"</span>
        <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"mt-6"</span>
      <span class="token operator">/</span><span class="token operator">&gt;</span>

      <span class="token operator">&lt;</span>BaseButton
        v<span class="token operator">-</span><span class="token keyword">if</span><span class="token operator">=</span><span class="token string">"subject.length &amp;&amp; selectedPlatforms.length &gt; 0"</span>
        type<span class="token operator">=</span><span class="token string">"submit"</span>
        variant<span class="token operator">=</span><span class="token string">"primary"</span>
        <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"mt-6 w-full"</span>
      <span class="token operator">&gt;</span>
        Generate my Curriculum
      <span class="token operator">&lt;</span><span class="token operator">/</span>BaseButton<span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span><span class="token operator">/</span>form<span class="token operator">&gt;</span>
  <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span>
<span class="token operator">&lt;</span><span class="token operator">/</span>template<span class="token operator">&gt;</span>

<span class="token operator">&lt;</span>script lang<span class="token operator">=</span><span class="token string">"ts"</span><span class="token operator">&gt;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> defineComponent<span class="token punctuation">,</span> ref<span class="token punctuation">,</span> onMounted <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'vue'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> useRouter <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'vue-router'</span><span class="token punctuation">;</span>

<span class="token keyword">import</span> FieldInput <span class="token keyword">from</span> <span class="token string">'@/components/fields/FieldInput.vue'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> FieldCardSelector <span class="token keyword">from</span> <span class="token string">'@/components/fields/FieldCardSelector.vue'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> BaseButton <span class="token keyword">from</span> <span class="token string">'@/components/base/BaseButton.vue'</span><span class="token punctuation">;</span>

<span class="token keyword">import</span> type <span class="token punctuation">{</span> Platform <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@/interfaces'</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token function">defineComponent</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  name<span class="token punctuation">:</span> <span class="token string">'HomeView'</span><span class="token punctuation">,</span>

  components<span class="token punctuation">:</span> <span class="token punctuation">{</span>
    BaseButton<span class="token punctuation">,</span>
    FieldInput<span class="token punctuation">,</span>
    FieldCardSelector
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token function">setup</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> router <span class="token operator">=</span> <span class="token function">useRouter</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">const</span> subject <span class="token operator">=</span> <span class="token function">ref</span><span class="token punctuation">(</span><span class="token string">''</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> selectedPlatforms <span class="token operator">=</span> ref<span class="token operator">&lt;</span>string<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">const</span> options <span class="token operator">=</span> ref<span class="token operator">&lt;</span><span class="token punctuation">{</span> value<span class="token punctuation">:</span> string<span class="token punctuation">;</span> text<span class="token punctuation">:</span> string<span class="token punctuation">;</span> logo<span class="token punctuation">:</span> string <span class="token punctuation">}</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token function">onMounted</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
      <span class="token keyword">const</span> response <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token string">'/platforms.json'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token keyword">const</span> data <span class="token operator">=</span> <span class="token keyword">await</span> response<span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

      options<span class="token punctuation">.</span>value <span class="token operator">=</span> data<span class="token punctuation">.</span>platforms<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span>platform<span class="token punctuation">:</span> Platform<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">(</span><span class="token punctuation">{</span>
        value<span class="token punctuation">:</span> platform<span class="token punctuation">.</span>id<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
        text<span class="token punctuation">:</span> platform<span class="token punctuation">.</span>name<span class="token punctuation">,</span>
        logo<span class="token punctuation">:</span> platform<span class="token punctuation">.</span>icon<span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">const</span> <span class="token function-variable function">onGenerateCurriculum</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
      router<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
        name<span class="token punctuation">:</span> <span class="token string">'PreviewView'</span><span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">;</span>

    <span class="token keyword">return</span> <span class="token punctuation">{</span>
      subject<span class="token punctuation">,</span>
      selectedPlatforms<span class="token punctuation">,</span>
      options<span class="token punctuation">,</span>
      onGenerateCurriculum
    <span class="token punctuation">}</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token operator">&lt;</span><span class="token operator">/</span>script<span class="token operator">&gt;</span>
</code></pre><p>There is a slight little issue with our logic: How do we know in the next steps what the user has chosen? We could send it to the backend and then ask for it again in a call to the API when loading the next step  but there must be a better way.</p><p> Enter the glorious concept of <em>state management</em> in Vue. Its role is simple: maintain data consistency and accessibility across all our components and views while simplifying its flow.</p><p>Using <a target="_blank" href="https://pinia.vuejs.org/">Pinia</a> (or <a target="_blank" href="https://vuex.vuejs.org/guide/">Vuex</a> if you&rsquo;re not that hip ), you can easily preserve your data while the user navigates steps in a complex form without having to always make endless calls to the backend. So let&rsquo;s implement it together:</p><ol><li>Create a <code class="inline-code">useCourseStore.ts</code>, where:</li></ol><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">import</span> <span class="token punctuation">{</span> defineStore <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'pinia'</span><span class="token punctuation">;</span>

<span class="token comment">// Define the store</span>
<span class="token keyword">export</span> <span class="token keyword">const</span> useCourseStore <span class="token operator">=</span> <span class="token function">defineStore</span><span class="token punctuation">(</span><span class="token string">'course'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span>
  <span class="token comment">// State: Contains all the state variables that will be shared across the application</span>
  state<span class="token punctuation">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">(</span><span class="token punctuation">{</span>
    subject<span class="token punctuation">:</span> <span class="token string">''</span><span class="token punctuation">,</span> <span class="token comment">// Holds the subject the user wants to study</span>
    selectedPlatforms<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token keyword">as</span> string<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token comment">// Holds the list of selected platforms</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  
  <span class="token comment">// Actions: Methods that can modify the state. They can also include business logic.</span>
  actions<span class="token punctuation">:</span> <span class="token punctuation">{</span>
    <span class="token comment">// Sets the subject in the state</span>
    <span class="token function">setSubject</span><span class="token punctuation">(</span>subject<span class="token punctuation">:</span> string<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">this</span><span class="token punctuation">.</span>subject <span class="token operator">=</span> subject<span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token comment">// Sets the selected platforms in the state</span>
    <span class="token function">setSelectedPlatforms</span><span class="token punctuation">(</span>platforms<span class="token punctuation">:</span> string<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">this</span><span class="token punctuation">.</span>selectedPlatforms <span class="token operator">=</span> platforms<span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>

  <span class="token comment">// Getters: Methods to get derived state or perform calculations based on state</span>
  getters<span class="token punctuation">:</span> <span class="token punctuation">{</span>
    <span class="token comment">// Returns the subject from the state</span>
    getSubject<span class="token punctuation">:</span> <span class="token punctuation">(</span>state<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> state<span class="token punctuation">.</span>subject<span class="token punctuation">,</span>
    <span class="token comment">// Returns the selected platforms from the state</span>
    getSelectedPlatforms<span class="token punctuation">:</span> <span class="token punctuation">(</span>state<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> state<span class="token punctuation">.</span>selectedPlatforms<span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><ol start="2"><li>Modify the <code class="inline-code">HomeView.vue</code> and particularly the <code class="inline-code">onGenerateCurriculum</code> function to store the user&rsquo;s input in the store:</li></ol><pre class=" language-js"><code class="prism  language-js"><span class="token operator">&lt;</span>script lang<span class="token operator">=</span><span class="token string">"ts"</span><span class="token operator">&gt;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> defineComponent<span class="token punctuation">,</span> ref<span class="token punctuation">,</span> onMounted <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'vue'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> useRouter <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'vue-router'</span><span class="token punctuation">;</span>
<span class="token comment">// Import the useCourseStore function to access the store</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> useCourseStore <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@/stores/useCourseStore'</span><span class="token punctuation">;</span>

<span class="token keyword">import</span> FieldInput <span class="token keyword">from</span> <span class="token string">'@/components/fields/FieldInput.vue'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> FieldCardSelector <span class="token keyword">from</span> <span class="token string">'@/components/fields/FieldCardSelector.vue'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> BaseButton <span class="token keyword">from</span> <span class="token string">'@/components/base/BaseButton.vue'</span><span class="token punctuation">;</span>

<span class="token keyword">import</span> type <span class="token punctuation">{</span> Platform <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@/interfaces'</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token function">defineComponent</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  name<span class="token punctuation">:</span> <span class="token string">'HomeView'</span><span class="token punctuation">,</span>

  components<span class="token punctuation">:</span> <span class="token punctuation">{</span>
    BaseButton<span class="token punctuation">,</span>
    FieldInput<span class="token punctuation">,</span>
    FieldCardSelector
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token function">setup</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> router <span class="token operator">=</span> <span class="token function">useRouter</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">// Initialize the course store to access and manipulate its state</span>
    <span class="token keyword">const</span> courseStore <span class="token operator">=</span> <span class="token function">useCourseStore</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Define </span>

    <span class="token keyword">const</span> subject <span class="token operator">=</span> <span class="token function">ref</span><span class="token punctuation">(</span><span class="token string">''</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> selectedPlatforms <span class="token operator">=</span> ref<span class="token operator">&lt;</span>string<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">const</span> options <span class="token operator">=</span> ref<span class="token operator">&lt;</span><span class="token punctuation">{</span> value<span class="token punctuation">:</span> string<span class="token punctuation">;</span> text<span class="token punctuation">:</span> string<span class="token punctuation">;</span> logo<span class="token punctuation">:</span> string <span class="token punctuation">}</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token function">onMounted</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
      <span class="token keyword">const</span> response <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token string">'/platforms.json'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token keyword">const</span> data <span class="token operator">=</span> <span class="token keyword">await</span> response<span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

      options<span class="token punctuation">.</span>value <span class="token operator">=</span> data<span class="token punctuation">.</span>platforms<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span>platform<span class="token punctuation">:</span> Platform<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">(</span><span class="token punctuation">{</span>
        value<span class="token punctuation">:</span> platform<span class="token punctuation">.</span>id<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
        text<span class="token punctuation">:</span> platform<span class="token punctuation">.</span>name<span class="token punctuation">,</span>
        logo<span class="token punctuation">:</span> platform<span class="token punctuation">.</span>icon<span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">const</span> <span class="token function-variable function">onGenerateCurriculum</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
      <span class="token comment">// Update the store with the current subject and selected platforms</span>
      courseStore<span class="token punctuation">.</span><span class="token function">setSubject</span><span class="token punctuation">(</span>subject<span class="token punctuation">.</span>value<span class="token punctuation">)</span><span class="token punctuation">;</span>
      courseStore<span class="token punctuation">.</span><span class="token function">setSelectedPlatforms</span><span class="token punctuation">(</span>selectedPlatforms<span class="token punctuation">.</span>value<span class="token punctuation">)</span><span class="token punctuation">;</span>

      router<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
        name<span class="token punctuation">:</span> <span class="token string">'PreviewView'</span><span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">;</span>

    <span class="token keyword">return</span> <span class="token punctuation">{</span>
      subject<span class="token punctuation">,</span>
      selectedPlatforms<span class="token punctuation">,</span>
      options<span class="token punctuation">,</span>
      onGenerateCurriculum
    <span class="token punctuation">}</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token operator">&lt;</span><span class="token operator">/</span>script<span class="token operator">&gt;</span>
</code></pre><p>See? It&rsquo;s as easy as 1, 2, 3!  Now, you only have to use the getters to call the data anywhere in your app.</p><h3 id="managing-nested-field-data">Managing Nested Field Data</h3><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2024/2024-07/multi-step-form.gif?sfvrsn=8ecebd05_2" alt="multi-step form" /></p><p>So now, our user is redirected to the checkout page after clicking on the <em>Get my Curriculum</em> call to action. And, of course, as with any checkout page, we have a billing address which is a nested field.</p><blockquote><p>You can find the code for the preview page here:</p><ul><li><a target="_blank" href="https://codepen.io/nrifki/pen/wvbqLOw"><code class="inline-code">PreviewView.vue</code></a></li><li><a target="_blank" href="https://codepen.io/nrifki/pen/gOJxNEy"><code class="inline-code">BaseCourse.vue</code></a></li><li><a target="_blank" href="https://codepen.io/nrifki/pen/vYwJqMQ"><code class="inline-code">BaseLoader.vue</code></a></li><li><a target="_blank" href="https://codepen.io/nrifki/pen/eYaEwaW?editors=0010"><code class="inline-code">output.json</code></a></li><li><a target="_blank" href="https://codepen.io/nrifki/pen/OJYjeeK?editors=0010"><code class="inline-code">platform.interface.ts</code></a></li><li><a target="_blank" href="https://codepen.io/nrifki/pen/jOoLjgq?editors=0010"><code class="inline-code">course.interface.ts</code></a></li></ul></blockquote><p>Here is our <code class="inline-code">CheckoutView.vue</code>:</p><pre class=" language-js"><code class="prism  language-js"><span class="token operator">&lt;</span>template<span class="token operator">&gt;</span>
  <span class="token operator">&lt;</span>div <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"w-full min-h-screen pt-40"</span><span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span>form @submit<span class="token punctuation">.</span>prevent<span class="token operator">=</span><span class="token string">"onCheckout"</span> <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"p-6 mx-auto md:shadow-2xl rounded-lg md:w-1/2"</span><span class="token operator">&gt;</span>
      <span class="token operator">&lt;</span>div <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"md:grid md:grid-cols-2 md:gap-3"</span><span class="token operator">&gt;</span>
        <span class="token operator">&lt;</span>FieldInput id<span class="token operator">=</span><span class="token string">"firstName"</span> label<span class="token operator">=</span><span class="token string">"First Name"</span> v<span class="token operator">-</span>model<span class="token operator">=</span><span class="token string">"firstName"</span> placeholder<span class="token operator">=</span><span class="token string">"Ada"</span> <span class="token operator">/</span><span class="token operator">&gt;</span>

        <span class="token operator">&lt;</span>FieldInput id<span class="token operator">=</span><span class="token string">"lastName"</span> label<span class="token operator">=</span><span class="token string">"Last Name"</span> v<span class="token operator">-</span>model<span class="token operator">=</span><span class="token string">"lastName"</span> placeholder<span class="token operator">=</span><span class="token string">"Lovelace"</span> <span class="token operator">/</span><span class="token operator">&gt;</span>
      <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span>

      <span class="token operator">&lt;</span>FieldInput
        id<span class="token operator">=</span><span class="token string">"email"</span>
        label<span class="token operator">=</span><span class="token string">"Email"</span>
        v<span class="token operator">-</span>model<span class="token operator">=</span><span class="token string">"email"</span>
        type<span class="token operator">=</span><span class="token string">"email"</span>
        placeholder<span class="token operator">=</span><span class="token string">"ada@example.com"</span>
      <span class="token operator">/</span><span class="token operator">&gt;</span>

      <span class="token operator">&lt;</span>FieldInput id<span class="token operator">=</span><span class="token string">"address"</span> label<span class="token operator">=</span><span class="token string">"Billing Address"</span> v<span class="token operator">-</span>model<span class="token operator">=</span><span class="token string">"address"</span> placeholder<span class="token operator">=</span><span class="token string">"address"</span> <span class="token operator">/</span><span class="token operator">&gt;</span>

      <span class="token operator">&lt;</span>div <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"md:grid md:grid-cols-3 md:gap-3"</span><span class="token operator">&gt;</span>
        <span class="token operator">&lt;</span>FieldInput id<span class="token operator">=</span><span class="token string">"zip"</span> v<span class="token operator">-</span>model<span class="token operator">=</span><span class="token string">"zip"</span> placeholder<span class="token operator">=</span><span class="token string">"zip code"</span> <span class="token operator">/</span><span class="token operator">&gt;</span>

        <span class="token operator">&lt;</span>FieldInput id<span class="token operator">=</span><span class="token string">"city"</span> v<span class="token operator">-</span>model<span class="token operator">=</span><span class="token string">"city"</span> placeholder<span class="token operator">=</span><span class="token string">"city"</span> <span class="token operator">/</span><span class="token operator">&gt;</span>

        <span class="token operator">&lt;</span>FieldInput id<span class="token operator">=</span><span class="token string">"country"</span> v<span class="token operator">-</span>model<span class="token operator">=</span><span class="token string">"country"</span> placeholder<span class="token operator">=</span><span class="token string">"country"</span> <span class="token operator">/</span><span class="token operator">&gt;</span>
      <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span>

      <span class="token operator">&lt;</span>BaseButton type<span class="token operator">=</span><span class="token string">"submit"</span> variant<span class="token operator">=</span><span class="token string">"primary"</span> <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"mt-6 w-full"</span><span class="token operator">&gt;</span>
        Send me my Curriculum
      <span class="token operator">&lt;</span><span class="token operator">/</span>BaseButton<span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span><span class="token operator">/</span>form<span class="token operator">&gt;</span>
  <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span>
<span class="token operator">&lt;</span><span class="token operator">/</span>template<span class="token operator">&gt;</span>

<span class="token operator">&lt;</span>script lang<span class="token operator">=</span><span class="token string">"ts"</span><span class="token operator">&gt;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> defineComponent<span class="token punctuation">,</span> ref <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'vue'</span>

<span class="token keyword">import</span> FieldInput <span class="token keyword">from</span> <span class="token string">'@/components/fields/FieldInput.vue'</span>
<span class="token keyword">import</span> BaseButton <span class="token keyword">from</span> <span class="token string">'@/components/base/BaseButton.vue'</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token function">defineComponent</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  name<span class="token punctuation">:</span> <span class="token string">'CheckoutView'</span><span class="token punctuation">,</span>

  components<span class="token punctuation">:</span> <span class="token punctuation">{</span>
    BaseButton<span class="token punctuation">,</span>
    FieldInput
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token function">setup</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> firstName <span class="token operator">=</span> <span class="token function">ref</span><span class="token punctuation">(</span><span class="token string">''</span><span class="token punctuation">)</span>
    <span class="token keyword">const</span> lastName <span class="token operator">=</span> <span class="token function">ref</span><span class="token punctuation">(</span><span class="token string">''</span><span class="token punctuation">)</span>
    <span class="token keyword">const</span> email <span class="token operator">=</span> <span class="token function">ref</span><span class="token punctuation">(</span><span class="token string">''</span><span class="token punctuation">)</span>
    <span class="token keyword">const</span> address <span class="token operator">=</span> <span class="token function">ref</span><span class="token punctuation">(</span><span class="token string">''</span><span class="token punctuation">)</span>
    <span class="token keyword">const</span> zip <span class="token operator">=</span> <span class="token function">ref</span><span class="token punctuation">(</span><span class="token string">''</span><span class="token punctuation">)</span>
    <span class="token keyword">const</span> city <span class="token operator">=</span> <span class="token function">ref</span><span class="token punctuation">(</span><span class="token string">''</span><span class="token punctuation">)</span>
    <span class="token keyword">const</span> country <span class="token operator">=</span> <span class="token function">ref</span><span class="token punctuation">(</span><span class="token string">''</span><span class="token punctuation">)</span>

    <span class="token keyword">const</span> <span class="token function-variable function">onCheckout</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
      console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Checkout'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span>
        firstName<span class="token punctuation">:</span> firstName<span class="token punctuation">.</span>value<span class="token punctuation">,</span>
        lastName<span class="token punctuation">:</span> lastName<span class="token punctuation">.</span>value<span class="token punctuation">,</span>
        email<span class="token punctuation">:</span> email<span class="token punctuation">.</span>value<span class="token punctuation">,</span>
        fullAddress<span class="token punctuation">:</span> <span class="token template-string"><span class="token string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>address<span class="token punctuation">.</span>value<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">, </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>zip<span class="token punctuation">.</span>value<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">, </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>city<span class="token punctuation">.</span>value<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">, </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>country<span class="token punctuation">.</span>value<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">`</span></span><span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">)</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">return</span> <span class="token punctuation">{</span>
      firstName<span class="token punctuation">,</span>
      lastName<span class="token punctuation">,</span>
      email<span class="token punctuation">,</span>
      address<span class="token punctuation">,</span>
      zip<span class="token punctuation">,</span>
      city<span class="token punctuation">,</span>
      country<span class="token punctuation">,</span>
      onCheckout
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token operator">&lt;</span><span class="token operator">/</span>script<span class="token operator">&gt;</span>
</code></pre><p>For now, I&rsquo;m just console-logging the data our user gives us. But, as you can see with our billing address nested inputs, you may need to send the full address to your backend.</p><h2 id="input-validation">Input Validation</h2><p>So far we haven&rsquo;t added any validation to our form. Let&rsquo;s see together what input validation we can put in place. </p><h3 id="built-in-validation">Built-in Validation</h3><p>I classify built-in validation features into three categories:</p><ul><li>UX design</li><li>HTML5 input attributes</li><li>And, of course, Vue&rsquo;s built-in validation features</li></ul><p>Building input validation into the UX may not seem like the first thing you&rsquo;d think of as a developer or as a designer, but developers and designers with enough experience know that integrating validation when designing the UX is crucial to minimize friction and provide a clear and seamless experience.</p><p>This is why, in the first part of our form, we only display the platform&rsquo;s field card selector after the user has typed in a subject. The user also cannot move to the next step, as we don&rsquo;t display the button until they&rsquo;ve chosen at least one platform.</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2024/2024-07/ux-built-in-validation.gif?sfvrsn=f7eacc4a_2" alt="UX built-in validation" /></p><p>This was done using just two lines of code in <code class="inline-code">HomeView.vue</code>: <code class="inline-code">v-if="subject.length &gt; 0"</code> and <code class="inline-code">v-if="subject.length &amp;&amp; selectedPlatforms.length &gt; 0"</code>.</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>form</span> <span class="token attr-name">@submit.prevent</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>onGenerateCurriculum<span class="token punctuation">"</span></span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>p-6 mx-auto md:shadow-2xl rounded-lg md:w-1/2<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>FieldInput</span>
    <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>subject<span class="token punctuation">"</span></span>
    <span class="token attr-name">label</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Subject<span class="token punctuation">"</span></span>
    <span class="token attr-name">v-model</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>subject<span class="token punctuation">"</span></span>
    <span class="token attr-name">placeholder</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Type the subject you want to learn here...<span class="token punctuation">"</span></span>
  <span class="token punctuation">/&gt;</span></span>

  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>FieldCardSelector</span>
    <span class="token attr-name">v-if</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>subject.length &gt; 0<span class="token punctuation">"</span></span>
    <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>platforms<span class="token punctuation">"</span></span>
    <span class="token attr-name">label</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Platforms<span class="token punctuation">"</span></span>
    <span class="token attr-name">v-model</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>selectedPlatforms<span class="token punctuation">"</span></span>
    <span class="token attr-name">:options</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>options<span class="token punctuation">"</span></span>
    <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>mt-6<span class="token punctuation">"</span></span>
  <span class="token punctuation">/&gt;</span></span>

  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>BaseButton</span>
    <span class="token attr-name">v-if</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>subject.length &amp;&amp; selectedPlatforms.length &gt; 0<span class="token punctuation">"</span></span>
    <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>submit<span class="token punctuation">"</span></span>
    <span class="token attr-name">variant</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>primary<span class="token punctuation">"</span></span>
    <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>mt-6 w-full<span class="token punctuation">"</span></span>
  <span class="token punctuation">&gt;</span></span>
    Generate my Curriculum
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>BaseButton</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>form</span><span class="token punctuation">&gt;</span></span>
</code></pre><p>I know, neat!  This approach helps users know exactly what they need to do without any hassle for them and the developer too!</p><p>Let&rsquo;s now, use HTML5 and Vue&rsquo;s built-in validation features in our <code class="inline-code">CheckoutView.vue</code>. </p><p>Right now, when you click on the &ldquo;Send me my curriculum&rdquo; button without filling in any input, the console log is empty and nothing on the screen shows that something must be filled.</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2024/2024-07/no-validation.png?sfvrsn=ac3d9d99_2" alt="No validation" /></p><p>It is, to say the least, very confusing for a user!</p><p>Let&rsquo;s fix that by first requiring every field in this form:</p><ol><li>Go to <code class="inline-code">FieldInput.vue</code> and add the required attribute to the input tag add the required prop and set it to false:</li></ol><pre class=" language-js"><code class="prism  language-js"><span class="token operator">&lt;</span>template<span class="token operator">&gt;</span>
    <span class="token operator">...</span>

    <span class="token operator">&lt;</span>input
      <span class="token punctuation">:</span>id<span class="token operator">=</span><span class="token string">"id"</span>
      v<span class="token operator">-</span>model<span class="token operator">=</span><span class="token string">"internalValue"</span>
      <span class="token punctuation">:</span>required<span class="token operator">=</span><span class="token string">"required"</span>
      <span class="token punctuation">:</span>type<span class="token operator">=</span><span class="token string">"type"</span>
      <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 rounded-md p-2 border"</span>
      <span class="token punctuation">:</span>placeholder<span class="token operator">=</span><span class="token string">"placeholder"</span>
    <span class="token operator">/</span><span class="token operator">&gt;</span>
    
<span class="token operator">...</span>
<span class="token operator">&lt;</span><span class="token operator">/</span>template<span class="token operator">&gt;</span>

<span class="token operator">&lt;</span>script lang<span class="token operator">=</span><span class="token string">"ts"</span><span class="token operator">&gt;</span>
<span class="token operator">...</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token function">defineComponent</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  <span class="token operator">...</span>

  props<span class="token punctuation">:</span> <span class="token punctuation">{</span>
    <span class="token operator">...</span>
    required<span class="token punctuation">:</span> <span class="token punctuation">{</span>
      type<span class="token punctuation">:</span> Boolean<span class="token punctuation">,</span>
      <span class="token keyword">default</span><span class="token punctuation">:</span> <span class="token boolean">false</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token operator">...</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>

  <span class="token operator">...</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token operator">&lt;</span><span class="token operator">/</span>script<span class="token operator">&gt;</span>
</code></pre><ol start="2"><li>Set the required prop to true in <code class="inline-code">CheckoutView.vue</code> on every field input:</li></ol><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>template</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>w-full min-h-screen pt-40<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>form</span> <span class="token attr-name">@submit.prevent</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>onCheckout<span class="token punctuation">"</span></span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>p-6 mx-auto md:shadow-2xl rounded-lg md:w-1/2<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>md:grid md:grid-cols-2 md:gap-3<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>FieldInput</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>firstName<span class="token punctuation">"</span></span> <span class="token attr-name">label</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>First Name<span class="token punctuation">"</span></span> <span class="token attr-name">v-model</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>firstName<span class="token punctuation">"</span></span> <span class="token attr-name">placeholder</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Ada<span class="token punctuation">"</span></span> <span class="token attr-name">required</span> <span class="token punctuation">/&gt;</span></span>

        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>FieldInput</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>lastName<span class="token punctuation">"</span></span> <span class="token attr-name">label</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Last Name<span class="token punctuation">"</span></span> <span class="token attr-name">v-model</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>lastName<span class="token punctuation">"</span></span> <span class="token attr-name">placeholder</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Lovelace<span class="token punctuation">"</span></span> <span class="token attr-name">required</span> <span class="token punctuation">/&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>

      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>FieldInput</span>
        <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>email<span class="token punctuation">"</span></span>
        <span class="token attr-name">label</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Email<span class="token punctuation">"</span></span>
        <span class="token attr-name">v-model</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>email<span class="token punctuation">"</span></span>
        <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>email<span class="token punctuation">"</span></span>
        <span class="token attr-name">placeholder</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>ada@example.com<span class="token punctuation">"</span></span>
        <span class="token attr-name">required</span>
      <span class="token punctuation">/&gt;</span></span>

      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>FieldInput</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>address<span class="token punctuation">"</span></span> <span class="token attr-name">label</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Billing Address<span class="token punctuation">"</span></span> <span class="token attr-name">v-model</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>address<span class="token punctuation">"</span></span> <span class="token attr-name">placeholder</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>address<span class="token punctuation">"</span></span> <span class="token attr-name">required</span> <span class="token punctuation">/&gt;</span></span>

      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>md:grid md:grid-cols-3 md:gap-3<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>FieldInput</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>zip<span class="token punctuation">"</span></span> <span class="token attr-name">v-model</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>zip<span class="token punctuation">"</span></span> <span class="token attr-name">placeholder</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>zip code<span class="token punctuation">"</span></span> <span class="token attr-name">required</span> <span class="token punctuation">/&gt;</span></span>

        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>FieldInput</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>city<span class="token punctuation">"</span></span> <span class="token attr-name">v-model</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>city<span class="token punctuation">"</span></span> <span class="token attr-name">placeholder</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>city<span class="token punctuation">"</span></span> <span class="token attr-name">required</span> <span class="token punctuation">/&gt;</span></span>

        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>FieldInput</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>country<span class="token punctuation">"</span></span> <span class="token attr-name">v-model</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>country<span class="token punctuation">"</span></span> <span class="token attr-name">placeholder</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>country<span class="token punctuation">"</span></span> <span class="token attr-name">required</span> <span class="token punctuation">/&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>

      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>BaseButton</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>submit<span class="token punctuation">"</span></span> <span class="token attr-name">variant</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>primary<span class="token punctuation">"</span></span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>mt-6 w-full<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
        Send me my Curriculum
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>BaseButton</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>form</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>template</span><span class="token punctuation">&gt;</span></span>
</code></pre><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2024/2024-07/required.gif?sfvrsn=df5e7ef2_1" alt="Required attribute in play" /></p><p>Using only HTML5 required attribute you can at least tell your user what&rsquo;s missing now.  You will notice also that the console log isn&rsquo;t executed until all inputs are filled.</p><p> The cherry on the cake is the email validation. That&rsquo;s because we used the <code class="inline-code">type="email"</code> attribute for the email field input.</p><blockquote><p> <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attributes">Here you can find all the attributes available for inputs.</a></p></blockquote><p>Let&rsquo;s improve on what we have done so far using Vue&rsquo;s reactivity to display clear error messages to our users:</p><ol><li>First, let&rsquo;s change our <code class="inline-code">FieldInput.vue</code> to include an error message using an <code class="inline-code">errorMessage</code> and an <code class="inline-code">onBlur</code> function ➕ change the field&rsquo;s border and outline color to red when the error message is displayed:</li></ol><pre class=" language-js"><code class="prism  language-js"><span class="token operator">&lt;</span>template<span class="token operator">&gt;</span>
  <span class="token operator">&lt;</span>div <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"mb-4"</span><span class="token operator">&gt;</span>
    <span class="token operator">...</span>

    <span class="token operator">&lt;</span>input
      <span class="token punctuation">:</span>id<span class="token operator">=</span><span class="token string">"id"</span>
      v<span class="token operator">-</span>model<span class="token operator">=</span><span class="token string">"internalValue"</span>
      <span class="token punctuation">:</span>required<span class="token operator">=</span><span class="token string">"required"</span>
      <span class="token punctuation">:</span>type<span class="token operator">=</span><span class="token string">"type"</span>
      @blur<span class="token operator">=</span><span class="token string">"onBlur"</span>
      <span class="token punctuation">:</span><span class="token keyword">class</span><span class="token operator">=</span>"<span class="token punctuation">[</span>
        <span class="token string">'shadow-sm block w-full sm:text-sm rounded-md p-2 border outline-none focus:ring-1'</span><span class="token punctuation">,</span>
        required <span class="token operator">&amp;&amp;</span> errorMessage<span class="token punctuation">.</span>length <span class="token operator">&gt;</span> <span class="token number">0</span> <span class="token operator">?</span> <span class="token string">'border-red-500 focus:ring-red-500 focus:border-red-500'</span> <span class="token punctuation">:</span> <span class="token string">'border-gray-300 focus:ring-indigo-500 focus:border-indigo-500'</span>
      <span class="token punctuation">]</span>"
      <span class="token punctuation">:</span>placeholder<span class="token operator">=</span><span class="token string">"placeholder"</span>
    <span class="token operator">/</span><span class="token operator">&gt;</span>

    <span class="token operator">&lt;</span>div v<span class="token operator">-</span><span class="token keyword">if</span><span class="token operator">=</span><span class="token string">"required &amp;&amp; errorMessage.length &gt; 0"</span> <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"text-sm text-red-500 mt-1"</span><span class="token operator">&gt;</span>
    <span class="token punctuation">{</span><span class="token punctuation">{</span> errorMessage <span class="token punctuation">}</span><span class="token punctuation">}</span>
    <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span>
  <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span>
<span class="token operator">&lt;</span><span class="token operator">/</span>template<span class="token operator">&gt;</span>

<span class="token operator">&lt;</span>script lang<span class="token operator">=</span><span class="token string">"ts"</span><span class="token operator">&gt;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> defineComponent<span class="token punctuation">,</span> ref<span class="token punctuation">,</span> computed <span class="token punctuation">}</span> <span class="token keyword">from</span>  <span class="token string">'vue'</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token function">defineComponent</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  <span class="token operator">...</span>

  props<span class="token punctuation">:</span> <span class="token punctuation">{</span>
    required<span class="token punctuation">:</span> <span class="token punctuation">{</span>
      type<span class="token punctuation">:</span> Boolean<span class="token punctuation">,</span>
      <span class="token keyword">default</span><span class="token punctuation">:</span> <span class="token boolean">false</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    errorMessage<span class="token punctuation">:</span> <span class="token punctuation">{</span>
      type<span class="token punctuation">:</span> String<span class="token punctuation">,</span>
      <span class="token keyword">default</span><span class="token punctuation">:</span> <span class="token string">''</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token operator">...</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>

  <span class="token operator">...</span>

  <span class="token function">setup</span><span class="token punctuation">(</span>props<span class="token punctuation">,</span> <span class="token punctuation">{</span> emit <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> touched <span class="token operator">=</span> <span class="token function">ref</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span>

    <span class="token operator">...</span>

    <span class="token keyword">const</span> showError <span class="token operator">=</span> <span class="token function">computed</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
      <span class="token keyword">return</span> touched<span class="token punctuation">.</span>value <span class="token operator">&amp;&amp;</span> props<span class="token punctuation">.</span>errorMessage<span class="token punctuation">.</span>length <span class="token operator">&gt;</span> <span class="token number">0</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span>

    <span class="token keyword">const</span> <span class="token function-variable function">onBlur</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
      touched<span class="token punctuation">.</span>value <span class="token operator">=</span> <span class="token boolean">true</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">return</span> <span class="token punctuation">{</span>
      internalValue<span class="token punctuation">,</span>
      showError<span class="token punctuation">,</span>
      onBlur
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token operator">&lt;</span><span class="token operator">/</span>script<span class="token operator">&gt;</span>
</code></pre><ol start="2"><li>Then in the <code class="inline-code">CheckoutView.vue</code>, we will add the <code class="inline-code">errorMessage</code> prop we created. The text for the error message will be generated through the <code class="inline-code">getError</code> function:</li></ol><pre class=" language-js"><code class="prism  language-js"><span class="token operator">&lt;</span>template<span class="token operator">&gt;</span>
  <span class="token operator">&lt;</span>div <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"w-full min-h-screen pt-40"</span><span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span>form @submit<span class="token punctuation">.</span>prevent<span class="token operator">=</span><span class="token string">"onCheckout"</span> <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"p-6 mx-auto md:shadow-2xl rounded-lg md:w-1/2"</span><span class="token operator">&gt;</span>
      <span class="token operator">&lt;</span>div <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"md:grid md:grid-cols-2 md:gap-3"</span><span class="token operator">&gt;</span>
        <span class="token operator">&lt;</span>FieldInput
          id<span class="token operator">=</span><span class="token string">"firstName"</span>
          label<span class="token operator">=</span><span class="token string">"First Name"</span>
          v<span class="token operator">-</span>model<span class="token operator">=</span><span class="token string">"firstName"</span>
          placeholder<span class="token operator">=</span><span class="token string">"Ada"</span>
          required
          <span class="token punctuation">:</span>error<span class="token operator">-</span>message<span class="token operator">=</span><span class="token string">"getError('First Name')"</span>
        <span class="token operator">/</span><span class="token operator">&gt;</span>

        <span class="token operator">&lt;</span>FieldInput
          id<span class="token operator">=</span><span class="token string">"lastName"</span>
          label<span class="token operator">=</span><span class="token string">"Last Name"</span>
          v<span class="token operator">-</span>model<span class="token operator">=</span><span class="token string">"lastName"</span>
          placeholder<span class="token operator">=</span><span class="token string">"Lovelace"</span>
          required
          <span class="token punctuation">:</span>error<span class="token operator">-</span>message<span class="token operator">=</span><span class="token string">"getError('Last Name')"</span>
        <span class="token operator">/</span><span class="token operator">&gt;</span>
      <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span>

      <span class="token operator">&lt;</span>FieldInput
        id<span class="token operator">=</span><span class="token string">"email"</span>
        label<span class="token operator">=</span><span class="token string">"Email"</span>
        v<span class="token operator">-</span>model<span class="token operator">=</span><span class="token string">"email"</span>
        type<span class="token operator">=</span><span class="token string">"email"</span>
        placeholder<span class="token operator">=</span><span class="token string">"ada@example.com"</span>
        required
        <span class="token punctuation">:</span>error<span class="token operator">-</span>message<span class="token operator">=</span><span class="token string">"getError('Email')"</span>
      <span class="token operator">/</span><span class="token operator">&gt;</span>

      <span class="token operator">&lt;</span>FieldInput
        id<span class="token operator">=</span><span class="token string">"address"</span>
        label<span class="token operator">=</span><span class="token string">"Billing Address"</span>
        v<span class="token operator">-</span>model<span class="token operator">=</span><span class="token string">"address"</span>
        placeholder<span class="token operator">=</span><span class="token string">"address"</span>
        required
        <span class="token punctuation">:</span>error<span class="token operator">-</span>message<span class="token operator">=</span><span class="token string">"getError('Address')"</span>
      <span class="token operator">/</span><span class="token operator">&gt;</span>

      <span class="token operator">&lt;</span>div <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"md:grid md:grid-cols-3 md:gap-3"</span><span class="token operator">&gt;</span>
        <span class="token operator">&lt;</span>FieldInput
          id<span class="token operator">=</span><span class="token string">"zip"</span>
          v<span class="token operator">-</span>model<span class="token operator">=</span><span class="token string">"zip"</span>
          placeholder<span class="token operator">=</span><span class="token string">"zip code"</span>
          required
          <span class="token punctuation">:</span>error<span class="token operator">-</span>message<span class="token operator">=</span><span class="token string">"getError('Zip Code')"</span>
        <span class="token operator">/</span><span class="token operator">&gt;</span>

        <span class="token operator">&lt;</span>FieldInput
          id<span class="token operator">=</span><span class="token string">"city"</span>
          v<span class="token operator">-</span>model<span class="token operator">=</span><span class="token string">"city"</span>
          placeholder<span class="token operator">=</span><span class="token string">"city"</span>
          required
          <span class="token punctuation">:</span>error<span class="token operator">-</span>message<span class="token operator">=</span><span class="token string">"getError('City')"</span>
        <span class="token operator">/</span><span class="token operator">&gt;</span>

        <span class="token operator">&lt;</span>FieldInput
          id<span class="token operator">=</span><span class="token string">"country"</span>
          v<span class="token operator">-</span>model<span class="token operator">=</span><span class="token string">"country"</span>
          placeholder<span class="token operator">=</span><span class="token string">"country"</span>
          required
          <span class="token punctuation">:</span>error<span class="token operator">-</span>message<span class="token operator">=</span><span class="token string">"getError('Country')"</span>
        <span class="token operator">/</span><span class="token operator">&gt;</span>
      <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span>

      <span class="token operator">...</span>
    <span class="token operator">&lt;</span><span class="token operator">/</span>form<span class="token operator">&gt;</span>
  <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span>
<span class="token operator">&lt;</span><span class="token operator">/</span>template<span class="token operator">&gt;</span>

<span class="token operator">&lt;</span>script lang<span class="token operator">=</span><span class="token string">"ts"</span><span class="token operator">&gt;</span>
<span class="token operator">...</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token function">defineComponent</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  <span class="token operator">...</span>
  
  <span class="token function">setup</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token operator">...</span>

    <span class="token keyword">const</span> <span class="token function-variable function">getError</span> <span class="token operator">=</span> <span class="token punctuation">(</span>fieldName<span class="token punctuation">:</span> string<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
      <span class="token keyword">if</span> <span class="token punctuation">(</span>fieldName <span class="token operator">===</span> <span class="token string">'Email'</span> <span class="token operator">&amp;&amp;</span> email<span class="token punctuation">.</span>value <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span><span class="token regex">/^[^\s@]+@[^\s@]+\.[^\s@]+$/</span><span class="token punctuation">.</span><span class="token function">test</span><span class="token punctuation">(</span>email<span class="token punctuation">.</span>value<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">return</span> <span class="token string">'Invalid email'</span>
      <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
        <span class="token keyword">return</span> <span class="token template-string"><span class="token string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>fieldName<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> is required`</span></span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">return</span> <span class="token punctuation">{</span>
      firstName<span class="token punctuation">,</span>
      lastName<span class="token punctuation">,</span>
      email<span class="token punctuation">,</span>
      address<span class="token punctuation">,</span>
      zip<span class="token punctuation">,</span>
      city<span class="token punctuation">,</span>
      country<span class="token punctuation">,</span>
      onCheckout<span class="token punctuation">,</span>
      getError
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token operator">&lt;</span><span class="token operator">/</span>script<span class="token operator">&gt;</span>
</code></pre><p>And voil&agrave;!</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2024/2024-07/vue-built-in-validation.gif?sfvrsn=2f7bbddb_2" alt="Vue built-in validation" /></p><p>As you can see, it seems that when we click on the button the HTML <code class="inline-code">required</code> attribute does its job. But the error message is only displayed for the first input (if we filled the first it would be displayed for the second). I don&rsquo;t think this is an optimal UX.  Another thing is that you only display the error message after we move on to another element without filling in the input field.</p><p>We could solve these issues by using watchers and so on. But honestly, I&rsquo;d rather avoid this by using third-party input validation libraries that can be more reliable than newly implemented custom code.  Plus, it saves so many lines of code, and the less code you have to write, the less chance your code will mutate into noodle code. </p><h3 id="third-party-input-validation-libraries">Third-party Input Validation Libraries</h3><p>You&rsquo;ve probably come across <a target="_blank" href="https://vee-validate.logaretm.com/v4/">VeeValidate</a> and <a target="_blank" href="https://vuelidate-next.netlify.app/">Vuelidate</a> in articles about building forms in Vue or Nuxt.  In this article, though, we want to build <em>complex forms in scalable apps</em>.  So we&rsquo;re going to use the <a target="_blank" href="https://vee-validate.logaretm.com/v4/integrations/zod-schema-validation/">VeeValidate in combination with Zod</a>.</p><p>Combining both will get us the benefits of VeeValidate&rsquo;s real-time validation without having to use a billion watchers and Zod&rsquo;s schema validation that centralizes our validation rules. Beautiful!</p><p>Let&rsquo;s get started then:</p><ol><li>Of course we need to install VeeValidate and Zod along with Zod&rsquo;s typed schemas for VeeValidate:</li></ol><pre class=" language-bash"><code class="prism  language-bash"><span class="token function">npm</span> <span class="token function">install</span> vee-validate zod @vee-validate/zod --legacy-peer-deps
</code></pre><blockquote><p>☝️ I&rsquo;m using <code class="inline-code">--legacy-peer-deps</code> because they conflict with the latest version of eslint I installed.</p></blockquote><ol start="2"><li>Create a <code class="inline-code">schemas</code> folder in the <code class="inline-code">src</code> directory where you&rsquo;ll create a <code class="inline-code">validationSchema.ts</code> file where we will centralize our validation rules:</li></ol><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">import</span> zod <span class="token keyword">from</span> <span class="token string">'zod'</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> validationSchema <span class="token operator">=</span> zod<span class="token punctuation">.</span><span class="token function">object</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  firstName<span class="token punctuation">:</span> zod<span class="token punctuation">.</span><span class="token keyword">string</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">min</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> message<span class="token punctuation">:</span> <span class="token string">'First Name is required'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  lastName<span class="token punctuation">:</span> zod<span class="token punctuation">.</span><span class="token keyword">string</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">min</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> message<span class="token punctuation">:</span> <span class="token string">'Last Name is required'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  email<span class="token punctuation">:</span> zod<span class="token punctuation">.</span><span class="token keyword">string</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">min</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> message<span class="token punctuation">:</span> <span class="token string">'Email is required'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">email</span><span class="token punctuation">(</span><span class="token punctuation">{</span> message<span class="token punctuation">:</span> <span class="token string">'Must be a valid email'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  address<span class="token punctuation">:</span> zod<span class="token punctuation">.</span><span class="token keyword">string</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">min</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> message<span class="token punctuation">:</span> <span class="token string">'Address is required'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  zip<span class="token punctuation">:</span> zod<span class="token punctuation">.</span><span class="token keyword">string</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">min</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> message<span class="token punctuation">:</span> <span class="token string">'Zip Code is required'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  city<span class="token punctuation">:</span> zod<span class="token punctuation">.</span><span class="token keyword">string</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">min</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> message<span class="token punctuation">:</span> <span class="token string">'City is required'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  country<span class="token punctuation">:</span> zod<span class="token punctuation">.</span><span class="token keyword">string</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">min</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> message<span class="token punctuation">:</span> <span class="token string">'Country is required'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> validationSchema<span class="token punctuation">;</span>
</code></pre><ol start="3"><li>Now, let&rsquo;s update our <code class="inline-code">FieldInput.vue</code> component so it displays error messages passed by VeeValidate:</li></ol><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token operator">&lt;</span>template<span class="token operator">&gt;</span>
  <span class="token operator">&lt;</span>div <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"mb-4"</span><span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span>label v<span class="token operator">-</span><span class="token keyword">if</span><span class="token operator">=</span><span class="token string">"label"</span> <span class="token punctuation">:</span><span class="token keyword">for</span><span class="token operator">=</span><span class="token string">"id"</span> <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"mb-2 block text-lg font-semibold text-gray-700"</span><span class="token operator">&gt;</span>
      <span class="token punctuation">{</span><span class="token punctuation">{</span> label <span class="token punctuation">}</span><span class="token punctuation">}</span>
    <span class="token operator">&lt;</span><span class="token operator">/</span>label<span class="token operator">&gt;</span>

    <span class="token operator">&lt;</span>input
      <span class="token punctuation">:</span>id<span class="token operator">=</span><span class="token string">"id"</span>
      <span class="token punctuation">:</span>name<span class="token operator">=</span><span class="token string">"id"</span>
      v<span class="token operator">-</span>model<span class="token operator">=</span><span class="token string">"internalValue"</span>
      <span class="token punctuation">:</span><span class="token keyword">type</span><span class="token operator">=</span><span class="token string">"type"</span>
      <span class="token punctuation">:</span><span class="token keyword">class</span><span class="token operator">=</span>"<span class="token punctuation">[</span>
        <span class="token string">'shadow-sm block w-full sm:text-sm rounded-md p-2 border outline-none focus:ring-1'</span><span class="token punctuation">,</span>
        error <span class="token operator">?</span> <span class="token string">'border-red-500 focus:ring-red-500 focus:border-red-500'</span> <span class="token punctuation">:</span> <span class="token string">'border-gray-300 focus:ring-indigo-500 focus:border-indigo-500'</span>
      <span class="token punctuation">]</span>"
      <span class="token punctuation">:</span>placeholder<span class="token operator">=</span><span class="token string">"placeholder"</span>
    <span class="token operator">/</span><span class="token operator">&gt;</span>
    
    <span class="token operator">&lt;</span>div v<span class="token operator">-</span><span class="token keyword">if</span><span class="token operator">=</span><span class="token string">"error"</span> <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"text-sm text-red-500 mt-1"</span><span class="token operator">&gt;</span>
      <span class="token punctuation">{</span><span class="token punctuation">{</span> error <span class="token punctuation">}</span><span class="token punctuation">}</span>
    <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span>
  <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span>
<span class="token operator">&lt;</span><span class="token operator">/</span>template<span class="token operator">&gt;</span>

<span class="token operator">&lt;</span>script lang<span class="token operator">=</span><span class="token string">"ts"</span><span class="token operator">&gt;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> defineComponent<span class="token punctuation">,</span> computed <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'vue'</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token function">defineComponent</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  name<span class="token punctuation">:</span> <span class="token string">'FieldInput'</span><span class="token punctuation">,</span>

  props<span class="token punctuation">:</span> <span class="token punctuation">{</span>
    id<span class="token punctuation">:</span> <span class="token punctuation">{</span>
      <span class="token keyword">type</span><span class="token punctuation">:</span> String<span class="token punctuation">,</span>
      required<span class="token punctuation">:</span> <span class="token keyword">true</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    label<span class="token punctuation">:</span> <span class="token punctuation">{</span>
      <span class="token keyword">type</span><span class="token punctuation">:</span> String<span class="token punctuation">,</span>
      <span class="token keyword">default</span><span class="token punctuation">:</span> <span class="token string">''</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    modelValue<span class="token punctuation">:</span> <span class="token punctuation">{</span>
      <span class="token keyword">type</span><span class="token punctuation">:</span> String<span class="token punctuation">,</span>
      required<span class="token punctuation">:</span> <span class="token keyword">true</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    error<span class="token punctuation">:</span> <span class="token punctuation">{</span>
      <span class="token keyword">type</span><span class="token punctuation">:</span> String<span class="token punctuation">,</span>
      <span class="token keyword">default</span><span class="token punctuation">:</span> <span class="token string">''</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token keyword">type</span><span class="token punctuation">:</span> <span class="token punctuation">{</span>
      <span class="token keyword">type</span><span class="token punctuation">:</span> String<span class="token punctuation">,</span>
      <span class="token keyword">default</span><span class="token punctuation">:</span> <span class="token string">'text'</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    placeholder<span class="token punctuation">:</span> <span class="token punctuation">{</span>
      <span class="token keyword">type</span><span class="token punctuation">:</span> String<span class="token punctuation">,</span>
      <span class="token keyword">default</span><span class="token punctuation">:</span> <span class="token string">''</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>

  emits<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">'update:modelValue'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>

  <span class="token function">setup</span><span class="token punctuation">(</span>props<span class="token punctuation">,</span> <span class="token punctuation">{</span> emit <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> internalValue <span class="token operator">=</span> <span class="token function">computed</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
      <span class="token keyword">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">return</span> props<span class="token punctuation">.</span>modelValue
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token keyword">set</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token function">emit</span><span class="token punctuation">(</span><span class="token string">'update:modelValue'</span><span class="token punctuation">,</span> value<span class="token punctuation">)</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span>

    <span class="token keyword">return</span> <span class="token punctuation">{</span>
      internalValue<span class="token punctuation">,</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token operator">&lt;</span><span class="token operator">/</span>script<span class="token operator">&gt;</span>
</code></pre><ol start="4"><li>Finally, let&rsquo;s set up validation in our <code class="inline-code">CheckoutView.vue</code> form:</li></ol><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token operator">&lt;</span>template<span class="token operator">&gt;</span>
  <span class="token operator">&lt;</span>div <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"w-full min-h-screen pt-40"</span><span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span>form @submit<span class="token punctuation">.</span>prevent<span class="token operator">=</span><span class="token string">"onCheckout"</span> <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"p-6 mx-auto md:shadow-2xl rounded-lg md:w-1/2"</span><span class="token operator">&gt;</span>
      <span class="token operator">&lt;</span>div <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"md:grid md:grid-cols-2 md:gap-3"</span><span class="token operator">&gt;</span>
        <span class="token operator">&lt;</span>FieldInput
          id<span class="token operator">=</span><span class="token string">"firstName"</span>
          label<span class="token operator">=</span><span class="token string">"First Name"</span>
          v<span class="token operator">-</span>model<span class="token operator">=</span><span class="token string">"firstName"</span>
          placeholder<span class="token operator">=</span><span class="token string">"Ada"</span>
          <span class="token punctuation">:</span>error<span class="token operator">=</span><span class="token string">"errors.firstName"</span>
        <span class="token operator">/</span><span class="token operator">&gt;</span>

        <span class="token operator">&lt;</span>FieldInput
          id<span class="token operator">=</span><span class="token string">"lastName"</span>
          label<span class="token operator">=</span><span class="token string">"Last Name"</span>
          v<span class="token operator">-</span>model<span class="token operator">=</span><span class="token string">"lastName"</span>
          placeholder<span class="token operator">=</span><span class="token string">"Lovelace"</span>
          <span class="token punctuation">:</span>error<span class="token operator">=</span><span class="token string">"errors.lastName"</span>
        <span class="token operator">/</span><span class="token operator">&gt;</span>
      <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span>

      <span class="token operator">&lt;</span>FieldInput
        id<span class="token operator">=</span><span class="token string">"email"</span>
        label<span class="token operator">=</span><span class="token string">"Email"</span>
        v<span class="token operator">-</span>model<span class="token operator">=</span><span class="token string">"email"</span>
        <span class="token keyword">type</span><span class="token operator">=</span><span class="token string">"email"</span>
        placeholder<span class="token operator">=</span><span class="token string">"ada@example.com"</span>
        <span class="token punctuation">:</span>error<span class="token operator">=</span><span class="token string">"errors.email"</span>
      <span class="token operator">/</span><span class="token operator">&gt;</span>

      <span class="token operator">&lt;</span>FieldInput
        id<span class="token operator">=</span><span class="token string">"address"</span>
        label<span class="token operator">=</span><span class="token string">"Billing Address"</span>
        v<span class="token operator">-</span>model<span class="token operator">=</span><span class="token string">"address"</span>
        placeholder<span class="token operator">=</span><span class="token string">"address"</span>
        <span class="token punctuation">:</span>error<span class="token operator">=</span><span class="token string">"errors.address"</span>
      <span class="token operator">/</span><span class="token operator">&gt;</span>

      <span class="token operator">&lt;</span>div <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"md:grid md:grid-cols-3 md:gap-3"</span><span class="token operator">&gt;</span>
        <span class="token operator">&lt;</span>FieldInput
          id<span class="token operator">=</span><span class="token string">"zip"</span>
          v<span class="token operator">-</span>model<span class="token operator">=</span><span class="token string">"zip"</span>
          placeholder<span class="token operator">=</span><span class="token string">"zip code"</span>
          <span class="token punctuation">:</span>error<span class="token operator">=</span><span class="token string">"errors.zip"</span>
        <span class="token operator">/</span><span class="token operator">&gt;</span>

        <span class="token operator">&lt;</span>FieldInput
          id<span class="token operator">=</span><span class="token string">"city"</span>
          v<span class="token operator">-</span>model<span class="token operator">=</span><span class="token string">"city"</span>
          placeholder<span class="token operator">=</span><span class="token string">"city"</span>
          <span class="token punctuation">:</span>error<span class="token operator">=</span><span class="token string">"errors.city"</span>
        <span class="token operator">/</span><span class="token operator">&gt;</span>

        <span class="token operator">&lt;</span>FieldInput
          id<span class="token operator">=</span><span class="token string">"country"</span>
          v<span class="token operator">-</span>model<span class="token operator">=</span><span class="token string">"country"</span>
          placeholder<span class="token operator">=</span><span class="token string">"country"</span>
          <span class="token punctuation">:</span>error<span class="token operator">=</span><span class="token string">"errors.country"</span>
        <span class="token operator">/</span><span class="token operator">&gt;</span>
      <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span>

      <span class="token operator">&lt;</span>BaseButton 
        <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"mt-6 w-full"</span>
        <span class="token keyword">type</span><span class="token operator">=</span><span class="token string">"submit"</span> 
        variant<span class="token operator">=</span><span class="token string">"primary"</span>
      <span class="token operator">&gt;</span>
        Send me my Curriculum
      <span class="token operator">&lt;</span><span class="token operator">/</span>BaseButton<span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span><span class="token operator">/</span>form<span class="token operator">&gt;</span>
  <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span>
<span class="token operator">&lt;</span><span class="token operator">/</span>template<span class="token operator">&gt;</span>

<span class="token operator">&lt;</span>script lang<span class="token operator">=</span><span class="token string">"ts"</span><span class="token operator">&gt;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> defineComponent<span class="token punctuation">,</span> ref <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'vue'</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> useField<span class="token punctuation">,</span> useForm <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'vee-validate'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> toTypedSchema <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@vee-validate/zod'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> validationSchema <span class="token keyword">from</span> <span class="token string">'@/schemas/validationSchema'</span><span class="token punctuation">;</span>

<span class="token keyword">import</span> FieldInput <span class="token keyword">from</span> <span class="token string">'@/components/fields/FieldInput.vue'</span>
<span class="token keyword">import</span> BaseButton <span class="token keyword">from</span> <span class="token string">'@/components/base/BaseButton.vue'</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token function">defineComponent</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  name<span class="token punctuation">:</span> <span class="token string">'CheckoutView'</span><span class="token punctuation">,</span>

  components<span class="token punctuation">:</span> <span class="token punctuation">{</span>
    BaseButton<span class="token punctuation">,</span>
    FieldInput
  <span class="token punctuation">}</span><span class="token punctuation">,</span>

  <span class="token function">setup</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> <span class="token punctuation">{</span> handleSubmit<span class="token punctuation">,</span> errors <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">useForm</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
      validationSchema<span class="token punctuation">:</span> <span class="token function">toTypedSchema</span><span class="token punctuation">(</span>validationSchema<span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">const</span> <span class="token punctuation">{</span> value<span class="token punctuation">:</span> firstName <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">useField</span><span class="token punctuation">(</span><span class="token string">'firstName'</span><span class="token punctuation">,</span> undefined<span class="token punctuation">,</span> <span class="token punctuation">{</span> initialValue<span class="token punctuation">:</span> <span class="token string">''</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> <span class="token punctuation">{</span> value<span class="token punctuation">:</span> lastName <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">useField</span><span class="token punctuation">(</span><span class="token string">'lastName'</span><span class="token punctuation">,</span> undefined<span class="token punctuation">,</span> <span class="token punctuation">{</span> initialValue<span class="token punctuation">:</span> <span class="token string">''</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> <span class="token punctuation">{</span> value<span class="token punctuation">:</span> email <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">useField</span><span class="token punctuation">(</span><span class="token string">'email'</span><span class="token punctuation">,</span> undefined<span class="token punctuation">,</span> <span class="token punctuation">{</span> initialValue<span class="token punctuation">:</span> <span class="token string">''</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> <span class="token punctuation">{</span> value<span class="token punctuation">:</span> address <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">useField</span><span class="token punctuation">(</span><span class="token string">'address'</span><span class="token punctuation">,</span> undefined<span class="token punctuation">,</span> <span class="token punctuation">{</span> initialValue<span class="token punctuation">:</span> <span class="token string">''</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> <span class="token punctuation">{</span> value<span class="token punctuation">:</span> zip <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">useField</span><span class="token punctuation">(</span><span class="token string">'zip'</span><span class="token punctuation">,</span> undefined<span class="token punctuation">,</span> <span class="token punctuation">{</span> initialValue<span class="token punctuation">:</span> <span class="token string">''</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> <span class="token punctuation">{</span> value<span class="token punctuation">:</span> city <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">useField</span><span class="token punctuation">(</span><span class="token string">'city'</span><span class="token punctuation">,</span> undefined<span class="token punctuation">,</span> <span class="token punctuation">{</span> initialValue<span class="token punctuation">:</span> <span class="token string">''</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> <span class="token punctuation">{</span> value<span class="token punctuation">:</span> country <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">useField</span><span class="token punctuation">(</span><span class="token string">'country'</span><span class="token punctuation">,</span> undefined<span class="token punctuation">,</span> <span class="token punctuation">{</span> initialValue<span class="token punctuation">:</span> <span class="token string">''</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">const</span> onCheckout <span class="token operator">=</span> <span class="token function">handleSubmit</span><span class="token punctuation">(</span><span class="token punctuation">(</span>values<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
      console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Checkout'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span>
        firstName<span class="token punctuation">:</span> firstName<span class="token punctuation">.</span>value<span class="token punctuation">,</span>
        lastName<span class="token punctuation">:</span> lastName<span class="token punctuation">.</span>value<span class="token punctuation">,</span>
        email<span class="token punctuation">:</span> email<span class="token punctuation">.</span>value<span class="token punctuation">,</span>
        fullAddress<span class="token punctuation">:</span> <span class="token template-string"><span class="token string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>address<span class="token punctuation">.</span>value<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">, </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>zip<span class="token punctuation">.</span>value<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">, </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>city<span class="token punctuation">.</span>value<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">, </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>country<span class="token punctuation">.</span>value<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">`</span></span>
      <span class="token punctuation">}</span><span class="token punctuation">)</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span>

    <span class="token keyword">return</span> <span class="token punctuation">{</span>
      firstName<span class="token punctuation">,</span>
      lastName<span class="token punctuation">,</span>
      email<span class="token punctuation">,</span>
      address<span class="token punctuation">,</span>
      zip<span class="token punctuation">,</span>
      city<span class="token punctuation">,</span>
      country<span class="token punctuation">,</span>
      errors<span class="token punctuation">,</span>
      onCheckout
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token operator">&lt;</span><span class="token operator">/</span>script<span class="token operator">&gt;</span>
</code></pre><p>As you can see, we removed:</p><ul><li>the required attribute/prop from <code class="inline-code">FieldInput.vue</code></li><li>the <code class="inline-code">onBlur</code> event listener</li><li>the <code class="inline-code">showError</code> computed</li></ul><p>Less code and, as you can see below, with a better user experience as we display the errors:</p><ul><li>when the user submits the form without filling in the fields, or</li><li>when the user does not finish filling in a field before submitting</li></ul><p>All this without using a single computed or a watcher.  Beautiful!</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2024/2024-07/veevalidate-zod.gif?sfvrsn=161be173_1" alt="VeeValidate and Zod at work" /></p><h3 id="input-validation-using-apis">Input Validation Using APIs</h3><p>As you can see, when we filled out our form, we could write anything in the address and it passed validation.</p><p>You&rsquo;ve probably come across an address field in a checkout form when shopping lately where you just input your address and it was validated through Google.</p><p>It&rsquo;s called <a target="_blank" href="https://developers.google.com/maps/documentation/javascript/place-autocomplete">Google Place Autocomplete</a> and that&rsquo;s what we gonna do together, folks. </p><ol><li>☝️ First, create a Google Cloud Console account to get and activate your Google Maps Platform API key.</li></ol><blockquote><p>When asked to protect your API key by restricting its usage select websites and just use <a target="_blank" href="http://localhost:5173/checkout">http://localhost:5173/checkout</a> for our example.</p></blockquote><ol start="2"><li><p>Then, create a <code class="inline-code">.env</code> that will host your Google Maps API key: <code class="inline-code">VITE_GOOGLE_API_KEY=YOUR_GOOGLE_API_KEY</code></p></li><li><p>Install <a target="_blank" href="https://www.npmjs.com/package/@googlemaps/js-api-loader">@googlemaps/js-api-loader</a> and its type package:</p></li></ol><pre class=" language-bash"><code class="prism  language-bash"><span class="token function">npm</span> <span class="token function">install</span> @googlemaps/js-api-loader --legacy-peer-deps
<span class="token function">npm</span> i -D @types/google.maps --legacy-peer-deps    
</code></pre><ol start="4"><li>Create a <code class="inline-code">utils</code> directory inside your <code class="inline-code">src</code> folder with a <code class="inline-code">loadGoogleMapsApi.ts</code> file:</li></ol><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> Loader <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@googlemaps/js-api-loader'</span><span class="token punctuation">;</span>

<span class="token comment">// Declare a variable to hold the promise for loading the Google Maps API.</span>
<span class="token comment">// This ensures that the API is loaded only once and the same promise is reused.</span>
<span class="token keyword">let</span> googleMapsApiPromise<span class="token punctuation">:</span> Promise<span class="token operator">&lt;</span><span class="token keyword">void</span><span class="token operator">&gt;</span> <span class="token operator">|</span> <span class="token keyword">null</span> <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span>

<span class="token comment">// Function to load the Google Maps API</span>
<span class="token keyword">export</span> <span class="token keyword">const</span> loadGoogleMapsApi <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> Promise<span class="token operator">&lt;</span><span class="token keyword">void</span><span class="token operator">&gt;</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token comment">// Check if the promise is already defined</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>googleMapsApiPromise<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// Create a new Loader instance with the API key and version</span>
    <span class="token keyword">const</span> loader <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Loader</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
      apiKey<span class="token punctuation">:</span> <span class="token keyword">import</span><span class="token punctuation">.</span>meta<span class="token punctuation">.</span>env<span class="token punctuation">.</span>VITE_GOOGLE_API_KEY<span class="token punctuation">,</span> <span class="token comment">// Load API key from environment variables</span>
      version<span class="token punctuation">:</span> <span class="token string">'weekly'</span><span class="token punctuation">,</span> <span class="token comment">// Use the weekly version of the API to get the latest features and updates</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">// Use the Loader to import the 'places' library</span>
    googleMapsApiPromise <span class="token operator">=</span> loader<span class="token punctuation">.</span><span class="token function">importLibrary</span><span class="token punctuation">(</span><span class="token string">'places'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
      <span class="token comment">// Resolve the promise when the library is successfully loaded</span>
      <span class="token keyword">return</span> Promise<span class="token punctuation">.</span><span class="token function">resolve</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token keyword">catch</span><span class="token punctuation">(</span>error <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
      <span class="token comment">// If there is an error, reset the promise to null</span>
      googleMapsApiPromise <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
      <span class="token comment">// Reject the promise with the error</span>
      <span class="token keyword">return</span> Promise<span class="token punctuation">.</span><span class="token function">reject</span><span class="token punctuation">(</span>error<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token comment">// Return the promise (either the existing one or the newly created one)</span>
  <span class="token keyword">return</span> googleMapsApiPromise<span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><ol start="5"><li>Instead of using our <code class="inline-code">FieldInput.vue</code> for filling out the address, let&rsquo;s create a <code class="inline-code">FieldAddress.vue</code> component:</li></ol><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token operator">&lt;</span>template<span class="token operator">&gt;</span>
  <span class="token operator">&lt;</span>div <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"mb-4"</span><span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span>label <span class="token punctuation">:</span><span class="token keyword">for</span><span class="token operator">=</span><span class="token string">"id"</span> <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"mb-2 block text-lg font-semibold text-gray-700"</span><span class="token operator">&gt;</span>
      <span class="token punctuation">{</span><span class="token punctuation">{</span> label <span class="token punctuation">}</span><span class="token punctuation">}</span>
    <span class="token operator">&lt;</span><span class="token operator">/</span>label<span class="token operator">&gt;</span>

    <span class="token operator">&lt;</span>input
      <span class="token punctuation">:</span>id<span class="token operator">=</span><span class="token string">"id"</span>
      <span class="token punctuation">:</span>name<span class="token operator">=</span><span class="token string">"id"</span>
      ref<span class="token operator">=</span><span class="token string">"autocompleteInput"</span>
      v<span class="token operator">-</span>model<span class="token operator">=</span><span class="token string">"internalValue"</span>
      <span class="token punctuation">:</span><span class="token keyword">type</span><span class="token operator">=</span><span class="token string">"type"</span>
      <span class="token punctuation">:</span><span class="token keyword">class</span><span class="token operator">=</span>"<span class="token punctuation">[</span>
        <span class="token string">'shadow-sm block w-full sm:text-sm rounded-md p-2 border outline-none focus:ring-1'</span><span class="token punctuation">,</span>
        error <span class="token operator">?</span> <span class="token string">'border-red-500 focus:ring-red-500 focus:border-red-500'</span> <span class="token punctuation">:</span> <span class="token string">'border-gray-300 focus:ring-indigo-500 focus:border-indigo-500'</span>
      <span class="token punctuation">]</span>"
      <span class="token punctuation">:</span>placeholder<span class="token operator">=</span><span class="token string">"placeholder"</span>
    <span class="token operator">/</span><span class="token operator">&gt;</span>

    <span class="token operator">&lt;</span>div v<span class="token operator">-</span><span class="token keyword">if</span><span class="token operator">=</span><span class="token string">"error"</span> <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"text-sm text-red-500 mt-1"</span><span class="token operator">&gt;</span>
      <span class="token punctuation">{</span><span class="token punctuation">{</span> error <span class="token punctuation">}</span><span class="token punctuation">}</span>
    <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span>
  <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span>
<span class="token operator">&lt;</span><span class="token operator">/</span>template<span class="token operator">&gt;</span>

<span class="token operator">&lt;</span>script lang<span class="token operator">=</span><span class="token string">"ts"</span><span class="token operator">&gt;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> defineComponent<span class="token punctuation">,</span> ref<span class="token punctuation">,</span> watch<span class="token punctuation">,</span> onMounted<span class="token punctuation">,</span> nextTick <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'vue'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> loadGoogleMapsApi <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@/utils/loadGoogleMapsApi'</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token function">defineComponent</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  name<span class="token punctuation">:</span> <span class="token string">'FieldAddress'</span><span class="token punctuation">,</span>

  props<span class="token punctuation">:</span> <span class="token punctuation">{</span>
    id<span class="token punctuation">:</span> <span class="token punctuation">{</span>
      <span class="token keyword">type</span><span class="token punctuation">:</span> String<span class="token punctuation">,</span>
      required<span class="token punctuation">:</span> <span class="token keyword">true</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    label<span class="token punctuation">:</span> <span class="token punctuation">{</span>
      <span class="token keyword">type</span><span class="token punctuation">:</span> String<span class="token punctuation">,</span>
      <span class="token keyword">default</span><span class="token punctuation">:</span> <span class="token string">''</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    modelValue<span class="token punctuation">:</span> <span class="token punctuation">{</span>
      <span class="token keyword">type</span><span class="token punctuation">:</span> String<span class="token punctuation">,</span>
      required<span class="token punctuation">:</span> <span class="token keyword">true</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    error<span class="token punctuation">:</span> <span class="token punctuation">{</span>
      <span class="token keyword">type</span><span class="token punctuation">:</span> String<span class="token punctuation">,</span>
      <span class="token keyword">default</span><span class="token punctuation">:</span> <span class="token string">''</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token keyword">type</span><span class="token punctuation">:</span> <span class="token punctuation">{</span>
      <span class="token keyword">type</span><span class="token punctuation">:</span> String<span class="token punctuation">,</span>
      <span class="token keyword">default</span><span class="token punctuation">:</span> <span class="token string">'text'</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    placeholder<span class="token punctuation">:</span> <span class="token punctuation">{</span>
      <span class="token keyword">type</span><span class="token punctuation">:</span> String<span class="token punctuation">,</span>
      <span class="token keyword">default</span><span class="token punctuation">:</span> <span class="token string">''</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>

  emits<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">'update:modelValue'</span><span class="token punctuation">,</span> <span class="token string">'address-selected'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>

  <span class="token function">setup</span><span class="token punctuation">(</span>props<span class="token punctuation">,</span> <span class="token punctuation">{</span> emit <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// Internal state for the input value, initialized with the prop modelValue</span>
    <span class="token keyword">const</span> internalValue <span class="token operator">=</span> <span class="token function">ref</span><span class="token punctuation">(</span>props<span class="token punctuation">.</span>modelValue<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">// Reference to the input element for autocomplete</span>
    <span class="token keyword">const</span> autocompleteInput <span class="token operator">=</span> ref<span class="token operator">&lt;</span>HTMLInputElement <span class="token operator">|</span> <span class="token keyword">null</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">// Function to initialize Google Places Autocomplete</span>
    <span class="token keyword">const</span> <span class="token function-variable function">initializeAutocomplete</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
      <span class="token keyword">if</span> <span class="token punctuation">(</span>autocompleteInput<span class="token punctuation">.</span>value<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">const</span> autocomplete <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">google<span class="token punctuation">.</span>maps<span class="token punctuation">.</span>places<span class="token punctuation">.</span>Autocomplete</span><span class="token punctuation">(</span>autocompleteInput<span class="token punctuation">.</span>value<span class="token punctuation">,</span> <span class="token punctuation">{</span>
          types<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">'address'</span><span class="token punctuation">]</span> <span class="token comment">// Restrict to address type</span>
        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token comment">// Add listener for when a place is selected</span>
        autocomplete<span class="token punctuation">.</span><span class="token function">addListener</span><span class="token punctuation">(</span><span class="token string">'place_changed'</span><span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
          <span class="token keyword">const</span> place <span class="token operator">=</span> autocomplete<span class="token punctuation">.</span><span class="token function">getPlace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
          <span class="token keyword">if</span> <span class="token punctuation">(</span>place<span class="token punctuation">.</span>address_components<span class="token punctuation">)</span> <span class="token punctuation">{</span>
            <span class="token keyword">const</span> address <span class="token operator">=</span> place<span class="token punctuation">.</span>formatted_address <span class="token operator">||</span> <span class="token string">''</span><span class="token punctuation">;</span>
            <span class="token comment">// Emit the selected address to the parent component</span>
            <span class="token function">emit</span><span class="token punctuation">(</span><span class="token string">'update:modelValue'</span><span class="token punctuation">,</span> address<span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token comment">// Wait for the DOM to update</span>
            <span class="token keyword">await</span> <span class="token function">nextTick</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token comment">// Emit the place object to the parent component</span>
            <span class="token function">emit</span><span class="token punctuation">(</span><span class="token string">'address-selected'</span><span class="token punctuation">,</span> place<span class="token punctuation">)</span><span class="token punctuation">;</span>
          <span class="token punctuation">}</span>
        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">}</span><span class="token punctuation">;</span>

    <span class="token comment">// Watch for changes in the modelValue prop and update internalValue</span>
    <span class="token function">watch</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> props<span class="token punctuation">.</span>modelValue<span class="token punctuation">,</span> <span class="token punctuation">(</span>newVal<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
      internalValue<span class="token punctuation">.</span>value <span class="token operator">=</span> newVal<span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">// On component mount, load the Google Maps API and initialize autocomplete</span>
    <span class="token function">onMounted</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
      <span class="token keyword">try</span> <span class="token punctuation">{</span>
        <span class="token keyword">await</span> <span class="token function">loadGoogleMapsApi</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token function">initializeAutocomplete</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">error</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token string">'Error loading Google Maps API:'</span><span class="token punctuation">,</span> error<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">return</span> <span class="token punctuation">{</span>
      internalValue<span class="token punctuation">,</span>
      autocompleteInput
    <span class="token punctuation">}</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token operator">&lt;</span><span class="token operator">/</span>script<span class="token operator">&gt;</span>
</code></pre><ol start="6"><li>And now the only thing left is to update our <code class="inline-code">CheckoutView.vue</code>:</li></ol><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token operator">&lt;</span>template<span class="token operator">&gt;</span>
  <span class="token operator">&lt;</span>div <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"w-full min-h-screen pt-40"</span><span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span>form @submit<span class="token punctuation">.</span>prevent<span class="token operator">=</span><span class="token string">"onCheckout"</span> <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"p-6 mx-auto md:shadow-2xl rounded-lg md:w-1/2"</span><span class="token operator">&gt;</span>
      <span class="token operator">...</span>

      <span class="token operator">&lt;</span>FieldAddress
        id<span class="token operator">=</span><span class="token string">"address"</span>
        label<span class="token operator">=</span><span class="token string">"Billing Address"</span>
        v<span class="token operator">-</span>model<span class="token operator">=</span><span class="token string">"address"</span>
        placeholder<span class="token operator">=</span><span class="token string">"Type your address"</span>
        <span class="token punctuation">:</span>error<span class="token operator">=</span><span class="token string">"errors.address"</span>
        @address<span class="token operator">-</span>selected<span class="token operator">=</span><span class="token string">"onAddressSelected"</span>
      <span class="token operator">/</span><span class="token operator">&gt;</span>

      <span class="token operator">...</span>
    <span class="token operator">&lt;</span><span class="token operator">/</span>form<span class="token operator">&gt;</span>
  <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span>
<span class="token operator">&lt;</span><span class="token operator">/</span>template<span class="token operator">&gt;</span>

<span class="token operator">&lt;</span>script lang<span class="token operator">=</span><span class="token string">"ts"</span><span class="token operator">&gt;</span>
<span class="token operator">...</span>

<span class="token keyword">import</span> FieldInput <span class="token keyword">from</span> <span class="token string">'@/components/fields/FieldInput.vue'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> BaseButton <span class="token keyword">from</span> <span class="token string">'@/components/base/BaseButton.vue'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> FieldAddress <span class="token keyword">from</span> <span class="token string">'@/components/fields/FieldAddress.vue'</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token function">defineComponent</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  name<span class="token punctuation">:</span> <span class="token string">'CheckoutView'</span><span class="token punctuation">,</span>

  components<span class="token punctuation">:</span> <span class="token punctuation">{</span>
    BaseButton<span class="token punctuation">,</span>
    FieldInput<span class="token punctuation">,</span>
    FieldAddress
  <span class="token punctuation">}</span><span class="token punctuation">,</span>

  <span class="token function">setup</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token operator">...</span>

    <span class="token keyword">const</span> <span class="token function-variable function">onAddressSelected</span> <span class="token operator">=</span> <span class="token punctuation">(</span>place<span class="token punctuation">:</span> google<span class="token punctuation">.</span>maps<span class="token punctuation">.</span>places<span class="token punctuation">.</span>PlaceResult<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
      place<span class="token punctuation">.</span>address_components<span class="token operator">?</span><span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span>component <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
        <span class="token keyword">const</span> types <span class="token operator">=</span> component<span class="token punctuation">.</span>types<span class="token punctuation">;</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>types<span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span><span class="token string">'postal_code'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
          zip<span class="token punctuation">.</span>value <span class="token operator">=</span> component<span class="token punctuation">.</span>long_name<span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>types<span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span><span class="token string">'locality'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
          city<span class="token punctuation">.</span>value <span class="token operator">=</span> component<span class="token punctuation">.</span>long_name<span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>types<span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span><span class="token string">'country'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
          country<span class="token punctuation">.</span>value <span class="token operator">=</span> component<span class="token punctuation">.</span>long_name<span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
      <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">;</span>

    <span class="token keyword">const</span> onCheckout <span class="token operator">=</span> <span class="token function">handleSubmit</span><span class="token punctuation">(</span><span class="token punctuation">(</span>values<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
      console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Checkout'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span>
        firstName<span class="token punctuation">:</span> firstName<span class="token punctuation">.</span>value<span class="token punctuation">,</span>
        lastName<span class="token punctuation">:</span> lastName<span class="token punctuation">.</span>value<span class="token punctuation">,</span>
        email<span class="token punctuation">:</span> email<span class="token punctuation">.</span>value<span class="token punctuation">,</span>
        fullAddress<span class="token punctuation">:</span> <span class="token template-string"><span class="token string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>address<span class="token punctuation">.</span>value<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">, </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>zip<span class="token punctuation">.</span>value<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">, </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>city<span class="token punctuation">.</span>value<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">, </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>country<span class="token punctuation">.</span>value<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">`</span></span>
      <span class="token punctuation">}</span><span class="token punctuation">)</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span>

    <span class="token keyword">return</span> <span class="token punctuation">{</span>
      firstName<span class="token punctuation">,</span>
      lastName<span class="token punctuation">,</span>
      email<span class="token punctuation">,</span>
      address<span class="token punctuation">,</span>
      zip<span class="token punctuation">,</span>
      city<span class="token punctuation">,</span>
      country<span class="token punctuation">,</span>
      errors<span class="token punctuation">,</span>
      onAddressSelected<span class="token punctuation">,</span>
      onCheckout
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token operator">&lt;</span><span class="token operator">/</span>script<span class="token operator">&gt;</span>
</code></pre><p>Let&rsquo;s see what we have now.</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2024/2024-07/google-place-autocomplete.gif?sfvrsn=bc84193c_1" alt="Google Place Autocomplete" /></p><p>Brilliant and beautiful. That&rsquo;s what good UX and code look like. Now, just celebrate! </p><h2 id="last-words">Last Words</h2><p>To conclude this article, here are a few things that I&rsquo;ve learned the hard way &zwj; and to keep in mind so your forms don&rsquo;t become your hell on earth:</p><ul><li><p><strong>State management is your best friend:</strong> Libraries like our lovely  Pinia  and our old trooper Vuex will help you maintain data consistency between views in multi-step forms using a few lines of code.</p></li><li><p><strong>Don&rsquo;t Repeat Yourself (DRY):</strong> Like we did when centralizing our validation roles with Zod, it&rsquo;s easing to keep your validation rules consistent and you can easily update them. If you want to save time, there are many UI libraries with all the form components you might need for your project with built-in validation like Vuetify, Element UI or even Nuxt UI.</p></li><li><p><strong>Keep components simple and modular:</strong> Like when we created a different input for the address managed by Google Place Autocomplete. It is easier to maintain and your colleagues won&rsquo;t have to get a PhD just to understand your component that does a million things. Plus, in a few months, it would take you forever to debug it yourself.</p></li><li><p><strong>Designers are developers&rsquo; best friends:</strong> Your designer is not your boss nor your enemy&mdash;we complement each other! When designers and developers work together, it results in better UX, less complicated code and fewer bugs.</p></li></ul><p>You can reach me on Twitter <a target="_blank" href="https://twitter.com/RifkiNada">@RifkiNada</a>. And in case you are curious about my work or other articles, you can look at them here <a target="_blank" href="https://www.nadarifki.com/">www.nadarifki.com</a>. </p><p>Happy coding, my lovely friends.</p><aside><hr data-sf-ec-immutable="" /><div class="row"><div class="col-4 u-normal-full u-small-mb0"><h4 class="u-fs20 u-fw5 u-lh125 u-mb0">Vue Basics: First Steps with Nuxt and Nuxt CLI</h4></div><div class="col-8"><p class="u-fs16 u-mb0"><a target="_blank" href="https://www.telerik.com/blogs/vue-basics-first-steps-nuxt-cli">Learn how to get started with Nuxt and the Nuxt CLI</a>&mdash;create a new Nuxt project, understand the directory structure and use the Nuxt CLI to enhance our development workflow.</p></div></div></aside><img src="https://feeds.telerik.com/link/23057/16819177.gif" height="1" width="1"/>]]></content>
  </entry>
</feed>
