<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: This Dot Media</title>
    <description>The latest articles on DEV Community by This Dot Media (@thisdotmedia_staff).</description>
    <link>https://dev.to/thisdotmedia_staff</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F118437%2F7f9e6d17-6bf6-439b-a5ed-7003c2f429a3.jpg</url>
      <title>DEV Community: This Dot Media</title>
      <link>https://dev.to/thisdotmedia_staff</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/thisdotmedia_staff"/>
    <language>en</language>
    <item>
      <title>Understanding the Difference Between `:focus` and `:focus-visible` in CSS</title>
      <dc:creator>This Dot Media</dc:creator>
      <pubDate>Tue, 12 Nov 2024 08:56:11 +0000</pubDate>
      <link>https://dev.to/thisdotmedia_staff/understanding-the-difference-between-focus-and-focus-visible-in-css-19c0</link>
      <guid>https://dev.to/thisdotmedia_staff/understanding-the-difference-between-focus-and-focus-visible-in-css-19c0</guid>
      <description>&lt;h1&gt;
  
  
  Understanding the Difference Between &lt;code&gt;:focus&lt;/code&gt; and &lt;code&gt;:focus-visible&lt;/code&gt; in CSS
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://www.thisdot.co/blog/i-broke-my-hand-so-you-dont-have-to-first-hand-accessibility-insights" rel="noopener noreferrer"&gt;I have learned my fair share about the importance of keyboard accessibility&lt;/a&gt;, so I know that visual indication of the focused element is very important. But the well-known &lt;code&gt;:focus&lt;/code&gt; pseudo-class is not always the best fit for this job. That's where &lt;code&gt;:focus-visible&lt;/code&gt; comes in. Let's look at the differences between these two pseudo-classes and explore the best practices for using them effectively.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the &lt;code&gt;:focus&lt;/code&gt; Pseudo-Class?
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:focus" rel="noopener noreferrer"&gt;:focus&lt;/a&gt; pseudo-class is a CSS selector that applies styles to any element that receives focus, regardless of how that focus was triggered. This includes focus events from keyboard navigation, mouse clicks, and touch interactions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example Usage of &lt;code&gt;:focus&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="nd"&gt;:focus&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;outline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="no"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, the button will display a blue outline whenever it is focused, whether the user clicks on it with a mouse, taps it on a touchscreen, or navigates to it using the keyboard.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the &lt;code&gt;:focus-visible&lt;/code&gt; Pseudo-Class?
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible" rel="noopener noreferrer"&gt;:focus-visible&lt;/a&gt; pseudo-class is more specialized. It only applies styles to an element when the browser determines that the focus should be visible. This typically occurs when the user navigates via the keyboard or assistive technologies rather than through mouse or touch input.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example Usage of &lt;code&gt;:focus-visible&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="nd"&gt;:focus-visible&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;outline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="no"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, the button will only show a blue outline when focused through keyboard navigation or another input method that usually requires visible focus indicators.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Differences Between &lt;code&gt;:focus&lt;/code&gt; and &lt;code&gt;:focus-visible&lt;/code&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;:focus&lt;/code&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Behavior:&lt;/strong&gt; Applies to any element that receives focus, regardless of the input method.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use Cases:&lt;/strong&gt; Ensures that all interactions with the element are visually indicated, whether by mouse, keyboard, or touch.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;:focus-visible&lt;/code&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Behavior:&lt;/strong&gt; Applies styles only when the focus should be visible, such as using a keyboard or assistive technology.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use Cases:&lt;/strong&gt; Ideal for scenarios where you want to provide focus indicators only to keyboard and assistive technology users while avoiding unnecessary outlines for mouse and touch users, typically required by design.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Accessibility Implications
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;:focus&lt;/code&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Guarantees that all users can see when an element is focused, which is critical for accessibility.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Can lead to a suboptimal experience for mouse users, as focus styles may appear unnecessarily during mouse interactions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;:focus-visible&lt;/code&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Enhances user experience by showing focus indicators only when necessary, thus keeping the interface clean for mouse and touch users.&lt;/li&gt;
&lt;li&gt;Tailors the experience for keyboard and assistive technology users, providing them with clear visual cues.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Additional considerations may be required to ensure that focus indicators are not accidentally omitted, especially in &lt;a href="https://caniuse.com/css-focus-visible" rel="noopener noreferrer"&gt;older browsers that do not support &lt;code&gt;:focus-visible&lt;/code&gt;&lt;/a&gt;.

&lt;ul&gt;
&lt;li&gt;There may be cases where you want to show focus indicators for all users, regardless of input method.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Best Practices for Using &lt;code&gt;:focus&lt;/code&gt; and &lt;code&gt;:focus-visible&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;To achieve the best accessibility and user experience, combining both &lt;code&gt;:focus&lt;/code&gt; and &lt;code&gt;:focus-visible&lt;/code&gt; in your CSS is often a good idea.&lt;/p&gt;

&lt;h3&gt;
  
  
  Combining &lt;code&gt;:focus&lt;/code&gt; and &lt;code&gt;:focus-visible&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="nd"&gt;:focus&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;outline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* Remove default focus for all interactions */&lt;/span&gt;
 &lt;span class="c"&gt;/* maybe some subtle general focus style */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="nd"&gt;:focus-visible&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;outline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="no"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* Only show outline when the element was interacted with via keyboard or assistive technology */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is a Stackblitz example of what such styling could look like for you to try out and play with:&lt;/p&gt;

&lt;h3&gt;
  
  
  Additional Tips
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Test with Keyboard and Assistive Technology:&lt;/strong&gt; Ensure that your web application is navigable using a keyboard (&lt;code&gt;Tab&lt;/code&gt;, &lt;code&gt;Shift + Tab&lt;/code&gt;, etc.) and that focus indicators are visible for those who rely on them. It's never a bad idea to &lt;a href="https://www.thisdot.co/blog/testing-accessibility-features-with-playwright" rel="noopener noreferrer"&gt;include accessibility testing in your e2e testing suite&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Provide Clear Focus Indicators:&lt;/strong&gt; Make sure that focus indicators are prominent and easy to see. A subtle or hard-to-spot focus indicator can severely impact accessibility for users who rely on keyboard navigation.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;:focus-visible&lt;/code&gt; pseudo-class offers a more refined way to manage focus indicators, improving accessibility and user experience, particularly for keyboard and assistive technology users. By understanding the differences between &lt;code&gt;:focus&lt;/code&gt; and &lt;code&gt;:focus-visible&lt;/code&gt;, and applying best practices in your CSS, you can create more accessible and user-friendly web applications.&lt;/p&gt;

&lt;p&gt;Remember, accessibility should never be an afterthought. By thoughtfully applying focus styles, you ensure that all users, regardless of how they interact with your site, can easily navigate and interact.&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>css</category>
      <category>playwright</category>
    </item>
    <item>
      <title>Exploring Angular Forms: A New Alternative with Signals</title>
      <dc:creator>This Dot Media</dc:creator>
      <pubDate>Wed, 06 Nov 2024 12:50:43 +0000</pubDate>
      <link>https://dev.to/thisdotmedia/exploring-angular-forms-a-new-alternative-with-signals-19l6</link>
      <guid>https://dev.to/thisdotmedia/exploring-angular-forms-a-new-alternative-with-signals-19l6</guid>
      <description>&lt;h1&gt;
  
  
  Exploring Angular Forms: A New Alternative with Signals
&lt;/h1&gt;

&lt;p&gt;In the world of Angular, forms are essential for user interaction, whether you're crafting a simple login page or a more complex user profile interface. Angular traditionally offers two primary approaches: &lt;strong&gt;template-driven forms&lt;/strong&gt; and &lt;strong&gt;reactive forms&lt;/strong&gt;. In my previous &lt;a href="https://www.thisdot.co/blog/a-guide-to-typed-reactive-forms-in-angular-part-i-the-basics" rel="noopener noreferrer"&gt;series on Angular Reactive Forms&lt;/a&gt;, I explored how to harness reactive forms' power to manage complex logic, create dynamic forms, and build custom form controls.&lt;/p&gt;

&lt;p&gt;A new tool for managing reactivity - &lt;strong&gt;signals&lt;/strong&gt; - has been introduced in version 16 of Angular and has been the focus of Angular maintainers ever since, becoming stable with version 17. Signals allow you to handle state changes declaratively, offering an exciting alternative that combines the simplicity of template-driven forms with the robust reactivity of reactive forms. This article will examine how signals can add reactivity to both simple and complex forms in Angular.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recap: Angular Forms Approaches
&lt;/h2&gt;

&lt;p&gt;Before diving into the topic of enhancing template-driven forms with signals, let’s quickly recap Angular's traditional forms approaches:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Template-Driven Forms&lt;/strong&gt;: Defined directly in the HTML template using directives like &lt;code&gt;ngModel&lt;/code&gt;, these forms are easy to set up and are ideal for simple forms. However, they may not provide the fine-grained control required for more complex scenarios.&lt;/p&gt;

&lt;p&gt;Here's a minimal example of a template-driven form:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;(ngSubmit)=&lt;/span&gt;&lt;span class="s"&gt;"onSubmit()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Name:&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt; &lt;span class="na"&gt;[(ngModel)]=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Submit&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;```typescript
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html'
})
export class AppComponent {
  name = '';

  onSubmit() {
    console.log(this.name);
  }
}
```
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Reactive Forms&lt;/strong&gt;: Managed programmatically in the component class using Angular's &lt;code&gt;FormGroup&lt;/code&gt;, &lt;code&gt;FormControl&lt;/code&gt;, and &lt;code&gt;FormArray&lt;/code&gt; classes; reactive forms offer granular control over form state and validation. This approach is well-suited for complex forms, as my previous articles on Angular Reactive Forms discussed.&lt;/p&gt;

&lt;p&gt;And here's a minimal example of a reactive form:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;FormGroup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;FormControl&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/forms&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app-root&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;templateUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./app.component.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppComponent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FormGroup&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FormControl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;onSubmit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;```html
&amp;lt;form [formGroup]="form" (ngSubmit)="onSubmit()"&amp;gt;
  &amp;lt;label for="name"&amp;gt;Name:&amp;lt;/label&amp;gt;
  &amp;lt;input id="name" formControlName="name"&amp;gt;
  &amp;lt;button type="submit"&amp;gt;Submit&amp;lt;/button&amp;gt;
&amp;lt;/form&amp;gt;
```
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Introducing Signals as a New Way to Handle Form Reactivity
&lt;/h2&gt;

&lt;p&gt;With the release of Angular 16, signals have emerged as a new way to manage reactivity. Signals provide a declarative approach to state management, making your code more predictable and easier to understand. When applied to forms, signals can enhance the simplicity of template-driven forms while offering the reactivity and control typically associated with reactive forms.&lt;/p&gt;

&lt;p&gt;Let’s explore how signals can be used in both simple and complex form scenarios.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example 1: A Simple Template-Driven Form with Signals
&lt;/h3&gt;

&lt;p&gt;Consider a basic login form. Typically, this would be implemented using template-driven forms like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- login.component.html --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"form"&lt;/span&gt; &lt;span class="na"&gt;(ngSubmit)=&lt;/span&gt;&lt;span class="s"&gt;"onSubmit()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;E-mail&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;[(ngModel)]=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Password&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt; &lt;span class="na"&gt;[(ngModel)]=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Login!&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// login.component.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@angular/core&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;app-login&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;templateUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./login.component.html&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LoginComponent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;onSubmit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Form submitted&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach works well for simple forms, but by introducing signals, we can keep the simplicity while adding reactive capabilities:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// login.component.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;signal&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@angular/core&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;FormsModule&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@angular/forms&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;app-login&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;standalone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;templateUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./login.component.html&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;FormsModule&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LoginComponent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Define signals for form fields&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Define a computed signal for the form value&lt;/span&gt;

  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;formValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;password&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;isFormValid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;password&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;onSubmit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Form submitted&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;formValue&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- login.component.html --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"form"&lt;/span&gt; &lt;span class="na"&gt;(ngSubmit)=&lt;/span&gt;&lt;span class="s"&gt;"onSubmit()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;E-mail&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;[(ngModel)]=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Password&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt; &lt;span class="na"&gt;[(ngModel)]=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Login!&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, the form fields are defined as signals, allowing for reactive updates whenever the form state changes. The &lt;code&gt;formValue&lt;/code&gt; signal provides a computed value that reflects the current state of the form. This approach offers a more declarative way to manage form state and reactivity, combining the simplicity of template-driven forms with the power of signals.&lt;/p&gt;

&lt;p&gt;You may be tempted to define the form directly as an object inside a signal. While such an approach may seem more concise, typing into the individual fields does not dispatch reactivity updates, which is usually a deal breaker. Here’s an example StackBlitz with a component suffering from such an issue:&lt;/p&gt;

&lt;p&gt;Therefore, if you'd like to react to changes in the form fields, it's better to define each field as a separate signal. By defining each form field as a separate signal, you ensure that changes to individual fields trigger reactivity updates correctly. &lt;/p&gt;

&lt;h3&gt;
  
  
  Example 2: A Complex Form with Signals
&lt;/h3&gt;

&lt;p&gt;You may see little benefit in using signals for simple forms like the login form above, but they truly shine when handling more complex forms. Let's explore a more intricate scenario - a user profile form that includes fields like &lt;code&gt;firstName&lt;/code&gt;, &lt;code&gt;lastName&lt;/code&gt;, &lt;code&gt;email&lt;/code&gt;, &lt;code&gt;phoneNumbers&lt;/code&gt;, and &lt;code&gt;address&lt;/code&gt;. The &lt;code&gt;phoneNumbers&lt;/code&gt; field is dynamic, allowing users to add or remove phone numbers as needed.&lt;/p&gt;

&lt;p&gt;Here's how this form might be defined using signals:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// user-profile.component.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;JsonPipe&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@angular/common&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;signal&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@angular/core&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;FormsModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Validators&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@angular/forms&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;standalone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;app-user-profile&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;templateUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./user-profile.component.html&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;styleUrls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./user-profile.component.scss&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;FormsModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JsonPipe&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserProfileComponent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;firstName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;lastName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
  &lt;span class="c1"&gt;// We need to use a signal for the phone numbers, so we get reactivity when typing in the input fields&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;phoneNumbers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nf"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)]);&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;street&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;city&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;zip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;formValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="c1"&gt;// We need to do a little mapping here, so we get the actual value for the phone numbers&lt;/span&gt;
      &lt;span class="na"&gt;phoneNumbers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;phoneNumbers&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;phoneNumber&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;phoneNumber&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
      &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;street&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;street&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;city&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;state&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="na"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;formValid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;phoneNumbers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;address&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;formValue&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Regex taken from the Angular email validator&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;EMAIL_REGEXP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;(?=&lt;/span&gt;&lt;span class="sr"&gt;.&lt;/span&gt;&lt;span class="se"&gt;{1,254}&lt;/span&gt;&lt;span class="sr"&gt;$&lt;/span&gt;&lt;span class="se"&gt;)(?=&lt;/span&gt;&lt;span class="sr"&gt;.&lt;/span&gt;&lt;span class="se"&gt;{1,64}&lt;/span&gt;&lt;span class="sr"&gt;@&lt;/span&gt;&lt;span class="se"&gt;)[&lt;/span&gt;&lt;span class="sr"&gt;a-zA-Z0-9!#$%&amp;amp;'*+&lt;/span&gt;&lt;span class="se"&gt;/&lt;/span&gt;&lt;span class="sr"&gt;=?^_`{|}~-&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;(?:\.[&lt;/span&gt;&lt;span class="sr"&gt;a-zA-Z0-9!#$%&amp;amp;'*+&lt;/span&gt;&lt;span class="se"&gt;/&lt;/span&gt;&lt;span class="sr"&gt;=?^_`{|}~-&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;*@&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;a-zA-Z0-9&lt;/span&gt;&lt;span class="se"&gt;](?:[&lt;/span&gt;&lt;span class="sr"&gt;a-zA-Z0-9-&lt;/span&gt;&lt;span class="se"&gt;]{0,61}[&lt;/span&gt;&lt;span class="sr"&gt;a-zA-Z0-9&lt;/span&gt;&lt;span class="se"&gt;])?(?:\.[&lt;/span&gt;&lt;span class="sr"&gt;a-zA-Z0-9&lt;/span&gt;&lt;span class="se"&gt;](?:[&lt;/span&gt;&lt;span class="sr"&gt;a-zA-Z0-9-&lt;/span&gt;&lt;span class="se"&gt;]{0,61}[&lt;/span&gt;&lt;span class="sr"&gt;a-zA-Z0-9&lt;/span&gt;&lt;span class="se"&gt;])?)&lt;/span&gt;&lt;span class="sr"&gt;*$/&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isEmailFormatValid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;EMAIL_REGEXP&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
      &lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
      &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
      &lt;span class="nx"&gt;isEmailFormatValid&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
      &lt;span class="nx"&gt;phoneNumbers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="c1"&gt;// Check if all phone numbers are valid&lt;/span&gt;
      &lt;span class="nx"&gt;phoneNumbers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;every&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;phoneNumber&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;phoneNumber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
      &lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;street&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
      &lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
      &lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
      &lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;addPhoneNumber&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;phoneNumbers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;phoneNumbers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;phoneNumbers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;phoneNumbers&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;removePhoneNumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;phoneNumbers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;phoneNumbers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;phoneNumbers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;splice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;phoneNumbers&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Notice that the &lt;code&gt;phoneNumbers&lt;/code&gt; field is defined as a signal of an array of signals. This structure allows us to track changes to individual phone numbers and update the form state reactively. The &lt;code&gt;addPhoneNumber&lt;/code&gt; and &lt;code&gt;removePhoneNumber&lt;/code&gt; methods update the &lt;code&gt;phoneNumbers&lt;/code&gt; signal array, triggering reactivity updates in the form.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- user-profile.component.html --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"form"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"firstName"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;First Name&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"firstName"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"firstName"&lt;/span&gt; &lt;span class="na"&gt;[(ngModel)]=&lt;/span&gt;&lt;span class="s"&gt;"firstName"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"lastName"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Last Name&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"lastName"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"lastName"&lt;/span&gt; &lt;span class="na"&gt;[(ngModel)]=&lt;/span&gt;&lt;span class="s"&gt;"lastName"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Email&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"emailAddress"&lt;/span&gt; &lt;span class="na"&gt;[(ngModel)]=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"phoneNumbers"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;Phone Numbers&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt; @for (phone of phoneNumbers(); track i; let i = $index) {
    &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"tel"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"phoneNumbers-{{ i }}"&lt;/span&gt; &lt;span class="na"&gt;[(ngModel)]=&lt;/span&gt;&lt;span class="s"&gt;"phone"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;(click)=&lt;/span&gt;&lt;span class="s"&gt;"removePhoneNumber(i)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Remove&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    } &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;(click)=&lt;/span&gt;&lt;span class="s"&gt;"addPhoneNumber()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Add Phone Number&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"street"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Street&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"street"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"street"&lt;/span&gt; &lt;span class="na"&gt;[(ngModel)]=&lt;/span&gt;&lt;span class="s"&gt;"street"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"city"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;City&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"city"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"city"&lt;/span&gt; &lt;span class="na"&gt;[(ngModel)]=&lt;/span&gt;&lt;span class="s"&gt;"city"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"state"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;State&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"state"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"state"&lt;/span&gt; &lt;span class="na"&gt;[(ngModel)]=&lt;/span&gt;&lt;span class="s"&gt;"state"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"zip"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;ZIP Code&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"zip"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"zip"&lt;/span&gt; &lt;span class="na"&gt;[(ngModel)]=&lt;/span&gt;&lt;span class="s"&gt;"zip"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

  @if(!formValid()) {
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"message message--error"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Form is invalid!&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  } @else {
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"message message--success"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Form is valid!&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  }
  &lt;span class="nt"&gt;&amp;lt;pre&amp;gt;&lt;/span&gt;
    {{ formValue() | json }}
  &lt;span class="nt"&gt;&amp;lt;/pre&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;In the template, we use the &lt;code&gt;phoneNumbers&lt;/code&gt; signal array to dynamically render the phone number input fields. The &lt;code&gt;addPhoneNumber&lt;/code&gt; and &lt;code&gt;removePhoneNumber&lt;/code&gt; methods allow users to reactively add or remove phone numbers, updating the form state. Notice the usage of the &lt;code&gt;track&lt;/code&gt; function, which is necessary to ensure that the &lt;code&gt;ngFor&lt;/code&gt; directive tracks changes to the &lt;code&gt;phoneNumbers&lt;/code&gt; array correctly.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here's a StackBlitz demo of the complex form example for you to play around with:&lt;/p&gt;

&lt;h3&gt;
  
  
  Validating Forms with Signals
&lt;/h3&gt;

&lt;p&gt;Validation is critical to any form, ensuring that user input meets the required criteria before submission. With signals, validation can be handled in a reactive and declarative manner. In the complex form example above, we've implemented a computed signal called &lt;code&gt;formValid&lt;/code&gt;, which checks whether all fields meet specific validation criteria.&lt;/p&gt;

&lt;p&gt;The validation logic can easily be customized to accommodate different rules, such as checking for valid email formats or ensuring that all required fields are filled out. Using signals for validation allows you to create more maintainable and testable code, as the validation rules are clearly defined and react automatically to changes in form fields. It can even be abstracted into a separate utility to make it reusable across different forms.&lt;/p&gt;

&lt;p&gt;In the complex form example, the &lt;code&gt;formValid&lt;/code&gt; signal ensures that all required fields are filled and validates the email and phone numbers format.&lt;/p&gt;

&lt;p&gt;This approach to validation is a bit simple and needs to be better connected to the actual form fields. While it will work for many use cases, in some cases, you might want to wait until explicit "signal forms" support is added to Angular. Tim Deschryver started implementing some abstractions around signal forms, including validation and wrote an &lt;a href="https://timdeschryver.dev/blog/bringing-the-power-of-signals-to-angular-forms-with-signal-forms" rel="noopener noreferrer"&gt;article&lt;/a&gt; about it. Let's see if something like this will be added to Angular in the future.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Use Signals in Angular Forms?
&lt;/h3&gt;

&lt;p&gt;The adoption of signals in Angular provides a powerful new way to manage form state and reactivity. Signals offer a flexible, declarative approach that can simplify complex form handling by combining the strengths of template-driven forms and reactive forms. Here are some key benefits of using signals in Angular forms:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Declarative State Management&lt;/strong&gt;: Signals allow you to define form fields and computed values declaratively, making your code more predictable and easier to understand.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Reactivity&lt;/strong&gt;: Signals provide reactive updates to form fields, ensuring that changes to the form state trigger reactivity updates automatically.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Granular Control&lt;/strong&gt;: Signals allow you to define form fields at a granular level, enabling fine-grained control over form state and validation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Dynamic Forms&lt;/strong&gt;: Signals can be used to create dynamic forms with fields that can be added or removed dynamically, providing a flexible way to handle complex form scenarios.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Simplicity&lt;/strong&gt;: Signals can offer a simpler, more concise way to manage form states than traditional reactive forms, making building and maintaining complex forms easier.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;In my previous articles, we explored the powerful features of Angular reactive forms, from dynamic form construction to custom form controls. With the introduction of signals, Angular developers have a new tool that merges the simplicity of template-driven forms with the reactivity of reactive forms.&lt;/p&gt;

&lt;p&gt;While many use cases warrant Reactive Forms, signals provide a fresh, powerful alternative for managing form state in Angular applications requiring a more straightforward, declarative approach. As Angular continues to evolve, experimenting with these new features will help you build more maintainable, performant applications.&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

</description>
      <category>angular</category>
      <category>javascript</category>
    </item>
    <item>
      <title>"How do I undo my most recent commit?" - Mastering the git reset command</title>
      <dc:creator>This Dot Media</dc:creator>
      <pubDate>Wed, 06 Nov 2024 12:50:08 +0000</pubDate>
      <link>https://dev.to/thisdotmedia/how-do-i-undo-my-most-recent-commit-mastering-the-git-reset-command-4cn5</link>
      <guid>https://dev.to/thisdotmedia/how-do-i-undo-my-most-recent-commit-mastering-the-git-reset-command-4cn5</guid>
      <description>&lt;h1&gt;
  
  
  "How do I undo my most recent commit?" - Mastering the git reset command
&lt;/h1&gt;

&lt;p&gt;Git is a powerful version control system, but even experienced developers sometimes make mistakes. Whether you've committed changes to the wrong branch, made a typo in your commit message, or simply want to undo recent changes, the &lt;code&gt;git reset&lt;/code&gt; command is your go-to solution. In this post, we'll explore how to use &lt;code&gt;git reset&lt;/code&gt; to manage your commit history effectively.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Basics of git reset
&lt;/h2&gt;

&lt;p&gt;At its core, &lt;code&gt;git reset&lt;/code&gt; moves the HEAD and current branch pointer to a specified commit, effectively "resetting" your working directory to a previous state. The basic syntax is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git reset [&amp;lt;mode&amp;gt;] [&amp;lt;commit&amp;gt;]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where &lt;code&gt;&amp;lt;mode&amp;gt;&lt;/code&gt; specifies how to handle changes in the working directory and staging area, and &lt;code&gt;&amp;lt;commit&amp;gt;&lt;/code&gt; is the commit you want to reset to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Soft vs. Hard Reset: Understanding the Difference
&lt;/h2&gt;

&lt;p&gt;Git reset has two main modes: &lt;code&gt;--soft&lt;/code&gt; and &lt;code&gt;--hard&lt;/code&gt;. Let's explore each:&lt;/p&gt;

&lt;h3&gt;
  
  
  Soft Reset (--soft)
&lt;/h3&gt;

&lt;p&gt;A soft reset moves your HEAD to the specified commit but keeps your changes staged. This is useful when you want to undo commits but keep the changes for recommitting.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git reset --soft HEAD~1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command undoes the last commit, keeping the changes staged. You can now make additional changes or recommit with a new message.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hard Reset (--hard)
&lt;/h3&gt;

&lt;p&gt;A hard reset is more drastic. It moves your HEAD to the specified commit and discards all changes after that commit.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git reset --hard HEAD~1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command undoes the last commit and discards all changes. Be cautious with this command, as it can lead to data loss!&lt;/p&gt;

&lt;h2&gt;
  
  
  Specifying a Commit Hash
&lt;/h2&gt;

&lt;p&gt;Instead of using relative references like &lt;code&gt;HEAD~1&lt;/code&gt;, you can specify an exact commit hash:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git reset --soft 1a2b3c4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This resets to the commit with hash &lt;code&gt;1a2b3c4&lt;/code&gt;, keeping changes staged.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using HEAD~n Notation
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;HEAD~n&lt;/code&gt; notation allows you to specify how many commits back you want to reset. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git reset --hard HEAD~3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This resets to three commits before the current HEAD, discarding all changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reverting Pushed Changes
&lt;/h2&gt;

&lt;p&gt;If you've already pushed commits you want to undo, you should use &lt;code&gt;git revert&lt;/code&gt; instead of &lt;code&gt;git reset&lt;/code&gt; to avoid rewriting public history. However, if you must use &lt;code&gt;git reset&lt;/code&gt; on a shared branch, you'll need to force push:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git reset --hard HEAD~2
git push --force origin branch-name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Be extremely cautious with force pushing, as it can cause issues for other developers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generic Usage of git reset
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;git reset&lt;/code&gt; can also be used to unstage changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git reset HEAD file.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This removes &lt;code&gt;file.txt&lt;/code&gt; from the staging area without changing its contents.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;git reset&lt;/code&gt; command is a powerful tool for managing your Git history. Whether you're undoing recent commits, unstaging changes, or completely resetting your working directory, understanding the different modes of &lt;code&gt;git reset&lt;/code&gt; can help you navigate tricky situations in your Git workflow.&lt;/p&gt;

&lt;p&gt;Remember to use &lt;code&gt;git reset&lt;/code&gt; with caution, especially when working with shared repositories. When in doubt, create a backup branch before performing any reset operations.&lt;/p&gt;

</description>
      <category>git</category>
    </item>
    <item>
      <title>The HTML Dialog Element: Enhancing Accessibility and Ease of Use</title>
      <dc:creator>This Dot Media</dc:creator>
      <pubDate>Wed, 06 Nov 2024 12:49:15 +0000</pubDate>
      <link>https://dev.to/thisdotmedia/the-html-dialog-element-enhancing-accessibility-and-ease-of-use-2nd0</link>
      <guid>https://dev.to/thisdotmedia/the-html-dialog-element-enhancing-accessibility-and-ease-of-use-2nd0</guid>
      <description>&lt;h1&gt;
  
  
  The HTML Dialog Element: Enhancing Accessibility and Ease of Use
&lt;/h1&gt;

&lt;p&gt;Dialogs are a common component added to applications, whether on the web or in native applications. Traditionally there has not been a standard way of implementing these on the web, resulting in many ad-hoc implementations that don’t act consistently across different web applications. Often, commonly expected features are missing from dialogs due to the complexity of implementing them.&lt;/p&gt;

&lt;p&gt;However, web browsers now offer a standard dialog element.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why use the dialog element?
&lt;/h2&gt;

&lt;p&gt;The native dialog element streamlines the implementation of dialogs, modals, and other kinds of non-modal dialogs. It does this by implementing many of the features needed by dialogs for you that are already baked into the browser.&lt;/p&gt;

&lt;p&gt;This is helpful as it reduces the burden on the developer when making their applications accessible by ensuring that user expectations concerning interaction are met, and it can also potentially simplify the implementation of dialogs in general.&lt;/p&gt;

&lt;h2&gt;
  
  
  Basic usage
&lt;/h2&gt;

&lt;p&gt;Adding a dialog using the new &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; tag can be achieved with just a few lines of code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dialog&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"example-dialog"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;autofocus&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Close&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;This is a modal with some text!&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dialog&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, adding the dialog alone won’t do anything to the page. It will show up only once you call the &lt;code&gt;.showModal()&lt;/code&gt; method against it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;example-dialog&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;showModal&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then if you want to close it you can call the &lt;code&gt;.close()&lt;/code&gt; method on the dialog, or press the escape key to close it, just like most other modals work. Also, note how a backdrop appears that darkens the rest of the page and prevents you from interacting with it. Neat!&lt;/p&gt;

&lt;h2&gt;
  
  
  Accessibility and focus management
&lt;/h2&gt;

&lt;p&gt;Correctly handling focus is important when making your web applications accessible to all users. Typically you have to move the current focus to the active dialog when showing them, but with the dialog element that’s done for you.&lt;/p&gt;

&lt;p&gt;By default, the focus will be set on the first focusable element in the dialog. You can optionally change which element receives focus first by setting the &lt;code&gt;autofocus&lt;/code&gt; attribute on the element you want the focus to start on, as seen in the previous example where that attribute was added to the close &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; element.&lt;/p&gt;

&lt;p&gt;Using the &lt;code&gt;.showModal()&lt;/code&gt; method to open the dialog also implicitly adds the dialog ARIA role to the dialog element. This helps screen readers understand that a modal has appeared and the screen so it can act accordingly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding forms to dialogs
&lt;/h2&gt;

&lt;p&gt;Forms can also be added to dialogs, and there’s even a special &lt;code&gt;method&lt;/code&gt; value for them. If you add a &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; element with the method set to &lt;code&gt;dialog&lt;/code&gt; then the form will have some different behaviors that differ from the standard &lt;code&gt;get&lt;/code&gt; and &lt;code&gt;post&lt;/code&gt; form methods.&lt;/p&gt;

&lt;p&gt;First off, no external HTTP request will be made with this new method. What will happen instead is that when the form gets submitted, the &lt;code&gt;returnValue&lt;/code&gt; property on the form element will be set to the &lt;code&gt;value&lt;/code&gt; of the submit button in the form.&lt;/p&gt;

&lt;p&gt;So given this example form:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"example-form"&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"dialog"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"value1"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"text-value1"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"value2"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"text-value2"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"Submit"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The form element with the &lt;code&gt;example-form&lt;/code&gt; id will have its &lt;code&gt;returnValue&lt;/code&gt; set to &lt;code&gt;Submit&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In addition to that, the dialog will close immediately after the &lt;code&gt;submit&lt;/code&gt; event is done being handled, though not before automatic form validation is done. If this fails then the &lt;code&gt;invalid&lt;/code&gt; event will be emitted.&lt;/p&gt;

&lt;p&gt;You may have already noticed one caveat to all of this. You might not want the form to close automatically when the submit handler is done running. If you perform an asynchronous request with an API or server you may want to wait for a response and show any errors that occur before dismissing the dialog.&lt;/p&gt;

&lt;p&gt;In this case, you can call &lt;code&gt;event.preventDefault()&lt;/code&gt; in the &lt;code&gt;submit&lt;/code&gt; event listener like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;exampleForm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;submit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once your desired response comes back from the server, you can close it manually by using the &lt;code&gt;.close()&lt;/code&gt; method on the dialog.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enhancing the backdrop
&lt;/h2&gt;

&lt;p&gt;The backdrop behind the dialog is a mostly translucent gray background by default. However, that backdrop is fully customizable using the &lt;code&gt;::backdrop&lt;/code&gt; pseudo-element. With it, you can set a &lt;code&gt;background-color&lt;/code&gt; to any value you want, including gradients, images, etc.&lt;/p&gt;

&lt;p&gt;You may also want to make clicking the backdrop dismiss the modal, as this is a commonly implemented feature of them. By default, the &lt;code&gt;&amp;amp;lt;dialog&amp;gt;&lt;/code&gt; element doesn’t do this for us. There are a couple of changes that we can make to the dialog to get this working.&lt;/p&gt;

&lt;p&gt;First, an event listener is needed so that we know when the user clicks away from the dialog.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;dialog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;dialog&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;dialog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alone this event listener looks strange. It appears to dismiss the dialog whenever the dialog is clicked, not the backdrop. That’s the opposite of what we want to do. Unfortunately, you cannot listen for a click event on the backdrop as it is considered to be part of the dialog itself. Adding this event listener by itself will effectively make clicking anywhere on the page dismiss the dialog.&lt;br&gt;
To correct for this we need to wrap the contents of the dialog content with another element that will effectively mask the dialog and receive the click instead. A simple &lt;/p&gt; element can do!&lt;br&gt;

&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dialog&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"example-dialog"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"dialog"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"dialog-container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;This is a modal with some text!&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"example-form"&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"dialog"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"value1"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"text-value1"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"value2"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"text-value2"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"Submit"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dialog&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Even this isn’t perfect though as the contents of the div may have elements with margins in them that will push the div down, resulting in clicks close to the edges of the dialog to dismiss it. This can be resolved by adding a couple of styles the the wrapping div that will make the margin stay contained within the wrapper element. The dialog element itself also has some default padding that will exacerbate this issue.&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.dialog&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.dialog-container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;inline-block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;The wrapping div can be made into an &lt;code&gt;inline-block&lt;/code&gt; element to contain the margin, and by moving the padding from the parent dialog to the wrapper, clicks made in the padded portions of the dialog will now interact with the wrapper element instead ensuring it won’t be dismissed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Using the dialog element offers significant advantages for creating dialogs and modals by simplifying implementation with reasonable default behavior, enhancing accessibility for users that need assistive technologies such as screen readers by using automatic ARIA role assignment, tailored support for form elements, and flexible styling options.  &lt;/p&gt;

</description>
      <category>html</category>
      <category>javascript</category>
    </item>
    <item>
      <title>How to build an AI assistant with OpenAI, Vercel AI SDK, and Ollama with Next.js</title>
      <dc:creator>This Dot Media</dc:creator>
      <pubDate>Wed, 06 Nov 2024 12:47:53 +0000</pubDate>
      <link>https://dev.to/thisdotmedia_staff/how-to-build-an-ai-assistant-with-openai-vercel-ai-sdk-and-ollama-with-nextjs-39ak</link>
      <guid>https://dev.to/thisdotmedia_staff/how-to-build-an-ai-assistant-with-openai-vercel-ai-sdk-and-ollama-with-nextjs-39ak</guid>
      <description>&lt;p&gt;In today’s blog post, we’ll build an AI Assistant using three different AI models: &lt;a href="https://platform.openai.com/docs/models/whisper" rel="noopener noreferrer"&gt;Whisper&lt;/a&gt; and &lt;a href="https://platform.openai.com/docs/models/tts" rel="noopener noreferrer"&gt;TTS&lt;/a&gt; from &lt;a href="https://openai.com/" rel="noopener noreferrer"&gt;OpenAI&lt;/a&gt; and &lt;a href="https://openai.com/" rel="noopener noreferrer"&gt;Llama 3.1&lt;/a&gt; from &lt;a href="https://openai.com/" rel="noopener noreferrer"&gt;Meta&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;While exploring AI, I wanted to try different things and create an AI assistant that works by voice. This curiosity led me to combine OpenAI’s Whisper and TTS models with Meta’s Llama 3.1 to build a voice-activated assistant. &lt;/p&gt;

&lt;p&gt;Here’s how these models will work together:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First, we’ll send our audio to the Whisper model, which will convert it from speech to text.&lt;/li&gt;
&lt;li&gt;Next, we’ll pass that text to the Llama 3.1 model. Llama will understand the text and generate a response.&lt;/li&gt;
&lt;li&gt;Finally, we’ll take Llama’s response and send it to the TTS model, turning the text back into speech. We’ll then stream that audio back to the client.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s dive in and start building this excellent AI Assistant!&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Getting started&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;We will use different tools to build our assistant. To build our client side, we will use &lt;a href="//Next.js"&gt;Next.js&lt;/a&gt;. However, you could choose whichever framework you prefer.&lt;/p&gt;

&lt;p&gt;To use our OpenAI models, we will use their TypeScript / JavaScript&lt;a href="https://platform.openai.com/docs/libraries/node-js-library" rel="noopener noreferrer"&gt; SDK&lt;/a&gt;. To use this API, we require the following environmental variable: &lt;code&gt;OPENAI_API_KEY—&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;To get this key, we need to log in to the OpenAI dashboard and find the API keys section. Here, we can generate a new key.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/zojzzdop0fzx/6PhMdLfqfJAqByP8QjRQRi/e240947302294b08b0043eac337b419d/image1__2_.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/zojzzdop0fzx/6PhMdLfqfJAqByP8QjRQRi/e240947302294b08b0043eac337b419d/image1__2_.png" alt="Open AI dashboard inside the API keys section"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Awesome. Now, to use our Llama 3.1 model, we will use &lt;a href="https://ollama.com/" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt; and the &lt;a href="https://sdk.vercel.ai/docs/introduction" rel="noopener noreferrer"&gt;Vercel AI SDK&lt;/a&gt;, utilizing a provider called &lt;a href="https://github.com/sgomez/ollama-ai-provider" rel="noopener noreferrer"&gt;ollama-ai-provider&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Ollama will allow us to download our preferred model (we could even use a different one, like &lt;a href="https://ollama.com/library/phi3" rel="noopener noreferrer"&gt;Phi&lt;/a&gt;) and run it locally. The Vercel SDK will facilitate its use in our Next.js project.&lt;/p&gt;

&lt;p&gt;To use Ollama, we just need to download it and choose our preferred model. For this blog post, we are going to select Llama 3.1. After installing Ollama, we can verify if it is working by opening our terminal and writing the following command:&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/zojzzdop0fzx/4x88tqiwpUAdP9EhMwwrNg/07a5e98f36b47daaacd91ee7abe7ba7e/image2__1_.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/zojzzdop0fzx/4x88tqiwpUAdP9EhMwwrNg/07a5e98f36b47daaacd91ee7abe7ba7e/image2__1_.png" alt="Terminal, with the command ‘ollama run llama3.1’"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice that I wrote “llama3.1” because that’s my chosen model, but you should use the one you downloaded.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Kicking things off&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;It's time to kick things off by setting up our Next.js app. Let's start with this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx create-next-app@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After running the command, you’ll see a few prompts to set the app's details. Let's go step by step:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Name your app&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enable app router&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The other steps are optional and entirely up to you. In my case, I also chose to use TypeScript and Tailwind CSS.&lt;/p&gt;

&lt;p&gt;Now that’s done, let’s go into our project and install the dependencies that we need to run our models:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm i ai ollama-ai-provider openai
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Building our client logic&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Now, our goal is to record our voice, send it to the backend, and then receive a voice response from it.&lt;/p&gt;

&lt;p&gt;To record our audio, we need to use client-side functions, which means we need to use client components. In our case, we don’t want to transform our whole page to use client capabilities and have the whole tree in the client bundle; instead, we would prefer to use Server components and import our client components to progressively enhance our application.&lt;/p&gt;

&lt;p&gt;So, let’s create a separate component that will handle the client-side logic.&lt;/p&gt;

&lt;p&gt;Inside our app folder, let's create a &lt;code&gt;components&lt;/code&gt; folder, and here, we will be creating our component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app
 ↳components
  ↳audio-recorder.tsx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s go ahead and initialize our component. I went ahead and added a button with some styles in it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/components/audio-recorder.tsx&lt;/span&gt;
&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;AudioRecorder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleClick&lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;section&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleClick&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`bg-blue-500 text-white px-4 py-2 rounded shadow-md hover:bg-blue-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:ring-offset-white transition duration-300 ease-in-out absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2`&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nx"&gt;Record&lt;/span&gt; &lt;span class="nx"&gt;voice&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/section&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then import it into our Page Server component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/page.tsx&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;AudioRecorder&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/app/components/audio-recorder&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Home&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AudioRecorder&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, if we run our app, we should see the following:&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/zojzzdop0fzx/5W7alGpRLDcorcNol01c2P/ee81c2a4218007cb1fa492b20450d687/image3__1_.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/zojzzdop0fzx/5W7alGpRLDcorcNol01c2P/ee81c2a4218007cb1fa492b20450d687/image3__1_.png" alt="First look of the app, showing a centered blue button"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Awesome! Now, our button doesn’t do anything, but our goal is to record our audio and send it to someplace; for that, let us create a hook that will contain our logic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;
 &lt;span class="err"&gt;↳&lt;/span&gt;&lt;span class="nx"&gt;hooks&lt;/span&gt;
  &lt;span class="err"&gt;↳&lt;/span&gt;&lt;span class="nx"&gt;useRecordVoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;useRecordVoice&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will use two APIs to record our voice: &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Navigator" rel="noopener noreferrer"&gt;navigator&lt;/a&gt; and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder" rel="noopener noreferrer"&gt;MediaRecorder&lt;/a&gt;. The navigator API will give us information about the user’s media devices like the user media audio, and the MediaRecorder will help us record the audio from it. This is how they’re going to play out together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// apps/hooks/useRecordVoice.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;useRecordVoice&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isRecording&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setIsRecording&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;mediaRecorder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setMediaRecorder&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;MediaRecorder&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

     &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;startRecording&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;mediaDevices&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
            &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Media devices not supported&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mediaDevices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getUserMedia&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mediaRecorder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MediaRecorder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;setIsRecording&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;setMediaRecorder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mediaRecorder&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;mediaRecorder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stopRecording&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mediaRecorder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;setIsRecording&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nx"&gt;mediaRecorder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;isRecording&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;startRecording&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;stopRecording&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s explain this code step by step. First, we create two new states. The first one is for keeping track of when we are recording, and the second one stores the instance of our MediaRecorder.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isRecording&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setIsRecording&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;mediaRecorder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setMediaRecorder&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;MediaRecorder&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, we’ll create our first method, &lt;code&gt;startRecording&lt;/code&gt;. Here, we are going to have the logic to start recording our audio.&lt;br&gt;
We first check if the user has media devices available thanks to the &lt;code&gt;navigator&lt;/code&gt; API that gives us information about the browser environment of our user:&lt;/p&gt;

&lt;p&gt;If we don’t have media devices to record our audio, we just return. If they do, then let us create a stream using their audio media device.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// check if they have media devices&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;mediaDevices&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
 &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Media devices not supported&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// create stream using the audio media device&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mediaDevices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getUserMedia&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, we go ahead and create an instance of a MediaRecorder to record this audio:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// create an instance passing in the stream as parameter&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mediaRecorder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MediaRecorder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Set this state to true to &lt;/span&gt;
&lt;span class="nf"&gt;setIsRecording&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// Store the instance in the state&lt;/span&gt;
&lt;span class="nf"&gt;setMediaRecorder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mediaRecorder&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Start recording inmediately&lt;/span&gt;
&lt;span class="nx"&gt;mediaRecorder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we need a method to stop our recording, which will be our &lt;code&gt;stopRecording&lt;/code&gt;. Here, we will just stop our recording in case a media recorder exists.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mediaRecorder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;setIsRecording&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;mediaRecorder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are recording our audio, but we are not storing it anywhere. Let’s add a new &lt;code&gt;useEffect&lt;/code&gt; and &lt;code&gt;ref&lt;/code&gt; to accomplish this.&lt;br&gt;
We would need a new ref, and this is where our chunks of audio data will be stored.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;audioChunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Blob&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;([]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In our &lt;code&gt;useEffect&lt;/code&gt; we are going to do two main things: store those chunks in our ref, and when it stops, we are going to create a new Blob of type audio/mp3:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;useRecordVoice&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;   
   &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;audioChunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Blob&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;([]);&lt;/span&gt;

   &lt;span class="p"&gt;...&lt;/span&gt;
   &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mediaRecorder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// listen to when data is available and store it as chunks in our ref&lt;/span&gt;
            &lt;span class="nx"&gt;mediaRecorder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ondataavailable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;audioChunks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="nx"&gt;mediaRecorder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onstop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// Listen to when we stop recording audio &lt;/span&gt;
                &lt;span class="c1"&gt;// Then, convert our data to a Blob of type audio/mp3 and reset the ref&lt;/span&gt;
                &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;audioBlob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Blob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;audioChunks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;audio/mp3&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
                &lt;span class="nx"&gt;audioChunks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;mediaRecorder&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is time to wire this hook with our &lt;code&gt;AudioRecorder&lt;/code&gt; component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useRecordVoice&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/hooks/useRecordVoice&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;AudioRecorder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;isRecording&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stopRecording&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;startRecording&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRecordVoice&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleClick&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isRecording&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
           &lt;span class="nf"&gt;stopRecording&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
         &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
         &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;startRecording&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
       &lt;span class="p"&gt;}&lt;/span&gt;
     &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleClick&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`bg-blue-500 text-white px-4 py-2 rounded shadow-md hover:bg-blue-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:ring-offset-white transition duration-300 ease-in-out absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2`&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isRecording&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Stop Recording&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Start Recording&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s go to the other side of the coin, the backend!&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Setting up our Server side&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;We want to use our models on the server to keep things safe and run faster. Let’s create a new route and add a handler for it using &lt;a href="https://nextjs.org/docs/app/building-your-application/routing/route-handlers" rel="noopener noreferrer"&gt;route handlers&lt;/a&gt; from Next.js. In our App folder, let’s make an “Api” folder with the following route in it:&lt;/p&gt;

&lt;p&gt;We want to use our models on the server to keep things safe and run faster. Let’s create a new route and add a handler for it using &lt;a href="https://nextjs.org/docs/app/building-your-application/routing/route-handlers" rel="noopener noreferrer"&gt;route handlers&lt;/a&gt; from Next.js. In our App folder, let’s make an “Api” folder with the following route in it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app
 ↳api
  ↳chat
    ↳route.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our route is called ‘chat’. In the &lt;code&gt;route.ts&lt;/code&gt; file, we’ll set up our handler. Let’s start by setting up our OpenAI SDK.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;openai&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getOpenai&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// our logic will go here&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// inside a utils folder apps/utils/get-openai.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;OpenAI&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;openai&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;openai&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getOpenai&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this route, we’ll send the audio from the front end as a base64 string. Then, we’ll receive it and turn it into a Buffer object.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;audio&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;audioBuffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It’s time to use our first model. We want to turn this audio into text and use OpenAI’s Whisper Speech-To-Text model. Whisper needs an audio file to create the text. Since we have a Buffer instead of a file, we’ll use their ‘toFile’ method to convert our audio Buffer into an audio file like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;toFile&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;openai&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;audio&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;audioBuffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// FileLike object&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;audioFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;toFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;audioBuffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;audio.mp3&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;err&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error converting audio&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that we specified “mp3”. This is one of the many extensions that the Whisper model can use. You can see the full list of supported extensions here:&lt;a href="https://platform.openai.com/docs/api-reference/audio/createTranscription#audio-createtranscription-file" rel="noopener noreferrer"&gt; https://platform.openai.com/docs/api-reference/audio/createTranscription#audio-createtranscription-file&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now that our file is ready, let’s pass it to Whisper! Using our OpenAI instance, this is how we will invoke our model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;toFile&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;openai&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;openai&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getOpenai&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;audioFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;toFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;audioBuffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;audio.mp3&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transcription&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transcriptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="c1"&gt;// here we specify the model&lt;/span&gt;
            &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;whisper-1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="c1"&gt;// our audio file&lt;/span&gt;
            &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;audioFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s it! Now, we can move on to the next step: using Llama 3.1 to interpret this text and give us an answer. We’ll use two methods for this. First, we’ll use ‘ollama’ from the ‘ollama-ai-provider’ package, which lets us use this model with our locally running Ollama. Then, we’ll use ‘generateText’ from the Vercel AI SDK to generate the text.&lt;br&gt;
Side note: To make our Ollama run locally, we need to write the following command in the terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ollama serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;toFile&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;openai&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// new imports&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ollama&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ollama-ai-provider&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;generateText&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ai&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;openai&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getOpenai&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;audioFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;toFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;audioBuffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;audio.mp3&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transcription&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transcriptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;whisper-1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;audioFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;generateText&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="c1"&gt;// we specify our model running locally in the background&lt;/span&gt;
            &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;ollama&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;llama3.1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="c1"&gt;// we can set initial instructions to our model&lt;/span&gt;
            &lt;span class="na"&gt;system&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;You know a lot about video games&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="c1"&gt;// the text we want the model to interpret&lt;/span&gt;
            &lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;transcription&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, we have our last model: TTS from OpenAI. We want to reply to our user with audio, so this model will be really helpful. It will turn our text into speech:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;toFile&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;openai&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// new imports&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ollama&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ollama-ai-provider&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;generateText&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ai&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;openai&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getOpenai&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;audioFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;toFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;audioBuffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;audio.mp3&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transcription&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transcriptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;whisper-1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;audioFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;generateText&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;ollama&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;llama3.1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="na"&gt;system&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;You know a lot about video games&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;transcription&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;voiceResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;speech&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="c1"&gt;// Specify here our tts model&lt;/span&gt;
            &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tts-1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="c1"&gt;// we pass in our response&lt;/span&gt;
            &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="c1"&gt;// We can choose a variety of different voices&lt;/span&gt;
            &lt;span class="c1"&gt;// I chose 'onyx' but you can pick from this list: &amp;lt;https://platform.openai.com/docs/guides/text-to-speech/quickstart&amp;gt;&lt;/span&gt;
            &lt;span class="na"&gt;voice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;onyx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The TTS model will turn our response into an audio file. We want to stream this audio back to the user like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;toFile&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;openai&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getOpenai&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/utils/getOpenai&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ollama&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ollama-ai-provider&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;generateText&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ai&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;openai&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getOpenai&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;audio&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;audioBuffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;audioFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;toFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;audioBuffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;audio.mp3&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transcription&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transcriptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;whisper-1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;audioFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;generateText&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;ollama&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;llama3.1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="na"&gt;system&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;You know a lot about video games&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;transcription&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;voiceResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;speech&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tts-1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;voice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;onyx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="c1"&gt;// stream back our audio&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;voiceResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                 &lt;span class="c1"&gt;// we specify the content type         &lt;/span&gt;
                &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;audio/mpeg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="c1"&gt;// we indicate that this is going to be streamed in chunks of data&lt;/span&gt;
                &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Transfer-Encoding&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;chunked&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;err&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error converting audio&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that’s all the whole backend code! Now, back to the frontend to finish wiring everything up.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Putting It All Together&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;In our &lt;code&gt;useRecordVoice.tsx&lt;/code&gt; hook, let's create a new method that will call our API endpoint. This method will also take the response back and play to the user the audio that we are streaming from the backend.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/hooks/useRecordVoice.tsx&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;useRecordVoice&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="c1"&gt;// new state to track when our server is loading the response for us&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setLoading&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;audioBlob&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Blob&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// We transform our audio to base64 to send it to the endpoint&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;audioBase64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;transformBlobToBase64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;audioBlob&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;setLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="c1"&gt;// Calling out "chat" endpoint&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/chat&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="c1"&gt;// Sending our base64 audio here&lt;/span&gt;
                &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;audioBase64&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
                &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;

            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error getting response&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
         &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;setLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mediaRecorder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;mediaRecorder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ondataavailable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;audioChunks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;};&lt;/span&gt;

            &lt;span class="nx"&gt;mediaRecorder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onstop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;audioBlob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Blob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;audioChunks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;audio/mp3&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;});&lt;/span&gt;
                &lt;span class="c1"&gt;// we call our method here&lt;/span&gt;
                &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;getResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;audioBlob&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="nx"&gt;audioChunks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
            &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;mediaRecorder&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="p"&gt;...&lt;/span&gt;

&lt;span class="c1"&gt;// app/utils/transform-blob-to-base64.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;transformBlobToBase64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Blob&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FileReader&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onloadend&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onerror&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readAsDataURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great! Now that we’re getting our streamed response, we need to handle it and play the audio back to the user. We’ll use the AudioContext API for this. This API allows us to store the audio, decode it and play it to the user once it’s ready:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;audioBlob&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Blob&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;audioBase64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;transformBlobToBase64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;audioBlob&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;setLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/chat&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;audioBase64&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
            &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error getting response&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Create an instance of AudioContext&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;audioContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AudioContext&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Create a reader to read the streaming response&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;getReader&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error getting response&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Create a buffer source to store the audio&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;audioContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createBufferSource&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Array to hold the audio chunks received from the backend&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;audioChunks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

        &lt;span class="c1"&gt;// Flag to check if the audio streaming has finished&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;isDataStreamed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isDataStreamed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Start reading the data&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;done&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

            &lt;span class="c1"&gt;// If true, the stream has finished&lt;/span&gt;
            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;isDataStreamed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="c1"&gt;// Add each data chunk to our list of audio chunks&lt;/span&gt;
            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;audioChunks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Merge all buffer chunks into a single Uint8Array&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;audioBuffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nx"&gt;audioChunks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
                &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Decode the audio data and store it in our source buffer&lt;/span&gt;
        &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;audioContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decodeAudioData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nx"&gt;audioBuffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Connect the source to the audio output (speakers or headphones)&lt;/span&gt;
        &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;audioContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Start playing the audio&lt;/span&gt;
        &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;setLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;...&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;startRecording&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;stopRecording&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;isRecording&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// Return the loading state&lt;/span&gt;
    &lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's it! Now the user should hear the audio response on their device. To wrap things up, let's make our app a bit nicer by adding a little loading indicator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/components/audio-recorder.tsx&lt;/span&gt;

&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useRecordVoice&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/hooks/useRecordVoice&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;AudioRecorder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;isRecording&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stopRecording&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;startRecording&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;loading&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="nf"&gt;useRecordVoice&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleClick&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isRecording&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;stopRecording&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;startRecording&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// New condition&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Loading&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt;
                &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleClick&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`bg-blue-500 text-white px-4 py-2 rounded shadow-md hover:bg-blue-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:ring-offset-white transition duration-300 ease-in-out absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2`&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isRecording&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Stop Recording&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Start Recording&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Conclusion&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;In this blog post, we saw how combining multiple AI models can help us achieve our goals. We learned to run AI models like Llama 3.1 locally and use them in our Next.js app. We also discovered how to send audio to these models and stream back a response, playing the audio back to the user.&lt;/p&gt;

&lt;p&gt;This is just one of many ways you can use AI—the possibilities are endless. AI models are amazing tools that let us create things that were once hard to achieve with such quality. Thanks for reading; now, it’s your turn to build something amazing with AI!&lt;/p&gt;

&lt;p&gt;You can find the complete demo on GitHub: &lt;a href="https://github.com/thisdot/blog-demos/tree/main/20240919-ai-assistant-with-whisper-tts-and-ollama-using-nextjs" rel="noopener noreferrer"&gt;AI Assistant with Whisper TTS and Ollama using Next.js&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>nextjs</category>
      <category>javascript</category>
    </item>
    <item>
      <title>How to Run End-to-End Tests on Vercel Preview Deployments</title>
      <dc:creator>This Dot Media</dc:creator>
      <pubDate>Mon, 23 Sep 2024 08:18:34 +0000</pubDate>
      <link>https://dev.to/thisdotmedia_staff/how-to-run-end-to-end-tests-on-vercel-preview-deployments-5e65</link>
      <guid>https://dev.to/thisdotmedia_staff/how-to-run-end-to-end-tests-on-vercel-preview-deployments-5e65</guid>
      <description>&lt;h1&gt;
  
  
  How to Run End-to-End Tests on Vercel Preview Deployments
&lt;/h1&gt;

&lt;p&gt;End-to-end (E2E) tests are an &lt;a href="https://www.thisdot.co/blog/e2e-testing-basics-with-playwright" rel="noopener noreferrer"&gt;essential part of modern web development&lt;/a&gt;. They simulate real user interactions and ensure your application works as expected in a live environment. When you're working with platforms like Vercel, which provide seamless deployment experiences, running E2E tests on preview deployments makes sense to catch any issues before merging your changes into the main branch.&lt;/p&gt;

&lt;p&gt;However, running E2E tests on preview deployments can be challenging as if the deployment isn’t ready when the tests start running, you’ll get errors when they should have succeeded.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Challenge: Timing Your E2E Tests
&lt;/h2&gt;

&lt;p&gt;Imagine you're working on a feature and pushing your changes to a branch. Vercel starts deploying the preview, and once that's done, you want to run your E2E tests automatically. But if the deployment isn't ready when the tests start, they might fail, and you'll be left wondering if the issue is with your code or the deployment process.&lt;/p&gt;

&lt;p&gt;Github Actions allow us to leverage a combination of actions that work together to ensure that your E2E tests only start once the Vercel deployment is ready.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: Combining GitHub Actions for Seamless Testing
&lt;/h2&gt;

&lt;p&gt;To solve this problem, we can use two GitHub Actions in tandem:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/zentered/vercel-preview-url" rel="noopener noreferrer"&gt;vercel-preview-url&lt;/a&gt;: This action retrieves the preview URL of the Vercel deployment. It runs on push and pull request events, ensuring you get the exact URL of your deployment, which can then be used for further tests or other integrations.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/UnlyEd/github-action-await-vercel" rel="noopener noreferrer"&gt;github-action-await-vercel&lt;/a&gt;: This action waits until the Vercel deployment is marked as "READY". It continuously checks the deployment status, ensuring your tests only run once the deployment is fully prepared.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;e2e-tests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# Step 1: Retrieve the Vercel Preview URL&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;vercel-preview-url&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;zentered/vercel-preview-url@v1.1.9&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.event_name != 'pull_request'&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;vercel_preview_url&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;VERCEL_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.VERCEL_TOKEN }}&lt;/span&gt;
          &lt;span class="na"&gt;GITHUB_REF&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;main"&lt;/span&gt;
          &lt;span class="na"&gt;GITHUB_REPOSITORY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your-repo/your-project"&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;vercel_project_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.VERCEL_PROJECT_ID }}&lt;/span&gt;

      &lt;span class="c1"&gt;# Step 2: Wait for the Vercel deployment to be ready&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;UnlyEd/github-action-await-vercel@v1&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;await-vercel&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.event_name != 'pull_request'&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;VERCEL_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.VERCEL_TOKEN }}&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;deployment-url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.vercel_preview_url.outputs.preview_url }}&lt;/span&gt;
          &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;420&lt;/span&gt;
          &lt;span class="na"&gt;poll-interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;15&lt;/span&gt;

      &lt;span class="c1"&gt;# Step 3: Run your end-to-end tests&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run E2E Tests&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run test:e2e&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Breaking Down the Workflow
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Fetch the Preview URL&lt;/strong&gt;: The &lt;code&gt;vercel-preview-url&lt;/code&gt; action first retrieves the URL of the Vercel deployment. This URL is unique to the deployment, and we need to know it to run our E2E tests against the correct environment. This step ensures that we have the right URL before proceeding to the next steps.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Await Deployment Readiness&lt;/strong&gt;: The &lt;code&gt;github-action-await-vercel&lt;/code&gt; action waits for the deployment to be fully ready. It checks the deployment status at intervals (e.g., every 15 seconds) and only proceeds once the deployment is marked as "READY." This step ensures that your E2E tests don't run prematurely, preventing false failures.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Run E2E Tests&lt;/strong&gt;: Once the deployment is ready, your E2E tests are executed against the live preview. By this point, you can be confident that the environment is fully set up, and any failures will likely be due to actual issues in the code rather than deployment timing.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why This Workflow Matters
&lt;/h2&gt;

&lt;p&gt;This workflow is essential for teams that rely on automated testing as part of their CI/CD pipelines. By waiting for the Vercel deployment to be fully ready, you minimize the risk of flaky tests that fail due to timing issues rather than code problems. This approach saves time and effort, as developers can focus on fixing real bugs rather than troubleshooting deployment issues.&lt;/p&gt;

&lt;p&gt;Moreover, this setup is flexible and can be adapted to various project needs. Whether you're running tests on a simple static site or a complex application, ensuring that your deployment is ready before running tests can dramatically improve the reliability of your CI/CD pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In modern web development, ensuring the reliability of your CI/CD pipeline is crucial. By leveraging GitHub Actions like &lt;code&gt;vercel-preview-url&lt;/code&gt; and &lt;code&gt;github-action-await-vercel&lt;/code&gt;, you can confidently run E2E tests on Vercel preview deployments.&lt;/p&gt;

&lt;p&gt;However, remember that the Vercel deployment can take some time to deploy, especially for larger projects, so use this approach wisely and ensure you only run it when necessary.&lt;/p&gt;

</description>
      <category>infrastructure</category>
      <category>vercel</category>
      <category>playwright</category>
      <category>cypress</category>
    </item>
    <item>
      <title>How to Truncate Strings Easily with CSS</title>
      <dc:creator>This Dot Media</dc:creator>
      <pubDate>Tue, 17 Sep 2024 11:29:41 +0000</pubDate>
      <link>https://dev.to/thisdotmedia_staff/how-to-truncate-strings-easily-with-css-5d66</link>
      <guid>https://dev.to/thisdotmedia_staff/how-to-truncate-strings-easily-with-css-5d66</guid>
      <description>&lt;p&gt;You'll often need to truncate text when working with user interfaces, especially when displaying content within a limited space. CSS provides a straightforward way to handle this scenario, ensuring that long text strings are cut off gracefully without affecting the overall layout.&lt;/p&gt;

&lt;h2&gt;
  
  
  CSS Truncation Techniques
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Single-Line Truncation
&lt;/h3&gt;

&lt;p&gt;If you want to truncate a single line of text, CSS provides a simple solution. The key properties to use here are overflow, white-space, and text-overflow.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.truncate&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;white-space&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;nowrap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* Prevent the text from wrapping to the next line */&lt;/span&gt;
  &lt;span class="nl"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* Ensure content is clipped within the container */&lt;/span&gt;
  &lt;span class="nl"&gt;text-overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ellipsis&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* Add ellipsis (…) when the text is truncated */&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;200px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* Adjust the width as needed */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Explanation of properties:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;white-space: nowrap&lt;/code&gt;: This ensures the text stays on a single line, preventing wrapping.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;overflow: hidden&lt;/code&gt;: This hides any content that overflows the container.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;text-overflow: ellipsis&lt;/code&gt;: This adds the ellipsis (…) at the end of the truncated text.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Multi-Line Truncation
&lt;/h3&gt;

&lt;p&gt;While truncating a single line of text is common, sometimes you may want to display multiple lines but still cut off the text when it exceeds a certain number of lines. You can use a combination of CSS properties such as &lt;code&gt;-webkit-line-clamp&lt;/code&gt; along with the display and overflow properties.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.multi-line-truncate&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;-webkit-box&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;-webkit-line-clamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* Limit to 3 lines */&lt;/span&gt;
  &lt;span class="nl"&gt;-webkit-box-orient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;vertical&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;text-overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ellipsis&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Live example:&lt;/p&gt;

&lt;h4&gt;
  
  
  Explanation of properties:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;display: -webkit-box&lt;/code&gt;: This is a legacy flexbox-like display property that works with the -webkit-line-clamp property.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-webkit-line-clamp&lt;/code&gt;: Specifies the number of lines to show before truncating.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-webkit-box-orient: vertical&lt;/code&gt;: Ensures the box is laid out vertically for proper multi-line truncation.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;overflow: hidden&lt;/code&gt;: Prevents content overflow.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;text-overflow: ellipsis&lt;/code&gt;: Adds an ellipsis after the truncated content.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Use CSS for Text Truncation Over JavaScript Techniques?
&lt;/h2&gt;

&lt;p&gt;While it’s possible to truncate text using JavaScript, CSS is often a better choice for this task for several reasons. Let's explore why CSS-based truncation techniques are generally preferred over JavaScript.&lt;/p&gt;

&lt;h3&gt;
  
  
  Performance Efficiency
&lt;/h3&gt;

&lt;p&gt;CSS operates directly within the browser's layout engine, meaning it doesn’t require additional processing or event handling as JavaScript does. When using JavaScript to truncate text, the script needs to run on page load (or after DOM manipulation), and sometimes, it needs to listen for events such as window resizing to adjust truncation. This can introduce unnecessary overhead, especially in complex or resource-constrained environments like mobile devices.&lt;/p&gt;

&lt;p&gt;CSS, on the other hand, is declarative. Once applied, it allows the browser to handle text rendering without any further execution or processing. This leads to faster load times and a smoother user experience.&lt;/p&gt;

&lt;h3&gt;
  
  
  Simplicity and Maintainability
&lt;/h3&gt;

&lt;p&gt;CSS solutions are much simpler to implement and maintain than their JavaScript counterparts. All it takes is a few lines of CSS to implement truncation. In contrast, a JavaScript solution would require you to write and maintain a function that manually trims strings, inserts ellipses, and re-adjusts the text whenever the window is resized.&lt;/p&gt;

&lt;p&gt;Here's the JavaScript Truncation Example to compare the complexity:&lt;/p&gt;

&lt;p&gt;JavaScript Truncation Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;truncateText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;maxLength&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;originalText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;originalText&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;maxLength&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;originalText&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;maxLength&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.truncate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;truncateText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At the example above, we truncated the text to 50 characters which may be 1 line on large screens and 6 lines on mobile and in that case we will need to add more code to truncate it responsively. As you can see, the CSS solution we used earlier is more concise and readable, whereas the JavaScript version is more verbose and requires managing the string length manually.&lt;/p&gt;

&lt;h3&gt;
  
  
  Responsiveness Without Extra Code
&lt;/h3&gt;

&lt;p&gt;With CSS, truncation can adapt automatically to different screen sizes and layouts. You can use relative units (like percentages or vw/vh), media queries, or flexbox/grid properties to ensure the text truncates appropriately in various contexts.&lt;/p&gt;

&lt;p&gt;If you were using JavaScript, you’d need to write additional logic to detect changes in the viewport size and update the truncation manually. This would likely involve adding event listeners for window resize events, which can degrade performance and lead to more complex code.&lt;/p&gt;

&lt;p&gt;CSS Example for Responsive Truncation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.truncate&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;white-space&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;nowrap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;text-overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ellipsis&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* Automatically adjusts based on screen size */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To achieve this in JavaScript, you’d need to add more code to handle the width adjustments dynamically, making it more complex to maintain and troubleshoot.&lt;/p&gt;

&lt;h3&gt;
  
  
  Separation of Concerns
&lt;/h3&gt;

&lt;p&gt;CSS handles the presentation layer of your website, while JavaScript should focus on dynamic functionality or data manipulation. By keeping truncation logic within your CSS, you're adhering to the principle of separation of concerns, where each layer of your web application has a clear, well-defined role.&lt;/p&gt;

&lt;p&gt;Using JavaScript for visual tasks like truncation mixes these concerns, making your codebase harder to maintain, debug, and scale. CSS is purpose-built for layout and visual control, and truncation is naturally a part of that domain.&lt;/p&gt;

&lt;h3&gt;
  
  
  Browser Support and Cross-Browser Consistency
&lt;/h3&gt;

&lt;p&gt;Modern CSS properties like &lt;code&gt;text-overflow&lt;/code&gt; and &lt;code&gt;-webkit-line-clamp&lt;/code&gt; are widely supported across all major browsers. This means that CSS solutions for truncation are generally consistent and reliable. JavaScript solutions, on the other hand, may behave differently depending on the browser environment and require additional testing and handling for cross-browser compatibility.&lt;/p&gt;

&lt;p&gt;While older browsers may not support specific CSS truncation techniques (e.g., multi-line truncation), fallback options (like single-line truncation) can be easily managed through CSS alone. With JavaScript, more complex logic might be required to handle such situations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reduced Risk of Layout Shifting
&lt;/h3&gt;

&lt;p&gt;JavaScript-based text truncation risks causing layout shifting, especially during initial page loads or window resizes. The browser may need to recalculate the layout multiple times, leading to content flashing or jumpy behavior as text truncation is applied.&lt;/p&gt;

&lt;p&gt;CSS-based truncation is applied as part of the browser’s natural rendering flow, eliminating this risk and ensuring a smoother experience for the user.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;CSS is the optimal solution for truncating text in most cases due to its simplicity, efficiency, and responsiveness. It leverages the power of the browser’s rendering engine, avoids the overhead of JavaScript, and keeps your code clean and maintainable. While JavaScript truncation has its use cases, CSS should always be your go-to solution for truncating strings, especially in static or predictable layouts. If you like this post, check out the other CSS posts on our blog!&lt;/p&gt;

</description>
      <category>css</category>
    </item>
    <item>
      <title>Quality of Life With Static Site Generator</title>
      <dc:creator>This Dot Media</dc:creator>
      <pubDate>Thu, 25 Mar 2021 16:00:51 +0000</pubDate>
      <link>https://dev.to/thisdotmedia_staff/quality-of-life-with-static-site-generator-p5n</link>
      <guid>https://dev.to/thisdotmedia_staff/quality-of-life-with-static-site-generator-p5n</guid>
      <description>&lt;h3&gt;
  
  
  Intro
&lt;/h3&gt;

&lt;p&gt;We're building a new marketing site here at Fancy Pants Inc. A totally new greenfield project within an existing organization is fun and exciting, but offers a unique set of challenges. Let's outline some of the most common scenarios and see how to tackle them head on.&lt;/p&gt;

&lt;p&gt;For this, we're going to start with the idea of sort of a team meeting, a kind of round table discussion. We have a new site that has static content and maybe some forms.&lt;/p&gt;

&lt;p&gt;Some of the requirements from our collective bosses: everyone needs to be able contribute, but only the director or those bestowed with the power can publish content. The other big need is to be able to see changes, updates, new material on the site and pass around an internal url to gather feedback before it ever goes live. There are a few more catches that will come up as we scaffold out this exciting new project.&lt;/p&gt;

&lt;p&gt;Like all good sized projects, we've made some choices up front and want to tease out if they will meet our needs before writing up the work and getting started. Based on a few of the requirements outlined earlier, we'll be using Sanity content platform. Everything will hopefully go there from importing existing content, rich media, and even copy for the landing page. For our development choice and deployment, we've chosen Eleventy paired with Netlify for deploy. The idea is to make the developer experience vastly superior to our current system: to make deployment and preview of the project as seamless and transparent as possible. Let's get started.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where do we start
&lt;/h3&gt;

&lt;p&gt;When we talk about everyone in the company being able to 'contribute', what exactly do we mean? Everyone should be able to make a blog post about the latest and greatest? That sounds fine. What about changing the copy on the homepage or updating the Terms and Service fine print buried in the footer? That's a little less clear now, right? These questions raise important technical decisions about were to apply friction. We often talk about 'reducing friction' as a positive thing. We want to reduce the friction of custom checkout so that more people will buy our product or goods. There are cases, however, were we want to &lt;em&gt;increase&lt;/em&gt; friction. We want to ensure that updating legal copy or replacing the hero image on the home page is not just a one click away. There needs to be some amount of process or intentional friction to be a sort of checks and balance.&lt;/p&gt;

&lt;p&gt;With that in mind we'll create some simple roles within Sanity 'creator' and 'moderator'. Sanity has a great sample project and the CMS studio itself is React components all the way down, so we can customize it to great extent. We're not entirely sure what the fine grain controls will look like, so let's start broad and then we can narrow down those controls overtime.&lt;/p&gt;

&lt;p&gt;For now, creators can create any new content that includes page updates and posts, but only moderators can actually publish that content from its draft version to production. With the content up and running, the actual site will be built leveraging &lt;a href="https://www.11ty.dev"&gt;Eleventy Static Site Generator&lt;/a&gt;, and the main reason is for its simplicity and flexibility.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In Eleventy, data is merged from multiple different sources before the template is rendered. The data is merged in what Eleventy calls the Data Cascade.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;While we are only getting data from Sanity to start with, we have multiple other data sources that will need to be added. Even if that list were zero, a growing business and product will need to evolve and take on new responsibilities in the future. You'll thank your past self when you need to add Shopify or some other datasource in addition to the CMS.&lt;/p&gt;

&lt;h3&gt;
  
  
  We don't needs gates
&lt;/h3&gt;

&lt;p&gt;Just when we are starting to get some momentum in the project, a hiccup. As we onboard different departments into adding content, there are several requests for a more fine grain approach to content permissions. Our "anyone can create" approach isn't going to fly. After some research, it's decided that Contentful has the sort of finer permissions built in that teams are looking for. Great, but what do we do with our implementation now?&lt;/p&gt;

&lt;p&gt;A suggestion is to run &lt;code&gt;git init&lt;/code&gt; a new project and just start over. We've spend a nontrivial mount of time working on templates and layouts in Eleventy, is it salvagable? The short answer is, yeah absolutely. Remember: our site doesn't care where the data comes from or how many places it comes from. We can wire-up Contentful as a data source, adjust the templates to any changes in the structure of the content (&lt;code&gt;post.body&lt;/code&gt; may now be something like &lt;code&gt;post.content&lt;/code&gt;) and then run a new build. Having this deployed on Netlify, a couple of us can work on a feature branch, deploy, and share it without slowing down the group working on the page layout, footers, or contact page!&lt;/p&gt;

&lt;h3&gt;
  
  
  Cache is still hard
&lt;/h3&gt;

&lt;p&gt;Cache continues to be awful. Humans are not built for the level complexity Goldberg machines that deploy software. We want to keep things simple, and more importantly deterministic. We don't want deployment should not be a constant consideration when creating, improving, or trying new and inventive things within the company.&lt;/p&gt;

&lt;p&gt;Our goal is whatever is merged into the main branch will be deployed to the production url. So let's follow those steps. A pull request is merged into &lt;code&gt;main&lt;/code&gt;. This kicks off a build of the app on Netlify. It runs &lt;code&gt;npx @11ty/eleventy&lt;/code&gt;, gathers up all the data sources from across a pleura of sources, and starts to build, on success Netlify points, all the incoming http requests from Fancy Pants dot com to the newly built site that our build step outputted to &lt;code&gt;_site&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;What if it fails? That might happen. Sooner or later it's going to happen. Will the customers of our fine establishment be forever disappointed with the HTTP 500 ERROR they witnessed? Nope. They won't even know. When a build fails, not if, the site continues to run the last successful build it has been. We can setup a web hook to send us a slack message, 'ops, looks like the build failed'. This is a very different message then: 'the software team broke production and we're losing money every minute it's down, everything is on fire!!!'.&lt;/p&gt;

&lt;p&gt;Those messages invoke very different feelings, don't they? When you've broken a build, gone back and fixed it, then deployed again, you likely forget about it the next day. If you've ever broken production and spent endless hours trying to put it back together again, you'll never forget it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Quality of Life
&lt;/h3&gt;

&lt;p&gt;This is very much a quality of life that a system like the JamStack provides us: it's secret sauce. Things are going to go wrong, and when they do, you have the time, space, and mental where-with-all to fix it. When it fails, and it will at some point fail, it doesn't affect the product in production. Other teams internally may not even notice. This gives everyone explicit permission to ship. If that's publishing content with some cool new media, or the latest and greatest GraphQL data collection: if it fails, it's fine. The risk is so low as to be trivial to ship and to try new things. If this all sounds too good to be true, it's not, I promise. Come join us on the moon.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;'What a waste of time. It's physically draining to do release engineering. There are other things we want to be doing. And it's not FE release engineering. ' ~ Jason Lengstorf&lt;/p&gt;

&lt;p&gt;'It's not something we worry about on our team' ~ Zach Leatherman&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I want to thank my guests on Build In Better, Jason Lengstorf with Netlify and Zach Leatherman, Open Source Author of Eleventy, for an amazing conversation and sharing their knowledge with me. This article would not be possible without their time and insight. Thank you. 👋&lt;/p&gt;

&lt;p&gt;Special Thanks to the fine folks at Fancy Pants wearables and underoos 😉&lt;/p&gt;

</description>
      <category>architecture</category>
    </item>
    <item>
      <title>Improve User Experience in Vue 3 with Suspense</title>
      <dc:creator>This Dot Media</dc:creator>
      <pubDate>Wed, 24 Mar 2021 17:08:28 +0000</pubDate>
      <link>https://dev.to/thisdotmedia_staff/improve-user-experience-in-vue-3-with-suspense-376f</link>
      <guid>https://dev.to/thisdotmedia_staff/improve-user-experience-in-vue-3-with-suspense-376f</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

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

&lt;p&gt;While we can't control internet speeds, we can plan accordingly and prevent users from experiencing a broken application. Luckily, Vue 3 provides a new way to handle situations like this, called Suspense. "Suspense" is a new built-in component in Vue that we can wrap around another component needing to perform an asynchronous action before it can render. The implementation of Suspense in Vue is very similar to &lt;a href="https://reactjs.org/docs/concurrent-mode-suspense.html"&gt;React Suspense&lt;/a&gt;. If the component inside &lt;code&gt;&amp;lt;Suspense&amp;gt;&amp;lt;/Suspense&amp;gt;&lt;/code&gt; has an async &lt;code&gt;setup()&lt;/code&gt; method, then a fallback is presented to the user until it is completed.&lt;/p&gt;

&lt;p&gt;An example of the below code can be found &lt;a href="https://codesandbox.io/s/vue-suspense-w010b"&gt;on CodeSandbox&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;&amp;lt;Suspense&amp;gt; Component&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Let's explore a basic example using Suspense. We will build a basic application that utilizes the &lt;a href="https://pokeapi.co/"&gt;Pokemon API&lt;/a&gt; to fetch a list of berries and display them in a dropdown. To start, we have two files, &lt;code&gt;App.vue&lt;/code&gt; and &lt;code&gt;Berries.vue&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Berries.vue&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;h1 class="text-3xl pb-2"&amp;gt;Select a Berry&amp;lt;/h1&amp;gt;
  &amp;lt;select v-model="selectedBerry" class="px-4 py-2 w-40 shadow"&amp;gt;
    &amp;lt;option value="" disabled&amp;gt;Select...&amp;lt;/option&amp;gt;
    &amp;lt;option
      v-for="berry in berries.results"
      :key="berry.url"
      :value="berry.url"
    &amp;gt;
      {{ berry.name }}
    &amp;lt;/option&amp;gt;
  &amp;lt;/select&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script lang="ts"&amp;gt;
import { defineComponent, ref } from "vue";
import useBerries from "../hooks/useBerries";

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

    return { berries, selectedBerry };
  },
});
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;App.vue&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;Suspense&amp;gt;
    &amp;lt;Berries /&amp;gt;

    &amp;lt;template #fallback&amp;gt;
      &amp;lt;span class="text-3xl"&amp;gt;Picking berries...&amp;lt;/span&amp;gt;
    &amp;lt;/template&amp;gt;
  &amp;lt;/Suspense&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script lang="ts"&amp;gt;
import { defineComponent } from "vue";
import Berries from "./components/Berries.vue";

export default defineComponent({
  name: "App",
  components: {
    Berries,
  },
});
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's take a look at these two files.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;Berries.vue&lt;/code&gt;, we have an async &lt;code&gt;setup()&lt;/code&gt; method which returns two values: &lt;code&gt;selectedBerry&lt;/code&gt; and &lt;code&gt;berries&lt;/code&gt;. In this example, &lt;code&gt;berries&lt;/code&gt; is created by the custom function &lt;code&gt;useBerries&lt;/code&gt;, but under the hood, it's a regular HTTP request to the Pokemon API for a list of berries. The exact implementation of the API request is not important to our example. These values are then referenced in the template as normal. If any of this looks confusing, I would recommend checking out &lt;a href="[https://labs.thisdot.co/blog](https://labs.thisdot.co/blog)"&gt;this article on using "ref" and "reactive"&lt;/a&gt; or &lt;a href="https://labs.thisdot.co/blog/vue-3-composition-api-do-you-really-need-it"&gt;this article explaining the Composition API in general&lt;/a&gt;.&lt;/p&gt;

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

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

&lt;p&gt;Below are the changes:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;BerryDetails.vue&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;template&amp;gt;This berry is {{ berryFlavor.flavor.name }}.&amp;lt;/template&amp;gt;

&amp;lt;script lang="ts"&amp;gt;
import { defineComponent } from "vue";
import useBerryFlavor from "../hooks/useBerryFlavor";

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

    return {
      berryFlavor,
    };
  },
});
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Berries.vue&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

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

      &amp;lt;template #fallback&amp;gt; Fetching berry details... &amp;lt;/template&amp;gt;
    &amp;lt;/Suspense&amp;gt;
  &amp;lt;/div&amp;gt;

&amp;lt;/template&amp;gt;

&amp;lt;script lang="ts"&amp;gt;
import { defineComponent, ref } from "vue";
import useBerries from "../hooks/useBerries";
import BerryDetails from "./BerryDetails.vue";

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

    return { berries, selectedBerry };
  },
  components: {
    BerryDetails,
  },
});
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

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

&lt;h2&gt;
  
  
  Error Handling
&lt;/h2&gt;

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

&lt;p&gt;&lt;code&gt;Berries.vue&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

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

&amp;lt;script lang="ts"&amp;gt;
import { defineComponent, ref, onErrorCaptured, watch } from "vue";
import useBerries from "../hooks/useBerries";
import BerryDetails from "./BerryDetails.vue";

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

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

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

    const berries = await useBerries();

    return { berries, selectedBerry, error };
  },
  components: {
    BerryDetails,
  },
});
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We did three things in this component:&lt;/p&gt;

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

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

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

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

&lt;p&gt;An example of the code presented above can be found &lt;a href="https://codesandbox.io/s/vue-suspense-w010b"&gt;on CodeSandbox&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This Dot Labs is a modern web consultancy focused on helping companies realize their digital transformation efforts. For expert architectural guidance, training, or consulting in React, Angular, Vue, Web Components, GraphQL, Node, Bazel, or Polymer, visit &lt;a href="https://www.thisdotlabs.com"&gt;thisdotlabs.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This Dot Media is focused on creating an inclusive and educational web for all. We keep you up to date with advancements in the modern web through events, podcasts, and free content. To learn, visit &lt;a href="https://www.thisdot.co"&gt;thisdot.co&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>vuevue3</category>
    </item>
    <item>
      <title>Vue 3 Composition API - "ref" and "reactive"</title>
      <dc:creator>This Dot Media</dc:creator>
      <pubDate>Tue, 23 Mar 2021 14:11:24 +0000</pubDate>
      <link>https://dev.to/thisdotmedia_staff/vue-3-composition-api-ref-and-reactive-c1a</link>
      <guid>https://dev.to/thisdotmedia_staff/vue-3-composition-api-ref-and-reactive-c1a</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;With the release of Vue 3, developers now have access to the Composition API, a new way to write Vue components. This API allows features to be grouped together logically, rather than having to organize your single-file components by function. Using the Composition API can lead to more readable code, and gives the developer more flexibility when developing their applications.&lt;/p&gt;

&lt;p&gt;The Composition API provides two different ways to store data locally in the component - “ref” and “reactive”. These two methods serve a similar role as the “data” function in the traditional Options API that is commonly used in Vue applications today. In this article, we will explore both of these new methods, and when to use them in your own application.&lt;/p&gt;

&lt;p&gt;If you want to read more about the Composition API, you can read &lt;a href="https://labs.thisdot.co/blog/vue-3-composition-api-do-you-really-need-it"&gt;this article by Bilal Haidar&lt;/a&gt; for an overview.&lt;/p&gt;

&lt;p&gt;You can view the code examples below in a working app on CodeSandbox by &lt;a href="https://codesandbox.io/s/ref-vs-reactive-rzf6q"&gt;clicking this link&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Options API - &lt;code&gt;data&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;If you have used Vue in the past, you have probably seen how different parts of your components are divided by function. Each single-file component has access to a number of attributes: data, computed, methods, lifecycle hooks, and so on. This is referred to as the "Options API". Below is an example application implementing local state with the Options API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;button @click="count++"&amp;gt;count is: {{ count }}&amp;lt;/button&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script lang="ts"&amp;gt;
import { defineComponent } from "vue";

export default defineComponent({
  name: "App",
  data() {
    return {
      count: 0
    };
  }
});
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Composition API - &lt;code&gt;ref&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Now let's look at the same example as above, using the Composition API. First, we'll take a look at &lt;code&gt;ref&lt;/code&gt;. &lt;a href="https://v3.vuejs.org/api/refs-api.html"&gt;From the Vue 3 documentation&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ref takes an inner value and returns a reactive and mutable ref object. The ref object has a single property .value that points to the inner value.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Below is our example code using &lt;code&gt;ref&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;button @click="count++"&amp;gt;count is: {{ count }}&amp;lt;/button&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script lang="ts"&amp;gt;
import { ref, defineComponent } from "vue";

export default defineComponent({
  name: "App",
  setup: () =&amp;gt; {
    const count = ref(0);

    return { count };
  },
});
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's take a closer look. What changed between these two examples?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Rather than using the &lt;code&gt;data&lt;/code&gt; function to add local state, we are using the &lt;code&gt;setup&lt;/code&gt; function. This new function replaces &lt;code&gt;data&lt;/code&gt;, &lt;code&gt;beforeCreate&lt;/code&gt;, and &lt;code&gt;created&lt;/code&gt;, and is the place for utilizing the Composition API.&lt;/li&gt;
&lt;li&gt;Like with &lt;code&gt;data&lt;/code&gt;, &lt;code&gt;setup&lt;/code&gt; returns an object. The contents of that object is any variable that needs to be accessible from the template. Since we want the count to be available in the template, we include it in the return object.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Before we continue to &lt;code&gt;reactive&lt;/code&gt;, we should make one more change. Let's move the click event into its own method, rather than performing the action in the template.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;button @click="increaseCount"&amp;gt;count is: {{ count }}&amp;lt;/button&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script lang="ts"&amp;gt;
import { ref, defineComponent } from "vue";

export default defineComponent({
  name: "App",
  setup: () =&amp;gt; {
    const count = ref(0);

    const increaseCount = () =&amp;gt; {
      count.value++;
    }

    return { count, increaseCount };
  },
});
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unlike in the Options API, methods are simply functions. We don't need any special syntax or Composition API methods to make them work. However, just like the &lt;code&gt;count&lt;/code&gt; variable above, we do need to return the method from the &lt;code&gt;setup&lt;/code&gt; function in order for it to be available in the template.&lt;/p&gt;

&lt;p&gt;Notice that in the template, we used &lt;code&gt;count++&lt;/code&gt; to increase the value, but in the &lt;code&gt;setup&lt;/code&gt; function, we use &lt;code&gt;count.value&lt;/code&gt;. This is because in the &lt;code&gt;setup&lt;/code&gt; function, &lt;code&gt;count&lt;/code&gt; has a type of &lt;code&gt;Ref&amp;lt;number&amp;gt;&lt;/code&gt;, where in the template the internal value is directly available.&lt;/p&gt;

&lt;h2&gt;
  
  
  Composition API - &lt;code&gt;reactive&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Now let's try out the &lt;code&gt;reactive&lt;/code&gt; method. &lt;a href="https://v3.vuejs.org/api/basic-reactivity.html#reactive"&gt;From the Vue 3 docs&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;reactive returns a reactive copy of the object. The reactive conversion is "deep"—it affects all nested properties. In the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy"&gt;ES2015 Proxy&lt;/a&gt; based implementation, the returned proxy is not equal to the original object. It is recommended to work exclusively with the reactive proxy, and avoid relying on the original object.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here is our code example using &lt;code&gt;reactive&lt;/code&gt; instead of &lt;code&gt;ref&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;button @click="increaseCount"&amp;gt;count is: {{ state.count }}&amp;lt;/button&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script lang="ts"&amp;gt;
import { reactive, defineComponent } from "vue";

export default defineComponent({
  name: "App",
  setup: () =&amp;gt; {
    const state = reactive({
      count: 0
    });

    const increaseCount = () =&amp;gt; {
      state.count++;
    }

    return { state, increaseCount };
  },
});
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Within the &lt;code&gt;setup&lt;/code&gt; function, we can use the return value of &lt;code&gt;reactive&lt;/code&gt; very similarly to how we use the &lt;code&gt;data&lt;/code&gt; function in the Options API. Because the object is deeply reactive, we can also make changes to it directly. So instead of &lt;code&gt;count.value++&lt;/code&gt;, we can simply increment the value with &lt;code&gt;state.value++&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to use &lt;code&gt;ref&lt;/code&gt; and &lt;code&gt;reactive&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;In general, &lt;code&gt;ref&lt;/code&gt; is useful for primitives (string, number, boolean, etc), and &lt;code&gt;reactive&lt;/code&gt; is useful for objects and arrays. However, there are some key points to consider:&lt;/p&gt;

&lt;p&gt;First, if you pass an object into a &lt;code&gt;ref&lt;/code&gt; function call, it will return an object that has been passed through &lt;code&gt;reactive&lt;/code&gt;. The below code works perfectly fine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;button @click="increaseCount"&amp;gt;count is: {{ state.count }}&amp;lt;/button&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script lang="ts"&amp;gt;
import { ref, defineComponent } from "vue";

export default defineComponent({
  name: "App",
  setup: () =&amp;gt; {
    const state = ref({
      count: 0
    });

    const increaseCount = () =&amp;gt; {
      state.value.count++;
    }

    return { state, increaseCount };
  },
});
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Second, while the object returned from &lt;code&gt;reactive&lt;/code&gt; is deeply reactive (setting any value to the object will trigger a reaction in Vue), you can still accidentally make the values non-reactive. If you try to destructure or spread the values of the object, for example, they will no longer be reactive. The below code does &lt;em&gt;not&lt;/em&gt; work as you might expect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;button @click="increaseCount"&amp;gt;count is: {{ count }}&amp;lt;/button&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script lang="ts"&amp;gt;
import { reactive, defineComponent } from "vue";

export default defineComponent({
  name: "App",
  setup: () =&amp;gt; {
    const state = reactive({
      count: 0
    });

    const increaseCount = () =&amp;gt; {
      state.count++;
    }

    return { ...state, increaseCount };
  },
});
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to do something like this, Vue 3 has you covered. There are a number of functions to convert between Refs and their values. In this case, we will use the &lt;code&gt;toRefs&lt;/code&gt; function. &lt;a href="https://v3.vuejs.org/api/refs-api.html#torefs"&gt;From the docs:&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Converts a reactive object to a plain object where each property of the resulting object is a ref pointing to the corresponding property of the original object.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If we update our above code example to spread the result of &lt;code&gt;toRefs&lt;/code&gt;, then everything works as we'd expect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;button @click="increaseCount"&amp;gt;count is: {{ count }}&amp;lt;/button&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script lang="ts"&amp;gt;
import { toRefs, reactive, defineComponent } from "vue";

export default defineComponent({
  name: "App",
  setup: () =&amp;gt; {
    const state = reactive({
      count: 0
    });

    const increaseCount = () =&amp;gt; {
      state.count++;
    }

    return { ...toRefs(state), increaseCount };
  },
});
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are other ways to combine these two functions, both with setting a value in a &lt;code&gt;reactive&lt;/code&gt; object to its own variable with &lt;code&gt;toRef&lt;/code&gt; or adding a &lt;code&gt;ref&lt;/code&gt; to an object directly. &lt;code&gt;ref&lt;/code&gt; and &lt;code&gt;reactive&lt;/code&gt; are two parts of the solution, and you will find yourself reaching for each of them as needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The Vue 3 Composition API provides a lot of benefit to developers. By allowing features to be grouped together, rather than functions, developers can focus on what they are building, and less on fitting their code into a predetermined structure.&lt;/p&gt;

&lt;p&gt;By leveraging the &lt;code&gt;ref&lt;/code&gt; and &lt;code&gt;reactive&lt;/code&gt; functions to maintain local state, developers have new tools to write more maintainable and readable code. These methods work well together, rather than one or the other, each solving a different problem.&lt;/p&gt;

&lt;p&gt;Remember that the Composition API is optional! The existing Options API is not going anywhere, and will continue to work as expected. I would encourage you to try out the Composition API, and see how these new methods can improve your own workflow and applications.&lt;/p&gt;

&lt;p&gt;To view the above examples in a working application, &lt;a href="https://codesandbox.io/s/ref-vs-reactive-rzf6q"&gt;click here&lt;/a&gt; to view them in CodeSandbox.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This Dot Labs is a modern web consultancy focused on helping companies realize their digital transformation efforts. For expert architectural guidance, training, or consulting in React, Angular, Vue, Web Components, GraphQL, Node, Bazel, or Polymer, visit &lt;a href="https://www.thisdotlabs.com"&gt;thisdotlabs.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This Dot Media is focused on creating an inclusive and educational web for all. We keep you up to date with advancements in the modern web through events, podcasts, and free content. To learn, visit &lt;a href="https://www.thisdot.co"&gt;thisdot.co&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>vue3vue</category>
    </item>
    <item>
      <title>Nuxt.js for Complete Beginners</title>
      <dc:creator>This Dot Media</dc:creator>
      <pubDate>Mon, 22 Mar 2021 15:47:38 +0000</pubDate>
      <link>https://dev.to/thisdotmedia_staff/nuxt-js-for-complete-beginners-37kp</link>
      <guid>https://dev.to/thisdotmedia_staff/nuxt-js-for-complete-beginners-37kp</guid>
      <description>&lt;p&gt;The Vuejs Amsterdam online conference was held at the end of February in 2021. It brought together Vue.js enthusiasts and community members from around the world. Many interesting topics were presented and covered. The focus, of course, was on Vue.js 3. In addition, the creators of Nuxt.js had the opportunity to showcase it from the current standpoint in development, to their intentions for it down the track. They even demonstrated a &lt;a href="https://nuxtjs.slides.com/atinux/nuxt-3-in-action" rel="noopener noreferrer"&gt;pre-alpha version&lt;/a&gt; of Nuxt.js 3, that’s based on Vue.js 3.&lt;/p&gt;

&lt;p&gt;This article will start by briefly covering general Nuxt.js concepts, creating a new Nuxt.js app using the &lt;strong&gt;create-nuxt-app&lt;/strong&gt; CLI, and finally, going through the different files and folders that Nuxt.js auto-generates for us.&lt;/p&gt;

&lt;p&gt;Let’s start!&lt;/p&gt;

&lt;h2&gt;
  
  
  Nuxt.js Concepts
&lt;/h2&gt;

&lt;p&gt;Nuxt.js is a web development framework that builds on top of the Vue.js framework. It allows you to use your Vue.js skills to build a more confident, structured, SEO friendly website purely in Vue.js.&lt;/p&gt;

&lt;p&gt;Remember when you had to mess with the &lt;a href="https://ssr.vuejs.org/" rel="noopener noreferrer"&gt;Vue.js SSR&lt;/a&gt; module and the &lt;a href="https://vue-meta.nuxtjs.org/" rel="noopener noreferrer"&gt;Vue Meta&lt;/a&gt; module to build a SEO friendly website? On top of that, you had to install and use &lt;a href="https://vuex.vuejs.org/" rel="noopener noreferrer"&gt;Vuex&lt;/a&gt; and &lt;a href="https://router.vuejs.org/" rel="noopener noreferrer"&gt;Vue Router&lt;/a&gt; too!&lt;/p&gt;

&lt;p&gt;Well, Nuxt.js takes care of that! No more chaotic setup and scaffolding to start a new Vue.js app. With the help of the &lt;strong&gt;&lt;a href="https://nuxtjs.org/docs/2.x/get-started/installation#using-create-nuxt-app" rel="noopener noreferrer"&gt;create-nuxt-app&lt;/a&gt;&lt;/strong&gt; CLI, you can scaffold a Nuxt.js app in no time.&lt;/p&gt;

&lt;p&gt;What’s remarkable about Nuxt.js is its capacity to enforce convention over configuration. This means, you write less configuration files by sticking to a specific directory structure that makes Nuxt.js happy and saves you a ton of time!&lt;/p&gt;

&lt;h3&gt;
  
  
  Apps supported by Nuxt.js
&lt;/h3&gt;

&lt;p&gt;Nuxt.js supports building a variety of web apps, including the following:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Server-Side Rendering (SSR)&lt;/strong&gt;SSR apps are also known as Universal Apps. The app gets rendered on the server-side before it is sent to the client-side, and is displayed in the browser. This is the best option when working on an SEO friendly website written in Vue.js.&lt;/p&gt;

&lt;p&gt;You can read the full documentation for the SSR apps here: &lt;a href="https://nuxtjs.org/docs/2.x/concepts/server-side-rendering" rel="noopener noreferrer"&gt;SSR Apps&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Single Page Apps (SPA)&lt;/strong&gt;This is what you’ve been doing so far with Vue.js. The app is compiled into a few JS and CSS files. When the user requests the app, the files are downloaded to the client-side, and the Vue.js engine takes over rendering and displaying the app.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Static Site Generation (SSG)&lt;/strong&gt;Nuxt.js can pre-render your app at build time. This means the entire app will be converted to simple static HTML files that can be hosted and served over a Content Delivery Network (CDN). The SSG option makes an app a legal &lt;a href="https://jamstack.org/generators/nuxt/" rel="noopener noreferrer"&gt;JAMStack&lt;/a&gt; app.&lt;/p&gt;

&lt;p&gt;You can read the full documentation for the Static Site Generation here: &lt;a href="https://nuxtjs.org/docs/2.x/concepts/static-site-generation" rel="noopener noreferrer"&gt;Static Site Generation&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  File System Routing
&lt;/h3&gt;

&lt;p&gt;Nuxt.js automatically generates all the Vue.js Routes in your app based on the folder structure inside the &lt;em&gt;pages&lt;/em&gt; folder.&lt;/p&gt;

&lt;p&gt;For example, consider having this folder structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pages/
  --| user/
  -----| index.vue
  --| index.vue
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nuxt.js automatically generates the following route configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;router: {
  routes: [
    {
      name: 'index',
      path: '/',
      component: 'pages/index.vue'
    },
    {
      name: 'user',
      path: '/user',
      component: 'pages/user/index.vue'
    },
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can read about &lt;em&gt;File System Routing&lt;/em&gt; here: &lt;a href="https://nuxtjs.org/docs/2.x/features/file-system-routing" rel="noopener noreferrer"&gt;File System Routing&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Data Fetching
&lt;/h3&gt;

&lt;p&gt;Inside a Nuxt.js app, you can still use the old techniques you kmow when developing Vue.js apps. However, we have a new player here! Server-side rendering. Nuxt.js provides a new set of data-hooks that you can implement so that Nuxt.js can prefetch data when generating the app at the server-side.&lt;/p&gt;

&lt;p&gt;Here are two data-hooks offered by Nuxt.js:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;fetch() hook&lt;/strong&gt;This hook was introduced with Nuxt.js 2.12+ release. It can be used inside Vue.js components stored in the &lt;em&gt;pages&lt;/em&gt; folder and &lt;em&gt;components&lt;/em&gt; folder.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;asyncData() hook&lt;/strong&gt;This hook has been around for a while now, and can be used &lt;strong&gt;only&lt;/strong&gt; inside the Vue.js components stored in the &lt;em&gt;pages&lt;/em&gt; folder.&lt;/p&gt;

&lt;p&gt;You can read more about &lt;em&gt;Data Fetching&lt;/em&gt; hooks here: &lt;a href="https://nuxtjs.org/docs/2.x/features/data-fetching" rel="noopener noreferrer"&gt;Data Fetching&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Meta Tags and SEO
&lt;/h3&gt;

&lt;p&gt;Nuxt.js makes it so intuitive to add SEO support to your SSR app. You can add Metadata to your app at two different levels:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Globally using the &lt;em&gt;nuxt.config.js&lt;/em&gt; file&lt;/li&gt;
&lt;li&gt;Locally inside a Nuxt.js Page&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can read about &lt;em&gt;Meta Tags and SEO&lt;/em&gt; hooks here: &lt;a href="https://nuxtjs.org/docs/2.x/features/meta-tags-seo" rel="noopener noreferrer"&gt;Meta Tags and SEO&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create our first Nuxt.js app
&lt;/h2&gt;

&lt;p&gt;Let’s use the &lt;strong&gt;create-nuxt-app&lt;/strong&gt; CLI, and create our first Nuxt.js app!&lt;/p&gt;

&lt;p&gt;Before you start, you want to make sure you have all the &lt;a href="https://nuxtjs.org/docs/2.x/get-started/installation#prerequisites" rel="noopener noreferrer"&gt;perquisites&lt;/a&gt; required before you can install and run the CLI.&lt;/p&gt;

&lt;p&gt;For this article, I am going to use &lt;a href="https://www.npmjs.com/package/npx" rel="noopener noreferrer"&gt;npx&lt;/a&gt;. However, you can also use &lt;a href="https://www.npmjs.com/" rel="noopener noreferrer"&gt;npm&lt;/a&gt;, or feel free to use &lt;a href="https://yarnpkg.com/" rel="noopener noreferrer"&gt;yarn&lt;/a&gt;.&lt;/p&gt;

&lt;h5&gt;
  
  
  Step 0
&lt;/h5&gt;

&lt;p&gt;Start by running the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx create-nuxt-app my-first-nuxt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command uses the create-nuxt-app tool, and specifies the name of the project- in this case &lt;em&gt;my-first-nuxt-app&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The CLI will ask you a few questions that are important to scaffold the new Nuxt.js app based on your own preferences and decisions. Here’s what to expect.&lt;/p&gt;

&lt;h5&gt;
  
  
  Step 1
&lt;/h5&gt;

&lt;p&gt;First, let’s confirm the project name as shown in the &lt;strong&gt;Figure 1&lt;/strong&gt;. Give the app a name and hit Enter.&lt;/p&gt;

&lt;p&gt;_ &lt;strong&gt;Figure 1&lt;/strong&gt; : Specify project name_&lt;a href="https://images.ctfassets.net/zojzzdop0fzx/3ZCujV6NLmRmyMx63DPA6O/12e56e4145146d131c25a78f7ca4fadc/1.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2Fzojzzdop0fzx%2F3ZCujV6NLmRmyMx63DPA6O%2F12e56e4145146d131c25a78f7ca4fadc%2F1.png" alt="Figure 1: Specify project name"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  Step 2
&lt;/h5&gt;

&lt;p&gt;You’ve got to choose whether you want to develop your app with &lt;a href="https://www.typescriptlang.org/" rel="noopener noreferrer"&gt;TypeScript&lt;/a&gt; or JavaScript. I will select &lt;em&gt;JavaScript&lt;/em&gt; as shown in &lt;strong&gt;Figure 2&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;_ &lt;strong&gt;Figure 2&lt;/strong&gt; : Programming language_&lt;a href="https://images.ctfassets.net/zojzzdop0fzx/2tHKTwaBHZdACZppRKsChw/cc77e050baa9f544b9a541affa577ba9/2.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2Fzojzzdop0fzx%2F2tHKTwaBHZdACZppRKsChw%2Fcc77e050baa9f544b9a541affa577ba9%2F2.png" alt="Figure 2: Programming language"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  Step 3
&lt;/h5&gt;

&lt;p&gt;Next, you need to choose between Npm or Yarn. I will select &lt;em&gt;Npm&lt;/em&gt; as shown in &lt;strong&gt;Figure 3&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;_ &lt;strong&gt;Figure 3&lt;/strong&gt; : Package manager_&lt;a href="https://images.ctfassets.net/zojzzdop0fzx/1z99WQA1Kppv7RvflvP49C/608592ae12556caadd59b3db4dece11b/3.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2Fzojzzdop0fzx%2F1z99WQA1Kppv7RvflvP49C%2F608592ae12556caadd59b3db4dece11b%2F3.png" alt="Figure 3: Package manager"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  Step 4
&lt;/h5&gt;

&lt;p&gt;In this step, you’ve got to select the UI framework you are going to use in the app. I will select &lt;em&gt;&lt;a href="https://tailwindcss.com/" rel="noopener noreferrer"&gt;Tailwind CSS&lt;/a&gt;&lt;/em&gt; as shown in &lt;strong&gt;Figure 4&lt;/strong&gt;. Even if you skip this step, you can add any UI framework you want later.&lt;/p&gt;

&lt;p&gt;_ &lt;strong&gt;Figure 4&lt;/strong&gt; : UI framework_&lt;a href="https://images.ctfassets.net/zojzzdop0fzx/1RoXS25sa6QaZCoeVOdoxi/c978633733b04f295761619c4f99556d/4.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2Fzojzzdop0fzx%2F1RoXS25sa6QaZCoeVOdoxi%2Fc978633733b04f295761619c4f99556d%2F4.png" alt="Figure 4: UI framework"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  Step 5
&lt;/h5&gt;

&lt;p&gt;Nuxt.js offers a set of modules that you can use right away in your apps. For this app, I will pick the &lt;em&gt;&lt;a href="https://axios.nuxtjs.org/" rel="noopener noreferrer"&gt;Axios&lt;/a&gt;&lt;/em&gt; module. &lt;strong&gt;Figure 5&lt;/strong&gt; shows the selection.&lt;/p&gt;

&lt;p&gt;_ &lt;strong&gt;Figure 5&lt;/strong&gt; : Nuxt.js modules_&lt;a href="https://images.ctfassets.net/zojzzdop0fzx/4zqcUHCkKgLRgRkd7NUUSN/2447d697b85487871feeda9b9456a3ee/5.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2Fzojzzdop0fzx%2F4zqcUHCkKgLRgRkd7NUUSN%2F2447d697b85487871feeda9b9456a3ee%2F5.png" alt="Figure 5: Nuxt.js modules"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  Step 6
&lt;/h5&gt;

&lt;p&gt;The CLI makes it super intuitive to integrate linters in your app. I will pick up both &lt;em&gt;&lt;a href="https://eslint.org/" rel="noopener noreferrer"&gt;ESLint&lt;/a&gt;&lt;/em&gt; and &lt;em&gt;&lt;a href="https://prettier.io/" rel="noopener noreferrer"&gt;Prettier&lt;/a&gt;&lt;/em&gt;. &lt;strong&gt;Figure 6&lt;/strong&gt; shows the selection.&lt;/p&gt;

&lt;p&gt;_ &lt;strong&gt;Figure 6&lt;/strong&gt; : Linting tools_&lt;a href="https://images.ctfassets.net/zojzzdop0fzx/5gnR3dd8AZRcEWnf29S3gi/e43fcc9abcd11d2d8d6480302bddc2ed/6.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2Fzojzzdop0fzx%2F5gnR3dd8AZRcEWnf29S3gi%2Fe43fcc9abcd11d2d8d6480302bddc2ed%2F6.png" alt="Figure 6: Linting tools"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  Step 7
&lt;/h5&gt;

&lt;p&gt;Now it’s time to select a testing framework. For the sake of this article, I will select &lt;em&gt;None&lt;/em&gt;. Feel free to add any. &lt;strong&gt;Figure 7&lt;/strong&gt; shows the selection.&lt;/p&gt;

&lt;p&gt;_ &lt;strong&gt;Figure 7&lt;/strong&gt; : Testing framework_&lt;a href="https://images.ctfassets.net/zojzzdop0fzx/3u0eiBgemrXVf57YT3pFyh/8a6907c62b012728eda7f7f97ec74ce6/7.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2Fzojzzdop0fzx%2F3u0eiBgemrXVf57YT3pFyh%2F8a6907c62b012728eda7f7f97ec74ce6%2F7.png" alt="Figure 7: Testing framework"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  Step 8
&lt;/h5&gt;

&lt;p&gt;By default, Nuxt.js supports two rendering modes. SSR/SSG and SPA. I will pick &lt;em&gt;SSR/SSG&lt;/em&gt; to take advantage of the Server-side rendering. &lt;strong&gt;Figure 8&lt;/strong&gt; shows the selection.&lt;/p&gt;

&lt;p&gt;_ &lt;strong&gt;Figure 8&lt;/strong&gt; : Rendering mode_&lt;a href="https://images.ctfassets.net/zojzzdop0fzx/20BvUuyCCB6PmKjkSwSIJU/ab4690ca7e3019bde046de433a075a94/8.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2Fzojzzdop0fzx%2F20BvUuyCCB6PmKjkSwSIJU%2Fab4690ca7e3019bde046de433a075a94%2F8.png" alt="Figure 8: Rendering mode"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  Step 9
&lt;/h5&gt;

&lt;p&gt;The deployment target depends on our selection in Step 8. In this case, we have two options to select from. Server (using a Node.js server hosting) or Static (CDN/JAMStack hosting). I will select the &lt;em&gt;Server&lt;/em&gt; deployment target as shown in &lt;strong&gt;Figure 9&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;_ &lt;strong&gt;Figure 9&lt;/strong&gt; : Deployment target_&lt;a href="https://images.ctfassets.net/zojzzdop0fzx/5jC3uPEZL6RzMkxfsxMEM0/315cb572b380f762c3902acb94b26c61/9.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2Fzojzzdop0fzx%2F5jC3uPEZL6RzMkxfsxMEM0%2F315cb572b380f762c3902acb94b26c61%2F9.png" alt="Figure 9: Deployment target_"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  Step 10
&lt;/h5&gt;

&lt;p&gt;For the development tools, I will keep it simple and select the &lt;em&gt;jsconfig.json&lt;/em&gt; option as shown in &lt;strong&gt;Figure 10&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;_ &lt;strong&gt;Figure 10&lt;/strong&gt; : Development tools_&lt;a href="https://images.ctfassets.net/zojzzdop0fzx/2MQvPa46qQ745wsqMppbwN/d1383dc83274d8971cdb71da07c4eb15/10.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2Fzojzzdop0fzx%2F2MQvPa46qQ745wsqMppbwN%2Fd1383dc83274d8971cdb71da07c4eb15%2F10.png" alt="Figure 10: Development tools"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  Step 11
&lt;/h5&gt;

&lt;p&gt;I won’t be using any continuous integration for this app. I will simply select &lt;em&gt;None&lt;/em&gt; as shown in &lt;strong&gt;Figure 11&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;_ &lt;strong&gt;Figure 11&lt;/strong&gt; : Continuous integration_&lt;a href="https://images.ctfassets.net/zojzzdop0fzx/4AnKROc9eD9ubPBANmpYrb/08a4efb4396d07b2c8bd89dd1950333b/11.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2Fzojzzdop0fzx%2F4AnKROc9eD9ubPBANmpYrb%2F08a4efb4396d07b2c8bd89dd1950333b%2F11.png" alt="Figure 11: Continuous integration"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  Step 12
&lt;/h5&gt;

&lt;p&gt;Finally, the CLI asks whether you want to use any version control system. A version control system is always recommended when doing any kind of development. I will select &lt;em&gt;Git&lt;/em&gt; as shown in &lt;strong&gt;Figure 12&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;_ &lt;strong&gt;Figure 12&lt;/strong&gt; : Version control system_&lt;a href="https://images.ctfassets.net/zojzzdop0fzx/4jTffrtXVkL6mY4vd9HHoa/a5eba893ac09a8ed5e515bdce1293a5f/12.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2Fzojzzdop0fzx%2F4jTffrtXVkL6mY4vd9HHoa%2Fa5eba893ac09a8ed5e515bdce1293a5f%2F12.png" alt="Figure 12: Version control system"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These twelve questions are enough for the CLI to start scaffolding and generating your app based on your preferences. It takes a few seconds to have everything ready for you.&lt;/p&gt;

&lt;p&gt;If all goes well, you should see the following as in &lt;strong&gt;Figure 13&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;_ &lt;strong&gt;Figure 13&lt;/strong&gt; : create-nuxt-app new app instructions_&lt;a href="https://images.ctfassets.net/zojzzdop0fzx/2x95OjKH2v2X9rvK3ruhfX/71d3ae59601e84dc610225bfea81f897/13.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2Fzojzzdop0fzx%2F2x95OjKH2v2X9rvK3ruhfX%2F71d3ae59601e84dc610225bfea81f897%2F13.png" alt="Figure 13: create-nuxt-app new app instructions"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The CLI gives you instructions on how to run and build the app.&lt;/p&gt;

&lt;h5&gt;
  
  
  Step 13
&lt;/h5&gt;

&lt;p&gt;Let’s run the app by following the steps highlighted in Figure 13. Run the following commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd my-first-nuxt
npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CLI compiles both the client and server parts of the app and starts the Node.js server on port 3000 as shown in &lt;strong&gt;Figure 14&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;_ &lt;strong&gt;Figure 14&lt;/strong&gt; : App is running_&lt;a href="https://images.ctfassets.net/zojzzdop0fzx/6sccU3xmhyd7ISETODSBEX/ecd95a7f821dbecaa987c8584c61e7a8/14.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2Fzojzzdop0fzx%2F6sccU3xmhyd7ISETODSBEX%2Fecd95a7f821dbecaa987c8584c61e7a8%2F14.png" alt="Figure 14: App is running"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  Step 14
&lt;/h5&gt;

&lt;p&gt;Open a browser instance and navigate to the URL &lt;em&gt;&lt;a href="http://localhost:3000/" rel="noopener noreferrer"&gt;http://localhost:3000/&lt;/a&gt;&lt;/em&gt; and you should see the default Nuxt.js app rendering. &lt;strong&gt;Figure 15&lt;/strong&gt; shows the app running in a browser.&lt;/p&gt;

&lt;p&gt;_ &lt;strong&gt;Figure 15&lt;/strong&gt; : App rendering in a browser_&lt;a href="https://images.ctfassets.net/zojzzdop0fzx/7IlVRkQSVQ2shPLFEXFao5/137cc91caac699f7fe8a82432ae5ab7e/15.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2Fzojzzdop0fzx%2F7IlVRkQSVQ2shPLFEXFao5%2F137cc91caac699f7fe8a82432ae5ab7e%2F15.png" alt="Figure 15: App rendering in a browser"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That’s all you need to get started on your first Nuxt.js app. Enjoy!&lt;/p&gt;

&lt;h2&gt;
  
  
  Nuxt.js Directory Structure
&lt;/h2&gt;

&lt;p&gt;Let’s quickly go through the different folders the CLI generated for us. I will start by opening the new app inside &lt;a href="https://code.visualstudio.com/" rel="noopener noreferrer"&gt;Visual Studio Code&lt;/a&gt;. &lt;strong&gt;Figure 16&lt;/strong&gt; shows the app open inside the editor.&lt;/p&gt;

&lt;p&gt;_ &lt;strong&gt;Figure 16&lt;/strong&gt; : App folders and files_&lt;a href="https://images.ctfassets.net/zojzzdop0fzx/2PnMnO4ZCJAxrGKDWF2LDL/286ec439bfbc87de0df446ab8d183265/16.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2Fzojzzdop0fzx%2F2PnMnO4ZCJAxrGKDWF2LDL%2F286ec439bfbc87de0df446ab8d183265%2F16.png" alt="Figure 16: App folders and files"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s go through each folder, and explain their roles briefly.&lt;/p&gt;

&lt;h5&gt;
  
  
  .nuxt
&lt;/h5&gt;

&lt;p&gt;The &lt;em&gt;.nuxt&lt;/em&gt; folder is (re-)generated by the CLI every time you run or build the app. It has all the automatically generated files that Nuxt.js uses to run your app.&lt;/p&gt;

&lt;p&gt;You can read more about the &lt;em&gt;.nuxt&lt;/em&gt; folder here: &lt;a href="https://nuxtjs.org/docs/2.x/directory-structure/nuxt" rel="noopener noreferrer"&gt;.nuxt&lt;/a&gt;.&lt;/p&gt;

&lt;h5&gt;
  
  
  assets
&lt;/h5&gt;

&lt;p&gt;The &lt;em&gt;assets&lt;/em&gt; folder contains all of your uncompiled files such as Sass files, images, or font files. Nuxt.js makes use of Webpack to load all the files inside this folder.&lt;/p&gt;

&lt;p&gt;You can read more about the &lt;em&gt;assets&lt;/em&gt; folder here: &lt;a href="https://nuxtjs.org/docs/2.x/directory-structure/assets" rel="noopener noreferrer"&gt;assets&lt;/a&gt;.&lt;/p&gt;

&lt;h5&gt;
  
  
  components
&lt;/h5&gt;

&lt;p&gt;This folder holds all of your Vue.js components. Nuxt.js components are not different from any other Vue.js component.&lt;/p&gt;

&lt;p&gt;Read more about the &lt;em&gt;components&lt;/em&gt; folder here: &lt;a href="https://nuxtjs.org/docs/2.x/directory-structure/components" rel="noopener noreferrer"&gt;components&lt;/a&gt;.&lt;/p&gt;

&lt;h6&gt;
  
  
  layouts
&lt;/h6&gt;

&lt;p&gt;This folder contains all of your layout components. These are Vue.js components with placeholders for content. At run time, the component and the layout it uses get merged together into a single component. Layouts in Nuxt.js allows you to define fixed UI sections in your app instead of having to repeat things over and over.&lt;/p&gt;

&lt;p&gt;You can read more about the &lt;em&gt;layouts&lt;/em&gt; folder here: &lt;a href="https://nuxtjs.org/docs/2.x/directory-structure/layouts" rel="noopener noreferrer"&gt;layouts&lt;/a&gt;.&lt;/p&gt;

&lt;h5&gt;
  
  
  pages
&lt;/h5&gt;

&lt;p&gt;This folder also holds Vue.js components. It is unique because Nuxt.js converts this folder structure (components with sub-folders) into actual Vue.js Routes.&lt;/p&gt;

&lt;p&gt;You can read about the &lt;em&gt;pages&lt;/em&gt; folder here: &lt;a href="https://nuxtjs.org/docs/2.x/directory-structure/pages" rel="noopener noreferrer"&gt;pages&lt;/a&gt;.&lt;/p&gt;

&lt;h5&gt;
  
  
  plugins
&lt;/h5&gt;

&lt;p&gt;This folder contains global JavaScript functions that you want to run before Nuxt.js instantiates the root Vue.js app. These functions are called &lt;em&gt;Plugins&lt;/em&gt;. They can take multiple forms. For instance, you create a Nuxt.js Plugin to load a Vue.js Plugin to your app. You can also install, and make use of a third-party Nuxt.js Plugin.&lt;/p&gt;

&lt;p&gt;You can read more about the &lt;em&gt;plugins&lt;/em&gt; folder here: &lt;a href="https://nuxtjs.org/docs/2.x/directory-structure/plugins" rel="noopener noreferrer"&gt;plugins&lt;/a&gt;.&lt;/p&gt;

&lt;h5&gt;
  
  
  static
&lt;/h5&gt;

&lt;p&gt;The name of this folder says it all! This folder is directly mapped to the server root. You can place any file in this folder that you do not want Webpack to process. For example, you can place a favicon file, any CSS files, and many other such files.&lt;/p&gt;

&lt;p&gt;You can read more about the &lt;em&gt;static&lt;/em&gt; folder here: &lt;a href="https://nuxtjs.org/docs/2.x/directory-structure/static" rel="noopener noreferrer"&gt;static&lt;/a&gt;.&lt;/p&gt;

&lt;h5&gt;
  
  
  store
&lt;/h5&gt;

&lt;p&gt;The &lt;em&gt;store&lt;/em&gt; folder holds all the Vuex store files. Nuxt.js comes with the Vuex module installed, but keeps it disabled. In order to enable the Vue store in your Nuxt.js app, create an &lt;em&gt;index.js&lt;/em&gt; file inside this folder, and Nuxt.js will automatically enable it for you.&lt;/p&gt;

&lt;p&gt;You can read more about &lt;em&gt;store&lt;/em&gt; folder here: &lt;a href="https://nuxtjs.org/docs/2.x/directory-structure/store" rel="noopener noreferrer"&gt;store&lt;/a&gt;.&lt;/p&gt;

&lt;h5&gt;
  
  
  nuxt.config.js
&lt;/h5&gt;

&lt;p&gt;As I mentioned, Nuxt.js prefers convention over configuration. However, at some times you may need to tweak the configuration a little. Nuxt.js provides the &lt;em&gt;nuxt.config.js&lt;/em&gt; file to allow for custom configuration settings on the app level.&lt;/p&gt;

&lt;p&gt;Read more about &lt;em&gt;nuxt.config&lt;/em&gt; file here: &lt;a href="https://nuxtjs.org/docs/2.x/directory-structure/nuxt-config" rel="noopener noreferrer"&gt;nuxt.config&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That’s just a quick overview of what folders are generated by the Nuxt.js CLI. There are more you can read up on. You can find all the information you need on &lt;a href="https://nuxtjs.org/docs/2.x/get-started/installation" rel="noopener noreferrer"&gt;Nuxt.js Documentation&lt;/a&gt; website.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this article, you were introduced to Nuxt.js and took a few baby steps towards creating your first Nuxt.js app. In the coming articles, we will immerse ourselves in the Nuxt.js world, and explore this fresh and promising web development framework together.&lt;/p&gt;

&lt;p&gt;You can follow me on &lt;a href="https://twitter.com/bhaidar" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; to see more of my work.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This Dot Labs is a modern web consultancy focused on helping companies realize their digital transformation efforts. For expert architectural guidance, training, or consulting in React, Angular, Vue, Web Components, GraphQL, Node, Bazel, or Polymer, visit &lt;a href="https://www.thisdotlabs.com" rel="noopener noreferrer"&gt;thisdotlabs.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This Dot Media is focused on creating an inclusive and educational web for all. We keep you up to date with advancements in the modern web through events, podcasts, and free content. To learn, visit &lt;a href="https://www.thisdot.co" rel="noopener noreferrer"&gt;thisdot.co&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>nuxtjsvue</category>
    </item>
    <item>
      <title>Introduction to building an Angular app with Nx Workspace</title>
      <dc:creator>This Dot Media</dc:creator>
      <pubDate>Thu, 18 Mar 2021 17:52:25 +0000</pubDate>
      <link>https://dev.to/thisdotmedia_staff/introduction-to-building-an-angular-app-with-nx-workspace-1dgo</link>
      <guid>https://dev.to/thisdotmedia_staff/introduction-to-building-an-angular-app-with-nx-workspace-1dgo</guid>
      <description>&lt;h1&gt;
  
  
  Introduction to building an Angular app with Nx Workspace
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://nx.dev"&gt;Nx Workspace&lt;/a&gt; is a tool suite designed to architect, build and manage monorepos at any scale. It has an out of the box support for multiple frontend frameworks like Angular and React as well as backend technologies including Nest, Next and Express. In this article, we will focus on building a workspace for an Angular-based project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monorepo fundamentals
&lt;/h2&gt;

&lt;p&gt;The most basic definition of a monorepo is that it is a single repository that consists of multiple applications and libraries. This all is accompanied by a set of tooling which enables to work with those projects. This approach has several benefits as following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;shared code&lt;/strong&gt; - it enables to share the code across the whole company or organization. This can result in code that is more DRY as we can reuse the common patterns, components, and types. This enables to share the logic between frontend and backend as well.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;atomic changes&lt;/strong&gt; - without the monorepo approach whenever we need to make a change that will affect multiple projects we might need to coordinate those changes across multiple repositories and possibly by multiple teams. Ie. API change might need to be reflected both on a server app and a client app. With monorepo all of those changes can be applied in one commit on one repository which greatly limits the coordination efforts necessary&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;developer mobility&lt;/strong&gt; - with a monorepo approach we get one, consistent way of performing similar tasks even when using multiple technologies. The developers can now contribute to other team's projects and make sure that their changes are safe across the whole organization.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;single set of dependencies&lt;/strong&gt; - By using a single repository with one set of dependencies we make sure that our whole codebase depends on one single version of the given dependency. This way there are no version conflicts between libraries. It is also less likely that the less used part of the repository will be left with an obsolete dependency because it will be updated along the way when other parts of the repository do this update.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Create a new workspace
&lt;/h2&gt;

&lt;p&gt;With all that said about the monorepo how do we actually create one using Nx Workspace and Angular? Just like with Angular CLI there is an Nx CLI that does all the heavy lifting for us. With the following command we can create a new workspace that leverages all of the aforementioned benefits of a monorepo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx create-nx-workspace --preset=angular
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tool will ask for a project name, stylesheet format, and linting tool. For the linting I recommend using ESLint which is a more modern tool. The CLI will also ask whether we want to use &lt;a href="https://nx.app"&gt;Nx Cloud&lt;/a&gt; in our workspace. We can opt-out from this for now as we can easily add that later on. After the command finishes, we end up with a brand new project all set up. Let's start by analyzing what has been generated for us.&lt;/p&gt;

&lt;p&gt;Nx uses certain toolset by default:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://jestjs.io"&gt;Jest&lt;/a&gt;&lt;/strong&gt; for testing (instead of Karma and Jasmine)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.cypress.io"&gt;Cypress&lt;/a&gt;&lt;/strong&gt; for e2e testing (instead of Protractor)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://eslint.org"&gt;ESLint&lt;/a&gt;&lt;/strong&gt; for linting (instead of TSLint) in case you have decided so when creating a workspace All of these are a modern tools and I recommend sticking to them as they provide very good developer's experience and are actively maintained.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The base structure that is created for us looks as following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- apps/
  - {{appName}}
  - {{appName}}-e2e
- libs
- tools
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;apps/*&lt;/code&gt;: here go all the application projects - by default it'll be the app we created and an accompanying e2e tests app&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;libs/*&lt;/code&gt;: here go all the libraries that we create&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tools/*&lt;/code&gt;: here we can put all the necessary tooling scripts etc. that are necessary in our project&lt;/li&gt;
&lt;li&gt;and all the root configuration files like angular.json, config files for Jest, ESLint, Prettier etc&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This whole structure is created for us so that we can focus on building the solution right from the beginning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Migration from an existing Angular project
&lt;/h2&gt;

&lt;p&gt;If you already have an existing Angular app that was build using Angular CLI you can still easily migrate to an Nx Workspace. A project that contains only a single Angular app can be migrated automatically with just one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ng add @nrwl/workspace
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will install all the dependencies required by Nx and create the folder structure mentioned in the previous section. It will also migrate the app into &lt;code&gt;apps&lt;/code&gt; folder and e2e suite into &lt;code&gt;apps/{{appName}}-e2e&lt;/code&gt; folder. Nx modifies &lt;code&gt;package.json&lt;/code&gt; script and decorates Angular CLI so you can still use the same commands like &lt;code&gt;ng build&lt;/code&gt;, &lt;code&gt;ng serve&lt;/code&gt;, or &lt;code&gt;npm start&lt;/code&gt;. It is important to remember that the version of Angular and Nx must match so that this process goes smoothly. Ie. if your project is using version 10 of Angular please make sure to use the latest &lt;code&gt;10.x.x&lt;/code&gt; version of Nx CLI.&lt;/p&gt;

&lt;p&gt;In case you already have multiple projects you still can migrate with few manual steps described in the &lt;a href="https://nx.dev/latest/angular/migration/migration-angular#transitioning-manually"&gt;Nx docs&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a library
&lt;/h2&gt;

&lt;p&gt;One of the core ideas behind the Nx Workspace monorepo approach is to divide our code into small, manageable libraries. So by using Nx we will end up creating a library often. Luckily this is done as simple as typing one command in the terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nx g @nrwl/angular:lib mylib
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will create a &lt;code&gt;libs/mylib&lt;/code&gt; folder with the library set up so we can build, test, and use it in other libraries or applications right away. To group your libraries you can use &lt;code&gt;--directory={{subfolderName}}&lt;/code&gt; additional parameter to specify a subfolder under which a library should be created. You don't have to worry though about choosing the perfect place for your library from the start. You can always move it around later on using &lt;code&gt;@nrwl/workspace:move&lt;/code&gt; &lt;a href="https://nx.dev/latest/angular/workspace/move"&gt;schematics&lt;/a&gt;. And you can find all the other options for generating a new Angular library in the &lt;a href="https://nx.dev/latest/angular/angular/library#options"&gt;official docs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every library has an &lt;code&gt;index.ts&lt;/code&gt; file at its root which should be the only access point to a library. Each part of the library that we want to be part of lib's public API should be exported from this file. Everything else is considered private to the library. This is important for maintaining the correct boundaries between libraries and applications which makes for a more well-structured code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Affected
&lt;/h2&gt;

&lt;p&gt;One of the greatest things about Nx Workspace is that it understands dependencies within the workspace. This allows for testing and linting only the projects that are affected by a given change. Nx comes with a few built-in commands for that.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx nx affected:lint
npx nx affected:test
npx nx affected:e2e
npx nx affected:build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those commands will run &lt;code&gt;lint&lt;/code&gt;, &lt;code&gt;test&lt;/code&gt;, &lt;code&gt;e2e&lt;/code&gt;, and &lt;code&gt;build&lt;/code&gt; targets, but only on projects that are affected and therefore it will lower the execution time by a lot in most use-cases. The commands below are equivalent to the ones above but they use more generic syntax which can be extended to different targets if necessary.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nx affected --target=lint
nx affected --target=test
nx affected --target=e2e
nx affected --target=build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For all of the commands mentioned above we can parallelize them by using &lt;code&gt;--parallel&lt;/code&gt; flag and &lt;code&gt;--maxParallel={{nr}}&lt;/code&gt; to cap the number of parallel tasks. There are multiple additional useful parameters that the &lt;code&gt;affected&lt;/code&gt; task can take. Please visit the &lt;a href="https://nx.dev/latest/angular/cli/affected#options"&gt;official docs&lt;/a&gt; for more details.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conslusion
&lt;/h2&gt;

&lt;p&gt;Working with a monorepo has a lot of advantages and Nx Workspace provides us with multiple tools to get the most of that. By using it we can speed up our development loop by being able to create atomic changes to the repository and make sure that the whole workspace is compatible with that change. All of this is done with blazing fast tooling that can be scaled to any project size we might have.&lt;/p&gt;

&lt;p&gt;In case you have any questions you can always tweet or DM me &lt;a href="https://twitter.com/ktrz__"&gt;@ktrz&lt;/a&gt;. I'm always happy to help!&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This Dot Labs is a modern web consultancy focused on helping companies realize their digital transformation efforts. For expert architectural guidance, training, or consulting in React, Angular, Vue, Web Components, GraphQL, Node, Bazel, or Polymer, visit &lt;a href="https://www.thisdotlabs.com"&gt;thisdotlabs.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This Dot Media is focused on creating an inclusive and educational web for all. We keep you up to date with advancements in the modern web through events, podcasts, and free content. To learn, visit &lt;a href="https://www.thisdot.co"&gt;thisdot.co&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>angularwebdevelopmen</category>
    </item>
  </channel>
</rss>
