<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ frontend - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ frontend - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Thu, 21 May 2026 16:11:18 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/frontend/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ The Front-End Monitoring Handbook: Track Performance, Errors, and User Behavior ]]>
                </title>
                <description>
                    <![CDATA[ A complete frontend monitoring system is essential for tracking application performance, errors, and user behavior. It consists of three main components: data collection and reporting, data processing and storage, and data visualization. This article... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/the-front-end-monitoring-handbook/</link>
                <guid isPermaLink="false">683e5606b7c8b1ebd3a32964</guid>
                
                    <category>
                        <![CDATA[ Frontend Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ monitoring ]]>
                    </category>
                
                    <category>
                        <![CDATA[ frontend ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Gordan Tan ]]>
                </dc:creator>
                <pubDate>Tue, 03 Jun 2025 01:55:18 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1748915696356/6e6edeed-2f41-40f9-97d6-8cc8686c3b25.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>A complete frontend monitoring system is essential for tracking application performance, errors, and user behavior. It consists of three main components: data collection and reporting, data processing and storage, and data visualization.</p>
<p>This article focuses specifically on the first component – data collection and reporting – and shows you how to build a monitoring SDK from scratch. By the end of this article, you'll understand how to gather critical metrics about your application's performance, capture errors, track user behavior, and implement efficient reporting mechanisms.</p>
<p>Below is an outline of the topics we'll cover:</p>
<pre><code class="lang-javascript">                       ┌────────────────────┐
                       │  Data Collection   │
                       └──────────┬─────────┘
                                  │
         ┌─────────────────┬──────┴──────────────┐
         │                 │                     │
┌────────┴────────┐ ┌──────┴──────┐     ┌────────┴────────┐
│ <span class="hljs-built_in">Error</span> Monitoring │ │ Performance  │     │ Behavior       │
│                  │ │ Monitoring   │     │ Monitoring     │
└────────┬─────────┘ └──────┬──────┘     └────────┬────────┘
         │                  │                     │
┌────────┴────────┐ ┌──────┴──────────┐  ┌────────┴────────┐
│                 │ │                 │  │                 │
│ Resource Loading│ │ Resource Loading│  │     UV, PV      │
│     Errors      │ │      Time       │  │                 │
│                 │ │                 │  │  Page Access    │
│   JS Errors     │ │  API Request    │  │     Depth       │
│                 │ │     Time        │  │                 │
│ <span class="hljs-built_in">Promise</span> Errors  │ │                 │  │   Page Stay     │
│                 │ │   DNS, TCP,     │  │    Duration     │
│ Custom Errors   │ │ First-byte Time │  │                 │
│                 │ │                 │  │  Custom Event   │
│                 │ │   FPS Rate      │  │    Tracking     │
│                 │ │                 │  │                 │
│                 │ │ Cache Hit Rate  │  │   User Clicks   │
│                 │ │                 │  │                 │
│                 │ │  First Screen   │  │ Page Navigation │
│                 │ │  Render Time    │  │                 │
│                 │ │                 │  └─────────────────┘
│                 │ │  FP, FCP, LCP,  │
│                 │ │   FID, LCS,     │
│                 │ │ DOMContentLoaded│
│                 │ │    onload       │
└─────────────────┘ └─────────────────┘
</code></pre>
<p>Once data is collected, it needs to be reported to your backend systems for processing and analysis:</p>
<pre><code class="lang-javascript">                  ┌─────────────────┐
                  │ Data Reporting  │
                  └────────┬────────┘
                           │
          ┌────────────────┴────────────────┐
          │                                 │
┌─────────────────────┐           ┌─────────────────────┐
│  Reporting Methods  │           │  Reporting Timing   │
└──────────┬──────────┘           └──────────┬──────────┘
           │                                 │
     ┌─────┼─────┐               ┌───────────┼───────────┐
     │     │     │               │           │           │
┌────┴───┐ │ ┌───┴────┐ ┌────────┴────────┐ │ ┌─────────┴─────────┐
│  xhr   │ │ │ image  │ │ requestIdle     │ │ │ Upload when cache │
└────────┘ │ └────────┘ │ Callback/       │ │ │ limit is reached  │
           │            │ <span class="hljs-built_in">setTimeout</span>      │ │ └───────────────────┘
     ┌─────┴─────┐      └─────────────────┘ │
     │ sendBeacon│                          │
     └───────────┘                ┌─────────┴──────────┐
                                  │    beforeunload    │
                                  └────────────────────┘
</code></pre>
<h2 id="heading-prerequisites"><strong>Prerequisites</strong></h2>
<p>Before diving into this tutorial, you should have:</p>
<ul>
<li><p>Basic knowledge of JavaScript and web development</p>
</li>
<li><p>Familiarity with browser APIs and event handling</p>
</li>
<li><p>Understanding of asynchronous programming concepts</p>
</li>
<li><p>Some experience with performance optimization concepts</p>
</li>
</ul>
<p>Since theoretical knowledge alone can be difficult to grasp, I've created a simple <a target="_blank" href="https://github.com/woai3c/monitor-demo">monitoring SDK</a> that implements these technical concepts. You can use it to create simple demos and gain a better understanding. Reading this article while experimenting with the SDK will provide the best learning experience.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><p><a class="post-section-overview" href="#heading-collect-performance-data">Collect Performance Data</a></p>
<ul>
<li><p><a class="post-section-overview" href="##heading-fp-first-paint">FP (First Paint)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-fcp-first-contentful-paint">FCP (First Contentful Paint)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-lcp-largest-contentful-paint">LCP (Largest Contentful Paint)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-cls-cumulative-layout-shift">CLS (Cumulative Layout Shift)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-domcontentloaded-and-load-events">DOMContentLoaded and Load Events</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-first-screen-rendering-time">First Screen Rendering Time</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-api-request-timing">API Request Timing</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-resource-loading-time-and-cache-hit-rate">Resource Loading Time and Cache Hit Rate</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-browser-backforward-cache-bfc">Browser Back/Forward Cache</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-fps">FPS</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-vue-router-change-rendering-time">Vue Router Change Rendering Time</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-error-data-collection">Error Data Collection</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-resource-loading-errors">Resource Loading Errors</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-javascript-errors">JavaScript Errors</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-promise-errors">Promise Errors</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-sourcemap">Sourcemap</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-vue-errors">Vue Errors</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-behavior-data-collection">Behavior Data Collection</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-pv-and-uv">PV and UV</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-page-stay-duration">Page Stay Duration</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-page-access-depth">Page Access Depth</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-user-clicks">User Clicks</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-page-navigation">Page Navigation</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-vue-router-changes">Vue Router Changes</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-data-reporting">Data Reporting</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-reporting-methods">Reporting Methods</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-reporting-timing">Reporting Timing</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-summary">Summary</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-references">References</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-performance-monitoring">Performance Monitoring</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-error-monitoring">Error Monitoring</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-behavior-monitoring">Behavior Monitoring</a></p>
</li>
</ul>
</li>
</ol>
<h2 id="heading-collect-performance-data">Collect Performance Data</h2>
<p>Monitoring performance is crucial for providing users with a smooth, responsive experience. Slow websites lead to higher bounce rates and reduced conversions. By collecting performance metrics, you can identify bottlenecks, optimize critical rendering paths, and improve overall user satisfaction.</p>
<p>The Chrome developer team has proposed a series of metrics to monitor page performance, each measuring a different aspect of the user experience:</p>
<ul>
<li><p><strong>FP (First Paint)</strong> – Time from when the page starts loading until the first pixel is painted on the screen (essentially the white screen time)</p>
</li>
<li><p><strong>FCP (First Contentful Paint)</strong> – Time from page load start until any part of page content is rendered</p>
</li>
<li><p><strong>LCP (Largest Contentful Paint)</strong> – Time from page load start until the largest text block or image element completes rendering</p>
</li>
<li><p><strong>CLS (Cumulative Layout Shift)</strong> – Cumulative score of all unexpected layout shifts occurring between page load start and when the <a target="_blank" href="https://developer.chrome.com/docs/web-platform/page-lifecycle-api">page's lifecycle state</a> becomes hidden</p>
</li>
</ul>
<p>We can obtain these four performance metrics through <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/PerformanceObserver">PerformanceObserver</a> (they can also be retrieved via <code>performance.getEntriesByName()</code>, but this method doesn't provide real-time notifications when events occur). PerformanceObserver is a performance monitoring interface used to observe performance measurement events.</p>
<p>Let's examine each of these metrics in detail and see how to implement them in our SDK.</p>
<h3 id="heading-fp-first-paint">FP (First Paint)</h3>
<p>First Paint (FP) marks the point when the browser renders anything visually different from what was on the screen before navigation. This could be a background color change or any visual element that indicates to the user that the page is loading.</p>
<p>Implementation code:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> entryHandler = <span class="hljs-function">(<span class="hljs-params">list</span>) =&gt;</span> {        
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> entry <span class="hljs-keyword">of</span> list.getEntries()) {
        <span class="hljs-keyword">if</span> (entry.name === <span class="hljs-string">'first-paint'</span>) {
            observer.disconnect()
        }
        <span class="hljs-built_in">console</span>.log(entry)
    }
}

<span class="hljs-keyword">const</span> observer = <span class="hljs-keyword">new</span> PerformanceObserver(entryHandler)
<span class="hljs-comment">// The buffered property indicates whether to observe cached data, </span>
<span class="hljs-comment">// allowing observation even if the monitoring code is added after the event occurs</span>
observer.observe({ <span class="hljs-attr">type</span>: <span class="hljs-string">'paint'</span>, <span class="hljs-attr">buffered</span>: <span class="hljs-literal">true</span> })
</code></pre>
<p>This code creates a new PerformanceObserver that watches for 'paint' type events. When the first-paint event occurs, it logs the entry information and disconnects the observer since we only need to capture this event once per page load. The observer's <code>observe()</code> method is configured with <code>buffered: true</code> to ensure we can catch paint events that occurred before our code runs.</p>
<p>The FP measurement output:</p>
<pre><code class="lang-javascript">{
    <span class="hljs-attr">duration</span>: <span class="hljs-number">0</span>,
    <span class="hljs-attr">entryType</span>: <span class="hljs-string">"paint"</span>,
    <span class="hljs-attr">name</span>: <span class="hljs-string">"first-paint"</span>,
    <span class="hljs-attr">startTime</span>: <span class="hljs-number">359</span>, <span class="hljs-comment">// FP time</span>
}
</code></pre>
<p>The <code>startTime</code> value represents the paint timing we need. This value (359ms in this example) tells us how long it took from the start of navigation until the first visual change appeared on screen. You can use this metric to optimize your critical rendering path and reduce the time users spend looking at a blank screen.</p>
<h3 id="heading-fcp-first-contentful-paint">FCP (First Contentful Paint)</h3>
<p>FCP (First Contentful Paint) refers to the time from page load start until any part of page content is rendered. The "content" in this metric refers to text, images (including background images), <code>&lt;svg&gt;</code> elements, and non-white <code>&lt;canvas&gt;</code> elements.</p>
<p><a target="_blank" href="https://camo.githubusercontent.com/8a8b6762583fbb357b8ac6488a1f972e583bab8e6dfaab599e9a5ca3ea5e2403/68747470733a2f2f70362d6a75656a696e2e62797465696d672e636f6d2f746f732d636e2d692d6b3375316662706663702f61346631633962363130323934343864616532623163666235376234656637357e74706c762d6b3375316662706663702d77617465726d61726b2e696d6167653f"><img src="https://camo.githubusercontent.com/8a8b6762583fbb357b8ac6488a1f972e583bab8e6dfaab599e9a5ca3ea5e2403/68747470733a2f2f70362d6a75656a696e2e62797465696d672e636f6d2f746f732d636e2d692d6b3375316662706663702f61346631633962363130323934343864616532623163666235376234656637357e74706c762d6b3375316662706663702d77617465726d61726b2e696d6167653f" alt="FCP visualization showing content being painted on screen" width="600" height="400" loading="lazy"></a></p>
<p>To provide a good user experience, the FCP score should be kept under 1.8 seconds.</p>
<p><a target="_blank" href="https://camo.githubusercontent.com/5a0734e52cbed48e8639fe185204c237fd658e5d60560b18578c848d74dac12c/68747470733a2f2f70332d6a75656a696e2e62797465696d672e636f6d2f746f732d636e2d692d6b3375316662706663702f39383138633636383739623334356533623438343566663366653031653863397e74706c762d6b3375316662706663702d77617465726d61726b2e696d6167653f"><img src="https://camo.githubusercontent.com/5a0734e52cbed48e8639fe185204c237fd658e5d60560b18578c848d74dac12c/68747470733a2f2f70332d6a75656a696e2e62797465696d672e636f6d2f746f732d636e2d692d6b3375316662706663702f39383138633636383739623334356533623438343566663366653031653863397e74706c762d6b3375316662706663702d77617465726d61726b2e696d6167653f" alt="FCP scoring scale: Good (0-1.8s), Needs Improvement (1.8-3s), Poor (3s+)" width="600" height="400" loading="lazy"></a></p>
<p>The measurement code:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> entryHandler = <span class="hljs-function">(<span class="hljs-params">list</span>) =&gt;</span> {        
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> entry <span class="hljs-keyword">of</span> list.getEntries()) {
        <span class="hljs-keyword">if</span> (entry.name === <span class="hljs-string">'first-contentful-paint'</span>) {
            observer.disconnect()
        }

        <span class="hljs-built_in">console</span>.log(entry)
    }
}

<span class="hljs-keyword">const</span> observer = <span class="hljs-keyword">new</span> PerformanceObserver(entryHandler)
observer.observe({ <span class="hljs-attr">type</span>: <span class="hljs-string">'paint'</span>, <span class="hljs-attr">buffered</span>: <span class="hljs-literal">true</span> })
</code></pre>
<p>We can get the value of FCP via the above code:</p>
<pre><code class="lang-javascript">{
    <span class="hljs-attr">duration</span>: <span class="hljs-number">0</span>,
    <span class="hljs-attr">entryType</span>: <span class="hljs-string">"paint"</span>,
    <span class="hljs-attr">name</span>: <span class="hljs-string">"first-contentful-paint"</span>,
    <span class="hljs-attr">startTime</span>: <span class="hljs-number">459</span>, <span class="hljs-comment">// fcp 时间</span>
}
</code></pre>
<p>The <code>startTime</code> value is the painting time we need.</p>
<h3 id="heading-lcp-largest-contentful-paint">LCP (Largest Contentful Paint)</h3>
<p>LCP (Largest Contentful Paint) refers to the time from page load start until the largest text block or image element completes rendering. The LCP metric reports the relative render time of the largest visible image or text block in the viewport, measured from when the page first begins loading.</p>
<p>A good LCP score should be kept under 2.5 seconds.</p>
<p><a target="_blank" href="https://camo.githubusercontent.com/76d0f2b9a24d36f12714e9ce39a61ce426eda6ae087c643745c71337352d5c27/68747470733a2f2f70392d6a75656a696e2e62797465696d672e636f6d2f746f732d636e2d692d6b3375316662706663702f63303930646438623034326334366432616461626135333935636136386634377e74706c762d6b3375316662706663702d77617465726d61726b2e696d6167653f"><img src="https://camo.githubusercontent.com/76d0f2b9a24d36f12714e9ce39a61ce426eda6ae087c643745c71337352d5c27/68747470733a2f2f70392d6a75656a696e2e62797465696d672e636f6d2f746f732d636e2d692d6b3375316662706663702f63303930646438623034326334366432616461626135333935636136386634377e74706c762d6b3375316662706663702d77617465726d61726b2e696d6167653f" alt="LCP scoring scale: Good (0-2.5s), Needs Improvement (2.5-4s), Poor (4s+)" width="600" height="400" loading="lazy"></a></p>
<p>The measurement code:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> entryHandler = <span class="hljs-function">(<span class="hljs-params">list</span>) =&gt;</span> {
    <span class="hljs-keyword">if</span> (observer) {
        observer.disconnect()
    }

    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> entry <span class="hljs-keyword">of</span> list.getEntries()) {
        <span class="hljs-built_in">console</span>.log(entry)
    }
}

<span class="hljs-keyword">const</span> observer = <span class="hljs-keyword">new</span> PerformanceObserver(entryHandler)
observer.observe({ <span class="hljs-attr">type</span>: <span class="hljs-string">'largest-contentful-paint'</span>, <span class="hljs-attr">buffered</span>: <span class="hljs-literal">true</span> })
</code></pre>
<p>We can get the value of LCP via the above code:</p>
<pre><code class="lang-javascript">{
    <span class="hljs-attr">duration</span>: <span class="hljs-number">0</span>,
    <span class="hljs-attr">element</span>: p,
    <span class="hljs-attr">entryType</span>: <span class="hljs-string">"largest-contentful-paint"</span>,
    <span class="hljs-attr">id</span>: <span class="hljs-string">""</span>,
    <span class="hljs-attr">loadTime</span>: <span class="hljs-number">0</span>,
    <span class="hljs-attr">name</span>: <span class="hljs-string">""</span>,
    <span class="hljs-attr">renderTime</span>: <span class="hljs-number">1021.299</span>,
    <span class="hljs-attr">size</span>: <span class="hljs-number">37932</span>,
    <span class="hljs-attr">startTime</span>: <span class="hljs-number">1021.299</span>,
    <span class="hljs-attr">url</span>: <span class="hljs-string">""</span>,
}
</code></pre>
<p>The <code>startTime</code> value is the painting time we need. And <code>element</code> refers to the element being painted during LCP.</p>
<p>The difference between FCP and LCP is: FCP event occurs when any content is painted, while LCP event occurs when the largest content finishes rendering.</p>
<p><a target="_blank" href="https://camo.githubusercontent.com/ed1fac2eb0ad92326cb76a6f71f7c661164d881fe13f18e8de9a1d7f9423ad53/68747470733a2f2f70332d6a75656a696e2e62797465696d672e636f6d2f746f732d636e2d692d6b3375316662706663702f30653634363337616339643234336135383130316438656430316665383836657e74706c762d6b3375316662706663702d77617465726d61726b2e696d6167653f"><img src="https://camo.githubusercontent.com/ed1fac2eb0ad92326cb76a6f71f7c661164d881fe13f18e8de9a1d7f9423ad53/68747470733a2f2f70332d6a75656a696e2e62797465696d672e636f6d2f746f732d636e2d692d6b3375316662706663702f30653634363337616339643234336135383130316438656430316665383836657e74706c762d6b3375316662706663702d77617465726d61726b2e696d6167653f" alt="Comparison of FCP and LCP timing on webpage loading timeline" width="600" height="400" loading="lazy"></a></p>
<p>LCP considers these elements:</p>
<ul>
<li><p><code>&lt;img&gt;</code> elements</p>
</li>
<li><p><code>&lt;image&gt;</code> elements inside <code>&lt;svg&gt;</code></p>
</li>
<li><p><code>&lt;video&gt;</code> elements (using poster images)</p>
</li>
<li><p>Elements with background images loaded via the <a target="_blank" href="https://developer.mozilla.org/docs/Web/CSS/url\(\)"><code>url()</code></a> function (not using <a target="_blank" href="https://developer.mozilla.org/docs/Web/CSS/CSS_Images/Using_CSS_gradients">CSS gradients</a>)</p>
</li>
<li><p>Block-level elements containing text nodes or other inline-level text elements</p>
</li>
</ul>
<h3 id="heading-cls-cumulative-layout-shift">CLS (Cumulative Layout Shift)</h3>
<p>CLS (Cumulative Layout Shift) refers to the cumulative score of all unexpected layout shifts occurring between page load start and when the <a target="_blank" href="https://developer.chrome.com/docs/web-platform/page-lifecycle-api">page's lifecycle state</a> becomes hidden.</p>
<p>An "unexpected layout shift" occurs when elements on a page move around without user interaction. Common examples include:</p>
<ul>
<li><p>A banner or ad suddenly appearing at the top of the page, pushing content down</p>
</li>
<li><p>A font loading and changing the size of text</p>
</li>
<li><p>An image loading without predefined dimensions, expanding and pushing other content out of the way</p>
</li>
<li><p>A button appearing below where a user is about to click, causing them to click the wrong element</p>
</li>
</ul>
<p>These shifts are frustrating for users and lead to accidental clicks, lost reading position, and overall poor user experience. CLS helps quantify this problem so you can identify and fix problematic elements.</p>
<p>The layout shift score is calculated as follows:</p>
<pre><code class="lang-javascript">layout shift score = impact score × distance score
</code></pre>
<p>The <a target="_blank" href="https://github.com/WICG/layout-instability#Impact-Fraction">impact score</a> measures how <em>unstable elements</em> affect the visible area between two frames. The <em>distance score</em> is calculated by taking the greatest distance any <em>unstable element</em> has moved (either horizontally or vertically) and dividing it by the viewport's largest dimension (width or height, whichever is greater).</p>
<p><strong>CLS is the sum of all layout shift scores.</strong></p>
<p>A layout shift occurs when a DOM element changes position between two rendered frames, as shown below:</p>
<p><a target="_blank" href="https://camo.githubusercontent.com/d70250d691a70bb776e1b6748c2b39c7c8ebf17a5e0299bc0bebe064a4d44d91/68747470733a2f2f70362d6a75656a696e2e62797465696d672e636f6d2f746f732d636e2d692d6b3375316662706663702f66663037643431633632343234386131623636633537363166303438326632637e74706c762d6b3375316662706663702d77617465726d61726b2e696d6167653f"><img src="https://camo.githubusercontent.com/d70250d691a70bb776e1b6748c2b39c7c8ebf17a5e0299bc0bebe064a4d44d91/68747470733a2f2f70362d6a75656a696e2e62797465696d672e636f6d2f746f732d636e2d692d6b3375316662706663702f66663037643431633632343234386131623636633537363166303438326632637e74706c762d6b3375316662706663702d77617465726d61726b2e696d6167653f" alt="Layout shift visualization showing element position change" width="600" height="400" loading="lazy"></a></p>
<p><a target="_blank" href="https://camo.githubusercontent.com/826f631d4e71a8eb8f53738821f788c864988cff7fae350b77a43b3b3c22d331/68747470733a2f2f70332d6a75656a696e2e62797465696d672e636f6d2f746f732d636e2d692d6b3375316662706663702f64306435616238313030633934383961393931646430626538653139386166307e74706c762d6b3375316662706663702d77617465726d61726b2e696d6167653f"><img src="https://camo.githubusercontent.com/826f631d4e71a8eb8f53738821f788c864988cff7fae350b77a43b3b3c22d331/68747470733a2f2f70332d6a75656a696e2e62797465696d672e636f6d2f746f732d636e2d692d6b3375316662706663702f64306435616238313030633934383961393931646430626538653139386166307e74706c762d6b3375316662706663702d77617465726d61726b2e696d6167653f" alt="Rectangle movement illustration demonstrating layout shift from top-left to right" width="600" height="400" loading="lazy"></a></p>
<p>In the above diagram, the rectangle moves from the top-left to the right side, counting as one layout shift. In CLS terminology, there's a concept called "session window": one or more individual layout shifts occurring in rapid succession, with less than 1 second between each shift and a maximum window duration of 5 seconds.</p>
<p><a target="_blank" href="https://camo.githubusercontent.com/e2a49800b87d502b7a81f69ebc4259c27ec3c101faac3a65df30cd080ec29a85/68747470733a2f2f70392d6a75656a696e2e62797465696d672e636f6d2f746f732d636e2d692d6b3375316662706663702f63366166326563353639363434303133393632363435383230656662313664337e74706c762d6b3375316662706663702d77617465726d61726b2e696d6167653f"><img src="https://camo.githubusercontent.com/e2a49800b87d502b7a81f69ebc4259c27ec3c101faac3a65df30cd080ec29a85/68747470733a2f2f70392d6a75656a696e2e62797465696d672e636f6d2f746f732d636e2d692d6b3375316662706663702f63366166326563353639363434303133393632363435383230656662313664337e74706c762d6b3375316662706663702d77617465726d61726b2e696d6167653f" alt="Session window concept showing multiple layout shifts grouped within time constraints" width="600" height="400" loading="lazy"></a></p>
<p>For example, in the second session window shown above, there are four layout shifts. Each shift must occur less than 1 second after the previous one, and the time between the first and last shifts must not exceed 5 seconds to qualify as a session window. If these conditions aren't met, it's considered a new session window. This specification comes from extensive experimentation and research by the Chrome team, as detailed in <a target="_blank" href="https://web.dev/blog/evolving-cls">Evolving the CLS metric</a>.</p>
<p>CLS has three calculation methods:</p>
<ol>
<li><p>Cumulative</p>
</li>
<li><p>Average of all session windows</p>
</li>
<li><p>Maximum of all session windows</p>
</li>
</ol>
<h4 id="heading-cumulative"><strong>Cumulative</strong></h4>
<p>This method adds up all layout shift scores from page load start. However, this approach disadvantages long-lived pages - the longer a page is open, the higher the CLS score becomes.</p>
<h4 id="heading-average-of-all-session-windows"><strong>Average of All Session Windows</strong></h4>
<p>This method calculates based on session windows rather than individual layout shifts, taking the average of all session window scores. However, this approach has limitations.</p>
<p><a target="_blank" href="https://camo.githubusercontent.com/b4e2797c9a0b1de2c2f0c291374a53cfcb6b5d84c79c4d045ce2a78de1ed22fc/68747470733a2f2f70362d6a75656a696e2e62797465696d672e636f6d2f746f732d636e2d692d6b3375316662706663702f34326535323038643833663334396462383463663461323731393461353766327e74706c762d6b3375316662706663702d77617465726d61726b2e696d6167653f"><img src="https://camo.githubusercontent.com/b4e2797c9a0b1de2c2f0c291374a53cfcb6b5d84c79c4d045ce2a78de1ed22fc/68747470733a2f2f70362d6a75656a696e2e62797465696d672e636f6d2f746f732d636e2d692d6b3375316662706663702f34326535323038643833663334396462383463663461323731393461353766327e74706c762d6b3375316662706663702d77617465726d61726b2e696d6167653f" alt="Comparison of session windows with different CLS scores showing averaging limitations" width="600" height="400" loading="lazy"></a></p>
<p>As shown above, if the first session window has a high CLS score and the second has a low score, averaging them masks the actual page behavior. The average doesn't reflect that the page had more shifts early on and fewer later.</p>
<h4 id="heading-maximum-of-all-session-windows"><strong>Maximum of All Session Windows</strong></h4>
<p>This is currently the optimal calculation method, using the highest session window score to reflect the worst-case scenario for layout shifts. For more details, see <a target="_blank" href="https://web.dev/blog/evolving-cls">Evolving the CLS metric</a>.</p>
<p>Below is the implementation code for the third calculation method:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">let</span> sessionValue = <span class="hljs-number">0</span>
<span class="hljs-keyword">let</span> sessionEntries = []
<span class="hljs-keyword">const</span> cls = {
    <span class="hljs-attr">subType</span>: <span class="hljs-string">'layout-shift'</span>,
    <span class="hljs-attr">name</span>: <span class="hljs-string">'layout-shift'</span>,
    <span class="hljs-attr">type</span>: <span class="hljs-string">'performance'</span>,
    <span class="hljs-attr">pageURL</span>: getPageURL(),
    <span class="hljs-attr">value</span>: <span class="hljs-number">0</span>,
}

<span class="hljs-keyword">const</span> entryHandler = <span class="hljs-function">(<span class="hljs-params">list</span>) =&gt;</span> {
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> entry <span class="hljs-keyword">of</span> list.getEntries()) {
        <span class="hljs-comment">// Only count layout shifts without recent user input.</span>
        <span class="hljs-keyword">if</span> (!entry.hadRecentInput) {
            <span class="hljs-keyword">const</span> firstSessionEntry = sessionEntries[<span class="hljs-number">0</span>]
            <span class="hljs-keyword">const</span> lastSessionEntry = sessionEntries[sessionEntries.length - <span class="hljs-number">1</span>]

            <span class="hljs-comment">// If the entry occurred less than 1 second after the previous entry and</span>
            <span class="hljs-comment">// less than 5 seconds after the first entry in the session, include the</span>
            <span class="hljs-comment">// entry in the current session. Otherwise, start a new session.</span>
            <span class="hljs-keyword">if</span> (
                sessionValue
                &amp;&amp; entry.startTime - lastSessionEntry.startTime &lt; <span class="hljs-number">1000</span>
                &amp;&amp; entry.startTime - firstSessionEntry.startTime &lt; <span class="hljs-number">5000</span>
            ) {
                sessionValue += entry.value
                sessionEntries.push(formatCLSEntry(entry))
            } <span class="hljs-keyword">else</span> {
                sessionValue = entry.value
                sessionEntries = [formatCLSEntry(entry)]
            }

            <span class="hljs-comment">// If the current session value is larger than the current CLS value,</span>
            <span class="hljs-comment">// update CLS and the entries contributing to it.</span>
            <span class="hljs-keyword">if</span> (sessionValue &gt; cls.value) {
                cls.value = sessionValue
                cls.entries = sessionEntries
                cls.startTime = performance.now()
                lazyReportCache(deepCopy(cls))
            }
        }
    }
}

<span class="hljs-keyword">const</span> observer = <span class="hljs-keyword">new</span> PerformanceObserver(entryHandler)
observer.observe({ <span class="hljs-attr">type</span>: <span class="hljs-string">'layout-shift'</span>, <span class="hljs-attr">buffered</span>: <span class="hljs-literal">true</span> })
</code></pre>
<p>A single layout shift measurement contains the following data:</p>
<pre><code class="lang-javascript">{
  <span class="hljs-attr">duration</span>: <span class="hljs-number">0</span>,
  <span class="hljs-attr">entryType</span>: <span class="hljs-string">"layout-shift"</span>,
  <span class="hljs-attr">hadRecentInput</span>: <span class="hljs-literal">false</span>,
  <span class="hljs-attr">lastInputTime</span>: <span class="hljs-number">0</span>,
  <span class="hljs-attr">name</span>: <span class="hljs-string">""</span>,
  <span class="hljs-attr">sources</span>: (<span class="hljs-number">2</span>) [LayoutShiftAttribution, LayoutShiftAttribution],
  <span class="hljs-attr">startTime</span>: <span class="hljs-number">1176.199999999255</span>,
  <span class="hljs-attr">value</span>: <span class="hljs-number">0.000005752046026677329</span>,
}
</code></pre>
<p>The <code>value</code> field represents the layout shift score.</p>
<h3 id="heading-domcontentloaded-and-load-events"><strong>DOMContentLoaded and Load Events</strong></h3>
<p>The <code>DOMContentLoaded</code> event is triggered when the HTML is fully loaded and parsed, without waiting for CSS, images, and iframes to load.</p>
<p>The <code>load</code> event is triggered when the entire page and all dependent resources such as stylesheets and images have finished loading.</p>
<p>Although these performance metrics are older, they still provide valuable insights into page behavior. Monitoring them remains necessary.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { lazyReportCache } <span class="hljs-keyword">from</span> <span class="hljs-string">'../utils/report'</span>

[<span class="hljs-string">'load'</span>, <span class="hljs-string">'DOMContentLoaded'</span>].forEach(<span class="hljs-function"><span class="hljs-params">type</span> =&gt;</span> onEvent(type))

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">onEvent</span>(<span class="hljs-params">type</span>) </span>{
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">callback</span>(<span class="hljs-params"></span>) </span>{
        lazyReportCache({
            <span class="hljs-attr">type</span>: <span class="hljs-string">'performance'</span>,
            <span class="hljs-attr">subType</span>: type.toLocaleLowerCase(),
            <span class="hljs-attr">startTime</span>: performance.now(),
        })

        <span class="hljs-built_in">window</span>.removeEventListener(type, callback, <span class="hljs-literal">true</span>)
    }

    <span class="hljs-built_in">window</span>.addEventListener(type, callback, <span class="hljs-literal">true</span>)
}
</code></pre>
<h3 id="heading-first-screen-rendering-time"><strong>First Screen Rendering Time</strong></h3>
<p>In most cases, the first screen rendering time can be obtained through the <code>load</code> event. However, there are exceptions, such as asynchronously loaded images and DOM elements.</p>
<pre><code class="lang-javascript">&lt;script&gt;
    <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> {
        <span class="hljs-built_in">document</span>.body.innerHTML = <span class="hljs-string">`
            &lt;div&gt;
                &lt;!-- lots of code... --&gt;
            &lt;/div&gt;
        `</span>
    }, <span class="hljs-number">3000</span>)
&lt;/script&gt;
</code></pre>
<p>In such cases, we cannot obtain the first screen rendering time through the <code>load</code> event. Instead, we need to use <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver/MutationObserver">MutationObserver</a> to get the first screen rendering time. MutationObserver triggers events when the properties of the DOM elements it's monitoring change.</p>
<p>The process of calculating first screen rendering time:</p>
<ol>
<li><p>Use MutationObserver to monitor the document object, triggering events whenever DOM element properties change.</p>
</li>
<li><p>Check if the DOM element is in the first screen. If it is, call <code>performance.now()</code> in the <code>requestAnimationFrame()</code> callback function to get the current time as its rendering time.</p>
</li>
<li><p>Compare the rendering time of the last DOM element with the loading time of all images in the first screen, and use the maximum value as the first screen rendering time.</p>
</li>
</ol>
<h4 id="heading-monitoring-dom"><strong>Monitoring DOM</strong></h4>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> next = <span class="hljs-built_in">window</span>.requestAnimationFrame ? requestAnimationFrame : <span class="hljs-built_in">setTimeout</span>
<span class="hljs-keyword">const</span> ignoreDOMList = [<span class="hljs-string">'STYLE'</span>, <span class="hljs-string">'SCRIPT'</span>, <span class="hljs-string">'LINK'</span>]

observer = <span class="hljs-keyword">new</span> MutationObserver(<span class="hljs-function"><span class="hljs-params">mutationList</span> =&gt;</span> {
    <span class="hljs-keyword">const</span> entry = {
        <span class="hljs-attr">children</span>: [],
    }

    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> mutation <span class="hljs-keyword">of</span> mutationList) {
        <span class="hljs-keyword">if</span> (mutation.addedNodes.length &amp;&amp; isInScreen(mutation.target)) {
             <span class="hljs-comment">// ...</span>
        }
    }

    <span class="hljs-keyword">if</span> (entry.children.length) {
        entries.push(entry)
        next(<span class="hljs-function">() =&gt;</span> {
            entry.startTime = performance.now()
        })
    }
})

observer.observe(<span class="hljs-built_in">document</span>, {
    <span class="hljs-attr">childList</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">subtree</span>: <span class="hljs-literal">true</span>,
})
</code></pre>
<p>The code above monitors DOM changes while filtering out <code>style</code>, <code>script</code>, and <code>link</code> tags.</p>
<h4 id="heading-checking-if-element-is-in-first-screen"><strong>Checking if Element is in First Screen</strong></h4>
<p>A page may have a lot of content, but users can only see one screen at a time. Therefore, when calculating first screen rendering time, we need to limit the scope to content visible in the current screen.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> viewportWidth = <span class="hljs-built_in">window</span>.innerWidth
<span class="hljs-keyword">const</span> viewportHeight = <span class="hljs-built_in">window</span>.innerHeight

<span class="hljs-comment">// Check if DOM element is in screen</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">isInScreen</span>(<span class="hljs-params">dom</span>) </span>{
    <span class="hljs-keyword">const</span> rectInfo = dom.getBoundingClientRect()
    <span class="hljs-keyword">if</span> (
        rectInfo.left &gt;= <span class="hljs-number">0</span> 
        &amp;&amp; rectInfo.left &lt; viewportWidth
        &amp;&amp; rectInfo.top &gt;= <span class="hljs-number">0</span>
        &amp;&amp; rectInfo.top &lt; viewportHeight
    ) {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>
    }
}
</code></pre>
<h4 id="heading-using-requestanimationframe-to-get-dom-rendering-time"><strong>Using</strong> <code>requestAnimationFrame()</code> to Get DOM Rendering Time</h4>
<p>When DOM changes trigger the MutationObserver event, it only means the DOM content can be read, not that it has been painted to the screen.</p>
<p><a target="_blank" href="https://camo.githubusercontent.com/1c118ccd17b38cf8054796f978f864bcb055b3db7659b9c4b84acc349c978531/68747470733a2f2f70332d6a75656a696e2e62797465696d672e636f6d2f746f732d636e2d692d6b3375316662706663702f36373233306335653538666634633639396265373735383635366534353034667e74706c762d6b3375316662706663702d77617465726d61726b2e696d6167653f"><img src="https://camo.githubusercontent.com/1c118ccd17b38cf8054796f978f864bcb055b3db7659b9c4b84acc349c978531/68747470733a2f2f70332d6a75656a696e2e62797465696d672e636f6d2f746f732d636e2d692d6b3375316662706663702f36373233306335653538666634633639396265373735383635366534353034667e74706c762d6b3375316662706663702d77617465726d61726b2e696d6167653f" alt="Browser rendering pipeline showing DOM content loaded but not yet painted" width="600" height="400" loading="lazy"></a></p>
<p>As shown in the image above, when the MutationObserver event is triggered, we can read that <code>document.body</code> already has content, but the left side of the screen hasn't painted anything yet. Therefore, we need to call <code>requestAnimationFrame()</code> to get the current time as the DOM rendering time after the browser has successfully painted.</p>
<h4 id="heading-comparing-with-all-image-loading-times-in-first-screen"><strong>Comparing with All Image Loading Times in First Screen</strong></h4>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getRenderTime</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">let</span> startTime = <span class="hljs-number">0</span>
    entries.forEach(<span class="hljs-function"><span class="hljs-params">entry</span> =&gt;</span> {
        <span class="hljs-keyword">if</span> (entry.startTime &gt; startTime) {
            startTime = entry.startTime
        }
    })

    <span class="hljs-comment">// Need to compare with all image loading times in current page, take the maximum</span>
    <span class="hljs-comment">// Image request time must be less than startTime, response end time must be greater than startTime</span>
    performance.getEntriesByType(<span class="hljs-string">'resource'</span>).forEach(<span class="hljs-function"><span class="hljs-params">item</span> =&gt;</span> {
        <span class="hljs-keyword">if</span> (
            item.initiatorType === <span class="hljs-string">'img'</span>
            &amp;&amp; item.fetchStart &lt; startTime 
            &amp;&amp; item.responseEnd &gt; startTime
        ) {
            startTime = item.responseEnd
        }
    })

    <span class="hljs-keyword">return</span> startTime
}
</code></pre>
<h4 id="heading-optimization"><strong>Optimization</strong></h4>
<p>The current code still needs optimization, with two main points to consider:</p>
<ol>
<li><p>When should we report the rendering time?</p>
</li>
<li><p>How to handle asynchronously added DOM elements?</p>
</li>
</ol>
<p>For the first point, we must report the rendering time after DOM changes stop, which typically happens after the load event triggers. Therefore, we can report at this point.</p>
<p>For the second point, we can report after the LCP event triggers. Whether DOM elements are loaded synchronously or asynchronously, they need to be painted, so we can monitor the LCP event and only allow reporting after it triggers.</p>
<p>Combining these two approaches, we get the following code:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">let</span> isOnLoaded = <span class="hljs-literal">false</span>
executeAfterLoad(<span class="hljs-function">() =&gt;</span> {
    isOnLoaded = <span class="hljs-literal">true</span>
})

<span class="hljs-keyword">let</span> timer
<span class="hljs-keyword">let</span> observer
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">checkDOMChange</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-built_in">clearTimeout</span>(timer)
    timer = <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> {
        <span class="hljs-comment">// Calculate first screen rendering time after load and LCP events trigger and DOM tree stops changing</span>
        <span class="hljs-keyword">if</span> (isOnLoaded &amp;&amp; isLCPDone()) {
            observer &amp;&amp; observer.disconnect()
            lazyReportCache({
                <span class="hljs-attr">type</span>: <span class="hljs-string">'performance'</span>,
                <span class="hljs-attr">subType</span>: <span class="hljs-string">'first-screen-paint'</span>,
                <span class="hljs-attr">startTime</span>: getRenderTime(),
                <span class="hljs-attr">pageURL</span>: getPageURL(),
            })

            entries = <span class="hljs-literal">null</span>
        } <span class="hljs-keyword">else</span> {
            checkDOMChange()
        }
    }, <span class="hljs-number">500</span>)
}
</code></pre>
<p>The <code>checkDOMChange()</code> function is called each time the MutationObserver event triggers and needs to be debounced.</p>
<h3 id="heading-api-request-timing"><strong>API Request Timing</strong></h3>
<p>To monitor API request timing, we need to intercept both XMLHttpRequest and fetch requests.</p>
<p><strong>Monitoring XMLHttpRequest</strong></p>
<pre><code class="lang-javascript">originalProto.open = <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">newOpen</span>(<span class="hljs-params">...args</span>) </span>{
    <span class="hljs-built_in">this</span>.url = args[<span class="hljs-number">1</span>]
    <span class="hljs-built_in">this</span>.method = args[<span class="hljs-number">0</span>]
    originalOpen.apply(<span class="hljs-built_in">this</span>, args)
}

originalProto.send = <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">newSend</span>(<span class="hljs-params">...args</span>) </span>{
    <span class="hljs-built_in">this</span>.startTime = <span class="hljs-built_in">Date</span>.now()

    <span class="hljs-keyword">const</span> onLoadend = <span class="hljs-function">() =&gt;</span> {
        <span class="hljs-built_in">this</span>.endTime = <span class="hljs-built_in">Date</span>.now()
        <span class="hljs-built_in">this</span>.duration = <span class="hljs-built_in">this</span>.endTime - <span class="hljs-built_in">this</span>.startTime

        <span class="hljs-keyword">const</span> { status, duration, startTime, endTime, url, method } = <span class="hljs-built_in">this</span>
        <span class="hljs-keyword">const</span> reportData = {
            status,
            duration,
            startTime,
            endTime,
            url,
            <span class="hljs-attr">method</span>: (method || <span class="hljs-string">'GET'</span>).toUpperCase(),
            <span class="hljs-attr">success</span>: status &gt;= <span class="hljs-number">200</span> &amp;&amp; status &lt; <span class="hljs-number">300</span>,
            <span class="hljs-attr">subType</span>: <span class="hljs-string">'xhr'</span>,
            <span class="hljs-attr">type</span>: <span class="hljs-string">'performance'</span>,
        }

        lazyReportCache(reportData)

        <span class="hljs-built_in">this</span>.removeEventListener(<span class="hljs-string">'loadend'</span>, onLoadend, <span class="hljs-literal">true</span>)
    }

    <span class="hljs-built_in">this</span>.addEventListener(<span class="hljs-string">'loadend'</span>, onLoadend, <span class="hljs-literal">true</span>)
    originalSend.apply(<span class="hljs-built_in">this</span>, args)
}
</code></pre>
<p>To determine if an XML request is successful, we can check if its status code is between 200 and 299. If it is, the request was successful; otherwise, it failed.</p>
<p><strong>Monitoring fetch</strong></p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> originalFetch = <span class="hljs-built_in">window</span>.fetch

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">overwriteFetch</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-built_in">window</span>.fetch = <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">newFetch</span>(<span class="hljs-params">url, config</span>) </span>{
        <span class="hljs-keyword">const</span> startTime = <span class="hljs-built_in">Date</span>.now()
        <span class="hljs-keyword">const</span> reportData = {
            startTime,
            url,
            <span class="hljs-attr">method</span>: (config?.method || <span class="hljs-string">'GET'</span>).toUpperCase(),
            <span class="hljs-attr">subType</span>: <span class="hljs-string">'fetch'</span>,
            <span class="hljs-attr">type</span>: <span class="hljs-string">'performance'</span>,
        }

        <span class="hljs-keyword">return</span> originalFetch(url, config)
        .then(<span class="hljs-function"><span class="hljs-params">res</span> =&gt;</span> {
            reportData.endTime = <span class="hljs-built_in">Date</span>.now()
            reportData.duration = reportData.endTime - reportData.startTime

            <span class="hljs-keyword">const</span> data = res.clone()
            reportData.status = data.status
            reportData.success = data.ok

            lazyReportCache(reportData)

            <span class="hljs-keyword">return</span> res
        })
        .catch(<span class="hljs-function"><span class="hljs-params">err</span> =&gt;</span> {
            reportData.endTime = <span class="hljs-built_in">Date</span>.now()
            reportData.duration = reportData.endTime - reportData.startTime
            reportData.status = <span class="hljs-number">0</span>
            reportData.success = <span class="hljs-literal">false</span>

            lazyReportCache(reportData)

            <span class="hljs-keyword">throw</span> err
        })
    }
}
</code></pre>
<p>For fetch requests, we can determine success by checking the <code>ok</code> field in the response data. If it's <code>true</code>, the request was successful; otherwise, it failed.</p>
<p><strong>Note</strong>: The API request timing we monitor may differ from what's shown in Chrome DevTools. This is because Chrome DevTools shows the time for the entire HTTP request and interface process. But XHR and fetch are asynchronous requests – after the interface request succeeds, the callback function needs to be called. When the event triggers, the callback function is placed in the message queue, and then the browser processes it. There's also a waiting period in between.</p>
<h3 id="heading-resource-loading-time-and-cache-hit-rate"><strong>Resource Loading Time and Cache Hit Rate</strong></h3>
<p>We can monitor <code>resource</code> and <code>navigation</code> events through <code>PerformanceObserver</code>. If the browser doesn't support <code>PerformanceObserver</code>, we can fall back to using <code>performance.getEntriesByType(entryType)</code>.</p>
<p>When the <code>resource</code> event triggers, we can get the corresponding resource list. Each resource object contains the following fields:</p>
<p><a target="_blank" href="https://camo.githubusercontent.com/cf1c72dd2f2e45626e1f601a11b5c1a9a829125d30bc291f4f2bedc8a7d2391c/68747470733a2f2f70392d6a75656a696e2e62797465696d672e636f6d2f746f732d636e2d692d6b3375316662706663702f30653663623330616539613434343762626534336266636666366336633461317e74706c762d6b3375316662706663702d77617465726d61726b2e696d6167653f"><img src="https://camo.githubusercontent.com/cf1c72dd2f2e45626e1f601a11b5c1a9a829125d30bc291f4f2bedc8a7d2391c/68747470733a2f2f70392d6a75656a696e2e62797465696d672e636f6d2f746f732d636e2d692d6b3375316662706663702f30653663623330616539613434343762626534336266636666366336633461317e74706c762d6b3375316662706663702d77617465726d61726b2e696d6167653f" alt="Resource object fields in PerformanceResourceTiming interface" width="600" height="400" loading="lazy"></a></p>
<p>From these fields, we can extract useful information:</p>
<pre><code class="lang-javascript">{
    <span class="hljs-attr">name</span>: entry.name, <span class="hljs-comment">// Resource name</span>
    <span class="hljs-attr">subType</span>: entryType,
    <span class="hljs-attr">type</span>: <span class="hljs-string">'performance'</span>,
    <span class="hljs-attr">sourceType</span>: entry.initiatorType, <span class="hljs-comment">// Resource type</span>
    <span class="hljs-attr">duration</span>: entry.duration, <span class="hljs-comment">// Resource loading duration</span>
    <span class="hljs-attr">dns</span>: entry.domainLookupEnd - entry.domainLookupStart, <span class="hljs-comment">// DNS duration</span>
    <span class="hljs-attr">tcp</span>: entry.connectEnd - entry.connectStart, <span class="hljs-comment">// TCP connection duration</span>
    <span class="hljs-attr">redirect</span>: entry.redirectEnd - entry.redirectStart, <span class="hljs-comment">// Redirect duration</span>
    <span class="hljs-attr">ttfb</span>: entry.responseStart, <span class="hljs-comment">// Time to first byte</span>
    <span class="hljs-attr">protocol</span>: entry.nextHopProtocol, <span class="hljs-comment">// Request protocol</span>
    <span class="hljs-attr">responseBodySize</span>: entry.encodedBodySize, <span class="hljs-comment">// Response body size</span>
    <span class="hljs-attr">responseHeaderSize</span>: entry.transferSize - entry.encodedBodySize, <span class="hljs-comment">// Response header size</span>
    <span class="hljs-attr">resourceSize</span>: entry.decodedBodySize, <span class="hljs-comment">// Resource size after decompression</span>
    <span class="hljs-attr">isCache</span>: isCache(entry), <span class="hljs-comment">// Whether cache was hit</span>
    <span class="hljs-attr">startTime</span>: performance.now(),
}
</code></pre>
<h4 id="heading-determining-if-resource-hit-cache">Determining if Resource Hit Cache</h4>
<p>Among these resource objects, there's a <code>transferSize</code> field that represents the size of the resource being fetched, including response header fields and response data size. If this value is 0, it means the resource was read directly from cache (forced cache). If this value is not 0 but the <code>encodedBodySize</code> field is 0, it means it used negotiated cache (<code>encodedBodySize</code> represents the size of the response data body).</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">isCache</span>(<span class="hljs-params">entry</span>) </span>{
    <span class="hljs-comment">// Read directly from cache or 304</span>
    <span class="hljs-keyword">return</span> entry.transferSize === <span class="hljs-number">0</span> || (entry.transferSize !== <span class="hljs-number">0</span> &amp;&amp; entry.encodedBodySize === <span class="hljs-number">0</span>)
}
</code></pre>
<p>If it doesn't meet the above conditions, it means the cache was not hit. Then we can calculate the cache hit rate by dividing <code>all cached data/total data</code>.</p>
<h3 id="heading-browser-backforward-cache-bfc"><strong>Browser Back/Forward Cache (BFC)</strong></h3>
<p>BFC is a memory cache that saves the entire page in memory. When users navigate back, they can see the entire page immediately without refreshing. According to the article <a target="_blank" href="https://web.dev/bfcache/">bfcache</a>, Firefox and Safari have always supported BFC, while Chrome only supports it in high-version mobile browsers. But when I tested it, only Safari supported it – my Firefox version might have been different.</p>
<p>Still, BFC also has drawbacks. When users navigate back and restore the page from BFC, the original page's code won't execute again. For this reason, browsers provide a <code>pageshow</code> event where we can put code that needs to be executed again.</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">window</span>.addEventListener(<span class="hljs-string">'pageshow'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">event</span>) </span>{
  <span class="hljs-comment">// If this property is true, it means the page was restored from BFC</span>
  <span class="hljs-keyword">if</span> (event.persisted) {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'This page was restored from the bfcache.'</span>);
  } <span class="hljs-keyword">else</span> {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'This page was loaded normally.'</span>);
  }
});
</code></pre>
<p>For pages restored from BFC, we also need to collect their FP, FCP, LCP, and other timing metrics.</p>
<pre><code class="lang-javascript">onBFCacheRestore(<span class="hljs-function"><span class="hljs-params">event</span> =&gt;</span> {
    requestAnimationFrame(<span class="hljs-function">() =&gt;</span> {
        [<span class="hljs-string">'first-paint'</span>, <span class="hljs-string">'first-contentful-paint'</span>].forEach(<span class="hljs-function"><span class="hljs-params">type</span> =&gt;</span> {
            lazyReportCache({
                <span class="hljs-attr">startTime</span>: performance.now() - event.timeStamp,
                <span class="hljs-attr">name</span>: type,
                <span class="hljs-attr">subType</span>: type,
                <span class="hljs-attr">type</span>: <span class="hljs-string">'performance'</span>,
                <span class="hljs-attr">pageURL</span>: getPageURL(),
                <span class="hljs-attr">bfc</span>: <span class="hljs-literal">true</span>,
            })
        })
    })
})
</code></pre>
<p>The code above is easy to understand. After the <code>pageshow</code> event triggers, we subtract the event timestamp from the current time – this time difference is the rendering time of the performance metrics.</p>
<p><strong>Note</strong>: For pages restored from BFC, these performance metrics usually have very small values, around 10 ms. This means that we need to add an identifier field <code>bfc: true</code> so we can ignore them when doing performance statistics.</p>
<h3 id="heading-fps"><strong>FPS</strong></h3>
<p>We can calculate the current page's FPS using <code>requestAnimationFrame()</code>.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> next = <span class="hljs-built_in">window</span>.requestAnimationFrame 
    ? requestAnimationFrame : <span class="hljs-function">(<span class="hljs-params">callback</span>) =&gt;</span> { <span class="hljs-built_in">setTimeout</span>(callback, <span class="hljs-number">1000</span> / <span class="hljs-number">60</span>) }

<span class="hljs-keyword">const</span> frames = []

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">fps</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">let</span> frame = <span class="hljs-number">0</span>
    <span class="hljs-keyword">let</span> lastSecond = <span class="hljs-built_in">Date</span>.now()

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">calculateFPS</span>(<span class="hljs-params"></span>) </span>{
        frame++
        <span class="hljs-keyword">const</span> now = <span class="hljs-built_in">Date</span>.now()
        <span class="hljs-keyword">if</span> (lastSecond + <span class="hljs-number">1000</span> &lt;= now) {
            <span class="hljs-comment">// Since now - lastSecond is in milliseconds, frame needs to be multiplied by 1000</span>
            <span class="hljs-keyword">const</span> fps = <span class="hljs-built_in">Math</span>.round((frame * <span class="hljs-number">1000</span>) / (now - lastSecond))
            frames.push(fps)

            frame = <span class="hljs-number">0</span>
            lastSecond = now
        }

        <span class="hljs-comment">// Avoid reporting too frequently, cache a certain amount before reporting</span>
        <span class="hljs-keyword">if</span> (frames.length &gt;= <span class="hljs-number">60</span>) {
            report(deepCopy({
                frames,
                <span class="hljs-attr">type</span>: <span class="hljs-string">'performace'</span>,
                <span class="hljs-attr">subType</span>: <span class="hljs-string">'fps'</span>,
            }))

            frames.length = <span class="hljs-number">0</span>
        }

        next(calculateFPS)
    }

    calculateFPS()
}
</code></pre>
<p>The code logic is as follows:</p>
<p>First record an initial time, then each time <code>requestAnimationFrame()</code> triggers, increment the frame count by 1. After one second passes, we can get the current frame rate by dividing <code>frame count/elapsed time</code>.</p>
<p>When three consecutive FPS values below 20 appear, we can determine that the page has become unresponsive. This technique is based on the observation that smooth animations require at least 20 FPS to appear fluid to users.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">isBlocking</span>(<span class="hljs-params">fpsList, below = <span class="hljs-number">20</span>, last = <span class="hljs-number">3</span></span>) </span>{
    <span class="hljs-keyword">let</span> count = <span class="hljs-number">0</span>
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; fpsList.length; i++) {
        <span class="hljs-keyword">if</span> (fpsList[i] &amp;&amp; fpsList[i] &lt; below) {
            count++
        } <span class="hljs-keyword">else</span> {
            count = <span class="hljs-number">0</span>
        }

        <span class="hljs-keyword">if</span> (count &gt;= last) {
            <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>
        }
    }

    <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>
}
</code></pre>
<h3 id="heading-vue-router-change-rendering-time"><strong>Vue Router Change Rendering Time</strong></h3>
<p>We already know how to calculate first screen rendering time, but how do we calculate the page rendering time caused by route changes in SPA applications? This article uses Vue as an example to explain my approach.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">onVueRouter</span>(<span class="hljs-params">Vue, router</span>) </span>{
    <span class="hljs-keyword">let</span> isFirst = <span class="hljs-literal">true</span>
    <span class="hljs-keyword">let</span> startTime
    router.beforeEach(<span class="hljs-function">(<span class="hljs-params">to, <span class="hljs-keyword">from</span>, next</span>) =&gt;</span> {
        <span class="hljs-comment">// First page load already has other rendering time metrics available</span>
        <span class="hljs-keyword">if</span> (isFirst) {
            isFirst = <span class="hljs-literal">false</span>
            <span class="hljs-keyword">return</span> next()
        }

        <span class="hljs-comment">// Add a new field to router to indicate whether to calculate rendering time</span>
        <span class="hljs-comment">// Only needed for route changes</span>
        router.needCalculateRenderTime = <span class="hljs-literal">true</span>
        startTime = performance.now()

        next()
    })

    <span class="hljs-keyword">let</span> timer
    Vue.mixin({
        mounted() {
            <span class="hljs-keyword">if</span> (!router.needCalculateRenderTime) <span class="hljs-keyword">return</span>

            <span class="hljs-built_in">this</span>.$nextTick(<span class="hljs-function">() =&gt;</span> {
                <span class="hljs-comment">// Code that only runs after the entire view has been rendered</span>
                <span class="hljs-keyword">const</span> now = performance.now()
                <span class="hljs-built_in">clearTimeout</span>(timer)

                timer = <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> {
                    router.needCalculateRenderTime = <span class="hljs-literal">false</span>
                    lazyReportCache({
                        <span class="hljs-attr">type</span>: <span class="hljs-string">'performance'</span>,
                        <span class="hljs-attr">subType</span>: <span class="hljs-string">'vue-router-change-paint'</span>,
                        <span class="hljs-attr">duration</span>: now - startTime,
                        <span class="hljs-attr">startTime</span>: now,
                        <span class="hljs-attr">pageURL</span>: getPageURL(),
                    })
                }, <span class="hljs-number">1000</span>)
            })
        },
    })
}
</code></pre>
<p>The code logic is as follows:</p>
<ol>
<li><p>Monitor route hooks – when route changes occur, the <code>router.beforeEach()</code> hook triggers. In this hook's callback function, record the current time as the rendering start time.</p>
</li>
<li><p>Use <code>Vue.mixin()</code> to inject a function into all components' <code>mounted()</code> hooks. Each function executes a debounced function.</p>
</li>
<li><p>When the last component's <code>mounted()</code> triggers, it means all components under this route have been mounted. We can get the rendering time in the <code>this.$nextTick()</code> callback function.</p>
</li>
</ol>
<p>Also, we need to consider another case. When not changing routes, there may also be component changes, in which case we shouldn't calculate rendering time in these components' <code>mounted()</code> hooks. Therefore, we need to add a <code>needCalculateRenderTime</code> field - set it to true when changing routes to indicate that rendering time can be calculated.</p>
<h2 id="heading-error-data-collection"><strong>Error Data Collection</strong></h2>
<p>Error monitoring is a critical aspect of frontend monitoring that helps identify issues users encounter while interacting with your application. By tracking and analyzing these errors, you can proactively fix bugs before they affect more users, improving both user experience and application reliability.</p>
<p>In this section, we'll explore how to capture various types of errors including resource loading failures, JavaScript runtime errors, unhandled promises, and framework-specific errors.</p>
<h3 id="heading-resource-loading-errors"><strong>Resource Loading Errors</strong></h3>
<p>Resource loading errors occur when the browser fails to load external resources like images, stylesheets, scripts, and fonts. These errors can significantly impact user experience by causing missing content, broken layouts, or even preventing core functionality from working.</p>
<p>Using <code>addEventListener()</code> to monitor the error event can capture resource loading failure errors.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Capture resource loading failure errors js css img...</span>
<span class="hljs-built_in">window</span>.addEventListener(<span class="hljs-string">'error'</span>, <span class="hljs-function"><span class="hljs-params">e</span> =&gt;</span> {
    <span class="hljs-keyword">const</span> target = e.target
    <span class="hljs-keyword">if</span> (!target) <span class="hljs-keyword">return</span>

    <span class="hljs-keyword">if</span> (target.src || target.href) {
        <span class="hljs-keyword">const</span> url = target.src || target.href
        lazyReportCache({
            url,
            <span class="hljs-attr">type</span>: <span class="hljs-string">'error'</span>,
            <span class="hljs-attr">subType</span>: <span class="hljs-string">'resource'</span>,
            <span class="hljs-attr">startTime</span>: e.timeStamp,
            <span class="hljs-attr">html</span>: target.outerHTML,
            <span class="hljs-attr">resourceType</span>: target.tagName,
            <span class="hljs-attr">paths</span>: e.path.map(<span class="hljs-function"><span class="hljs-params">item</span> =&gt;</span> item.tagName).filter(<span class="hljs-built_in">Boolean</span>),
            <span class="hljs-attr">pageURL</span>: getPageURL(),
        })
    }
}, <span class="hljs-literal">true</span>)
</code></pre>
<p>This code listens for the global <code>error</code> event with the capture option set to true, which allows it to catch errors from resource elements like <code>&lt;img&gt;</code>, <code>&lt;link&gt;</code>, and <code>&lt;script&gt;</code>. When a resource fails to load, it collects important information including:</p>
<ul>
<li><p>The URL of the failed resource</p>
</li>
<li><p>The element type (img, link, script)</p>
</li>
<li><p>The HTML of the element that failed</p>
</li>
<li><p>The DOM path to the element</p>
</li>
<li><p>The page URL where the error occurred</p>
</li>
</ul>
<p>With this data, you can identify which resources are failing most frequently, prioritize fixes, and implement fallback strategies for critical resources.</p>
<h3 id="heading-javascript-errors"><strong>JavaScript Errors</strong></h3>
<p>JavaScript errors occur during script execution and can prevent features from working properly. These include syntax errors, reference errors, type errors, and other runtime exceptions.</p>
<p>Using <code>window.onerror</code> can monitor JavaScript errors.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Monitor JavaScript errors</span>
<span class="hljs-built_in">window</span>.onerror = <span class="hljs-function">(<span class="hljs-params">msg, url, line, column, error</span>) =&gt;</span> {
    lazyReportCache({
        msg,
        line,
        column,
        <span class="hljs-attr">error</span>: error.stack,
        <span class="hljs-attr">subType</span>: <span class="hljs-string">'js'</span>,
        <span class="hljs-attr">pageURL</span>: url,
        <span class="hljs-attr">type</span>: <span class="hljs-string">'error'</span>,
        <span class="hljs-attr">startTime</span>: performance.now(),
    })
}
</code></pre>
<p>This handler captures detailed information about JavaScript errors:</p>
<ul>
<li><p>The error message</p>
</li>
<li><p>The file URL where the error occurred</p>
</li>
<li><p>The line and column number of the error</p>
</li>
<li><p>The full error stack trace</p>
</li>
</ul>
<p>This information is invaluable for debugging and fixing issues, especially in production environments where direct debugging isn't possible. By analyzing these errors, you can identify patterns and prioritize fixes for the most common or impactful issues.</p>
<h3 id="heading-promise-errors"><strong>Promise Errors</strong></h3>
<p>Modern JavaScript applications heavily use Promises for asynchronous operations. When a Promise rejection isn't handled with <code>.catch()</code> or a second argument to <code>.then()</code>, it results in an unhandled rejection which can cause silent failures.</p>
<p>Using <code>addEventListener()</code> to monitor the unhandledrejection event can capture unhandled promise errors.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Monitor promise errors - drawback is can't get column data</span>
<span class="hljs-built_in">window</span>.addEventListener(<span class="hljs-string">'unhandledrejection'</span>, <span class="hljs-function"><span class="hljs-params">e</span> =&gt;</span> {
    lazyReportCache({
        <span class="hljs-attr">reason</span>: e.reason?.stack,
        <span class="hljs-attr">subType</span>: <span class="hljs-string">'promise'</span>,
        <span class="hljs-attr">type</span>: <span class="hljs-string">'error'</span>,
        <span class="hljs-attr">startTime</span>: e.timeStamp,
        <span class="hljs-attr">pageURL</span>: getPageURL(),
    })
})
</code></pre>
<p>This code captures unhandled Promise rejections and reports:</p>
<ul>
<li><p>The rejection reason (usually an error object with a stack trace)</p>
</li>
<li><p>The timestamp when the rejection occurred</p>
</li>
<li><p>The page URL where the rejection happened</p>
</li>
</ul>
<p>Tracking unhandled Promise rejections is particularly important for asynchronous operations like API calls, where errors might otherwise go unnoticed. By monitoring these rejections, you can ensure that all asynchronous errors are properly handled and resolved.</p>
<h3 id="heading-sourcemap"><strong>Sourcemap</strong></h3>
<p>Generally, production environment code is minified, and sourcemap files are not uploaded to production. Therefore, error messages in production environment code are difficult to read. For this reason, we can use <a target="_blank" href="https://github.com/mozilla/source-map">source-map</a> to restore these minified code error messages.</p>
<p>When code errors occur, we can get the corresponding filename, line number, and column number:</p>
<pre><code class="lang-javascript">{
    <span class="hljs-attr">line</span>: <span class="hljs-number">1</span>,
    <span class="hljs-attr">column</span>: <span class="hljs-number">17</span>,
    <span class="hljs-attr">file</span>: <span class="hljs-string">'https:/www.xxx.com/bundlejs'</span>,
}
</code></pre>
<p>Then call the following code to restore:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">parse</span>(<span class="hljs-params">error</span>) </span>{
    <span class="hljs-keyword">const</span> mapObj = <span class="hljs-built_in">JSON</span>.parse(getMapFileContent(error.url))
    <span class="hljs-keyword">const</span> consumer = <span class="hljs-keyword">await</span> <span class="hljs-keyword">new</span> sourceMap.SourceMapConsumer(mapObj)
    <span class="hljs-comment">// Remove ./ from webpack://source-map-demo/./src/index.js file</span>
    <span class="hljs-keyword">const</span> sources = mapObj.sources.map(<span class="hljs-function"><span class="hljs-params">item</span> =&gt;</span> format(item))
    <span class="hljs-comment">// Get original line and column numbers and source file based on minified error information</span>
    <span class="hljs-keyword">const</span> originalInfo = consumer.originalPositionFor({ <span class="hljs-attr">line</span>: error.line, <span class="hljs-attr">column</span>: error.column })
    <span class="hljs-comment">// sourcesContent contains the original source code of each file before minification, find corresponding source code by filename</span>
    <span class="hljs-keyword">const</span> originalFileContent = mapObj.sourcesContent[sources.indexOf(originalInfo.source)]
    <span class="hljs-keyword">return</span> {
        <span class="hljs-attr">file</span>: originalInfo.source,
        <span class="hljs-attr">content</span>: originalFileContent,
        <span class="hljs-attr">line</span>: originalInfo.line,
        <span class="hljs-attr">column</span>: originalInfo.column,
        <span class="hljs-attr">msg</span>: error.msg,
        <span class="hljs-attr">error</span>: error.error
    }
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">format</span>(<span class="hljs-params">item</span>) </span>{
    <span class="hljs-keyword">return</span> item.replace(<span class="hljs-regexp">/(\.\/)*/g</span>, <span class="hljs-string">''</span>)
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getMapFileContent</span>(<span class="hljs-params">url</span>) </span>{
    <span class="hljs-keyword">return</span> fs.readFileSync(path.resolve(__dirname, <span class="hljs-string">`./maps/<span class="hljs-subst">${url.split(<span class="hljs-string">'/'</span>).pop()}</span>.map`</span>), <span class="hljs-string">'utf-8'</span>)
}
</code></pre>
<p>Each time the project is built, if sourcemap is enabled, each JS file will have a corresponding map file.</p>
<pre><code class="lang-javascript">bundle.js
bundle.js.map
</code></pre>
<p>At this point, the JS file is placed on the static server for user access, while the map file is stored on the server for error message restoration. The <code>source-map</code> library can restore error messages from minified code to their original state. For example, if the minified code error location is <code>line 1, column 47</code>, the restored location might be <code>line 4, column 10</code>. Besides location information, we can also get the original source code.</p>
<p><a target="_blank" href="https://camo.githubusercontent.com/be3d758960a8e8e494066820ef5708d8d06bda3df1a380bab37e007a91d16003/68747470733a2f2f70332d6a75656a696e2e62797465696d672e636f6d2f746f732d636e2d692d6b3375316662706663702f62316336623565656262376234656635396434646436616436313334383465627e74706c762d6b3375316662706663702d77617465726d61726b2e696d6167653f"><img src="https://camo.githubusercontent.com/be3d758960a8e8e494066820ef5708d8d06bda3df1a380bab37e007a91d16003/68747470733a2f2f70332d6a75656a696e2e62797465696d672e636f6d2f746f732d636e2d692d6b3375316662706663702f62316336623565656262376234656635396434646436616436313334383465627e74706c762d6b3375316662706663702d77617465726d61726b2e696d6167653f" alt="Sourcemap error restoration example showing minified vs original code" width="600" height="400" loading="lazy"></a></p>
<p>The image above shows an example of code error restoration. Since this part doesn't belong to the SDK's scope, I created another <a target="_blank" href="https://github.com/woai3c/source-map-demo">repository</a> to handle this. Feel free to check it out if you're interested.</p>
<h3 id="heading-vue-errors"><strong>Vue Errors</strong></h3>
<p>Using <code>window.onerror</code> cannot capture Vue errors – we need to use Vue's provided API for monitoring.</p>
<pre><code class="lang-javascript">Vue.config.errorHandler = <span class="hljs-function">(<span class="hljs-params">err, vm, info</span>) =&gt;</span> {
    <span class="hljs-comment">// Print error information to console</span>
    <span class="hljs-built_in">console</span>.error(err)

    lazyReportCache({
        info,
        <span class="hljs-attr">error</span>: err.stack,
        <span class="hljs-attr">subType</span>: <span class="hljs-string">'vue'</span>,
        <span class="hljs-attr">type</span>: <span class="hljs-string">'error'</span>,
        <span class="hljs-attr">startTime</span>: performance.now(),
        <span class="hljs-attr">pageURL</span>: getPageURL(),
    })
}
</code></pre>
<h2 id="heading-behavior-data-collection"><strong>Behavior Data Collection</strong></h2>
<p>Understanding how users interact with your application is crucial for optimizing user experience, improving engagement, and driving business goals. Behavior monitoring tracks user actions, navigation patterns, and engagement metrics to provide insights into how your application is actually being used.</p>
<p>In this section, we'll explore how to collect key behavioral metrics that can help you make data-driven decisions to improve your application.</p>
<h3 id="heading-pv-and-uv"><strong>PV and UV</strong></h3>
<p>PV (Page View) is the number of page views, while UV (Unique Visitor) is the number of unique users visiting. PV counts each page visit, while UV only counts once per user per day.</p>
<p><strong>Why this matters</strong>: PV and UV metrics help you understand your application's traffic patterns. A high PV-to-UV ratio indicates users are viewing multiple pages, suggesting good engagement. Tracking these metrics over time helps you identify growth trends, seasonal patterns, and the impact of marketing campaigns or feature releases.</p>
<p>For the front end, we just need to report PV each time a page is entered. UV statistics are handled on the server side, mainly analyzing reported data to calculate UV.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">pv</span>(<span class="hljs-params"></span>) </span>{
    lazyReportCache({
        <span class="hljs-attr">type</span>: <span class="hljs-string">'behavior'</span>,
        <span class="hljs-attr">subType</span>: <span class="hljs-string">'pv'</span>,
        <span class="hljs-attr">startTime</span>: performance.now(),
        <span class="hljs-attr">pageURL</span>: getPageURL(),
        <span class="hljs-attr">referrer</span>: <span class="hljs-built_in">document</span>.referrer,
        <span class="hljs-attr">uuid</span>: getUUID(),
    })
}
</code></pre>
<p>You can use this data to:</p>
<ul>
<li><p>Track which pages are most popular</p>
</li>
<li><p>Identify underperforming pages that need improvement</p>
</li>
<li><p>Analyze user flow through your application</p>
</li>
<li><p>Measure the effectiveness of new features or content</p>
</li>
</ul>
<h3 id="heading-page-stay-duration"><strong>Page Stay Duration</strong></h3>
<p>To get the stay duration, you just need to record an initial time when users enter the page, then subtract the initial time from the current time when users leave the page. This calculation logic can be placed in the <code>beforeunload</code> event.</p>
<p><strong>Why this matters</strong>: Page stay duration indicates how engaging your content is. Longer durations typically suggest users find the content valuable, while very short durations might indicate confusion, irrelevant content, or usability issues. This metric helps you identify which pages effectively capture user attention and which ones need improvement.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">pageAccessDuration</span>(<span class="hljs-params"></span>) </span>{
    onBeforeunload(<span class="hljs-function">() =&gt;</span> {
        report({
            <span class="hljs-attr">type</span>: <span class="hljs-string">'behavior'</span>,
            <span class="hljs-attr">subType</span>: <span class="hljs-string">'page-access-duration'</span>,
            <span class="hljs-attr">startTime</span>: performance.now(),
            <span class="hljs-attr">pageURL</span>: getPageURL(),
            <span class="hljs-attr">uuid</span>: getUUID(),
        }, <span class="hljs-literal">true</span>)
    })
}
</code></pre>
<p>With page stay duration data, you can:</p>
<ul>
<li><p>Identify engaging vs. problematic content</p>
</li>
<li><p>Set benchmarks for content performance</p>
</li>
<li><p>Detect potential usability issues (extremely short durations)</p>
</li>
<li><p>Measure the effectiveness of content updates or redesigns</p>
</li>
</ul>
<h3 id="heading-page-access-depth"><strong>Page Access Depth</strong></h3>
<p>Recording page access depth is very useful. For example, for different activity pages a and b, if page a has an average access depth of 50% and page b has 80%, it indicates that page b is more popular with users. Based on this, we can make targeted improvements to page a.</p>
<p><strong>Why this matters</strong>: Access depth measures how far users scroll down a page, revealing whether they're viewing all your content or abandoning it partway through. This metric helps identify content engagement patterns and potential issues with content structure or page length.</p>
<p>Also, we can use access depth and stay duration to identify e-commerce order fraud. For example, if someone enters the page and immediately scrolls to the bottom, then waits a while before purchasing, while another person slowly scrolls down the page before purchasing. Even though they have the same stay duration, the first person is more likely to be committing fraud.</p>
<p>The page access depth calculation process is slightly more complex:</p>
<ol>
<li><p>When users enter the page, record the current time, scrollTop value, viewport height, and total page height.</p>
</li>
<li><p>When users scroll the page, the <code>scroll</code> event triggers. In the callback function, use the data from point 1 to calculate page access depth and stay duration.</p>
</li>
<li><p>When users stop scrolling at a certain point to continue viewing the page, record the current time, scrollTop value, viewport height, and total page height.</p>
</li>
<li><p>Repeat point 2...</p>
</li>
</ol>
<p>Here's the specific code:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">let</span> timer
<span class="hljs-keyword">let</span> startTime = <span class="hljs-number">0</span>
<span class="hljs-keyword">let</span> hasReport = <span class="hljs-literal">false</span>
<span class="hljs-keyword">let</span> pageHeight = <span class="hljs-number">0</span>
<span class="hljs-keyword">let</span> scrollTop = <span class="hljs-number">0</span>
<span class="hljs-keyword">let</span> viewportHeight = <span class="hljs-number">0</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">pageAccessHeight</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-built_in">window</span>.addEventListener(<span class="hljs-string">'scroll'</span>, onScroll)

    onBeforeunload(<span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">const</span> now = performance.now()
        report({
            <span class="hljs-attr">startTime</span>: now,
            <span class="hljs-attr">duration</span>: now - startTime,
            <span class="hljs-attr">type</span>: <span class="hljs-string">'behavior'</span>,
            <span class="hljs-attr">subType</span>: <span class="hljs-string">'page-access-height'</span>,
            <span class="hljs-attr">pageURL</span>: getPageURL(),
            <span class="hljs-attr">value</span>: toPercent((scrollTop + viewportHeight) / pageHeight),
            <span class="hljs-attr">uuid</span>: getUUID(),
        }, <span class="hljs-literal">true</span>)
    })

    <span class="hljs-comment">// Initialize and record current access height and time after page loads</span>
    executeAfterLoad(<span class="hljs-function">() =&gt;</span> {
        startTime = performance.now()
        pageHeight = <span class="hljs-built_in">document</span>.documentElement.scrollHeight || <span class="hljs-built_in">document</span>.body.scrollHeight
        scrollTop = <span class="hljs-built_in">document</span>.documentElement.scrollTop || <span class="hljs-built_in">document</span>.body.scrollTop
        viewportHeight = <span class="hljs-built_in">window</span>.innerHeight
    })
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">onScroll</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-built_in">clearTimeout</span>(timer)
    <span class="hljs-keyword">const</span> now = performance.now()

    <span class="hljs-keyword">if</span> (!hasReport) {
        hasReport = <span class="hljs-literal">true</span>
        lazyReportCache({
            <span class="hljs-attr">startTime</span>: now,
            <span class="hljs-attr">duration</span>: now - startTime,
            <span class="hljs-attr">type</span>: <span class="hljs-string">'behavior'</span>,
            <span class="hljs-attr">subType</span>: <span class="hljs-string">'page-access-height'</span>,
            <span class="hljs-attr">pageURL</span>: getPageURL(),
            <span class="hljs-attr">value</span>: toPercent((scrollTop + viewportHeight) / pageHeight),
            <span class="hljs-attr">uuid</span>: getUUID(),
        })
    }

    timer = <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> {
        hasReport = <span class="hljs-literal">false</span>
        startTime = now
        pageHeight = <span class="hljs-built_in">document</span>.documentElement.scrollHeight || <span class="hljs-built_in">document</span>.body.scrollHeight
        scrollTop = <span class="hljs-built_in">document</span>.documentElement.scrollTop || <span class="hljs-built_in">document</span>.body.scrollTop
        viewportHeight = <span class="hljs-built_in">window</span>.innerHeight        
    }, <span class="hljs-number">500</span>)
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">toPercent</span>(<span class="hljs-params">val</span>) </span>{
    <span class="hljs-keyword">if</span> (val &gt;= <span class="hljs-number">1</span>) <span class="hljs-keyword">return</span> <span class="hljs-string">'100%'</span>
    <span class="hljs-keyword">return</span> (val * <span class="hljs-number">100</span>).toFixed(<span class="hljs-number">2</span>) + <span class="hljs-string">'%'</span>
}
</code></pre>
<p>With page access depth data, you can:</p>
<ul>
<li><p>Identify where users lose interest in your content</p>
</li>
<li><p>Optimize content placement (put important elements where users actually look)</p>
</li>
<li><p>Improve long-form content structure with better hierarchy</p>
</li>
<li><p>Detect unusual user behavior patterns that might indicate fraud or bots</p>
</li>
</ul>
<h3 id="heading-user-clicks"><strong>User Clicks</strong></h3>
<p>Using <code>addEventListener()</code> to monitor <code>mousedown</code> and <code>touchstart</code> events, we can collect information about each click area's size, click coordinates' specific position in the page, clicked element's content, and other information.</p>
<p><strong>Why this matters</strong>: Click tracking reveals what elements users interact with most frequently, helping you understand user interests and optimize UI element placement. It also helps identify usability issues where users might be clicking on non-interactive elements expecting a response.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">onClick</span>(<span class="hljs-params"></span>) </span>{
    [<span class="hljs-string">'mousedown'</span>, <span class="hljs-string">'touchstart'</span>].forEach(<span class="hljs-function"><span class="hljs-params">eventType</span> =&gt;</span> {
        <span class="hljs-keyword">let</span> timer
        <span class="hljs-built_in">window</span>.addEventListener(eventType, <span class="hljs-function"><span class="hljs-params">event</span> =&gt;</span> {
            <span class="hljs-built_in">clearTimeout</span>(timer)
            timer = <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> {
                <span class="hljs-keyword">const</span> target = event.target
                <span class="hljs-keyword">const</span> { top, left } = target.getBoundingClientRect()

                lazyReportCache({
                    top,
                    left,
                    eventType,
                    <span class="hljs-attr">pageHeight</span>: <span class="hljs-built_in">document</span>.documentElement.scrollHeight || <span class="hljs-built_in">document</span>.body.scrollHeight,
                    <span class="hljs-attr">scrollTop</span>: <span class="hljs-built_in">document</span>.documentElement.scrollTop || <span class="hljs-built_in">document</span>.body.scrollTop,
                    <span class="hljs-attr">type</span>: <span class="hljs-string">'behavior'</span>,
                    <span class="hljs-attr">subType</span>: <span class="hljs-string">'click'</span>,
                    <span class="hljs-attr">target</span>: target.tagName,
                    <span class="hljs-attr">paths</span>: event.path?.map(<span class="hljs-function"><span class="hljs-params">item</span> =&gt;</span> item.tagName).filter(<span class="hljs-built_in">Boolean</span>),
                    <span class="hljs-attr">startTime</span>: event.timeStamp,
                    <span class="hljs-attr">pageURL</span>: getPageURL(),
                    <span class="hljs-attr">outerHTML</span>: target.outerHTML,
                    <span class="hljs-attr">innerHTML</span>: target.innerHTML,
                    <span class="hljs-attr">width</span>: target.offsetWidth,
                    <span class="hljs-attr">height</span>: target.offsetHeight,
                    <span class="hljs-attr">viewport</span>: {
                        <span class="hljs-attr">width</span>: <span class="hljs-built_in">window</span>.innerWidth,
                        <span class="hljs-attr">height</span>: <span class="hljs-built_in">window</span>.innerHeight,
                    },
                    <span class="hljs-attr">uuid</span>: getUUID(),
                })
            }, <span class="hljs-number">500</span>)
        })
    })
}
</code></pre>
<p>With this click data, you can:</p>
<ul>
<li><p>Create heatmaps showing where users click most frequently</p>
</li>
<li><p>Identify non-functional elements users try to interact with</p>
</li>
<li><p>Optimize button placement and size for better conversion</p>
</li>
<li><p>Detect rage clicks (multiple rapid clicks in the same area) indicating user frustration</p>
</li>
</ul>
<h3 id="heading-page-navigation"><strong>Page Navigation</strong></h3>
<p>Using <code>addEventListener()</code> to monitor <code>popstate</code> and <code>hashchange</code> page navigation events allows you to track how users navigate through your application.</p>
<p><strong>Why this matters</strong>: Navigation tracking helps you understand user flow patterns - how users move between pages, which navigation paths are most common, and where users might be getting lost or trapped in navigation loops. This data is crucial for optimizing site structure and improving user journey flows.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">pageChange</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">let</span> <span class="hljs-keyword">from</span> = <span class="hljs-string">''</span>
    <span class="hljs-built_in">window</span>.addEventListener(<span class="hljs-string">'popstate'</span>, <span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">const</span> to = getPageURL()

        lazyReportCache({
            <span class="hljs-keyword">from</span>,
            to,
            <span class="hljs-attr">type</span>: <span class="hljs-string">'behavior'</span>,
            <span class="hljs-attr">subType</span>: <span class="hljs-string">'popstate'</span>,
            <span class="hljs-attr">startTime</span>: performance.now(),
            <span class="hljs-attr">uuid</span>: getUUID(),
        })

        <span class="hljs-keyword">from</span> = to
    }, <span class="hljs-literal">true</span>)

    <span class="hljs-keyword">let</span> oldURL = <span class="hljs-string">''</span>
    <span class="hljs-built_in">window</span>.addEventListener(<span class="hljs-string">'hashchange'</span>, <span class="hljs-function"><span class="hljs-params">event</span> =&gt;</span> {
        <span class="hljs-keyword">const</span> newURL = event.newURL

        lazyReportCache({
            <span class="hljs-attr">from</span>: oldURL,
            <span class="hljs-attr">to</span>: newURL,
            <span class="hljs-attr">type</span>: <span class="hljs-string">'behavior'</span>,
            <span class="hljs-attr">subType</span>: <span class="hljs-string">'hashchange'</span>,
            <span class="hljs-attr">startTime</span>: performance.now(),
            <span class="hljs-attr">uuid</span>: getUUID(),
        })

        oldURL = newURL
    }, <span class="hljs-literal">true</span>)
}
</code></pre>
<p>With navigation data, you can:</p>
<ul>
<li><p>Identify common user paths through your application</p>
</li>
<li><p>Detect navigation dead-ends or loops where users get stuck</p>
</li>
<li><p>Optimize navigation menus based on actual usage patterns</p>
</li>
<li><p>Improve information architecture to better match user behavior</p>
</li>
</ul>
<h3 id="heading-vue-router-changes"><strong>Vue Router Changes</strong></h3>
<p>For applications built with Vue, you can use the router's hooks to monitor navigation between routes, providing similar insights to general page navigation tracking but specific to Vue's routing system.</p>
<p><strong>Why this matters</strong>: In single-page applications, traditional page navigation events don't capture all route changes. Framework-specific router monitoring ensures you don't miss important navigation data in modern web applications.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">onVueRouter</span>(<span class="hljs-params">router</span>) </span>{
    router.beforeEach(<span class="hljs-function">(<span class="hljs-params">to, <span class="hljs-keyword">from</span>, next</span>) =&gt;</span> {
        <span class="hljs-comment">// Don't count first page load</span>
        <span class="hljs-keyword">if</span> (!<span class="hljs-keyword">from</span>.name) {
            <span class="hljs-keyword">return</span> next()
        }

        <span class="hljs-keyword">const</span> data = {
            <span class="hljs-attr">params</span>: to.params,
            <span class="hljs-attr">query</span>: to.query,
        }

        lazyReportCache({
            data,
            <span class="hljs-attr">name</span>: to.name || to.path,
            <span class="hljs-attr">type</span>: <span class="hljs-string">'behavior'</span>,
            <span class="hljs-attr">subType</span>: [<span class="hljs-string">'vue-router-change'</span>, <span class="hljs-string">'pv'</span>],
            <span class="hljs-attr">startTime</span>: performance.now(),
            <span class="hljs-attr">from</span>: <span class="hljs-keyword">from</span>.fullPath,
            <span class="hljs-attr">to</span>: to.fullPath,
            <span class="hljs-attr">uuid</span>: getUUID(),
        })

        next()
    })
}
</code></pre>
<p>This data helps you:</p>
<ul>
<li><p>Track the most frequently accessed routes in your Vue application</p>
</li>
<li><p>Understand navigation patterns specific to your application's structure</p>
</li>
<li><p>Identify potential optimization opportunities in your routing setup</p>
</li>
<li><p>Measure the impact of UX improvements on navigation behavior</p>
</li>
</ul>
<h2 id="heading-data-reporting"><strong>Data Reporting</strong></h2>
<p>Once you've collected performance, error, and behavior data, you need a reliable system to transmit this information to your backend for processing and analysis. Data reporting is the critical bridge between client-side data collection and server-side analytics.</p>
<p>Effective data reporting must balance several concerns:</p>
<ol>
<li><p><strong>Reliability</strong> – Ensuring data is successfully transmitted, especially critical errors</p>
</li>
<li><p><strong>Performance</strong> – Minimizing impact on the user experience and application performance</p>
</li>
<li><p><strong>Timing</strong> – Deciding when to send data to avoid interference with user interactions</p>
</li>
<li><p><strong>Bandwidth</strong> – Managing the amount of data transmitted to reduce network usage</p>
</li>
</ol>
<p>Let's explore the various methods and strategies for implementing efficient data reporting.</p>
<h3 id="heading-reporting-methods"><strong>Reporting Methods</strong></h3>
<p>Data can be reported using the following methods:</p>
<ul>
<li><p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Navigator/sendBeacon">sendBeacon</a></p>
</li>
<li><p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest">XMLHttpRequest</a></p>
</li>
<li><p>image</p>
</li>
</ul>
<p>My simple SDK uses a combination of the first and second methods for reporting. Using sendBeacon for reporting has very obvious advantages.</p>
<p><strong>Note</strong>: Using the <code>sendBeacon()</code> method will send data to the server asynchronously when the user agent has an opportunity, without delaying page unload or affecting the performance of the next navigation. This solves all the problems with submitting analytics data: data is reliable, transmission is asynchronous, and it won't affect the loading of the next page.</p>
<p>For browsers that don't support sendBeacon, we can use XMLHttpRequest for reporting. An HTTP request consists of sending and receiving two steps.</p>
<p>Actually, for reporting, we just need to ensure the data can be sent – we don't need to receive the response. For this reason, I did an experiment where I sent 30kb of data (generally reported data rarely exceeds this size) using XMLHttpRequest in beforeunload, tested with different browsers, and all were able to send successfully. Of course, this also depends on hardware performance and network conditions.</p>
<p>Here's a sample implementation of a reporting function that uses both methods:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">report</span>(<span class="hljs-params">data, isImmediate = false</span>) </span>{
    <span class="hljs-keyword">if</span> (!config.reportUrl) {
        <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Report URL is not set'</span>)
        <span class="hljs-keyword">return</span>
    }

    <span class="hljs-comment">// Add timestamp and other common properties</span>
    <span class="hljs-keyword">const</span> reportData = {
        ...data,
        <span class="hljs-attr">timestamp</span>: <span class="hljs-built_in">Date</span>.now(),
        <span class="hljs-attr">userAgent</span>: navigator.userAgent,
        <span class="hljs-attr">userId</span>: getUserId(),
        <span class="hljs-comment">// Add other common properties as needed</span>
    }

    <span class="hljs-comment">// Choose reporting method based on browser support and timing</span>
    <span class="hljs-keyword">if</span> (isImmediate) {
        sendData(reportData)
    } <span class="hljs-keyword">else</span> {
        <span class="hljs-comment">// Queue data for batch sending</span>
        reportQueue.push(reportData)

        <span class="hljs-comment">// Send when queue reaches threshold</span>
        <span class="hljs-keyword">if</span> (reportQueue.length &gt;= config.batchSize) {
            sendBatchData()
        }
    }
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sendData</span>(<span class="hljs-params">data</span>) </span>{
    <span class="hljs-comment">// Try sendBeacon first</span>
    <span class="hljs-keyword">if</span> (navigator.sendBeacon) {
        <span class="hljs-keyword">const</span> blob = <span class="hljs-keyword">new</span> Blob([<span class="hljs-built_in">JSON</span>.stringify(data)], { <span class="hljs-attr">type</span>: <span class="hljs-string">'application/json'</span> })
        <span class="hljs-keyword">const</span> success = navigator.sendBeacon(config.reportUrl, blob)

        <span class="hljs-keyword">if</span> (success) <span class="hljs-keyword">return</span>
    }

    <span class="hljs-comment">// Fall back to XMLHttpRequest</span>
    <span class="hljs-keyword">const</span> xhr = <span class="hljs-keyword">new</span> XMLHttpRequest()
    xhr.open(<span class="hljs-string">'POST'</span>, config.reportUrl, <span class="hljs-literal">true</span>)
    xhr.setRequestHeader(<span class="hljs-string">'Content-Type'</span>, <span class="hljs-string">'application/json'</span>)
    xhr.send(<span class="hljs-built_in">JSON</span>.stringify(data))
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sendBatchData</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">if</span> (reportQueue.length === <span class="hljs-number">0</span>) <span class="hljs-keyword">return</span>

    <span class="hljs-keyword">const</span> data = [...reportQueue]
    reportQueue.length = <span class="hljs-number">0</span>

    sendData({ <span class="hljs-attr">type</span>: <span class="hljs-string">'batch'</span>, data })
}
</code></pre>
<h3 id="heading-reporting-timing"><strong>Reporting Timing</strong></h3>
<p>There are three reporting timings:</p>
<ol>
<li><p>Use <code>requestIdleCallback/setTimeout</code> for delayed reporting</p>
</li>
<li><p>Report in the beforeunload callback function</p>
</li>
<li><p>Cache reported data and report when reaching a certain amount</p>
</li>
</ol>
<p>It's recommended to combine all three methods:</p>
<ol>
<li><p>First cache the reported data, and when reaching a certain amount, use <code>requestIdleCallback/setTimeout</code> for delayed reporting</p>
</li>
<li><p>Report all unreported data when leaving the page</p>
</li>
</ol>
<p>Here's how you might implement this combined approach:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Cache for storing reports until they're sent</span>
<span class="hljs-keyword">let</span> reportCache = []
<span class="hljs-keyword">const</span> MAX_CACHE_SIZE = <span class="hljs-number">10</span>
<span class="hljs-keyword">let</span> timer = <span class="hljs-literal">null</span>

<span class="hljs-comment">// Report data with requestIdleCallback when browser is idle</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">lazyReportCache</span>(<span class="hljs-params">data</span>) </span>{
    reportCache.push(data)

    <span class="hljs-comment">// If cache reaches threshold, schedule sending</span>
    <span class="hljs-keyword">if</span> (reportCache.length &gt;= MAX_CACHE_SIZE) {
        <span class="hljs-comment">// Use requestIdleCallback if available, otherwise setTimeout</span>
        <span class="hljs-keyword">const</span> scheduleFn = <span class="hljs-built_in">window</span>.requestIdleCallback || <span class="hljs-built_in">setTimeout</span>

        <span class="hljs-keyword">if</span> (timer) {
            cancelScheduledReport()
        }

        timer = scheduleFn(<span class="hljs-function">() =&gt;</span> {
            <span class="hljs-comment">// Send cached data in bulk</span>
            <span class="hljs-keyword">const</span> dataToSend = [...reportCache]
            reportCache = []
            report({
                <span class="hljs-attr">type</span>: <span class="hljs-string">'batch'</span>,
                <span class="hljs-attr">data</span>: dataToSend,
            })
            timer = <span class="hljs-literal">null</span>
        }, { <span class="hljs-attr">timeout</span>: <span class="hljs-number">2000</span> }) <span class="hljs-comment">// For requestIdleCallback, timeout after 2s</span>
    }
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">cancelScheduledReport</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">window</span>.requestIdleCallback &amp;&amp; timer) {
        <span class="hljs-built_in">window</span>.cancelIdleCallback(timer)
    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (timer) {
        <span class="hljs-built_in">clearTimeout</span>(timer)
    }
    timer = <span class="hljs-literal">null</span>
}

<span class="hljs-comment">// Report any remaining data when user leaves the page</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setupUnloadReporting</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-built_in">window</span>.addEventListener(<span class="hljs-string">'beforeunload'</span>, <span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">if</span> (reportCache.length &gt; <span class="hljs-number">0</span>) {
            <span class="hljs-comment">// Cancel any scheduled reporting</span>
            cancelScheduledReport()

            <span class="hljs-comment">// Send remaining cached data immediately</span>
            report({
                <span class="hljs-attr">type</span>: <span class="hljs-string">'batch'</span>,
                <span class="hljs-attr">data</span>: reportCache,
            }, <span class="hljs-literal">true</span>) <span class="hljs-comment">// true for immediate sending</span>

            reportCache = []
        }
    })
}
</code></pre>
<p>This implementation:</p>
<ol>
<li><p>Collects data in a cache until it reaches a threshold</p>
</li>
<li><p>Uses <code>requestIdleCallback</code> (or <code>setTimeout</code> as fallback) to send data when the browser is idle</p>
</li>
<li><p>Ensures any remaining data is sent when the user leaves the page</p>
</li>
<li><p>Batches multiple reports together to reduce network requests</p>
</li>
</ol>
<p>By combining these methods, you create a robust reporting system that minimizes performance impact while ensuring data reliability.</p>
<h2 id="heading-summary"><strong>Summary</strong></h2>
<p>In this comprehensive guide, we've explored how to build a complete frontend monitoring SDK for collecting and reporting critical application data. Let's recap what we've covered:</p>
<ol>
<li><p><strong>Performance Monitoring</strong></p>
<ul>
<li><p>We implemented methods to capture key web vitals like FP, FCP, LCP, and CLS</p>
</li>
<li><p>We tracked page load events, API request timing, and resource loading metrics</p>
</li>
<li><p>We measured first screen rendering time and frame rates to ensure smooth user experiences</p>
</li>
<li><p>We added support for SPA-specific metrics like Vue router change rendering time</p>
</li>
</ul>
</li>
<li><p><strong>Error Monitoring</strong></p>
<ul>
<li><p>We built systems to capture resource loading errors, JavaScript exceptions, and Promise rejections</p>
</li>
<li><p>We explored how to use sourcemaps to make minified production errors readable</p>
</li>
<li><p>We integrated with framework-specific error handling for Vue applications</p>
</li>
</ul>
</li>
<li><p><strong>User Behavior Tracking</strong></p>
<ul>
<li><p>We implemented tracking for page views, stay duration, and scroll depth</p>
</li>
<li><p>We created methods to monitor user clicks and navigation patterns</p>
</li>
<li><p>We built custom tracking for SPA navigation with Vue Router</p>
</li>
</ul>
</li>
<li><p><strong>Data Reporting</strong></p>
<ul>
<li><p>We developed robust reporting mechanisms using sendBeacon and XMLHttpRequest</p>
</li>
<li><p>We implemented intelligent reporting timing strategies to minimize performance impact</p>
</li>
<li><p>We created batching mechanisms to reduce network requests</p>
</li>
</ul>
</li>
</ol>
<p>Building your own monitoring SDK gives you complete control over what data you collect and how you process it. This approach offers several advantages over third-party solutions:</p>
<ul>
<li><p><strong>Privacy</strong>: You own all the data and can ensure compliance with regulations like GDPR</p>
</li>
<li><p><strong>Performance</strong>: You can optimize the SDK specifically for your application's needs</p>
</li>
<li><p><strong>Customization</strong>: You can add custom metrics unique to your business requirements</p>
</li>
<li><p><strong>Integration</strong>: Your SDK can easily integrate with your existing systems</p>
</li>
</ul>
<p>As you implement your own monitoring solution, remember these best practices:</p>
<ol>
<li><p><strong>Respect User Privacy</strong>: Only collect what you need and be transparent about it</p>
</li>
<li><p><strong>Minimize Performance Impact</strong>: Ensure your monitoring doesn't degrade the user experience</p>
</li>
<li><p><strong>Balance Detail and Volume</strong>: More data isn't always better if it overwhelms your analysis</p>
</li>
<li><p><strong>Act on Insights</strong>: The ultimate goal is to improve your application based on the data</p>
</li>
</ol>
<p>By following the approaches outlined in this article, you'll be well-equipped to build a comprehensive monitoring system that helps you deliver better user experiences through data-driven decision making.</p>
<h2 id="heading-references"><strong>References</strong></h2>
<h3 id="heading-performance-monitoring"><strong>Performance Monitoring</strong></h3>
<ul>
<li><p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Performance_API">Performance API</a></p>
</li>
<li><p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming">PerformanceResourceTiming</a></p>
</li>
<li><p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Resource_Timing_API/Using_the_Resource_Timing_API">Using_the_Resource_Timing_API</a></p>
</li>
<li><p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/PerformanceTiming">PerformanceTiming</a></p>
</li>
<li><p><a target="_blank" href="https://web.dev/metrics/">Metrics</a></p>
</li>
<li><p><a target="_blank" href="https://web.dev/evolving-cls/">evolving-cls</a></p>
</li>
<li><p><a target="_blank" href="https://web.dev/custom-metrics/">custom-metrics</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/GoogleChrome/web-vitals">web-vitals</a></p>
</li>
<li><p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/PerformanceObserver">PerformanceObserver</a></p>
</li>
<li><p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Element_timing_API">Element_timing_API</a></p>
</li>
<li><p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/PerformanceEventTiming">PerformanceEventTiming</a></p>
</li>
<li><p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Timing-Allow-Origin">Timing-Allow-Origin</a></p>
</li>
<li><p><a target="_blank" href="https://web.dev/bfcache/">bfcache</a></p>
</li>
<li><p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver">MutationObserver</a></p>
</li>
<li><p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest">XMLHttpRequest</a></p>
</li>
<li><p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Navigator/sendBeacon">sendBeacon</a></p>
</li>
</ul>
<h3 id="heading-error-monitoring"><strong>Error Monitoring</strong></h3>
<ul>
<li><p><a target="_blank" href="https://github.com/joeyguo/noerror">noerror</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/mozilla/source-map">source-map</a></p>
</li>
</ul>
<h3 id="heading-behavior-monitoring"><strong>Behavior Monitoring</strong></h3>
<ul>
<li><p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Window/popstate_event">popstate</a></p>
</li>
<li><p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Window/hashchange_event">hashchange</a></p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How Microfrontends Work: From iframes to Module Federation ]]>
                </title>
                <description>
                    <![CDATA[ Microfrontends are transforming how teams build and deploy frontend applications at scale. This tutorial explores the architectural landscape, from traditional approaches to modern Module Federation implementations. By the end, you'll be equipped to ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-microfrontends-work-iframes-to-module-federation/</link>
                <guid isPermaLink="false">6839c4a906e067fe415d54b4</guid>
                
                    <category>
                        <![CDATA[ frontend ]]>
                    </category>
                
                    <category>
                        <![CDATA[ architecture ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Microfrontend ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Frontend Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Rahul gupta ]]>
                </dc:creator>
                <pubDate>Fri, 30 May 2025 14:46:01 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1748613557891/39037981-d514-4f26-8a48-be0cdd9ca29b.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Microfrontends are transforming how teams build and deploy frontend applications at scale. This tutorial explores the architectural landscape, from traditional approaches to modern Module Federation implementations.</p>
<p>By the end, you'll be equipped to evaluate whether microfrontends are the right solution for your team's specific needs.</p>
<h3 id="heading-ill-cover-the-following"><strong>I’ll cover the following:</strong></h3>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-are-microfrontends">What are Microfrontends?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-traditional-microfrontend-patterns">Traditional Microfrontend Patterns</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-server-side-composition">Server-Side Composition</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-iframes">iframes</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-build-time-integration-packages">Build Time Integration – Packages</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-modern-microfrontend-patterns">Modern Microfrontend Patterns</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-module-federation">Module Federation</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-single-spa">Single SPA</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-detailed-comparison">Detailed Comparison</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-tradeoffs-and-challenges-with-module-federation">Tradeoffs and Challenges with Module Federation</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-setup-complexity">Setup Complexity</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-runtime-challenges">Runtime Challenges</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-operational-concerns">Operational Concerns</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusions">Conclusions</a></p>
<ul>
<li><a class="post-section-overview" href="#heading-whats-next">What’s next?</a></li>
</ul>
</li>
</ul>
<h2 id="heading-what-are-microfrontends">What are Microfrontends?</h2>
<p>If you've heard about microservices on the backend, microfrontends represent a similar approach in the frontend world, with many of the same benefits.</p>
<p>Your team might adopt a microfrontend approach to enable team autonomy, reduce deployment risks, and scale development across multiple teams. Each team owns its technology stack, deployment cadence, and workflows. Yet they still deliver a single, cohesive user interface.</p>
<p>The overall idea is to move away from a big monolithic UI to decoupled UI codebases that can be owned, managed, and deployed by separate teams independently.</p>
<p>The simplest way to think about Microfrontends is the following:</p>
<blockquote>
<p>Integrate one piece of UI into another</p>
</blockquote>
<p>What can this <strong>piece</strong> of UI be, you may ask? Here are some examples:</p>
<ul>
<li><p><strong>Pages</strong> – parts of a website owned by specific teams. For example, the Auth team may own login/signup pages, whereas the engagement team may own the marketing pages, and so on.</p>
</li>
<li><p><strong>Components</strong> – Components like header and footer are good candidates for a microfrontend approach as well. They’re relatively static but need to stay consistent across the website and may integrate with teams who own different sets of pages.</p>
</li>
<li><p><strong>Widgets</strong> – A recommendation widget may be owned by a recommendations team, for example, and it can be integrated into different parts of the page based on the context. This is different from a static component, as given the context, the recommendation widget may also fetch relevant data via APIs (also owned by the recommendations teams).</p>
</li>
</ul>
<h2 id="heading-traditional-microfrontend-patterns">Traditional Microfrontend Patterns</h2>
<p>After reading the definition of a microfrontend, you might be thinking, oh, wait, who builds UI with a big monolith these days anyway (except giants like Google)? If that’s the case, your team is most likely using one of these traditional approaches to building Microfrontends:</p>
<h3 id="heading-server-side-composition"><strong>Server-Side Composition</strong></h3>
<p>This is the most common approach I've encountered across various organisations. The idea is to split your website based on route patterns or pages. For example, you might route users to the accounts team for any routes starting with <code>/account/*</code> (<code>/account/login</code> or <code>/account/signup</code> may fall under this pattern). Or you may have a similar route prefix for other parts of your web app, like <code>/blog/*</code> for the marketing section of your app.</p>
<p>This is typically implemented at the reverse proxy layer (such as using NGINX), which routes traffic to the appropriate downstream UI service based on the path matching.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747496226912/fda979cd-c95c-4d48-a7dc-87956672b24d.png" alt="Diagram showing a reverse proxy setup with nginx. The proxy routes `/blog/*` requests to the Marketing UI and `/account/*` requests to the Accounts UI." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h3 id="heading-iframes"><strong>iframes</strong></h3>
<p>Another common approach is using iframes, though this method has significant limitations.</p>
<p>Unlike server-side composition, which operates at the page level, iframes can integrate as widgets within pages. Using iframes, you can load another website as a part of the website you want to integrate it within using the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/iframe">&lt;iframe&gt;</a> tag.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747496936962/7c24a43a-80d4-45f4-a2df-3de0e0e0bc1c.png" alt="Diagram illustrating iframe integration, showing website.com/blog embedding a 'Widget' using an iframe with source 'website.com/widget'." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Some examples of this approach, which you may have seen, are websites that integrate Twitter feeds, Google Maps, and so on. Although these are examples of external widget integrations with iframes, companies may integrate certain widgets that are powered through iframes.</p>
<h3 id="heading-build-time-integration-packages"><strong>Build Time Integration – Packages</strong></h3>
<p>This approach involves publishing components as a UI library that other applications can integrate.</p>
<p>This is useful if you want to integrate full-blown apps with multiple pages, widgets, or static components like headers and footers, where this approach is pretty common.</p>
<p>Typically, this approach means that one team publishes their components as a package, while other teams integrate a specific version of this package.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747497485861/70385841-28c6-441e-ae4d-c977b3563ecf.png" alt="Diagram illustrating build-time integration via packages, showing website.com/blog integrating a 'Widget' component, version 1.0.0, fetched from a company's NPM registry." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>In this example, it’s important to note that the Widget component is pulled in during the dependency install phase of the app. The web app can utilise this widget like its own component, which gets built together as one module and shipped to the users.</p>
<h2 id="heading-modern-microfrontend-patterns">Modern Microfrontend Patterns</h2>
<h3 id="heading-module-federation">Module Federation</h3>
<p>Module Federation enables you to integrate remote UI pieces within a host application at runtime. These pieces can be full pages, widgets, or components.</p>
<p>Module Federation originated as a <a target="_blank" href="https://webpack.js.org/concepts/module-federation/">Webpack 5 feature</a>, extending the bundler's capabilities to load JavaScript code from remote sources at runtime.</p>
<p><a target="_blank" href="https://module-federation.io/">Module Federation 2.0</a> is the evolution/improvement of the original Webpack 5 feature, with implementations available for other popular bundlers like RSPack and Vite as well.</p>
<p>Even if you’re using Webpack 5, I would recommend using Module Federation 2.0 as it takes care of some common gotchas that exist in the original Webpack 5 implementation.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748011963774/fae404c6-efc9-4e0f-8667-4427dbcdfc0f.png" alt="Diagram illustrating Module Federation, showing a host application at website.com/blog loading a 'Widget' component at runtime from a remote application at Recommendation.com via 'remotes: recommendation/remoteEntry'." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Let’s take an example to understand some of the common pieces of Module Federation.</p>
<p>Imagine that we’ve a blog application, owned by the Content Team &amp; a Widget, which is owned by the Recommendations team.</p>
<p>Now, let’s say the content team wants to integrate a recommendation widget within their application. Assume these teams have separate codebases hosted on different domains. The content team is on <code>website.com</code> &amp; the recommendations team is on <code>recommendation.com</code></p>
<p>Here’s how you can achieve this MFE integration via Module Federation:</p>
<h4 id="heading-remote"><strong>Remote</strong></h4>
<p>Responsible for exposing JavaScript files as remote (for example, utilities, components, and so on).</p>
<p>In our example, it would be the Recommendation’s team acting as a remote &amp; would require a configuration to ‘expose’ the Widget.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">new</span> ModuleFederationPlugin({
  name: <span class="hljs-string">'recommendation'</span>,
  exposes: {
    <span class="hljs-string">'./Widget'</span>: <span class="hljs-string">'./src/Widget.js'</span>,
  }
})
</code></pre>
<h4 id="heading-remote-entry"><strong>Remote Entry</strong></h4>
<p>Remote entry is the URL for the entry point for a remote. A remote may expose multiple JavaScript files, &amp; remoteEntry file would be aware of all of them.</p>
<p>Module Federation by default hosts the remote entry file at the root. In our example, recommendation teams might host their remote entry on <code>https://recommendation.com/remoteEntry.js</code></p>
<h4 id="heading-host"><strong>Host</strong></h4>
<p>An independent website that consumes JavaScript from one or more remotes via <strong>Remote Entry.</strong> Think of remote entry as a namespace for your app under which it can export multiple things like components, utils, and so on, as exposed by a particular remote.</p>
<p>In our example, the Content Team would act as a Host &amp; they’ll define the recommendation team’s remote entry within remotes configuration.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">new</span> ModuleFederationPlugin({
  name: <span class="hljs-string">'content-blog'</span>,
  remotes: {
    <span class="hljs-string">"recommendation"</span>: <span class="hljs-string">'recommendation@https://recommendation.com/remoteEntry.js'</span>,
  },
  <span class="hljs-comment">// ... other configs</span>
})
</code></pre>
<h4 id="heading-shared"><strong>Shared</strong></h4>
<p>Both hosts and remote can specify dependencies as SemVer that are automatically negotiated and shared during runtime. These can include common framework dependencies, such as React, which may require being a singleton, or other vendor libraries that can be potentially shared.</p>
<p>Having the right shared configuration ensures that the client does not download libraries or code that is already available on the host when fetching UI pieces from a remote location, which is key for optimal performance when integrating Module Federation.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> deps = <span class="hljs-built_in">require</span>(<span class="hljs-string">"./package.json"</span>).dependencies;

<span class="hljs-keyword">new</span> ModuleFederationPlugin({
  shared: {
    ...deps,
    react: {
      singleton: <span class="hljs-literal">true</span>,
      requiredVersion: deps.react,
    }
  },
  <span class="hljs-comment">// ... other configs</span>
})
</code></pre>
<h4 id="heading-imports-and-usage"><strong>Imports and Usage</strong></h4>
<p>Module Federation integration lets you use imports as if those JS files were available locally. Module Federation does all the stitching behind the scenes at runtime, in terms of fetching the remote entry and appropriate dependencies to make it available when you use it.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Import is of the format - &lt;remote&gt;/&lt;expose-from-remote&gt;</span>
<span class="hljs-keyword">import</span> Widget <span class="hljs-keyword">from</span> <span class="hljs-string">'recommendation/Widget'</span>;

<span class="hljs-comment">// Render somewhere, making sure to handle loading via Suspense</span>
<span class="hljs-comment">// &amp; errors via error boundary in React</span>
&lt;ErrorBoundary&gt;
  &lt;Suspense fallback={&lt;Loading /&gt;}
    &lt;Widget /&gt;
  &lt;/Suspense&gt;
&lt;/ErrorBoundary&gt;
</code></pre>
<p>In a nutshell, the module federation concept is this simple —</p>
<blockquote>
<p>Fetching JS code (components, utils, and so on) from a remote server at runtime and still being able to share dependencies and be performant while doing so.</p>
</blockquote>
<h3 id="heading-single-spa">Single SPA</h3>
<p>When you look up microfrontends, <a target="_blank" href="https://single-spa.js.org">Single SPA</a> often appears as a popular solution. But its primary use case is quite specific: integrating components across multiple frameworks (for example, React + Angular + Vue in the same application). Here's how it works in practice:</p>
<p>Single SPA acts as a JavaScript router that mounts and unmounts entire applications based on URL routes. Each "single-spa application" is a framework-specific app that gets loaded when its route becomes active.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Register applications with Single SPA</span>
registerApplication({
 name: <span class="hljs-string">'@mycompany/react-app'</span>,
 app: <span class="hljs-function">() =&gt;</span> System.import(<span class="hljs-string">'@mycompany/react-app'</span>),
 activeWhen: [<span class="hljs-string">'/react-app'</span>]
});

registerApplication({
 name: <span class="hljs-string">'@mycompany/angular-app'</span>, 
 app: <span class="hljs-function">() =&gt;</span> System.import(<span class="hljs-string">'@mycompany/angular-app'</span>),
 activeWhen: [<span class="hljs-string">'/angular-app'</span>]
});
</code></pre>
<p>Single SPA handles the "orchestration" part – deciding which app should be active and managing their lifecycles. It doesn't solve the "how do I load remote code" problem – you still need to pair it with one of the approaches we've discussed (Module Federation, build-time packages, and so on).</p>
<p>If your applications use the same framework (like all React), you can skip Single SPA entirely and use Module Federation directly. Single SPA adds complexity that's only justified when you truly need multi-framework integration.</p>
<h2 id="heading-detailed-comparison">Detailed Comparison</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Criteria</strong></td><td><strong>Module Federation</strong></td><td><strong>Server-side composition</strong></td><td><strong>iframe</strong></td><td><strong>Build time integration (package)</strong></td></tr>
</thead>
<tbody>
<tr>
<td><strong>Independent deployments</strong></td><td>💚 Microfrontends are loaded at runtime on the client. This means that teams can do independent deployments and make changes that reflect immediately.</td><td>💚 Deployments stay independent, as each route pattern points to an individual app’s independent deployments</td><td>💚 Since iframes are also loaded at runtime, the deployments can be independent.</td><td>💔 Deployments are coupled from the host application. A change in the package would require publishing a new version and bumping it up in the host app.</td></tr>
<tr>
<td><strong>Performance</strong></td><td>💚 Enables shared dependencies and optimised loading, maintaining SPA performance.</td><td>💔 Requires full page reloads when navigating between applications, losing SPA benefits.</td><td>💔 Completely isolated and loads all the dependencies of the website in an iframe, which means a slower overall page load.</td><td>💚 Possible to share package dependencies to a certain extent by <a target="_blank" href="https://yarnpkg.com/cli/dedupe">deduping</a> dependencies when integrating a package, but it requires appropriate dev tooling. Otherwise, duplicate dependencies may sneak in for the same package.</td></tr>
<tr>
<td><strong>Scalability &amp; maintenance</strong></td><td>💚 Works well at scale. A page can be completely composed of federated components, with the smallest of the building blocks being pulled in from different remotes.</td><td>💔 Usually requires duplicating things like header/footer, to make it ‘look like’ the user is in the same app, but is being served by two different servers/codebases. The approach is limited to route-based segregation of apps’ entry points – so, a granular integration isn’t possible.</td><td>💔 Typically good to power whole pages and not portions of pages, can really slow down the app at scale and may encounter issues when optimising the app for SEO or building dynamic responsive layouts.</td><td>💔 Requires maintenance of package publishing, upgrades and version conflicts at scale. This can be simplified to some extent by CI tooling, but developers would still require significant effort in bumping the versions, verifying the impact from a feature/performance standpoint.</td></tr>
<tr>
<td><strong>Setup effort</strong></td><td>💔 Might be high depending on how your app is being built currently. A deeper level of understanding of your build tool may be required to make your desired integration work, or when you face issues. This is covered in detail in the next section.</td><td>💚 Simpler to implement, as there is no coupling apart from the reverse proxy layer, which is responsible for redirecting the traffic to the appropriate service.</td><td>💔 Easier to integrate but requires handling a lot of edge cases, which can take significant time. Some examples are communication between iframe/host app, layout issues, rendering beyond the boundaries of iframe (for example, toasts), cross-domain iframe restrictions and ensuring security, impact on SEO and a11y.</td><td>💔 Requires significant effort to stabilise the development pipeline for publishing a new package, maintaining a changeset, bumping a new version, and resolving version conflicts. Every change requires making sure there is no unintended impact on transitive dependencies of the host app due to the package bump.</td></tr>
<tr>
<td><strong>Authentication &amp; Authorisation</strong></td><td>💚 Depending on your app’s setup, module federated components can call the remote app’s server for any data fetching needs. Might require handling CORS if your remote is on a different domain than the host, and for the browser to send authentication cookies for such requests.</td><td>💚 Each app can independently integrate with a central authentication service.</td><td>💔 It can be challenging for iframes to access the parent website’s browser details like auth cookies, and so on. It also may require some trickery to get the authentication to work, especially if the iframe URL is on a different domain/subdomain than the application.</td><td>💚 Package components can choose to call APIs via a proxy API within the host application or directly integrate with an independent service’s endpoints.</td></tr>
<tr>
<td><strong>Devloop</strong></td><td>💚 Mature devloop with Module Federation 2.0, you can view source maps with hot reloading across these apps. Overall a seamless integration out of the box. You can also point to any federated endpoint from local to be able to integrate and verify end-to-end integration.</td><td>💔 Requires setting up both services and a reverse proxy locally to verify integration touchpoints, which may be non-trivial.</td><td>💔 Local testing doesn’t accurately mimic the issues that you may face due to cross-domain challenges with iframe.</td><td>💔 An appropriate dev workflow is required to test the in-development package changes within the host app locally. This is typically done with pre-publishing the package or by <a target="_blank" href="https://classic.yarnpkg.com/lang/en/docs/cli/link/">linking</a> local packages or using a tool like <a target="_blank" href="https://github.com/wclr/yalc">yalc</a>.</td></tr>
<tr>
<td><strong>Overall Recommendation ✨</strong></td><td>Suited for apps that are composed of integration with different teams who want to own their deployments and release cycles with low coupling.</td><td>Suited for apps that are rather isolated (subdomains) within a larger business domain. A question to ask is, how often would the user need to navigate between these apps? If the answer is ‘not often’, then this may be a suitable approach.</td><td>Not recommended due to the limitations that come with it. It may be suitable for some third-party integrations, for example, Twitter exposes a part of its feed which can be integrated within a website via iframe. This is rather more convenient than any of the other approaches.</td><td>Suited for apps where the changes need to be more controlled, with the host app upgrading the package, and may perform appropriate checks before releasing it to their end users.</td></tr>
</tbody>
</table>
</div><h2 id="heading-tradeoffs-and-challenges-with-module-federation">Tradeoffs and Challenges with Module Federation</h2>
<p>The primary tradeoff in using Module Federation is the initial setup effort, which I briefly discussed in the previous comparison table.</p>
<p>Here are some other challenges to anticipate when integrating via Module Federation:</p>
<h3 id="heading-setup-complexity"><strong>Setup Complexity</strong></h3>
<ol>
<li><p><strong>Bundler-specific challenges</strong> – Some things may require you to know your bundler's internals to make the integration work for your app. For example, with Webpack 5, if your remote not only exposes federated components but also serves a user experience, you’ll need the appropriate chunk setup to make that work. This is because Module Federation by default expects a certain chunk optimisation strategy and exposes the remoteEntry from the root of the app.</p>
</li>
<li><p><strong>Shared dependencies</strong> – You'll need to review your dependencies to make sure you share as many dependencies as possible to optimise bundle size and loading performance. You’ll also need to mark critical libraries (like React) as singletons to prevent runtime conflicts.</p>
</li>
</ol>
<h3 id="heading-runtime-challenges"><strong>Runtime Challenges</strong></h3>
<ol>
<li><p><strong>Cross domain</strong> – if your remote is on a different subdomain, for example <code>remote.website.com</code> and is being loaded from <code>host.website.com</code> You’ll need appropriate handling for CORS on your server to allow data fetching from the host’s subdomain. You’ll also need an appropriate <code>credentials</code> fetch configuration to make sure the browser sends the authentication cookies in data fetching requests to your remote endpoints.</p>
</li>
<li><p><strong>Styling conflicts</strong> – You’ll want to make sure the remote’s styles don’t override the host’s styles and that the remote components don’t inherit unintended styles from the host. There are multiple strategies here, from using styled components to a virtual DOM.</p>
</li>
</ol>
<h3 id="heading-operational-concerns"><strong>Operational Concerns</strong></h3>
<ol>
<li><p><strong>Observability and Analytics</strong> – Based on your requirements, you may want to either share an instance of your observability scripts, for example, an error monitoring service, or instantiate a completely different one within your MFE’s context. This becomes challenging, as there is no ‘index’ file being rendered, but rather components that are being exposed from the remotes.</p>
</li>
<li><p><strong>Deployment &amp; Caching</strong> – It’s recommended that MFE remote bundles be hosted on S3 buckets for high reliability as opposed to loading them from a remote server. You may require appropriate long-term caching for files other than the <code>remoteEntry.js</code> which is typically non-hashed and contains the link to other dependencies to be loaded.</p>
</li>
</ol>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Microfrontends offer a compelling solution for scaling frontend development across multiple teams, with Module Federation emerging as the most flexible modern approach.</p>
<p>While traditional methods like server-side composition remain valuable for specific use cases, Module Federation provides the runtime flexibility and performance characteristics needed for complex applications.</p>
<p>The decision ultimately depends on your team’s structure, technical requirements, and tolerance for implementation complexity. Start with simpler approaches if you're new to microfrontends, then consider Module Federation as your needs evolve.</p>
<h3 id="heading-whats-next">What’s next?</h3>
<p>This article was more about giving you a bird’s-eye view of the landscape. I’ll be writing more on Module Federation and going beyond the basics next. I’ll cover the technical challenges in more detail, along with possible solutions. Watch out for the same!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ The Front-End Performance Optimization Handbook – Tips and Strategies for Devs ]]>
                </title>
                <description>
                    <![CDATA[ When you’re building a website, you’ll want it to be responsive, fast, and efficient. This means making sure the site loads quickly, runs smoothly, and provides a seamless experience for your users, among other things. So as you build, you’ll want to... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/the-front-end-performance-optimization-handbook/</link>
                <guid isPermaLink="false">681b5e61b1ed0b90facd0adf</guid>
                
                    <category>
                        <![CDATA[ Frontend Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ frontend ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ performance ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Gordan Tan ]]>
                </dc:creator>
                <pubDate>Wed, 07 May 2025 13:21:37 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1746468304666/ca24ac6b-1591-4abf-a544-739fbfaecf49.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>When you’re building a website, you’ll want it to be responsive, fast, and efficient. This means making sure the site loads quickly, runs smoothly, and provides a seamless experience for your users, among other things.</p>
<p>So as you build, you’ll want to keep various performance optimizations in mind – like reducing file size, making fewer server requests, optimizing images in various ways, and so on.</p>
<p>But performance optimization is a double-edged sword, with both good and bad aspects. The good side is that it can improve website performance, while the bad side is that it's complicated to configure, and there are many rules to follow.</p>
<p>Also, some performance optimization rules aren't suitable for all scenarios and should be used with caution. So make sure you approach this handbook with a critical eye. In it, I’ll lay out a bunch of ways you can optimize your website’s performance, and share insights to help you chose which of these techniques to use.</p>
<p>I’ll also provide the references for these optimization suggestions after each one and at the end of the article.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><p><a class="post-section-overview" href="#heading-reduce-http-requests">Reduce HTTP Requests</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-use-http2">Use HTTP2</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-use-server-side-rendering">Use Server-Side Rendering</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-use-a-cdn-for-static-resources">Use a CDN for Static Resources</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-place-css-in-the-head-and-javascript-files-at-the-bottom">Place CSS in the Head and JavaScript Files at the Bottom</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-use-font-icons-iconfont-instead-of-image-icons">Use Font Icons (iconfont) Instead of Image Icons</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-make-good-use-of-caching-avoid-reloading-the-same-resources">Make Good Use of Caching, Avoid Reloading the Same Resources</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-compress-files">Compress Files</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-image-optimization">Image Optimization</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-lazy-loading-images">Lazy Loading Images</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-responsive-images">Responsive Images</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-adjust-image-size">Adjust Image Size</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-reduce-image-quality">Reduce Image Quality</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-use-css3-effects-instead-of-images-when-possible">Use CSS3 Effects Instead of Images When Possible</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-use-webp-format-images">Use webp Format Images</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-load-code-on-demand-through-webpack-extract-third-party-libraries-reduce-redundant-code-when-converting-es6-to-es5">Load Code on Demand Through Webpack, Extract Third-Party Libraries, Reduce Redundant Code When Converting ES6 to ES5</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-reduce-reflows-and-repaints">Reduce Reflows and Repaints</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-use-event-delegation">Use Event Delegation</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-pay-attention-to-program-locality">Pay Attention to Program Locality</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-if-else-vs-switch">if-else vs switch</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-lookup-tables">Lookup Tables</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-avoid-page-stuttering">Avoid Page Stuttering</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-use-requestanimationframe-to-implement-visual-changes">Use requestAnimationFrame to Implement Visual Changes</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-use-web-workers">Use Web Workers</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-use-bitwise-operations">Use Bitwise Operations</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-dont-override-native-methods">Don't Override Native Methods</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-reduce-the-complexity-of-css-selectors">Reduce the Complexity of CSS Selectors</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-use-flexbox-instead-of-earlier-layout-models">Use Flexbox Instead of Earlier Layout Models</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-use-transform-and-opacity-properties-to-implement-animations">Use Transform and Opacity Properties to Implement Animations</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-use-rules-reasonably-avoid-over-optimization">Use Rules Reasonably, Avoid Over-Optimization</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-other-references">Other References</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ol>
<h2 id="heading-1-reduce-http-requests"><strong>1. Reduce HTTP Requests</strong></h2>
<p>A complete HTTP request needs to go through DNS lookup, TCP handshake, browser sending the HTTP request, server receiving the request, server processing the request and sending back a response, browser receiving the response, and other processes. Let's look at a specific example to understand how HTTP works:</p>
<p><img src="https://camo.githubusercontent.com/7988c06bb7b698dcc66ac8f2556cbe03b239ba2c8bf17ecddb29004c74b0eb36/68747470733a2f2f692d626c6f672e6373646e696d672e636e2f626c6f675f6d6967726174652f64333736643731343630633763376331316462316338353134366230343164632e706e67" alt="HTTP request waterfall showing timing breakdown" width="600" height="400" loading="lazy"></p>
<p>This is an HTTP request, and the file size is 28.4KB.</p>
<p>Terminology explained:</p>
<ul>
<li><p>Queueing: Time spent in the request queue.</p>
</li>
<li><p>Stalled: The time difference between when the TCP connection is established and when data can actually be transmitted, including proxy negotiation time.</p>
</li>
<li><p>Proxy negotiation: Time spent negotiating with the proxy server.</p>
</li>
<li><p>DNS Lookup: Time spent performing DNS lookup. Each different domain on a page requires a DNS lookup.</p>
</li>
<li><p>Initial Connection / Connecting: Time spent establishing a connection, including TCP handshake/retry and SSL negotiation.</p>
</li>
<li><p>SSL: Time spent completing the SSL handshake.</p>
</li>
<li><p>Request sent: Time spent sending the network request, usually a millisecond.</p>
</li>
<li><p>Waiting (TFFB): TFFB is the time from when the page request is made until the first byte of response data is received.</p>
</li>
<li><p>Content Download: Time spent receiving the response data.</p>
</li>
</ul>
<p>From this example, we can see that the actual data download time accounts for only <code>13.05 / 204.16 = 6.39%</code> of the total. The smaller the file, the smaller this ratio – and the larger the file, the higher the ratio. This is why it's recommended to combine multiple small files into one large file, which reduces the number of HTTP requests.</p>
<h3 id="heading-how-to-combine-multiple-files"><strong>How to combine multiple files</strong></h3>
<p>There are several techniques to reduce the number of HTTP requests by combining files:</p>
<p><strong>1. Bundle JavaScript files with Webpack</strong></p>
<pre><code class="lang-typescript"><span class="hljs-comment">// webpack.config.js</span>
<span class="hljs-built_in">module</span>.<span class="hljs-built_in">exports</span> = {
  entry: <span class="hljs-string">'./src/index.js'</span>,
  output: {
    filename: <span class="hljs-string">'bundle.js'</span>,
    path: path.resolve(__dirname, <span class="hljs-string">'dist'</span>),
  },
};
</code></pre>
<p>This will combine all JavaScript files imported in your entry point into a single bundle.</p>
<p><strong>2. Combine CSS files</strong><br>Using CSS preprocessors like Sass:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">/* main.scss */</span>
<span class="hljs-meta">@import</span> <span class="hljs-string">'reset'</span>;
<span class="hljs-meta">@import</span> <span class="hljs-string">'variables'</span>;
<span class="hljs-meta">@import</span> <span class="hljs-string">'typography'</span>;
<span class="hljs-meta">@import</span> <span class="hljs-string">'layout'</span>;
<span class="hljs-meta">@import</span> <span class="hljs-string">'components'</span>;
</code></pre>
<p>Then compile to a single CSS file:</p>
<pre><code class="lang-typescript">sass main.scss:main.css
</code></pre>
<p>Reference:</p>
<ul>
<li><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Performance_API/Resource_timing">Resource_timing</a></li>
</ul>
<h2 id="heading-2-use-http2"><strong>2. Use HTTP2</strong></h2>
<p>Compared to HTTP1.1, HTTP2 has several advantages:</p>
<h3 id="heading-faster-parsing">Faster parsing</h3>
<p>When parsing HTTP1.1 requests, the server must continuously read bytes until it encounters the CRLF delimiter. Parsing HTTP2 requests isn't as complicated because HTTP2 is a frame-based protocol, and each frame has a field indicating its length.</p>
<h3 id="heading-multiplexing">Multiplexing</h3>
<p>With HTTP1.1, if you want to make multiple requests simultaneously, you need to establish multiple TCP connections because one TCP connection can only handle one HTTP1.1 request at a time.</p>
<p>In HTTP2, multiple requests can share a single TCP connection, which is called multiplexing. Each request and response is represented by a stream with a unique stream ID to identify it.<br>Multiple requests and responses can be sent out of order within the TCP connection and then reassembled at the destination using the stream ID.</p>
<h3 id="heading-header-compression">Header compression</h3>
<p>HTTP2 provides header compression functionality.</p>
<p>For example, consider the following two requests:</p>
<pre><code class="lang-typescript">:authority: unpkg.zhimg.com
:method: GET
:path: <span class="hljs-regexp">/za-js-sdk@2.16.0/</span>dist/zap.js
:scheme: https
accept: *<span class="hljs-comment">/*
accept-encoding: gzip, deflate, br
accept-language: zh-CN,zh;q=0.9
cache-control: no-cache
pragma: no-cache
referer: https://www.zhihu.com/
sec-fetch-dest: script
sec-fetch-mode: no-cors
sec-fetch-site: cross-site
user-agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36</span>
</code></pre>
<pre><code class="lang-typescript">:authority: zz.bdstatic.com
:method: GET
:path: <span class="hljs-regexp">/linksubmit/</span>push.js
:scheme: https
accept: *<span class="hljs-comment">/*
accept-encoding: gzip, deflate, br
accept-language: zh-CN,zh;q=0.9
cache-control: no-cache
pragma: no-cache
referer: https://www.zhihu.com/
sec-fetch-dest: script
sec-fetch-mode: no-cors
sec-fetch-site: cross-site
user-agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36</span>
</code></pre>
<p>From the two requests above, you can see that a lot of data is repeated. If we could store the same headers and only send the differences between them, we could save a lot of bandwidth and speed up the request time.</p>
<p>HTTP/2 uses "header tables" on the client and server sides to track and store previously sent key-value pairs, and for identical data, it's no longer sent through each request and response.</p>
<p>Here's a simplified example. Suppose the client sends the following header requests in sequence:</p>
<pre><code class="lang-typescript">Header1:foo
Header2:bar
Header3:bat
</code></pre>
<p>When the client sends a request, it creates a table based on the header values:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Index</td><td>Header Name</td><td>Value</td></tr>
</thead>
<tbody>
<tr>
<td>62</td><td>Header1</td><td>foo</td></tr>
<tr>
<td>63</td><td>Header2</td><td>bar</td></tr>
<tr>
<td>64</td><td>Header3</td><td>bat</td></tr>
</tbody>
</table>
</div><p>If the server receives the request, it will create the same table.<br>When the client sends the next request, if the headers are the same, it can directly send a header block like this:</p>
<pre><code class="lang-typescript"><span class="hljs-number">62</span> <span class="hljs-number">63</span> <span class="hljs-number">64</span>
</code></pre>
<p>The server will look up the previously established table and restore these numbers to the complete headers they correspond to.</p>
<h3 id="heading-priority">Priority</h3>
<p>HTTP2 can set a higher priority for more urgent requests, and the server can prioritize handling them after receiving such requests.</p>
<h3 id="heading-flow-control">Flow control</h3>
<p>Since the bandwidth of a TCP connection (depending on the network bandwidth from client to server) is fixed, when there are multiple concurrent requests, if one request occupies more traffic, another request will occupy less. Flow control can precisely control the flow of different streams.</p>
<h3 id="heading-server-push">Server push</h3>
<p>A powerful new feature added in HTTP2 is that the server can send multiple responses to a single client request. In other words, in addition to responding to the initial request, the server can also push additional resources to the client without the client explicitly requesting them.</p>
<p>For example, when a browser requests a website, in addition to returning the HTML page, the server can also proactively push resources based on the URLs of resources in the HTML page.</p>
<p>Many websites have already started using HTTP2, such as Zhihu:</p>
<p><a target="_blank" href="https://camo.githubusercontent.com/17c8f78f0341150240e6719ed82ee794e5c569404861581ccad306b88d9b6f6c/68747470733a2f2f696d672d626c6f672e6373646e696d672e636e2f696d675f636f6e766572742f39636165316532313931613035393466393833373636646635636265373562352e706e67"><img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb0ovwimn9pg7z7eo0qxd.png" alt="show hot to check HTTP1 and HTTP2 protocols" width="600" height="400" loading="lazy"></a></p>
<p>Where "h2" refers to the HTTP2 protocol, and "http/1.1" refers to the HTTP1.1 protocol.</p>
<p>References:</p>
<ul>
<li><p><a target="_blank" href="https://developers.google.com/web/fundamentals/performance/http2/?hl=zh-cn">HTTP2 Introduction</a></p>
</li>
<li><p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/HTTP">HTTP</a></p>
</li>
</ul>
<h2 id="heading-3-use-server-side-rendering"><strong>3. Use Server-Side Rendering</strong></h2>
<p>In client-side rendering, you get the HTML file, download JavaScript files as needed, run the files, generate the DOM, and then render.</p>
<p>And in server-side rendering, the server returns the HTML file, and the client only needs to parse the HTML.</p>
<ul>
<li><p>Pros: Faster first-screen rendering, better SEO.</p>
</li>
<li><p>Cons: Complicated configuration, increases the computational load on the server.</p>
</li>
</ul>
<p>Below, I'll use Vue SSR as an example to briefly describe the SSR process.</p>
<h3 id="heading-client-side-rendering-process">Client-side rendering process</h3>
<ol>
<li><p>Visit a client-rendered website.</p>
</li>
<li><p>The server returns an HTML file containing resource import statements and <code>&lt;div id="app"&gt;&lt;/div&gt;</code>.</p>
</li>
<li><p>The client requests resources from the server via HTTP, and when the necessary resources are loaded, it executes <code>new Vue()</code> to instantiate and render the page.</p>
</li>
</ol>
<p><strong>Example of client-side rendered app (Vue):</strong></p>
<pre><code class="lang-typescript">&lt;!-- index.html --&gt;
&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
  &lt;title&gt;Client-side Rendering Example&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;!-- Initially empty container --&gt;
  &lt;div id=<span class="hljs-string">"app"</span>&gt;&lt;/div&gt;

  &lt;!-- JavaScript bundle that will render the content --&gt;
  &lt;script src=<span class="hljs-string">"/dist/bundle.js"</span>&gt;&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<pre><code class="lang-typescript"><span class="hljs-comment">// main.js (compiled into bundle.js)</span>
<span class="hljs-keyword">import</span> Vue <span class="hljs-keyword">from</span> <span class="hljs-string">'vue'</span>;
<span class="hljs-keyword">import</span> App <span class="hljs-keyword">from</span> <span class="hljs-string">'./App.vue'</span>;

<span class="hljs-comment">// Client-side rendering happens here - after JS loads and executes</span>
<span class="hljs-keyword">new</span> Vue({
  render: <span class="hljs-function"><span class="hljs-params">h</span> =&gt;</span> h(App)
}).$mount(<span class="hljs-string">'#app'</span>);
</code></pre>
<pre><code class="lang-typescript"><span class="hljs-comment">// App.vue</span>
&lt;template&gt;
  &lt;div&gt;
    &lt;h1&gt;{{ title }}&lt;/h1&gt;
    &lt;p&gt;This content is rendered client-side.&lt;/p&gt;
  &lt;/div&gt;
&lt;/template&gt;

&lt;script&gt;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {
  data() {
    <span class="hljs-keyword">return</span> {
      title: <span class="hljs-string">'Hello World'</span>
    }
  },
  <span class="hljs-comment">// In client-side rendering, this lifecycle hook runs in the browser</span>
  mounted() {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Component mounted in browser'</span>);
  }
}
&lt;/script&gt;
</code></pre>
<h3 id="heading-server-side-rendering-process">Server-side rendering process</h3>
<ol>
<li><p>Visit a server-rendered website.</p>
</li>
<li><p>The server checks which resource files the current route component needs, then fills the content of these files into the HTML file. If there are AJAX requests, it will execute them for data pre-fetching and fill them into the HTML file, and finally return this HTML page.</p>
</li>
<li><p>When the client receives this HTML page, it can start rendering the page immediately. At the same time, the page also loads resources, and when the necessary resources are fully loaded, it begins to execute <code>new Vue()</code> to instantiate and take over the page.</p>
</li>
</ol>
<p><strong>Example of server-side rendered app (Vue):</strong></p>
<pre><code class="lang-typescript"><span class="hljs-comment">// server.js</span>
<span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express'</span>);
<span class="hljs-keyword">const</span> server = express();
<span class="hljs-keyword">const</span> { createBundleRenderer } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'vue-server-renderer'</span>);

<span class="hljs-comment">// Create a renderer based on the server bundle</span>
<span class="hljs-keyword">const</span> renderer = createBundleRenderer(<span class="hljs-string">'./dist/vue-ssr-server-bundle.json'</span>, {
  template: <span class="hljs-built_in">require</span>(<span class="hljs-string">'fs'</span>).readFileSync(<span class="hljs-string">'./index.template.html'</span>, <span class="hljs-string">'utf-8'</span>),
  clientManifest: <span class="hljs-built_in">require</span>(<span class="hljs-string">'./dist/vue-ssr-client-manifest.json'</span>)
});

<span class="hljs-comment">// Handle all routes with the same renderer</span>
server.get(<span class="hljs-string">'*'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> context = { url: req.url };

  <span class="hljs-comment">// Render our Vue app to a string</span>
  renderer.renderToString(context, <span class="hljs-function">(<span class="hljs-params">err, html</span>) =&gt;</span> {
    <span class="hljs-keyword">if</span> (err) {
      <span class="hljs-comment">// Handle error</span>
      res.status(<span class="hljs-number">500</span>).end(<span class="hljs-string">'Server Error'</span>);
      <span class="hljs-keyword">return</span>;
    }
    <span class="hljs-comment">// Send the rendered HTML to the client</span>
    res.end(html);
  });
});

server.listen(<span class="hljs-number">8080</span>);
</code></pre>
<pre><code class="lang-typescript">&lt;!-- index.template.html --&gt;
&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
  &lt;title&gt;Server-side Rendering Example&lt;/title&gt;
  &lt;!-- Resources injected by the server renderer --&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;!-- This will be replaced <span class="hljs-keyword">with</span> the app<span class="hljs-string">'s HTML --&gt;
  &lt;!--vue-ssr-outlet--&gt;
&lt;/body&gt;
&lt;/html&gt;</span>
</code></pre>
<pre><code class="lang-typescript"><span class="hljs-comment">// entry-server.js</span>
<span class="hljs-keyword">import</span> { createApp } <span class="hljs-keyword">from</span> <span class="hljs-string">'./app'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> context =&gt; {
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> { app, router } = createApp();

    <span class="hljs-comment">// Set server-side router's location</span>
    router.push(context.url);

    <span class="hljs-comment">// Wait until router has resolved possible async components and hooks</span>
    router.onReady(<span class="hljs-function">() =&gt;</span> {
      <span class="hljs-keyword">const</span> matchedComponents = router.getMatchedComponents();

      <span class="hljs-comment">// No matched routes, reject with 404</span>
      <span class="hljs-keyword">if</span> (!matchedComponents.length) {
        <span class="hljs-keyword">return</span> reject({ code: <span class="hljs-number">404</span> });
      }

      <span class="hljs-comment">// The Promise resolves to the app instance</span>
      resolve(app);
    }, reject);
  });
}
</code></pre>
<p>From the two processes above, you can see that the difference lies in the second step. A client-rendered website will directly return the HTML file, while a server-rendered website will render the page completely before returning this HTML file.</p>
<h4 id="heading-whats-the-benefit-of-doing-this-its-a-faster-time-to-content">What's the benefit of doing this? It's a faster time-to-content.</h4>
<p>Suppose your website needs to load four files (a, b, c, d) to render completely. And each file is 1 MB in size.</p>
<p>Calculating this way: a client-rendered website needs to load 4 files and an HTML file to complete the home page rendering, totaling 4MB (ignoring the HTML file size). While a server-rendered website only needs to load a fully rendered HTML file to complete the home page rendering, totaling the size of the already rendered HTML file (which isn't usually too large, generally a few hundred KB; my personal blog website (SSR) loads an HTML file of 400KB). <strong>This is why server-side rendering is faster.</strong></p>
<p>References:</p>
<ul>
<li><p><a target="_blank" href="https://github.com/woai3c/vue-ssr-demo">vue-ssr-demo</a></p>
</li>
<li><p><a target="_blank" href="https://ssr.vuejs.org/zh/">Vue.js Server-Side Rendering Guide</a></p>
</li>
</ul>
<h2 id="heading-4-use-a-cdn-for-static-resources"><strong>4. Use a CDN for Static Resources</strong></h2>
<p>A Content Delivery Network (CDN) is a set of web servers distributed across multiple geographic locations. We all know that the further the server is from the user, the higher the latency. CDNs are designed to solve this problem by deploying servers in multiple locations, bringing users closer to servers, thereby shortening request times.</p>
<h3 id="heading-cdn-principles">CDN Principles</h3>
<p>When a user visits a website without a CDN, the process is as follows:</p>
<ol>
<li><p>The browser needs to resolve the domain name into an IP address, so it makes a request to the local DNS.</p>
</li>
<li><p>The local DNS makes successive requests to the root server, top-level domain server, and authoritative server to get the IP address of the website's server.</p>
</li>
<li><p>The local DNS sends the IP address back to the browser, and the browser makes a request to the website server's IP address and receives the resources.</p>
</li>
</ol>
<p><a target="_blank" href="https://camo.githubusercontent.com/a9d8ea319521e8f560e8b68c2df8a4afaf27ed46e29e481b35bb78d013d23ca6/68747470733a2f2f6465762d746f2d75706c6f6164732e73332e616d617a6f6e6177732e636f6d2f75706c6f6164732f61727469636c65732f7a3079336a387a733733727a62617466616731342e706e67"><img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz0y3j8zs73rzbatfag14.png" alt="Diagram showing request flow without CDN: browser → DNS → root servers → top-level domain → authoritative server → website server" width="600" height="400" loading="lazy"></a></p>
<p>If the user is visiting a website that has deployed a CDN, the process is as follows:</p>
<ol>
<li><p>The browser needs to resolve the domain name into an IP address, so it makes a request to the local DNS.</p>
</li>
<li><p>The local DNS makes successive requests to the root server, top-level domain server, and authoritative server to get the IP address of the Global Server Load Balancing (GSLB) system.</p>
</li>
<li><p>The local DNS then makes a request to the GSLB. The main function of the GSLB is to determine the user's location based on the local DNS's IP address, filter out the closest local Server Load Balancing (SLB) system to the user, and return the IP address of that SLB to the local DNS.</p>
</li>
<li><p>The local DNS sends the SLB's IP address back to the browser, and the browser makes a request to the SLB.</p>
</li>
<li><p>The SLB selects the optimal cache server based on the resource and address requested by the browser and sends it back to the browser.</p>
</li>
<li><p>The browser then redirects to the cache server based on the address returned by the SLB.</p>
</li>
<li><p>If the cache server has the resource the browser needs, it sends the resource back to the browser. If not, it requests the resource from the source server, sends it to the browser, and caches it locally.</p>
</li>
</ol>
<p><a target="_blank" href="https://camo.githubusercontent.com/1ade29f05689af94c1066bccedab884a119d2fb4cba44f08fd95357cd9abdef6/68747470733a2f2f6465762d746f2d75706c6f6164732e73332e616d617a6f6e6177732e636f6d2f75706c6f6164732f61727469636c65732f616f70776c68783778386f33726176766e3170322e706e67"><img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faopwlhx7x8o3ravvn1p2.png" alt="Diagram showing request flow with CDN: browser → DNS → root servers → GSLB → SLB → cache servers → origin server" width="600" height="400" loading="lazy"></a></p>
<p>References:</p>
<ul>
<li><p><a target="_blank" href="https://en.wikipedia.org/wiki/Content_delivery_network">Content delivery network(CDN)</a></p>
</li>
<li><p><a target="_blank" href="https://www.freecodecamp.org/news/how-cdns-improve-performance-in-front-end-projects/">How to use CDNs to improve performance</a></p>
</li>
</ul>
<h2 id="heading-5-place-css-in-the-head-and-javascript-files-at-the-bottom"><strong>5. Place CSS in the Head and JavaScript Files at the Bottom</strong></h2>
<ul>
<li><p>CSS execution blocks rendering and prevents JS execution</p>
</li>
<li><p>JS loading and execution block HTML parsing and prevent CSSOM construction</p>
</li>
</ul>
<p>If these CSS and JS tags are placed in the HEAD tag, and they take a long time to load and parse, then the page will be blank. So you should place JS files at the bottom (not blocking DOM parsing but will block rendering) so that HTML parsing is completed before loading JS files. This presents the page content to the user as early as possible.</p>
<p>So then you might be wondering – why should CSS files still be placed in the head?</p>
<p>Because loading HTML first and then loading CSS will make users see an unstyled, "ugly" page at first glance. To avoid this situation, place CSS files in the head.</p>
<p>You can also place JS files in the head as long as the script tag has the defer attribute, which means asynchronous download and delayed execution.</p>
<p><strong>Here's an example of optimal placement:</strong></p>
<pre><code class="lang-typescript">&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
  &lt;meta charset=<span class="hljs-string">"UTF-8"</span>&gt;
  &lt;title&gt;Optimized Resource Loading&lt;/title&gt;

  &lt;!-- CSS <span class="hljs-keyword">in</span> the head <span class="hljs-keyword">for</span> faster rendering --&gt;
  &lt;link rel=<span class="hljs-string">"stylesheet"</span> href=<span class="hljs-string">"styles.css"</span>&gt;

  &lt;!-- Critical JS that must load early can use defer --&gt;
  &lt;script defer src=<span class="hljs-string">"critical.js"</span>&gt;&lt;/script&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;header&gt;
    &lt;h1&gt;My Website&lt;/h1&gt;
    &lt;!-- Page content here --&gt;
  &lt;/header&gt;

  &lt;main&gt;
    &lt;p&gt;Content that users need to see quickly...&lt;/p&gt;
  &lt;/main&gt;

  &lt;footer&gt;
    &lt;!-- Footer content --&gt;
  &lt;/footer&gt;

  &lt;!-- Non-critical JavaScript at the bottom --&gt;
  &lt;script src=<span class="hljs-string">"app.js"</span>&gt;&lt;/script&gt;
  &lt;script src=<span class="hljs-string">"analytics.js"</span>&gt;&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p><strong>Explanation of this approach:</strong></p>
<ol>
<li><p><strong>CSS in the</strong> <code>&lt;head&gt;</code>: Ensures the page is styled as soon as it renders, preventing the "flash of unstyled content" (FOUC). CSS is render-blocking, but that's actually what we want in this case.</p>
</li>
<li><p><strong>Critical JS with</strong> <code>defer</code>: The <code>defer</code> attribute tells the browser to:</p>
<ul>
<li><p>Download the script in parallel while parsing HTML</p>
</li>
<li><p>Only execute the script after HTML parsing is complete but before the <code>DOMContentLoaded</code> event</p>
</li>
<li><p>Maintain the order of execution if there are multiple deferred scripts</p>
</li>
</ul>
</li>
<li><p><strong>Non-critical JS before closing</strong> <code>&lt;/body&gt;</code>: Scripts without special attributes will:</p>
<ul>
<li><p>Block HTML parsing while they download and execute</p>
</li>
<li><p>By placing them at the bottom, we ensure that all the important content is parsed and displayed first</p>
</li>
<li><p>This improves perceived performance even if the total load time is the same</p>
</li>
</ul>
</li>
</ol>
<p>You can also use <code>async</code> for scripts that don't depend on DOM or other scripts:</p>
<pre><code class="lang-typescript">&lt;script <span class="hljs-keyword">async</span> src=<span class="hljs-string">"independent.js"</span>&gt;&lt;/script&gt;
</code></pre>
<p>The <code>async</code> attribute will download the script in parallel and execute it as soon as it's available, which may interrupt HTML parsing. Use this only for scripts that don't modify the DOM or depend on other scripts.</p>
<p>Reference:</p>
<ul>
<li><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Learn_web_development/Getting_started/Your_first_website/Adding_interactivity">Adding Interactivity with JavaScript</a></li>
</ul>
<h2 id="heading-6-use-font-icons-iconfont-instead-of-image-icons"><strong>6. Use Font Icons (iconfont) Instead of Image Icons</strong></h2>
<p>A font icon is an icon made into a font. When using it, it's just like a font, and you can set attributes such as font-size, color, and so on, which is very convenient. Font icons are also vector graphics and won't lose clarity. Another advantage is that the generated files are particularly small.</p>
<h3 id="heading-compress-font-files">Compress Font Files</h3>
<p>Use the <a target="_blank" href="https://github.com/patrickhulce/fontmin-webpack">fontmin-webpack</a> plugin to compress font files (thanks to <a target="_blank" href="https://juejin.im/user/237150239985165">Frontend Xiaowei</a> for providing this).</p>
<p><a target="_blank" href="https://camo.githubusercontent.com/8aec44850415bdf6f23aa59cae5daa0c6d06ec9414766ddfe34c294b663fcde4/68747470733a2f2f696d672d626c6f672e6373646e696d672e636e2f696d675f636f6e766572742f37376232656235653365303933323030383765333337303638366461393330302e706e67"><img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flmbq5m02e5myhbyz7c5d.png" alt="Showing difference between uncompressed and compressed files" width="600" height="400" loading="lazy"></a></p>
<p>References:</p>
<ul>
<li><p><a target="_blank" href="https://github.com/patrickhulce/fontmin-webpack">fontmin-webpack</a></p>
</li>
<li><p><a target="_blank" href="https://www.iconfont.cn/">Iconfont-Alibaba Vector Icon Library</a></p>
</li>
</ul>
<h2 id="heading-7-make-good-use-of-caching-avoid-reloading-the-same-resources"><strong>7. Make Good Use of Caching, Avoid Reloading the Same Resources</strong></h2>
<p>To prevent users from having to request files every time they visit a website, we can control this behavior by adding Expires or max-age. Expires sets a time, and as long as it's before this time, the browser won't request the file but will directly use the cache. Max-age is a relative time, and it's recommended to use max-age instead of Expires.</p>
<p>But this creates a problem: what happens when the file is updated? How do we notify the browser to request the file again?</p>
<p>This can be done by updating the resource link addresses referenced in the page, making the browser actively abandon the cache and load new resources.</p>
<p>The specific approach is to associate the URL modification of the resource address with the file content, which means that only when the file content changes, the corresponding URL will change. This achieves file-level precise cache control.</p>
<p>So what is related to file content? We naturally think of using <a target="_blank" href="https://www.okta.com/identity-101/md5/">digest algorithms</a> to derive digest information for the file. The digest information corresponds one-to-one with the file content, providing a basis for cache control that's precise to the granularity of individual files.</p>
<h3 id="heading-how-to-implement-caching-and-cache-busting"><strong>How to implement caching and cache-busting:</strong></h3>
<p><strong>1. Server-side cache headers (using Express.js as an example):</strong></p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Set cache control headers for static resources</span>
app.use(<span class="hljs-string">'/static'</span>, express.static(<span class="hljs-string">'public'</span>, {
  maxAge: <span class="hljs-string">'1y'</span>, <span class="hljs-comment">// Cache for 1 year</span>
  etag: <span class="hljs-literal">true</span>,   <span class="hljs-comment">// Use ETag for validation</span>
  lastModified: <span class="hljs-literal">true</span> <span class="hljs-comment">// Use Last-Modified for validation</span>
}));

<span class="hljs-comment">// For HTML files that shouldn't be cached as long</span>
app.get(<span class="hljs-string">'/*.html'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  res.set({
    <span class="hljs-string">'Cache-Control'</span>: <span class="hljs-string">'public, max-age=300'</span>, <span class="hljs-comment">// Cache for 5 minutes</span>
    <span class="hljs-string">'Expires'</span>: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(<span class="hljs-built_in">Date</span>.now() + <span class="hljs-number">300000</span>).toUTCString()
  });
  <span class="hljs-comment">// Send HTML content</span>
});
</code></pre>
<p><strong>2. Using content hashes in filenames (Webpack configuration):</strong></p>
<pre><code class="lang-typescript"><span class="hljs-comment">// webpack.config.js</span>
<span class="hljs-built_in">module</span>.<span class="hljs-built_in">exports</span> = {
  output: {
    filename: <span class="hljs-string">'[name].[contenthash].js'</span>, <span class="hljs-comment">// Uses content hash in filename</span>
    path: path.resolve(__dirname, <span class="hljs-string">'dist'</span>),
  },
  plugins: [
    <span class="hljs-comment">// Extract CSS into separate files with content hash</span>
    <span class="hljs-keyword">new</span> MiniCssExtractPlugin({
      filename: <span class="hljs-string">'[name].[contenthash].css'</span>
    }),
    <span class="hljs-comment">// Generate HTML with correct hashed filenames</span>
    <span class="hljs-keyword">new</span> HtmlWebpackPlugin({
      template: <span class="hljs-string">'src/index.html'</span>
    })
  ]
};
</code></pre>
<p>This will produce output files like:</p>
<ul>
<li><p><code>main.8e0d62a10c151dad4f8e.js</code></p>
</li>
<li><p><code>styles.f4e3a77c616562b26ca1.css</code></p>
</li>
</ul>
<p>When you change the content of a file, its hash will change, forcing the browser to download the new file instead of using the cached version.</p>
<p><strong>3. Example of generated HTML with cache-busting:</strong></p>
<pre><code class="lang-typescript">&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
  &lt;meta charset=<span class="hljs-string">"UTF-8"</span>&gt;
  &lt;title&gt;Cache Busting Example&lt;/title&gt;
  &lt;!-- Note the content hash <span class="hljs-keyword">in</span> the filename --&gt;
  &lt;link rel=<span class="hljs-string">"stylesheet"</span> href=<span class="hljs-string">"/static/styles.f4e3a77c616562b26ca1.css"</span>&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;div id=<span class="hljs-string">"app"</span>&gt;&lt;/div&gt;
  &lt;!-- Script <span class="hljs-keyword">with</span> content hash --&gt;
  &lt;script src=<span class="hljs-string">"/static/main.8e0d62a10c151dad4f8e.js"</span>&gt;&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p><strong>4. Version query parameters (simpler but less effective approach):</strong></p>
<pre><code class="lang-typescript">&lt;link rel=<span class="hljs-string">"stylesheet"</span> href=<span class="hljs-string">"styles.css?v=1.2.3"</span>&gt;
&lt;script src=<span class="hljs-string">"app.js?v=1.2.3"</span>&gt;&lt;/script&gt;
</code></pre>
<p>When updating files, manually change the version number to force a new download.</p>
<p>References:</p>
<ul>
<li><a target="_blank" href="https://webpack.js.org/guides/caching/#root">webpack-caching</a></li>
</ul>
<h2 id="heading-8-compress-files"><strong>8. Compress Files</strong></h2>
<p>Compressing files can reduce file download time, providing a better user experience.</p>
<p>Thanks to the development of Webpack and Node, file compression is now very convenient.</p>
<p>In Webpack, you can use the following plugins for compression:</p>
<ul>
<li><p>JavaScript: UglifyPlugin</p>
</li>
<li><p>CSS: MiniCssExtractPlugin</p>
</li>
<li><p>HTML: HtmlWebpackPlugin</p>
</li>
</ul>
<p>In fact, we can do even better by using gzip compression. This can be enabled by adding the gzip identifier to the Accept-Encoding header in the HTTP request header. Of course, the server must also support this feature.</p>
<p>Gzip is currently the most popular and effective compression method. For example, the app.js file generated after building a project I developed with Vue has a size of 1.4MB, but after gzip compression, it's only 573KB, reducing the volume by nearly 60%.</p>
<p>Here are the methods for configuring gzip in webpack and node.</p>
<p><strong>Download plugins</strong></p>
<pre><code class="lang-typescript">npm install compression-webpack-plugin --save-dev
npm install compression
</code></pre>
<p><strong>Webpack configuration</strong></p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> CompressionPlugin = <span class="hljs-built_in">require</span>(<span class="hljs-string">'compression-webpack-plugin'</span>);

<span class="hljs-built_in">module</span>.<span class="hljs-built_in">exports</span> = {
  plugins: [<span class="hljs-keyword">new</span> CompressionPlugin()],
}
</code></pre>
<p><strong>Node configuration</strong></p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> compression = <span class="hljs-built_in">require</span>(<span class="hljs-string">'compression'</span>)
<span class="hljs-comment">// Use before other middleware</span>
app.use(compression())
</code></pre>
<h2 id="heading-9-image-optimization"><strong>9. Image Optimization</strong></h2>
<h3 id="heading-1-lazy-loading-images"><strong>1. Lazy Loading Images</strong></h3>
<p>In a page, don't initially set the path for images – only load the actual image when it appears in the browser's viewport. This is lazy loading. For websites with many images, loading all images at once can have a significant impact on user experience, so image lazy loading is necessary.</p>
<p>First, set up the images like this, where images won't load when they're not visible in the page:</p>
<pre><code class="lang-typescript">&lt;img data-src=<span class="hljs-string">"https://avatars0.githubusercontent.com/u/22117876?s=460&amp;u=7bd8f32788df6988833da6bd155c3cfbebc68006&amp;v=4"</span>&gt;
</code></pre>
<p>When the page becomes visible, use JS to load the image:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> img = <span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">'img'</span>)
img.src = img.dataset.src
</code></pre>
<p>This is how the image gets loaded. For the complete code, please refer to the reference materials.</p>
<p>Reference:</p>
<ul>
<li><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/Performance/Guides/Lazy_loading">Lazy loading images for the web</a></li>
</ul>
<h3 id="heading-2-responsive-images"><strong>2. Responsive Images</strong></h3>
<p>The advantage of responsive images is that browsers can automatically load appropriate images based on screen size.</p>
<p>Implementation through <code>picture</code>:</p>
<pre><code class="lang-typescript">&lt;picture&gt;
    &lt;source srcset=<span class="hljs-string">"banner_w1000.jpg"</span> media=<span class="hljs-string">"(min-width: 801px)"</span>&gt;
    &lt;source srcset=<span class="hljs-string">"banner_w800.jpg"</span> media=<span class="hljs-string">"(max-width: 800px)"</span>&gt;
    &lt;img src=<span class="hljs-string">"banner_w800.jpg"</span> alt=<span class="hljs-string">""</span>&gt;
&lt;/picture&gt;
</code></pre>
<p>Implementation through <code>@media</code>:</p>
<pre><code class="lang-typescript"><span class="hljs-meta">@media</span> (min-width: <span class="hljs-number">769</span>px) {
    .bg {
        background-image: url(bg1080.jpg);
    }
}
<span class="hljs-meta">@media</span> (max-width: <span class="hljs-number">768</span>px) {
    .bg {
        background-image: url(bg768.jpg);
    }
}
</code></pre>
<h3 id="heading-3-adjust-image-size"><strong>3. Adjust Image Size</strong></h3>
<p>For example, if you have a 1920 * 1080 size image, you show it to users as a thumbnail, and only display the full image when users hover over it. If users never actually hover over the thumbnail, the time spent downloading the image is wasted.</p>
<p>So we can optimize this with two images. Initially, only load the thumbnail, and when users hover over the image, then load the large image. Another approach is to lazy load the large image, manually changing the src of the large image to download it after all elements have loaded.</p>
<p><strong>Example implementation of image size optimization:</strong></p>
<pre><code class="lang-typescript">&lt;!-- HTML Structure --&gt;
&lt;div <span class="hljs-keyword">class</span>=<span class="hljs-string">"image-container"</span>&gt;
  &lt;img <span class="hljs-keyword">class</span>=<span class="hljs-string">"thumbnail"</span> src=<span class="hljs-string">"thumbnail-small.jpg"</span> alt=<span class="hljs-string">"Small thumbnail"</span>&gt;
  &lt;img <span class="hljs-keyword">class</span>=<span class="hljs-string">"full-size"</span> data-src=<span class="hljs-string">"image-large.jpg"</span> alt=<span class="hljs-string">"Full-size image"</span>&gt;
&lt;/div&gt;
</code></pre>
<pre><code class="lang-typescript"><span class="hljs-comment">/* CSS for the container and images */</span>
.image-container {
  position: relative;
  width: <span class="hljs-number">200</span>px;
  height: <span class="hljs-number">150</span>px;
  overflow: hidden;
}

.thumbnail {
  width: <span class="hljs-number">100</span>%;
  height: <span class="hljs-number">100</span>%;
  <span class="hljs-built_in">object</span>-fit: cover;
  display: block;
}

.full-size {
  display: none;
  position: absolute;
  top: <span class="hljs-number">0</span>;
  left: <span class="hljs-number">0</span>;
  z-index: <span class="hljs-number">2</span>;
  max-width: <span class="hljs-number">600</span>px;
  max-height: <span class="hljs-number">400</span>px;
}

<span class="hljs-comment">/* Show full size on hover */</span>
.image-container:hover .full-size {
  display: block;
}
</code></pre>
<pre><code class="lang-typescript"><span class="hljs-comment">// JavaScript to lazy load the full-size image</span>
<span class="hljs-built_in">document</span>.addEventListener(<span class="hljs-string">'DOMContentLoaded'</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> containers = <span class="hljs-built_in">document</span>.querySelectorAll(<span class="hljs-string">'.image-container'</span>);

  containers.forEach(<span class="hljs-function"><span class="hljs-params">container</span> =&gt;</span> {
    <span class="hljs-keyword">const</span> thumbnail = container.querySelector(<span class="hljs-string">'.thumbnail'</span>);
    <span class="hljs-keyword">const</span> fullSize = container.querySelector(<span class="hljs-string">'.full-size'</span>);

    <span class="hljs-comment">// Load the full-size image when the user hovers over the thumbnail</span>
    container.addEventListener(<span class="hljs-string">'mouseenter'</span>, <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-keyword">if</span> (!fullSize.src &amp;&amp; fullSize.dataset.src) {
        fullSize.src = fullSize.dataset.src;
      }
    });

    <span class="hljs-comment">// Alternative: Load the full-size image after the page loads completely</span>
    <span class="hljs-comment">/*
    window.addEventListener('load', () =&gt; {
      setTimeout(() =&gt; {
        if (!fullSize.src &amp;&amp; fullSize.dataset.src) {
          fullSize.src = fullSize.dataset.src;
        }
      }, 1000); // Delay loading by 1 second after window load
    });
    */</span>
  });
});
</code></pre>
<p>This implementation:</p>
<ol>
<li><p>Shows only the thumbnail initially</p>
</li>
<li><p>Loads the full-size image only when the user hovers over the thumbnail</p>
</li>
<li><p>Provides an alternative approach to load all full-size images with a delay after page load</p>
</li>
</ol>
<h3 id="heading-4-reduce-image-quality"><strong>4. Reduce Image Quality</strong></h3>
<p>For example, with JPG format images, there's usually no noticeable difference between 100% quality and 90% quality, especially when used as background images. When cutting background images in Adobe Photoshop, I often cut the image into JPG format and compress it to 60% quality, and basically can't see any difference.</p>
<p>There are two compression methods: one is through the Webpack plugin <code>image-webpack-loader</code>, and the other is through online compression websites.</p>
<p>Here's how to use the Webpack plugin <code>image-webpack-loader</code>:</p>
<pre><code class="lang-typescript">npm i -D image-webpack-loader
</code></pre>
<p>Webpack configuration:</p>
<pre><code class="lang-typescript">{
  test: <span class="hljs-regexp">/\.(png|jpe?g|gif|svg)(\?.*)?$/</span>,
  use:[
    {
    loader: <span class="hljs-string">'url-loader'</span>,
    options: {
      limit: <span class="hljs-number">10000</span>, <span class="hljs-comment">/* Images smaller than 1000 bytes will be automatically converted to base64 code references */</span>
      name: utils.assetsPath(<span class="hljs-string">'img/[name].[hash:7].[ext]'</span>)
      }
    },
    <span class="hljs-comment">/* Compress images */</span>
    {
      loader: <span class="hljs-string">'image-webpack-loader'</span>,
      options: {
        bypassOnDebug: <span class="hljs-literal">true</span>,
      }
    }
  ]
}
</code></pre>
<h3 id="heading-5-use-css3-effects-instead-of-images-when-possible"><strong>5. Use CSS3 Effects Instead of Images When Possible</strong></h3>
<p>Many images can be drawn with CSS effects (gradients, shadows, and so on). In these cases, CSS3 effects are better. This is because code size is usually a fraction or even a tenth of the image size.</p>
<p>Reference:</p>
<ul>
<li><a target="_blank" href="https://webpack.js.org/guides/asset-management/">Asset Management</a></li>
</ul>
<h3 id="heading-6-use-webp-to-format-images"><strong>6. Use WebP to Format Images</strong></h3>
<p>WebP's advantage is reflected in its better image data compression algorithm, which brings smaller image volume while maintaining image quality that's indistinguishable to the naked eye. It also has lossless and lossy compression modes, Alpha transparency, and animation features. Its conversion effects on JPEG and PNG are quite excellent, stable, and uniform.</p>
<p><strong>Example of implementing WebP with fallbacks:</strong></p>
<pre><code class="lang-typescript">&lt;!-- Using the picture element <span class="hljs-keyword">for</span> WebP <span class="hljs-keyword">with</span> fallback --&gt;
&lt;picture&gt;
  &lt;source srcset=<span class="hljs-string">"image.webp"</span> <span class="hljs-keyword">type</span>=<span class="hljs-string">"image/webp"</span>&gt;
  &lt;source srcset=<span class="hljs-string">"image.jpg"</span> <span class="hljs-keyword">type</span>=<span class="hljs-string">"image/jpeg"</span>&gt;
  &lt;img src=<span class="hljs-string">"image.jpg"</span> alt=<span class="hljs-string">"Description of the image"</span>&gt;
&lt;/picture&gt;
</code></pre>
<p><strong>Server-side WebP detection and serving:</strong></p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Express.js example</span>
app.get(<span class="hljs-string">'/images/:imageName'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> supportsWebP = req.headers.accept &amp;&amp; req.headers.accept.includes(<span class="hljs-string">'image/webp'</span>);
  <span class="hljs-keyword">const</span> imagePath = supportsWebP 
    ? <span class="hljs-string">`public/images/<span class="hljs-subst">${req.params.imageName}</span>.webp`</span> 
    : <span class="hljs-string">`public/images/<span class="hljs-subst">${req.params.imageName}</span>.jpg`</span>;

  res.sendFile(path.resolve(__dirname, imagePath));
});
</code></pre>
<p>Reference:</p>
<ul>
<li><a target="_blank" href="https://en.wikipedia.org/wiki/WebP">WebP</a></li>
</ul>
<h2 id="heading-10-load-code-on-demand-through-webpack-extract-third-party-libraries-reduce-redundant-code-when-converting-es6-to-es5"><strong>10. Load Code on Demand Through Webpack, Extract Third-Party Libraries, Reduce Redundant Code When Converting ES6 to ES5</strong></h2>
<p>The following quote from the official Webpack documentation explains the concept of lazy loading:</p>
<blockquote>
<p>"Lazy loading or on-demand loading is a great way to optimize a website or application. This approach actually separates your code at some logical breakpoints, and then immediately references or is about to reference some new code blocks after completing certain operations in some code blocks. This speeds up the initial loading of the application and lightens its overall volume because some code blocks may never be loaded." <em>Source:</em> <a target="_blank" href="http://webpack.docschina.org/guides/lazy-loading/"><em>Lazy Loading</em></a></p>
</blockquote>
<p><strong>Note:</strong> While image lazy loading (discussed in section 9.1) delays the loading of image resources until they're visible in the viewport, code lazy loading splits JavaScript bundles and loads code fragments only when they're needed for specific functionality. They both improve initial load time, but they work at different levels of resource optimization.</p>
<h3 id="heading-generate-file-names-based-on-file-content-combined-with-import-dynamic-import-of-components-to-achieve-on-demand-loading"><strong>Generate File Names Based on File Content, Combined with Import Dynamic Import of Components to Achieve On-Demand Loading</strong></h3>
<p>This requirement can be achieved by configuring the filename property of output. One of the value options in the filename property is <code>[contenthash]</code>, which creates a unique hash based on file content. When the file content changes, <code>[contenthash]</code> also changes.</p>
<pre><code class="lang-typescript">output: {
    filename: <span class="hljs-string">'[name].[contenthash].js'</span>,
    chunkFilename: <span class="hljs-string">'[name].[contenthash].js'</span>,
    path: path.resolve(__dirname, <span class="hljs-string">'../dist'</span>),
},
</code></pre>
<p><strong>Example of code lazy loading in a Vue application:</strong></p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Instead of importing synchronously like this:</span>
<span class="hljs-comment">// import UserProfile from './components/UserProfile.vue'</span>

<span class="hljs-comment">// Use dynamic import for route components:</span>
<span class="hljs-keyword">const</span> UserProfile = <span class="hljs-function">() =&gt;</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">'./components/UserProfile.vue'</span>)

<span class="hljs-comment">// Then use it in your routes</span>
<span class="hljs-keyword">const</span> router = <span class="hljs-keyword">new</span> VueRouter({
  routes: [
    { path: <span class="hljs-string">'/user/:id'</span>, component: UserProfile }
  ]
})
</code></pre>
<p>This ensures the UserProfile component is only loaded when a user navigates to that route, not on initial page load.</p>
<h3 id="heading-extract-third-party-libraries"><strong>Extract Third-Party Libraries</strong></h3>
<p>Since imported third-party libraries are generally stable and don't change frequently, extracting them separately as long-term caches is a better choice. This requires using the cacheGroups option of Webpack4's splitChunk plugin.</p>
<pre><code class="lang-typescript">optimization: {
    runtimeChunk: {
        name: <span class="hljs-string">'manifest'</span> <span class="hljs-comment">// Split webpack's runtime code into a separate chunk.</span>
    },
    splitChunks: {
        cacheGroups: {
            vendor: {
                name: <span class="hljs-string">'chunk-vendors'</span>,
                test: <span class="hljs-regexp">/[\\/]node_modules[\\/]/</span>,
                priority: <span class="hljs-number">-10</span>,
                chunks: <span class="hljs-string">'initial'</span>
            },
            common: {
                name: <span class="hljs-string">'chunk-common'</span>,
                minChunks: <span class="hljs-number">2</span>,
                priority: <span class="hljs-number">-20</span>,
                chunks: <span class="hljs-string">'initial'</span>,
                reuseExistingChunk: <span class="hljs-literal">true</span>
            }
        },
    }
},
</code></pre>
<ul>
<li><p><code>test</code>: Used to control which modules are matched by this cache group. If passed unchanged, it defaults to select all modules. Types of values that can be passed: <code>RegExp</code>, <code>String</code>, and <code>Function</code>.</p>
</li>
<li><p><code>priority</code>: Indicates extraction weight, with higher numbers indicating higher priority. Since a module might meet the conditions of multiple <code>cacheGroups</code>, extraction is determined by the highest weight.</p>
</li>
<li><p><code>reuseExistingChunk</code>: Indicates whether to use existing chunks. If true, it means that if the current chunk contains modules that have already been extracted, new ones won't be generated.</p>
</li>
<li><p><code>minChunks</code> (default is 1): The minimum number of times this code block should be referenced before splitting (note: to ensure code block reusability, the default strategy doesn't require multiple references to be split).</p>
</li>
<li><p><code>chunks</code> (default is async): initial, async, and all.</p>
</li>
<li><p><code>name</code> (name of the packaged chunks): String or function (functions can customize names based on conditions).</p>
</li>
</ul>
<h3 id="heading-reduce-redundant-code-when-converting-es6-to-es5"><strong>Reduce Redundant Code When Converting ES6 to ES5</strong></h3>
<p>To achieve the same functionality as the original code after Babel conversion, some helper functions are needed. For example this:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">class</span> Person {}
</code></pre>
<p>will be converted to this:</p>
<pre><code class="lang-typescript"><span class="hljs-meta">"use strict"</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_classCallCheck</span>(<span class="hljs-params">instance, Constructor</span>) </span>{
  <span class="hljs-keyword">if</span> (!(instance <span class="hljs-keyword">instanceof</span> Constructor)) {
    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">TypeError</span>(<span class="hljs-string">"Cannot call a class as a function"</span>);
  }
}

<span class="hljs-keyword">var</span> Person = <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Person</span>(<span class="hljs-params"></span>) </span>{
  _classCallCheck(<span class="hljs-built_in">this</span>, Person);
};
</code></pre>
<p>Here, <code>_classCallCheck</code> is a <code>helper</code> function. If classes are declared in many files, then many such <code>helper</code> functions will be generated.</p>
<p>The <code>@babel/runtime</code> package declares all the helper functions needed, and the role of <code>@babel/plugin-transform-runtime</code> is to import all files that need <code>helper</code> functions from the <code>@babel/runtime package</code>:</p>
<pre><code class="lang-typescript"><span class="hljs-meta">"use strict"</span>;

<span class="hljs-keyword">var</span> _classCallCheck2 = <span class="hljs-built_in">require</span>(<span class="hljs-string">"@babel/runtime/helpers/classCallCheck"</span>);

<span class="hljs-keyword">var</span> _classCallCheck3 = _interopRequireDefault(_classCallCheck2);

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_interopRequireDefault</span>(<span class="hljs-params">obj</span>) </span>{
  <span class="hljs-keyword">return</span> obj &amp;&amp; obj.__esModule ? obj : { <span class="hljs-keyword">default</span>: obj };
}

<span class="hljs-keyword">var</span> Person = <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Person</span>(<span class="hljs-params"></span>) </span>{
  (<span class="hljs-number">0</span>, _classCallCheck3.default)(<span class="hljs-built_in">this</span>, Person);
};
</code></pre>
<p>Here, the <code>helper</code> function <code>classCallCheck</code> is no longer compiled, but instead references <code>helpers/classCallCheck</code> from <code>@babel/runtime</code>.</p>
<p><strong>Installation:</strong></p>
<pre><code class="lang-typescript">npm i -D <span class="hljs-meta">@babel</span>/plugin-transform-runtime <span class="hljs-meta">@babel</span>/runtime
</code></pre>
<p><strong>Usage:</strong><br>In the <code>.babelrc</code> file,</p>
<pre><code class="lang-typescript"><span class="hljs-string">"plugins"</span>: [
        <span class="hljs-string">"@babel/plugin-transform-runtime"</span>
]
</code></pre>
<p>References:</p>
<ul>
<li><p><a target="_blank" href="https://babeljs.io/">Babel</a></p>
</li>
<li><p><a target="_blank" href="https://router.vuejs.org/guide/advanced/lazy-loading.html">Vue Route Lazy Loading</a></p>
</li>
<li><p><a target="_blank" href="https://webpack.js.org/plugins/split-chunks-plugin/">SplitChunksPlugin</a></p>
</li>
</ul>
<h2 id="heading-11-reduce-reflows-and-repaints"><strong>11. Reduce Reflows and Repaints</strong></h2>
<h3 id="heading-browser-rendering-process"><strong>Browser Rendering Process</strong></h3>
<ol>
<li><p>Parse HTML to generate DOM tree.</p>
</li>
<li><p>Parse CSS to generate CSSOM rules tree.</p>
</li>
<li><p>Combine DOM tree and CSSOM rules tree to generate rendering tree.</p>
</li>
<li><p>Traverse the rendering tree to begin layout, calculating the position and size information of each node.</p>
</li>
<li><p>Paint each node of the rendering tree to the screen.</p>
</li>
</ol>
<p><a target="_blank" href="https://camo.githubusercontent.com/b01f818aab6cf14622f77ee3d2407b961b38b4654ab88c3fa391d2b43a77c46c/68747470733a2f2f696d672d626c6f672e6373646e696d672e636e2f696d675f636f6e766572742f35363437643961643461643561353731373839313964656165353137356238332e706e67"><img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft7yfvoxwqdvs7a9c6v9b.png" alt="Diagram of browser rendering process showing the steps from HTML/CSS to rendered pixels" width="600" height="400" loading="lazy"></a></p>
<h3 id="heading-reflow"><strong>Reflow</strong></h3>
<p>When the position or size of DOM elements is changed, the browser needs to regenerate the rendering tree, a process called reflow.</p>
<h3 id="heading-repaint"><strong>Repaint</strong></h3>
<p>After regenerating the rendering tree, each node of the rendering tree needs to be painted to the screen, a process called repaint. Not all actions will cause reflow – for example, changing font color will only cause repaint. Remember, reflow will cause repaint, but repaint will not cause reflow.</p>
<p>Both reflow and repaint operations are very expensive because the JavaScript engine thread and the GUI rendering thread are mutually exclusive, and only one can work at a time.</p>
<p>What operations will cause reflow?</p>
<ul>
<li><p>Adding or removing visible DOM elements</p>
</li>
<li><p>Element position changes</p>
</li>
<li><p>Element size changes</p>
</li>
<li><p>Content changes</p>
</li>
<li><p>Browser window size changes</p>
</li>
</ul>
<p>How to reduce reflows and repaints?</p>
<ul>
<li><p>When modifying styles with JavaScript, it's best not to write styles directly, but to replace classes to change styles.</p>
</li>
<li><p>If you need to perform a series of operations on a DOM element, you can take the DOM element out of the document flow, make modifications, and then bring it back to the document. It's recommended to use hidden elements (display:none) or document fragments (DocumentFragement), both of which can implement this approach well.</p>
</li>
</ul>
<p><strong>Example of causing unnecessary reflows (inefficient):</strong></p>
<pre><code class="lang-typescript"><span class="hljs-comment">// This causes multiple reflows as each style change triggers a reflow</span>
<span class="hljs-keyword">const</span> element = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'myElement'</span>);
element.style.width = <span class="hljs-string">'100px'</span>;
element.style.height = <span class="hljs-string">'200px'</span>;
element.style.margin = <span class="hljs-string">'10px'</span>;
element.style.padding = <span class="hljs-string">'20px'</span>;
element.style.borderRadius = <span class="hljs-string">'5px'</span>;
</code></pre>
<p><strong>Optimized version 1 – using CSS classes:</strong></p>
<pre><code class="lang-typescript"><span class="hljs-comment">/* style.css */</span>
.my-modified-element {
  width: <span class="hljs-number">100</span>px;
  height: <span class="hljs-number">200</span>px;
  margin: <span class="hljs-number">10</span>px;
  padding: <span class="hljs-number">20</span>px;
  border-radius: <span class="hljs-number">5</span>px;
}
</code></pre>
<pre><code class="lang-typescript"><span class="hljs-comment">// Only one reflow happens when the class is added</span>
<span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'myElement'</span>).classList.add(<span class="hljs-string">'my-modified-element'</span>);
</code></pre>
<p><strong>Optimized version 2 – batching style changes:</strong></p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Batching style changes using cssText</span>
<span class="hljs-keyword">const</span> element = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'myElement'</span>);
element.style.cssText = <span class="hljs-string">'width: 100px; height: 200px; margin: 10px; padding: 20px; border-radius: 5px;'</span>;
</code></pre>
<p><strong>Optimized version 3 – using document fragments (for multiple elements):</strong></p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Instead of adding elements one by one</span>
<span class="hljs-keyword">const</span> list = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'myList'</span>);
<span class="hljs-keyword">const</span> fragment = <span class="hljs-built_in">document</span>.createDocumentFragment();

<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">100</span>; i++) {
  <span class="hljs-keyword">const</span> item = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'li'</span>);
  item.textContent = <span class="hljs-string">`Item <span class="hljs-subst">${i}</span>`</span>;
  fragment.appendChild(item);
}

<span class="hljs-comment">// Only one reflow happens when the fragment is appended</span>
list.appendChild(fragment);
</code></pre>
<p><strong>Optimized version 4 – take element out of flow, modify, then reinsert:</strong></p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Remove from DOM, make changes, then reinsert</span>
<span class="hljs-keyword">const</span> element = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'myElement'</span>);
<span class="hljs-keyword">const</span> parent = element.parentNode;
<span class="hljs-keyword">const</span> nextSibling = element.nextSibling;

<span class="hljs-comment">// Remove (causes one reflow)</span>
parent.removeChild(element);

<span class="hljs-comment">// Make multiple changes (no reflows while detached)</span>
element.style.width = <span class="hljs-string">'100px'</span>;
element.style.height = <span class="hljs-string">'200px'</span>;
element.style.margin = <span class="hljs-string">'10px'</span>;
element.style.padding = <span class="hljs-string">'20px'</span>;
element.style.borderRadius = <span class="hljs-string">'5px'</span>;

<span class="hljs-comment">// Reinsert (causes one more reflow)</span>
<span class="hljs-keyword">if</span> (nextSibling) {
  parent.insertBefore(element, nextSibling);
} <span class="hljs-keyword">else</span> {
  parent.appendChild(element);
}
</code></pre>
<p><strong>Optimized version 5 – using display:none temporarily:</strong></p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> element = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'myElement'</span>);

<span class="hljs-comment">// Hide element (one reflow)</span>
element.style.display = <span class="hljs-string">'none'</span>;

<span class="hljs-comment">// Make multiple changes (no reflows while hidden)</span>
element.style.width = <span class="hljs-string">'100px'</span>;
element.style.height = <span class="hljs-string">'200px'</span>;
element.style.margin = <span class="hljs-string">'10px'</span>;
element.style.padding = <span class="hljs-string">'20px'</span>;
element.style.borderRadius = <span class="hljs-string">'5px'</span>;

<span class="hljs-comment">// Show element again (one more reflow)</span>
element.style.display = <span class="hljs-string">'block'</span>;
</code></pre>
<p>By using these optimization techniques, you can significantly reduce the number of reflows and repaints, leading to smoother performance, especially for animations and dynamic content updates.</p>
<h2 id="heading-12-use-event-delegation"><strong>12. Use Event Delegation</strong></h2>
<p>Event delegation takes advantage of event bubbling, allowing you to specify a single event handler to manage all events of a particular type. All events that use buttons (most mouse events and keyboard events) are suitable for the event delegation technique. Using event delegation can save memory.</p>
<pre><code class="lang-typescript">&lt;ul&gt;
  &lt;li&gt;Apple&lt;/li&gt;
  &lt;li&gt;Banana&lt;/li&gt;
  &lt;li&gt;Pineapple&lt;/li&gt;
&lt;/ul&gt;

<span class="hljs-comment">// good</span>
<span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">'ul'</span>).onclick = <span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> target = event.target
  <span class="hljs-keyword">if</span> (target.nodeName === <span class="hljs-string">'LI'</span>) {
    <span class="hljs-built_in">console</span>.log(target.innerHTML)
  }
}

<span class="hljs-comment">// bad</span>
<span class="hljs-built_in">document</span>.querySelectorAll(<span class="hljs-string">'li'</span>).forEach(<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
  e.onclick = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-built_in">console</span>.log(<span class="hljs-built_in">this</span>.innerHTML)
  }
})
</code></pre>
<h2 id="heading-13-pay-attention-to-program-locality"><strong>13. Pay Attention to Program Locality</strong></h2>
<p>A well-written computer program often has good locality – it tends to reference data items near recently referenced data items or the recently referenced data items themselves. This tendency is known as the principle of locality. Programs with good locality run faster than those with poor locality.</p>
<h3 id="heading-locality-usually-takes-two-different-forms"><strong>Locality usually takes two different forms:</strong></h3>
<ul>
<li><p>Temporal locality: In a program with good temporal locality, memory locations that have been referenced once are likely to be referenced multiple times in the near future.</p>
</li>
<li><p>Spatial locality: In a program with good spatial locality, if a memory location has been referenced once, the program is likely to reference a nearby memory location in the near future.</p>
</li>
</ul>
<h4 id="heading-temporal-locality-example">Temporal locality example:</h4>
<pre><code class="lang-typescript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sum</span>(<span class="hljs-params">arry</span>) </span>{
    <span class="hljs-keyword">let</span> i, sum = <span class="hljs-number">0</span>
    <span class="hljs-keyword">let</span> len = arry.length

    <span class="hljs-keyword">for</span> (i = <span class="hljs-number">0</span>; i &lt; len; i++) {
        sum += arry[i]
    }

    <span class="hljs-keyword">return</span> sum
}
</code></pre>
<p>In this example, the variable sum is referenced once in each loop iteration, so it has good temporal locality.</p>
<h4 id="heading-spatial-locality-example">Spatial locality example:</h4>
<p>Program with good spatial locality:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Two-dimensional array </span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sum1</span>(<span class="hljs-params">arry, rows, cols</span>) </span>{
    <span class="hljs-keyword">let</span> i, j, sum = <span class="hljs-number">0</span>

    <span class="hljs-keyword">for</span> (i = <span class="hljs-number">0</span>; i &lt; rows; i++) {
        <span class="hljs-keyword">for</span> (j = <span class="hljs-number">0</span>; j &lt; cols; j++) {
            sum += arry[i][j]
        }
    }
    <span class="hljs-keyword">return</span> sum
}
</code></pre>
<p>Program with poor spatial locality:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Two-dimensional array </span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sum2</span>(<span class="hljs-params">arry, rows, cols</span>) </span>{
    <span class="hljs-keyword">let</span> i, j, sum = <span class="hljs-number">0</span>

    <span class="hljs-keyword">for</span> (j = <span class="hljs-number">0</span>; j &lt; cols; j++) {
        <span class="hljs-keyword">for</span> (i = <span class="hljs-number">0</span>; i &lt; rows; i++) {
            sum += arry[i][j]
        }
    }
    <span class="hljs-keyword">return</span> sum
}
</code></pre>
<p>Looking at the two spatial locality examples above, the method of accessing each element of the array sequentially starting from each row, as shown in the examples, is called a reference pattern with a stride of 1.</p>
<p>If in an array, every k elements are accessed, it's called a reference pattern with a stride of k. Generally, as the stride increases, spatial locality decreases.</p>
<p>What's the difference between these two examples? Well, the first example scans the array by row, scanning one row completely before moving on to the next row. The second example scans the array by column, scanning one element in a row and immediately going to scan the same column element in the next row.</p>
<p>Arrays are stored in memory in row order, resulting in the example of scanning the array row by row getting a stride-1 reference pattern with good spatial locality. The other example has a stride of rows, with extremely poor spatial locality.</p>
<h3 id="heading-performance-testing"><strong>Performance Testing</strong></h3>
<p>Running environment:</p>
<ul>
<li><p>CPU: i5-7400</p>
</li>
<li><p>Browser: Chrome 70.0.3538.110</p>
</li>
</ul>
<p>Testing spatial locality on a two-dimensional array with a length of 9000 (child array length also 9000) 10 times, taking the average time (milliseconds), the results are as follows:</p>
<p>The examples used are the two spatial locality examples mentioned above.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Stride 1</td><td>Stride 9000</td></tr>
</thead>
<tbody>
<tr>
<td>124</td><td>2316</td></tr>
</tbody>
</table>
</div><p>From the test results above, the array with a stride of 1 executes an order of magnitude faster than the array with a stride of 9000.</p>
<p>So to sum up:</p>
<ul>
<li><p>Programs that repeatedly reference the same variables have good temporal locality</p>
</li>
<li><p>For programs with a reference pattern with a stride of k, the smaller the stride, the better the spatial locality; while programs that jump around in memory with large strides will have very poor spatial locality</p>
</li>
</ul>
<p>Reference:</p>
<ul>
<li><a target="_blank" href="https://www.amazon.sg/Computer-Systems-Programmers-Perspective-3rd/dp/013409266X">Computer Systems: A Programmer's Perspective</a></li>
</ul>
<h2 id="heading-14-if-else-vs-switch"><strong>14. if-else vs switch</strong></h2>
<p>As the number of judgment conditions increases, it becomes more preferable to use switch instead of if-else.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">if</span> (color == <span class="hljs-string">'blue'</span>) {

} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (color == <span class="hljs-string">'yellow'</span>) {

} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (color == <span class="hljs-string">'white'</span>) {

} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (color == <span class="hljs-string">'black'</span>) {

} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (color == <span class="hljs-string">'green'</span>) {

} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (color == <span class="hljs-string">'orange'</span>) {

} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (color == <span class="hljs-string">'pink'</span>) {

}

<span class="hljs-keyword">switch</span> (color) {
    <span class="hljs-keyword">case</span> <span class="hljs-string">'blue'</span>:

        <span class="hljs-keyword">break</span>
    <span class="hljs-keyword">case</span> <span class="hljs-string">'yellow'</span>:

        <span class="hljs-keyword">break</span>
    <span class="hljs-keyword">case</span> <span class="hljs-string">'white'</span>:

        <span class="hljs-keyword">break</span>
    <span class="hljs-keyword">case</span> <span class="hljs-string">'black'</span>:

        <span class="hljs-keyword">break</span>
    <span class="hljs-keyword">case</span> <span class="hljs-string">'green'</span>:

        <span class="hljs-keyword">break</span>
    <span class="hljs-keyword">case</span> <span class="hljs-string">'orange'</span>:

        <span class="hljs-keyword">break</span>
    <span class="hljs-keyword">case</span> <span class="hljs-string">'pink'</span>:

        <span class="hljs-keyword">break</span>
}
</code></pre>
<p>In situations like the one above, from a readability perspective, using switch is better (JavaScript's switch statement is not based on hash implementation but on loop judgment, so from a performance perspective, if-else and switch are the same).</p>
<h3 id="heading-why-switch-is-better-for-multiple-conditions"><strong>Why switch is better for multiple conditions:</strong></h3>
<ol>
<li><p><strong>Improved readability</strong>: Switch statements present a clearer visual structure when dealing with multiple conditions against the same variable. The case statements create a more organized, tabular format that's easier to scan and understand.</p>
</li>
<li><p><strong>Cleaner code maintenance</strong>: Adding or removing conditions in a switch statement is simpler and less error-prone. With if-else chains, it's easy to accidentally break the chain or forget an "else" keyword.</p>
</li>
<li><p><strong>Less repetition</strong>: In the if-else example, we repeat checking the same variable (<code>color</code>) multiple times, while in switch we specify it once at the top.</p>
</li>
<li><p><strong>Better for debugging</strong>: When debugging, it's easier to set breakpoints on specific cases in a switch statement than trying to identify which part of a long if-else chain you need to target.</p>
</li>
<li><p><strong>Intent signaling</strong>: Using switch communicates to other developers that you're checking multiple possible values of the same variable, rather than potentially unrelated conditions.</p>
</li>
</ol>
<p>For modern JavaScript, there's another alternative worth considering for simple value mapping – object literals:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> colorActions = {
  <span class="hljs-string">'blue'</span>: <span class="hljs-function">() =&gt;</span> { <span class="hljs-comment">/* blue action */</span> },
  <span class="hljs-string">'yellow'</span>: <span class="hljs-function">() =&gt;</span> { <span class="hljs-comment">/* yellow action */</span> },
  <span class="hljs-string">'white'</span>: <span class="hljs-function">() =&gt;</span> { <span class="hljs-comment">/* white action */</span> },
  <span class="hljs-string">'black'</span>: <span class="hljs-function">() =&gt;</span> { <span class="hljs-comment">/* black action */</span> },
  <span class="hljs-string">'green'</span>: <span class="hljs-function">() =&gt;</span> { <span class="hljs-comment">/* green action */</span> },
  <span class="hljs-string">'orange'</span>: <span class="hljs-function">() =&gt;</span> { <span class="hljs-comment">/* orange action */</span> },
  <span class="hljs-string">'pink'</span>: <span class="hljs-function">() =&gt;</span> { <span class="hljs-comment">/* pink action */</span> }
};

<span class="hljs-comment">// Execute the action if it exists</span>
<span class="hljs-keyword">if</span> (colorActions[color]) {
  colorActions[color]();
}
</code></pre>
<p>This approach provides even better performance (O(1) lookup time) compared to both if-else and switch statement approaches.</p>
<h2 id="heading-15-lookup-tables"><strong>15. Lookup Tables</strong></h2>
<p>When there are many conditional statements, using switch and if-else is not the best choice. In such cases, you might want to try lookup tables. Lookup tables can be constructed using arrays and objects.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">switch</span> (index) {
    <span class="hljs-keyword">case</span> <span class="hljs-string">'0'</span>:
        <span class="hljs-keyword">return</span> result0
    <span class="hljs-keyword">case</span> <span class="hljs-string">'1'</span>:
        <span class="hljs-keyword">return</span> result1
    <span class="hljs-keyword">case</span> <span class="hljs-string">'2'</span>:
        <span class="hljs-keyword">return</span> result2
    <span class="hljs-keyword">case</span> <span class="hljs-string">'3'</span>:
        <span class="hljs-keyword">return</span> result3
    <span class="hljs-keyword">case</span> <span class="hljs-string">'4'</span>:
        <span class="hljs-keyword">return</span> result4
    <span class="hljs-keyword">case</span> <span class="hljs-string">'5'</span>:
        <span class="hljs-keyword">return</span> result5
    <span class="hljs-keyword">case</span> <span class="hljs-string">'6'</span>:
        <span class="hljs-keyword">return</span> result6
    <span class="hljs-keyword">case</span> <span class="hljs-string">'7'</span>:
        <span class="hljs-keyword">return</span> result7
    <span class="hljs-keyword">case</span> <span class="hljs-string">'8'</span>:
        <span class="hljs-keyword">return</span> result8
    <span class="hljs-keyword">case</span> <span class="hljs-string">'9'</span>:
        <span class="hljs-keyword">return</span> result9
    <span class="hljs-keyword">case</span> <span class="hljs-string">'10'</span>:
        <span class="hljs-keyword">return</span> result10
    <span class="hljs-keyword">case</span> <span class="hljs-string">'11'</span>:
        <span class="hljs-keyword">return</span> result11
}
</code></pre>
<p>This switch statement can be converted to a lookup table:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> results = [result0,result1,result2,result3,result4,result5,result6,result7,result8,result9,result10,result11]

<span class="hljs-keyword">return</span> results[index]
</code></pre>
<p>If the conditional statements are not numerical values but strings, you can use an object to build a lookup table:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> map = {
  red: result0,
  green: result1,
}

<span class="hljs-keyword">return</span> map[color]
</code></pre>
<h3 id="heading-why-lookup-tables-are-better-for-many-conditions"><strong>Why lookup tables are better for many conditions:</strong></h3>
<ol>
<li><p><strong>Constant time complexity (O(1))</strong>: Lookup tables provide direct access to the result based on the index/key, making the operation time constant regardless of how many options there are. In contrast, both if-else chains and switch statements have linear time complexity (O(n)) because in the worst case, they might need to check all conditions.</p>
</li>
<li><p><strong>Performance gains with many conditions</strong>: As the number of conditions increases, the performance advantage of lookup tables becomes more significant. For a small number of cases (2-5), the difference is negligible, but with dozens or hundreds of cases, lookup tables are substantially faster.</p>
</li>
<li><p><strong>Code brevity</strong>: As shown in the examples, lookup tables typically require less code, making your codebase more maintainable.</p>
</li>
<li><p><strong>Dynamic configuration</strong>: Lookup tables can be easily populated dynamically:</p>
</li>
</ol>
<pre><code class="lang-typescript">   <span class="hljs-keyword">const</span> actionMap = {};

   <span class="hljs-comment">// Dynamically populate the map</span>
   <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">registerAction</span>(<span class="hljs-params">key, handler</span>) </span>{
     actionMap[key] = handler;
   }

   <span class="hljs-comment">// Register different handlers</span>
   registerAction(<span class="hljs-string">'save'</span>, saveDocument);
   registerAction(<span class="hljs-string">'delete'</span>, deleteDocument);

   <span class="hljs-comment">// Use it</span>
   <span class="hljs-keyword">if</span> (actionMap[userAction]) {
     actionMap[userAction]();
   }
</code></pre>
<ol start="5">
<li><strong>Reduced cognitive load</strong>: When there are many conditions, lookup tables eliminate the mental overhead of following long chains of logic.</li>
</ol>
<h3 id="heading-when-to-use-each-approach"><strong>When to use each approach:</strong></h3>
<ul>
<li><p><strong>If-else</strong>: Best for a few conditions (2-3) with complex logic or different variables being checked</p>
</li>
<li><p><strong>Switch</strong>: Good for moderate number of conditions (4-10) checking against the same variable</p>
</li>
<li><p><strong>Lookup tables</strong>: Ideal for many conditions (10+) or when you need O(1) access time</p>
</li>
</ul>
<p>In real applications, lookup tables might be populated from external sources like databases or configuration files, making them flexible for scenarios where the mapping logic might change without requiring code modifications.</p>
<h2 id="heading-16-avoid-page-stuttering"><strong>16. Avoid Page Stuttering</strong></h2>
<h3 id="heading-60fps-and-device-refresh-rate"><strong>60fps and Device Refresh Rate</strong></h3>
<blockquote>
<p>Currently, most devices have a screen refresh rate of 60 times/second. Therefore, if there's an animation or gradient effect on the page, or if the user is scrolling the page, the browser needs to render animations or pages at a rate that matches the device's screen refresh rate.  </p>
<p>The budget time for each frame is just over 16 milliseconds (1 second / 60 = 16.66 milliseconds). But in reality, the browser has housekeeping work to do, so all your work needs to be completed within 10 milliseconds. If you can't meet this budget, the frame rate will drop, and content will jitter on the screen.  </p>
<p>This phenomenon is commonly known as stuttering and has a negative impact on user experience. <em>Source:</em> <a target="_blank" href="https://developers.google.com/web/fundamentals/performance/rendering"><em>Google Web Fundamentals - Rendering Performance</em></a></p>
</blockquote>
<p><a target="_blank" href="https://camo.githubusercontent.com/300b19e6e2523e1dfba3a8addba37a65797cc55de57501768ce987a81d06332f/68747470733a2f2f696d672d626c6f672e6373646e696d672e636e2f696d675f636f6e766572742f31626565666137613665323039346465643966656261336165633832303135382e706e67"><img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp5pde3zqyadfrth7ypsf.png" alt="Frame budget timing diagram showing the 16ms frame budget and browser overhead" width="600" height="400" loading="lazy"></a></p>
<p>Suppose you use JavaScript to modify the DOM, trigger style changes, go through reflow and repaint, and finally paint to the screen. If any of these takes too long, it will cause the rendering time of this frame to be too long, and the average frame rate will drop. Suppose this frame took 50 ms, then the frame rate would be 1s / 50ms = 20fps, and the page would appear to stutter.</p>
<p>For some long-running JavaScript, we can use timers to split and delay execution.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>, len = arry.length; i &lt; len; i++) {
    process(arry[i])
}
</code></pre>
<p>Suppose the loop structure above takes too long due to either the high complexity of process() or too many array elements, or both, you might want to try splitting.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> todo = arry.concat()
<span class="hljs-built_in">setTimeout</span>(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
    process(todo.shift())
    <span class="hljs-keyword">if</span> (todo.length) {
        <span class="hljs-built_in">setTimeout</span>(<span class="hljs-built_in">arguments</span>.callee, <span class="hljs-number">25</span>)
    } <span class="hljs-keyword">else</span> {
        callback(arry)
    }
}, <span class="hljs-number">25</span>)
</code></pre>
<p>If you're interested in learning more, check out <a target="_blank" href="https://www.amazon.com/High-Performance-JavaScript-Application-Interfaces/dp/059680279X">High Performance JavaScript</a> Chapter 6.</p>
<p>Reference:</p>
<ul>
<li><a target="_blank" href="https://developers.google.com/web/fundamentals/performance/rendering">Rendering Performance</a></li>
</ul>
<h2 id="heading-17-use-requestanimationframe-to-implement-visual-changes"><strong>17. Use</strong> <code>requestAnimationFrame</code> <strong>to Implement Visual Changes</strong></h2>
<p>From point 16, we know that most devices have a screen refresh rate of 60 times/second, which means the average time per frame is 16.66 milliseconds. When using JavaScript to implement animation effects, the best case is that the code starts executing at the beginning of each frame. The only way to ensure JavaScript runs at the beginning of a frame is to use <code>requestAnimationFrame</code>.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">/**
 * If run as a requestAnimationFrame callback, this
 * will be run at the start of the frame.
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">updateScreen</span>(<span class="hljs-params">time</span>) </span>{
  <span class="hljs-comment">// Make visual updates here.</span>
}

requestAnimationFrame(updateScreen);
</code></pre>
<p>If you use <code>setTimeout</code> or <code>setInterval</code> to implement animations, the callback function will run at some point in the frame, possibly right at the end, which can often cause us to miss frames, leading to stuttering.</p>
<p><a target="_blank" href="https://camo.githubusercontent.com/6921c15237df7064a3fe41fa89a174d78b43a8a0764a3b7536051c59b223ef6d/68747470733a2f2f696d672d626c6f672e6373646e696d672e636e2f696d675f636f6e766572742f32386238663463313066646333393633303135386562646162626264356432662e706e67"><img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F72qwmqhf5972jig808qz.png" alt="show the execution time of javascript" width="600" height="400" loading="lazy"></a></p>
<p>Reference:</p>
<ul>
<li><p><a target="_blank" href="https://web.dev/articles/optimize-javascript-execution">Optimize JavaScript Execution</a></p>
</li>
<li><p><a target="_blank" href="https://www.freecodecamp.org/news/immutable-javascript-improve-application-performance/">Improve JS performance</a></p>
</li>
</ul>
<h2 id="heading-18-use-web-workers"><strong>18. Use Web Workers</strong></h2>
<p>Web Workers use other worker threads to operate independently of the main thread. They can perform tasks without interfering with the user interface. A worker can send messages to the JavaScript code that created it by sending messages to the event handler specified by that code (and vice versa).</p>
<p>Web Workers are suitable for processing pure data or long-running scripts unrelated to the browser UI.</p>
<p>Creating a new worker is simple – just specify a script URI to execute the worker thread (main.js):</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">var</span> myWorker = <span class="hljs-keyword">new</span> Worker(<span class="hljs-string">'worker.js'</span>);
<span class="hljs-comment">// You can send messages to the worker through the postMessage() method and onmessage event</span>
first.onchange = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
  myWorker.postMessage([first.value, second.value]);
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Message posted to worker'</span>);
}

second.onchange = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
  myWorker.postMessage([first.value, second.value]);
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Message posted to worker'</span>);
}
</code></pre>
<p>In the worker, after receiving the message, you can write an event handler function code as a response (worker.js):</p>
<pre><code class="lang-typescript">onmessage = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">e</span>) </span>{
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Message received from main script'</span>);
  <span class="hljs-keyword">var</span> workerResult = <span class="hljs-string">'Result: '</span> + (e.data[<span class="hljs-number">0</span>] * e.data[<span class="hljs-number">1</span>]);
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Posting message back to main script'</span>);
  postMessage(workerResult);
}
</code></pre>
<p>The <code>onmessage</code> handler function executes immediately after receiving the message, and the message itself is used as the data property of the event. Here we simply multiply the two numbers and use the <code>postMessage()</code> method again to send the result back to the main thread.</p>
<p>Back in the main thread, we use <code>onmessage</code> again to respond to the message sent back from the worker:</p>
<pre><code class="lang-typescript">myWorker.onmessage = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">e</span>) </span>{
  result.textContent = e.data;
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Message received from worker'</span>);
}
</code></pre>
<p>Here we get the data from the message event and set it as the <code>textContent</code> of result, so the user can directly see the result of the calculation.</p>
<p>Note that inside the worker, you cannot directly manipulate DOM nodes, nor can you use the default methods and properties of the window object. But you can use many things under the window object, including data storage mechanisms such as WebSockets, IndexedDB, and Firefox OS-specific Data Store API.</p>
<p>Reference:</p>
<ul>
<li><p><a target="_blank" href="https://developer.mozilla.org/zh-CN/docs/Web/API/Web_Workers_API/Using_web_workers">Web Workers</a></p>
</li>
<li><p><a target="_blank" href="https://www.freecodecamp.org/news/how-webworkers-work-in-javascript-with-example/">How web workers work in JS</a></p>
</li>
</ul>
<h2 id="heading-19-use-bitwise-operations"><strong>19. Use Bitwise Operations</strong></h2>
<p>Numbers in JavaScript are stored in 64-bit format using the IEEE-754 standard. But in bitwise operations, numbers are converted to 32-bit signed format. Even with the conversion, bitwise operations are much faster than other mathematical and boolean operations.</p>
<h3 id="heading-modulo"><strong>Modulo</strong></h3>
<p>Since the lowest bit of even numbers is 0 and odd numbers is 1, modulo operations can be replaced with bitwise operations.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">if</span> (value % <span class="hljs-number">2</span>) {
    <span class="hljs-comment">// Odd number</span>
} <span class="hljs-keyword">else</span> {
    <span class="hljs-comment">// Even number </span>
}
<span class="hljs-comment">// Bitwise operation</span>
<span class="hljs-keyword">if</span> (value &amp; <span class="hljs-number">1</span>) {
    <span class="hljs-comment">// Odd number</span>
} <span class="hljs-keyword">else</span> {
    <span class="hljs-comment">// Even number</span>
}
</code></pre>
<p><strong>How it works:</strong> The <code>&amp;</code> (bitwise AND) operator compares each bit of the first operand to the corresponding bit of the second operand. If both bits are 1, the corresponding result bit is set to 1; otherwise, it's set to 0.</p>
<p>When we do <code>value &amp; 1</code>, we're only checking the last bit of the number:</p>
<ul>
<li><p>For even numbers (for example, 4 = <code>100</code> in binary), the last bit is 0: <code>100 &amp; 001 = 000</code> (0)</p>
</li>
<li><p>For odd numbers (for example, 5 = <code>101</code> in binary), the last bit is 1: <code>101 &amp; 001 = 001</code> (1)</p>
</li>
</ul>
<h3 id="heading-floor"><strong>Floor</strong></h3>
<pre><code class="lang-typescript">~~<span class="hljs-number">10.12</span> <span class="hljs-comment">// 10</span>
~~<span class="hljs-number">10</span> <span class="hljs-comment">// 10</span>
~~<span class="hljs-string">'1.5'</span> <span class="hljs-comment">// 1</span>
~~<span class="hljs-literal">undefined</span> <span class="hljs-comment">// 0</span>
~~<span class="hljs-literal">null</span> <span class="hljs-comment">// 0</span>
</code></pre>
<p><strong>How it works:</strong> The <code>~</code> (bitwise NOT) operator inverts all bits in the operand. For a number <code>n</code>, <code>~n</code> equals <code>-(n+1)</code>. When applied twice (<code>~~n</code>), it effectively truncates the decimal part of a number, similar to <code>Math.floor()</code> for positive numbers and <code>Math.ceil()</code> for negative numbers.</p>
<p>The process:</p>
<ol>
<li><p>First <code>~</code>: Converts the number to a 32-bit integer and inverts all bits</p>
</li>
<li><p>Second <code>~</code>: Inverts all bits again, resulting in the original number but with decimal part removed</p>
</li>
</ol>
<p>For example:</p>
<pre><code class="lang-typescript">~<span class="hljs-number">10.12</span> → ~<span class="hljs-number">10</span> → -(<span class="hljs-number">10</span>+<span class="hljs-number">1</span>) → <span class="hljs-number">-11</span>
~(<span class="hljs-number">-11</span>) → -(<span class="hljs-number">-11</span>+<span class="hljs-number">1</span>) → -(<span class="hljs-number">-10</span>) → <span class="hljs-number">10</span>
</code></pre>
<h3 id="heading-bitmask"><strong>Bitmask</strong></h3>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> a = <span class="hljs-number">1</span>
<span class="hljs-keyword">const</span> b = <span class="hljs-number">2</span>
<span class="hljs-keyword">const</span> c = <span class="hljs-number">4</span>
<span class="hljs-keyword">const</span> options = a | b | c
</code></pre>
<p>By defining these options, you can use the bitwise AND operation to determine if a/b/c is in the options.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Is option b in the options?</span>
<span class="hljs-keyword">if</span> (b &amp; options) {
    ...
}
</code></pre>
<p><strong>How it works:</strong> In bitmasks, each bit represents a boolean flag. The values are typically powers of 2 so each has exactly one bit set.</p>
<ol>
<li><p><code>a = 1</code>: Binary <code>001</code></p>
</li>
<li><p><code>b = 2</code>: Binary <code>010</code></p>
</li>
<li><p><code>c = 4</code>: Binary <code>100</code></p>
</li>
<li><p><code>options = a | b | c</code>: The <code>|</code> (bitwise OR) combines them: <code>001 | 010 | 100 = 111</code> (binary) = 7 (decimal)</p>
</li>
</ol>
<p>When checking if a flag is set with <code>if (b &amp; options)</code>:</p>
<ul>
<li><p><code>b &amp; options</code> = <code>010 &amp; 111</code> = <code>010</code> = 2 (decimal)</p>
</li>
<li><p>Since this is non-zero, the condition evaluates to true</p>
</li>
</ul>
<p>This technique is extremely efficient for storing and checking multiple boolean values in a single number, and is commonly used in systems programming, graphics programming, and permission systems.</p>
<h2 id="heading-20-dont-override-native-methods"><strong>20. Don't Override Native Methods</strong></h2>
<p>No matter how optimized your JavaScript code is, it can't match native methods. This is because native methods are written in low-level languages (C/C++) and compiled into machine code, becoming part of the browser. When native methods are available, try to use them, especially for mathematical operations and DOM manipulations.</p>
<h3 id="heading-example-string-replacement-native-vs-custom"><strong>Example: String Replacement (Native vs. Custom)</strong></h3>
<p>A common pitfall is rewriting native string methods like <code>replaceAll()</code>. Below is an inefficient custom implementation versus the native method, with performance benchmarks:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Inefficient custom global replacement (manual loop)  </span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">customReplaceAll</span>(<span class="hljs-params">str, oldSubstr, newSubstr</span>) </span>{  
  <span class="hljs-keyword">let</span> result = <span class="hljs-string">''</span>;  
  <span class="hljs-keyword">let</span> index = <span class="hljs-number">0</span>;  
  <span class="hljs-keyword">while</span> (index &lt; str.length) {  
    <span class="hljs-keyword">if</span> (str.slice(index, index + oldSubstr.length) === oldSubstr) {  
      result += newSubstr;  
      index += oldSubstr.length;  
    } <span class="hljs-keyword">else</span> {  
      result += str[index];  
      index++;  
    }  
  }  
  <span class="hljs-keyword">return</span> result;  
}  

<span class="hljs-comment">// Efficient native method (browser-optimized)  </span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">nativeReplaceAll</span>(<span class="hljs-params">str, oldSubstr, newSubstr</span>) </span>{  
  <span class="hljs-keyword">return</span> str.replaceAll(oldSubstr, newSubstr);  
}  

<span class="hljs-comment">// Test with a large string (100,000 repetitions of "abc ")  </span>
<span class="hljs-keyword">const</span> largeString = <span class="hljs-string">'abc '</span>.repeat(<span class="hljs-number">100000</span>);  

<span class="hljs-comment">// Benchmark: Custom implementation  </span>
<span class="hljs-built_in">console</span>.time(<span class="hljs-string">'customReplaceAll'</span>);  
customReplaceAll(largeString, <span class="hljs-string">'abc'</span>, <span class="hljs-string">'xyz'</span>);  
<span class="hljs-built_in">console</span>.timeEnd(<span class="hljs-string">'customReplaceAll'</span>); <span class="hljs-comment">// Output: ~5ms (varies by browser)  </span>

<span class="hljs-comment">// Benchmark: Native method  </span>
<span class="hljs-built_in">console</span>.time(<span class="hljs-string">'nativeReplaceAll'</span>);  
nativeReplaceAll(largeString, <span class="hljs-string">'abc'</span>, <span class="hljs-string">'xyz'</span>);  
<span class="hljs-built_in">console</span>.timeEnd(<span class="hljs-string">'nativeReplaceAll'</span>); <span class="hljs-comment">// Output: ~2ms (typically 2-3x faster)</span>
</code></pre>
<p>Key takeaways:</p>
<ul>
<li><p><strong>Performance</strong>: Native methods like <code>replaceAll()</code> are optimized at the browser level, often outperforming handwritten code (as shown in the benchmark above).</p>
</li>
<li><p><strong>Maintainability</strong>: Native methods are standardized, well-documented, and less error-prone than custom logic (for example, handling edge cases like overlapping substrings).</p>
</li>
<li><p><strong>Ecosystem compatibility</strong>: Using native methods ensures consistency with libraries and tools that rely on JavaScript’s built-in behavior.</p>
</li>
</ul>
<h3 id="heading-when-to-use-custom-code"><strong>When to Use Custom Code</strong></h3>
<p>While native methods are usually superior, there are rare cases where you might need custom logic:</p>
<ul>
<li><p>When the native method doesn’t exist (for example, polyfilling for older browsers).</p>
</li>
<li><p>For highly specialized edge cases not covered by native APIs.</p>
</li>
<li><p>When you need to avoid function call overhead in extremely performance-critical loops (for example, tight numerical computations).</p>
</li>
</ul>
<p><strong>Remember</strong>: Browser vendors spend millions of hours optimizing native methods. By leveraging them, you gain free performance boosts and reduce the risk of reinventing flawed solutions.</p>
<h2 id="heading-21-reduce-the-complexity-of-css-selectors"><strong>21. Reduce the Complexity of CSS Selectors</strong></h2>
<h3 id="heading-1-when-browsers-read-selectors-they-follow-the-principle-of-reading-from-right-to-left"><strong>1. When browsers read selectors, they follow the principle of reading from right to left.</strong></h3>
<p>Let's look at an example:</p>
<pre><code class="lang-typescript">#block .text p {
    color: red;
}
</code></pre>
<ol>
<li><p>Find all P elements.</p>
</li>
<li><p>Check if the elements found in result 1 have parent elements with class name "text"</p>
</li>
<li><p>Check if the elements found in result 2 have parent elements with ID "block"</p>
</li>
</ol>
<p><strong>Why is this inefficient?</strong> This right-to-left evaluation process can be very expensive in complex documents. Take the selector <code>#block .text p</code> as an example:</p>
<ol>
<li><p>The browser first finds <strong>all</strong> <code>p</code> elements in the document (potentially hundreds)</p>
</li>
<li><p>For each of those paragraph elements, it must check if any of their ancestors have the class <code>text</code></p>
</li>
<li><p>For those that pass step 2, it must check if any of their ancestors have the ID <code>block</code></p>
</li>
</ol>
<p>This creates a significant performance bottleneck because:</p>
<ul>
<li><p>The initial selection (<code>p</code>) is very broad</p>
</li>
<li><p>Each subsequent step requires checking multiple ancestors in the DOM tree</p>
</li>
<li><p>This process repeats for every paragraph element</p>
</li>
</ul>
<p>A more efficient alternative would be:</p>
<pre><code class="lang-typescript">#block p.specific-text {
    color: red;
}
</code></pre>
<p>This is more efficient because it directly targets only paragraphs with a specific class, avoiding checking all paragraphs</p>
<h3 id="heading-2-css-selector-priority"><strong>2. CSS selector priority</strong></h3>
<pre><code class="lang-typescript">Inline &gt; ID selector &gt; Class selector &gt; Tag selector
</code></pre>
<p>Based on the above two pieces of information, we can draw conclusions:</p>
<ol>
<li><p>The shorter the selector, the better.</p>
</li>
<li><p>Try to use high-priority selectors, such as ID and class selectors.</p>
</li>
<li><p>Avoid using the universal selector *.</p>
</li>
</ol>
<p><strong>Practical advice for optimal CSS selectors:</strong></p>
<pre><code class="lang-typescript"><span class="hljs-comment">/* ❌ Inefficient: Too deep, starts with a tag selector */</span>
body div.container ul li a.link {
    color: blue;
}

<span class="hljs-comment">/* ✅ Better: Shorter, starts with a class selector */</span>
.container .link {
    color: blue;
}

<span class="hljs-comment">/* ✅ Best: Direct, single class selector */</span>
.nav-link {
    color: blue;
}
</code></pre>
<p>Finally, I should say that according to the materials I've found, there's no need to optimize CSS selectors because the performance difference between the slowest and fastest selectors is very small.</p>
<p>Reference:</p>
<ul>
<li><a target="_blank" href="https://www.sitepoint.com/optimizing-css-id-selectors-and-other-myths/">Optimizing CSS: ID Selectors and Other Myths</a></li>
</ul>
<h2 id="heading-22-use-flexbox-instead-of-earlier-layout-models"><strong>22. Use Flexbox Instead of Earlier Layout Models</strong></h2>
<p>In early CSS layout methods, we could position elements absolutely, relatively, or using floats. Now, we have a new layout method called <a target="_blank" href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concepts_of_Flexbox">Flexbox</a>, which has an advantage over earlier layout methods: better performance.</p>
<p>The screenshot below shows the layout cost of using floats on 1300 boxes:</p>
<p><a target="_blank" href="https://camo.githubusercontent.com/ff6a96a175ccd6a4a55e0a0ea2932833cae4f639ddfda73c330f056eb2311efa/68747470733a2f2f696d672d626c6f672e6373646e696d672e636e2f696d675f636f6e766572742f37343264613262643539656537613331396239363036643461393539323234392e706e67"><img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnwsgqx7lcp8q0bizrtmb.png" alt="layout timeline in dev tool" width="600" height="400" loading="lazy"></a></p>
<p>Then we recreate this example using Flexbox:</p>
<p><a target="_blank" href="https://camo.githubusercontent.com/18ad08d69431cc0ef0d60b3aa748aa1e0220329cb6043046eeb744ad3ec64abe/68747470733a2f2f696d672d626c6f672e6373646e696d672e636e2f696d675f636f6e766572742f63633831663131613634643232613863656334643935616638633136376537362e706e67"><img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo9pmmihyz1ql8761ae2k.png" alt="layout timeline in dev tool" width="600" height="400" loading="lazy"></a></p>
<p>Now, for the same number of elements and the same visual appearance, the layout time is much less (3.5 milliseconds versus 14 milliseconds in this example).</p>
<p>But Flexbox compatibility is still an issue, as not all browsers support it, so use it with caution.</p>
<p>Browser compatibility:</p>
<ul>
<li><p>Chrome 29+</p>
</li>
<li><p>Firefox 28+</p>
</li>
<li><p>Internet Explorer 11</p>
</li>
<li><p>Opera 17+</p>
</li>
<li><p>Safari 6.1+ (prefixed with -webkit-)</p>
</li>
<li><p>Android 4.4+</p>
</li>
<li><p>iOS 7.1+ (prefixed with -webkit-)</p>
</li>
</ul>
<p>Reference:</p>
<ul>
<li><a target="_blank" href="https://web.dev/articles/avoid-large-complex-layouts-and-layout-thrashing">Use flexbox instead of earlier layout models</a></li>
</ul>
<h2 id="heading-23-use-transform-and-opacity-properties-to-implement-animations"><strong>23. Use Transform and Opacity Properties to Implement Animations</strong></h2>
<p>In CSS, transforms and opacity property changes don't trigger reflow and repaint. They’re properties that can be processed by the compositor alone.</p>
<p><a target="_blank" href="https://camo.githubusercontent.com/00e5e11d0b2837e91e8118284520b5969ae69670c8607d791e2053599fee0b4e/68747470733a2f2f696d672d626c6f672e6373646e696d672e636e2f696d675f636f6e766572742f66626436333931363533376336623531373733633266623134343263663130632e706e67"><img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3ae64ihgp1781wrtfci8.png" alt="Diagram showing how transform and opacity properties bypass layout and paint processes" width="600" height="400" loading="lazy"></a></p>
<h3 id="heading-example-inefficient-vs-efficient-animation"><strong>Example: Inefficient vs. Efficient Animation</strong></h3>
<p>❌ Inefficient animation using properties that trigger reflow and repaint:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">/* CSS */</span>
.box-inefficient {
  position: absolute;
  left: <span class="hljs-number">0</span>;
  top: <span class="hljs-number">0</span>;
  width: <span class="hljs-number">100</span>px;
  height: <span class="hljs-number">100</span>px;
  background-color: #<span class="hljs-number">3498</span>db;
  animation: move-inefficient <span class="hljs-number">2</span>s infinite alternate;
}

<span class="hljs-meta">@keyframes</span> move-inefficient {
  to {
    left: <span class="hljs-number">300</span>px;
    top: <span class="hljs-number">200</span>px;
    width: <span class="hljs-number">150</span>px;
    height: <span class="hljs-number">150</span>px;
  }
}
</code></pre>
<p>This animation constantly triggers layout recalculations (reflow) because it animates position (<code>left</code>/<code>top</code>) and size (<code>width</code>/<code>height</code>) properties.</p>
<p>✅ Efficient animation using transform and opacity:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">/* CSS */</span>
.box-efficient {
  position: absolute;
  width: <span class="hljs-number">100</span>px;
  height: <span class="hljs-number">100</span>px;
  background-color: #<span class="hljs-number">3498</span>db;
  animation: move-efficient <span class="hljs-number">2</span>s infinite alternate;
}

<span class="hljs-meta">@keyframes</span> move-efficient {
  to {
    transform: translate(<span class="hljs-number">300</span>px, <span class="hljs-number">200</span>px) scale(<span class="hljs-number">1.5</span>);
    opacity: <span class="hljs-number">0.7</span>;
  }
}
</code></pre>
<p><strong>Why this is better:</strong></p>
<ol>
<li><p><code>transform: translate(300px, 200px)</code> replaces <code>left: 300px; top: 200px</code></p>
</li>
<li><p><code>transform: scale(1.5)</code> replaces <code>width: 150px; height: 150px</code></p>
</li>
<li><p>These transform operations and opacity changes can be handled directly by the GPU without triggering layout or paint operations</p>
</li>
</ol>
<p><strong>Performance comparison:</strong></p>
<ol>
<li><p>The inefficient version may drop frames on lower-end devices because each frame requires:</p>
<ul>
<li>JavaScript → Style calculations → Layout → Paint → Composite</li>
</ul>
</li>
<li><p>The efficient version typically maintains 60fps because it only requires:</p>
<ul>
<li>JavaScript → Style calculations → Composite</li>
</ul>
</li>
</ol>
<p><strong>HTML implementation:</strong></p>
<pre><code class="lang-typescript">&lt;div <span class="hljs-keyword">class</span>=<span class="hljs-string">"box-inefficient"</span>&gt;Inefficient&lt;/div&gt;
&lt;div <span class="hljs-keyword">class</span>=<span class="hljs-string">"box-efficient"</span>&gt;Efficient&lt;/div&gt;
</code></pre>
<p>For complex animations, you can use the Chrome DevTools Performance panel to visualize the difference. The inefficient animation will show many more layout and paint events compared to the efficient one.</p>
<p>Reference:</p>
<ul>
<li><a target="_blank" href="https://web.dev/articles/stick-to-compositor-only-properties-and-manage-layer-count">Use transform and opacity property changes to implement animations</a></li>
</ul>
<h2 id="heading-24-use-rules-reasonably-avoid-over-optimization"><strong>24. Use Rules Reasonably, Avoid Over-Optimization</strong></h2>
<p>Performance optimization is mainly divided into two categories:</p>
<ol>
<li><p>Load-time optimization</p>
</li>
<li><p>Runtime optimization</p>
</li>
</ol>
<p>Of the 23 suggestions above, the first 10 belong to load-time optimization, and the last 13 belong to runtime optimization. Usually, there's no need to apply all 23 performance optimization rules. It's best to make targeted adjustments based on the website's user group, saving effort and time.</p>
<p>Before solving a problem, you need to identify the problem first, otherwise you won't know where to start. So before doing performance optimization, it's best to investigate the website's loading and running performance.</p>
<h3 id="heading-check-loading-performance"><strong>Check Loading Performance</strong></h3>
<p>A website's loading performance mainly depends on white screen time and first screen time.</p>
<ul>
<li><p>White screen time: The time from entering the URL to when the page starts displaying content.</p>
</li>
<li><p>First screen time: The time from entering the URL to when the page is completely rendered.</p>
</li>
</ul>
<p>You can get the white screen time by placing the following script before <code>&lt;/head&gt;</code>.</p>
<pre><code class="lang-typescript">&lt;script&gt;
  <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>() - performance.timing.navigationStart
  <span class="hljs-comment">// You can also use domLoading and navigationStart</span>
  performance.timing.domLoading - performance.timing.navigationStart
&lt;/script&gt;
</code></pre>
<p>You can get the first screen time by executing <code>new Date() - performance.timing.navigationStart</code> in the <code>window.onload</code> event.</p>
<h3 id="heading-check-runtime-performance"><strong>Check Runtime Performance</strong></h3>
<p>With Chrome's developer tools, we can check the website's performance during runtime.</p>
<p>Open the website, press F12 and select performance, click the gray dot in the upper left corner, it turns red to indicate it has started recording. At this point, you can simulate users using the website, and after you're done, click stop, then you'll see the website's performance report during the runtime.</p>
<p>If there are red blocks, it means there are frame drops. If it's green, it means the FPS is good. For detailed usage of performance, you can search using a search engine, as the scope is limited.</p>
<p>By checking the loading and runtime performance, I believe you already have a general understanding of the website's performance. So what you need to do now is to use the 23 suggestions above to optimize your website. Go for it!</p>
<p>References:</p>
<ul>
<li><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/PerformanceTiming/navigationStart">performance.timing.navigationStart</a></li>
</ul>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>Performance optimization is a critical aspect of modern web development that directly impacts user experience, engagement, and ultimately, business outcomes. Throughout this article, we've explored 24 diverse techniques spanning various layers of web applications – from network optimization to rendering performance and JavaScript execution.</p>
<h3 id="heading-key-takeaways"><strong>Key Takeaways</strong></h3>
<ol>
<li><p><strong>Start with measurement, not optimization</strong>. As discussed in point #24, always identify your specific performance bottlenecks before applying optimization techniques. Tools like Chrome DevTools Performance panel, Lighthouse, and WebPageTest can help pinpoint exactly where your application is struggling.</p>
</li>
<li><p><strong>Focus on the critical rendering path</strong>. Many of our techniques (placing CSS in the head, JavaScript at the bottom, reducing HTTP requests, server-side rendering) are centered around speeding up the time to first meaningful paint – the moment when users see and can interact with your content.</p>
</li>
<li><p><strong>Understand the browser rendering process</strong>. Knowledge of how browsers parse HTML, execute JavaScript, and render pixels to the screen is essential for making informed optimization decisions, especially when dealing with animations and dynamic content.</p>
</li>
<li><p><strong>Balance implementation cost vs. performance gain</strong>. Not all optimization techniques are worth implementing for every project. For instance, server-side rendering adds complexity that might not be justified for simple applications, and bitwise operations provide performance gains only in specific heavy computation scenarios.</p>
</li>
<li><p><strong>Consider the device and network conditions of your users</strong>. If you're building for users in regions with slower internet connections or less powerful devices, techniques like image optimization, code splitting, and reducing JavaScript payloads become even more important.</p>
</li>
</ol>
<h3 id="heading-practical-implementation-strategy"><strong>Practical Implementation Strategy</strong></h3>
<p>Instead of trying to implement all 24 techniques at once, consider taking a phased approach:</p>
<ol>
<li><p><strong>First pass</strong>: Implement the easy wins with high impact</p>
<ul>
<li><p>Proper image optimization</p>
</li>
<li><p>HTTP/2</p>
</li>
<li><p>Basic caching</p>
</li>
<li><p>CSS/JS placement</p>
</li>
</ul>
</li>
<li><p><strong>Second pass</strong>: Address specific measured bottlenecks</p>
<ul>
<li><p>Use performance profiling to identify problem areas</p>
</li>
<li><p>Apply targeted optimizations based on findings</p>
</li>
</ul>
</li>
<li><p><strong>Ongoing maintenance</strong>: Make performance part of your development workflow</p>
<ul>
<li><p>Set performance budgets</p>
</li>
<li><p>Implement automated performance testing</p>
</li>
<li><p>Review new feature additions for performance impact</p>
</li>
</ul>
</li>
</ol>
<p>By treating performance as an essential feature rather than an afterthought, you'll create web applications that not only look good and function well but also provide the speed and responsiveness that modern users expect.</p>
<p>Remember that web performance is a continuous journey, not a destination. Browsers evolve, best practices change, and user expectations increase. The techniques in this article provide a strong foundation, but staying current with web performance trends will ensure your applications remain fast and effective for years to come.</p>
<h3 id="heading-other-references"><strong>Other References</strong></h3>
<ul>
<li><p><a target="_blank" href="https://web.dev/learn/performance/why-speed-matters">Why Performance Matters</a></p>
</li>
<li><p><a target="_blank" href="https://www.amazon.com/High-Performance-Web-Sites-Essential/dp/0596529309">High-Performance Website Construction Guide</a></p>
</li>
<li><p><a target="_blank" href="https://hpbn.co/">High Performance Browser Networking</a></p>
</li>
<li><p><a target="_blank" href="https://www.amazon.com/High-Performance-JavaScript-Application-Interfaces/dp/059680279X">High-Performance JavaScript</a></p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Use Zod for React API Validation ]]>
                </title>
                <description>
                    <![CDATA[ In React applications, handling API (Application Programming Interface) responses can be challenging. You might encounter data that’s missing crucial fields, that’s formatted unexpectedly, or that simply doesn’t match what you anticipated. This incon... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-use-zod-for-react-api-validation/</link>
                <guid isPermaLink="false">67c1f994107ebba152ad9e79</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ zod ]]>
                    </category>
                
                    <category>
                        <![CDATA[ API ]]>
                    </category>
                
                    <category>
                        <![CDATA[ TypeScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ frontend ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Emore Ogheneyoma Lawrence ]]>
                </dc:creator>
                <pubDate>Fri, 28 Feb 2025 17:59:48 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1740756200896/a57c4e95-b13e-412a-828e-09e97f22a6c4.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In React applications, handling API (Application Programming Interface) responses can be challenging. You might encounter data that’s missing crucial fields, that’s formatted unexpectedly, or that simply doesn’t match what you anticipated.</p>
<p>This inconsistency can lead to errors in your code and make it difficult to work with the data effectively. Imagine wrestling with unpredictable API responses as your application grows – it can quickly become a development nightmare!</p>
<p>This is where Zod comes in, offering a solution to effectively manage API data validation within React.</p>
<h3 id="heading-by-the-end-of-this-tutorial-youll-learn-how-to">By the end of this tutorial, you’ll learn how to:</h3>
<ol>
<li><p>Set up and use Zod for API response validation in React.</p>
</li>
<li><p>Define schemas to validate and transform incoming data.</p>
</li>
<li><p>integrate Zod into API calls to improve data handling and prevent UI crashes.</p>
</li>
</ol>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-is-zod-and-why-use-it-for-react-api-calls">What is Zod, and Why Use it for React API Calls?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-generate-a-new-typescript-react-project">How to Generate a New TypeScript React Project</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-core-zod-concepts-basic-usage-types-and-validation">Core Zod Concepts: Basic Usage, Types, and Validation</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-build-zod-schemas-for-api-responses">How to Build Zod Schemas for API Responses</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-integrate-zod-with-react-api-calls">How to Integrate Zod with React API Calls</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-render-the-user-interface-ui-and-handle-errors-in-react">How to Render the User Interface (UI) and Handle Errors in React</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-what-is-zod-and-why-use-it-for-react-api-calls">What is Zod, and Why Use it for React API Calls?</h2>
<p>Zod is a powerful TypeScript-first library that simplifies data validation. It lets you define clear rules (schemas) for your expected data format.</p>
<p>Zod can then validate incoming data (often from API responses) to ensure it conforms to these rules. This validation process guarantees that the data adheres to your defined format, enhancing the reliability and integrity of your application.</p>
<p>Here's why Zod shines for React API validation:</p>
<ul>
<li><p>Clear schemas: Zod helps you define concise blueprints for API responses, enhancing readability and maintainability.</p>
</li>
<li><p>Data validation: It offers powerful validation methods for various data types, enforcing rules like required fields and specific formats.</p>
</li>
<li><p>Early error detection: It helps you detect data inconsistencies during API calls, preventing unexpected errors later in the application.</p>
</li>
<li><p>Improved developer experience: It promotes type-safe coding, streamlining development time by eliminating manual data type checks.</p>
</li>
<li><p>Single source of truth: And finally, Zod serves as a central point for data model definitions, ensuring consistency across the React application and reducing errors.</p>
</li>
</ul>
<p>Using Zod, you can transform unpredictable API responses into clean, structured data, setting the stage for a smoother and more efficient development experience in your React applications.</p>
<h2 id="heading-how-to-generate-a-new-typescript-react-project">How to Generate a New TypeScript React Project</h2>
<p>Creating a new React project with TypeScript is straightforward. Here's how to get started. Execute the following command in your terminal:</p>
<pre><code class="lang-bash">npm create vite@latest my-react-app -- --template react-ts
</code></pre>
<p>Once the project is generated, navigate to the projects directory:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> my-react-app
npm install
npm run dev
</code></pre>
<p>That’s it! Your React Project with TypeScript is now up and ready to use. Run the command below to install the Zod package:</p>
<pre><code class="lang-bash">npm install zod
</code></pre>
<h2 id="heading-core-zod-concepts-basic-usage-types-and-validation">Core Zod Concepts: Basic Usage, Types, and Validation</h2>
<p>Zod helps you define clear expectations for your API responses using <strong>schemas</strong>. These schemas act like blueprints, specifying the types of data you expect to receive.</p>
<h3 id="heading-how-to-build-schemas">How to Build Schemas</h3>
<p>Zod provides builder functions like <code>z.string()</code>, <code>z.number()</code>, and <code>z.object()</code> to create schemas. These functions define the data type you want for a specific field in your response.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { z } <span class="hljs-keyword">from</span> <span class="hljs-string">'zod'</span>;
<span class="hljs-comment">// Define basic data types</span>
<span class="hljs-keyword">const</span> userName = z.string().min(<span class="hljs-number">5</span>).max(<span class="hljs-number">10</span>); <span class="hljs-comment">// String with min 5 and max 10 characters</span>
<span class="hljs-keyword">const</span> userAge = z.number().positive().int();  <span class="hljs-comment">// Positive integer</span>
<span class="hljs-keyword">const</span> userEmail = z.string().email();        <span class="hljs-comment">// Ensures a valid email format</span>

<span class="hljs-built_in">console</span>.log(userName.parse(<span class="hljs-string">'John Doe'</span>));       <span class="hljs-comment">// Output: John Doe (valid)</span>
<span class="hljs-built_in">console</span>.log(userAge.parse(<span class="hljs-number">30</span>));              <span class="hljs-comment">// Output: 30 (valid)</span>
<span class="hljs-built_in">console</span>.log(userEmail.parse(<span class="hljs-string">"johnDoe@gmail.com"</span>)); <span class="hljs-comment">// Output: johnDoe@gmail.com (valid)</span>
</code></pre>
<p>The code above defines three basic data types:</p>
<ul>
<li><p><code>userName</code>: Represents a string with a minimum length of 5 characters and a maximum length of 10 characters.</p>
</li>
<li><p><code>userAge</code>: Represents a positive integer.</p>
</li>
<li><p><code>userEmail</code>: Ensures a valid email format.</p>
</li>
</ul>
<p>Here’s the result of the code above:  </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738857987322/55a5943c-6633-4432-a441-c3dfa9400d93.png" alt="Image of the resulting code above" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h3 id="heading-how-to-add-validation-rules">How to Add Validation Rules</h3>
<p>Zod allows you to chain methods like <strong>min</strong>, <strong>max</strong>, <strong>positive</strong>, <strong>int</strong>, and <strong>email</strong> to enforce specific rules on these data types. Here’s an example of an invalid string exceeding the maximum length:</p>
<pre><code class="lang-typescript"><span class="hljs-built_in">console</span>.log(userName.parse(<span class="hljs-string">"Hello there, My Name is John Doe"</span>)); <span class="hljs-comment">// Throws ZodError</span>
</code></pre>
<p>The code throws a <code>ZodError</code> due to exceeding the maximum length of 10 strings, disrupting our application flow and eventually causing our application to break.  </p>
<p>Here’s the image of the resulting code error:  </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738859384410/27c2a5f3-2410-4709-ac27-8bf24f6c3fd8.png" alt="Image of the resulting code above" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h3 id="heading-validating-and-parsing">Validating and Parsing</h3>
<p>Zod offers two ways to check data against your schema:</p>
<ul>
<li><p><code>schema.parse(data)</code><strong>:</strong> This method attempts to parse the data according to your schema. But if there's a validation error, it throws a <code>ZodError</code>. This can disrupt your application's flow, as illustrated in the previous example.</p>
</li>
<li><p><code>schema.safeParse(data)</code>: This is the recommended approach. it parses the data and returns a <code>ZodResult object</code>. This object contains some key properties:</p>
<ul>
<li><p><code>success</code>: A boolean indicating whether the parsing was successful.</p>
</li>
<li><p><code>data</code>: The parsed data itself (if the success property is true)</p>
</li>
<li><p><code>error</code>: An error message if validation fails (if success property is false)</p>
</li>
</ul>
</li>
</ul>
<p>Here are two examples showcasing the usage of <code>safeParse</code> with both valid and invalid data so you can see the resulting outcomes.  </p>
<p>First, lets see an example using <code>safeParse</code> with valid data:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> userSchema = z.object({
  name: userName,
  age: userAge,
  email: userEmail,
});

<span class="hljs-keyword">const</span> userData = {
  name: <span class="hljs-string">"John Doe"</span>,
  age: <span class="hljs-number">24</span>,
  email: <span class="hljs-string">"johndoe@gmail.com"</span>
};

<span class="hljs-keyword">const</span> result = userSchema.safeParse(userData);

<span class="hljs-built_in">console</span>.log(result); <span class="hljs-comment">// ZodObject containing data and success status</span>
</code></pre>
<p>This code defines a schema for user data using Zod, including properties for name, age, and email. It then attempts to parse a sample <code>userData</code> object using this schema via <code>safeParse()</code>. If successful, it prints the parsed data – otherwise, it logs an error message indicating the use of invalid data.  </p>
<p>Here’s the image of the resulting code above:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738861119776/7f9cf3ec-fc83-452c-9477-1c7d6422efe3.png" alt="7f9cf3ec-fc83-452c-9477-1c7d6422efe3" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Let’s now see how <code>safeParse()</code> handles invalid data using the same example as above. We’ll pass invalid data to the <code>userSchema.safeParse()</code> function to observe its behaviour.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> userSchema = z.object({
  name: userName,
  age: userAge,
  email: userEmail,
});

<span class="hljs-keyword">const</span> userData = {
  name: <span class="hljs-string">"John Doe"</span>,
  age: <span class="hljs-number">24</span>,
  email: <span class="hljs-string">"johndoe.com"</span> <span class="hljs-comment">// invalid email</span>
};

<span class="hljs-keyword">const</span> result = userSchema.safeParse(userData);

<span class="hljs-built_in">console</span>.log(result); <span class="hljs-comment">// ZodObject containing error and success status</span>
</code></pre>
<p>In this code example, we defined the <code>userSchema</code>. Next, we attempted to parse the <code>userData</code> object. But the parsing failed because the email property was not correctly formatted. Here’s a visual representation of the resulting output:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740502631913/25dead7a-ab09-4d1d-9dcc-fada38e7d4d6.png" alt="25dead7a-ab09-4d1d-9dcc-fada38e7d4d6" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Unlike using <code>parse</code>, which completely halts your application upon encountering validation errors and throws a <code>ZodError</code>, utilizing <code>safeParse</code> allows you to gracefully handle these errors, ensuring uninterrupted operation.</p>
<h2 id="heading-how-to-build-zod-schemas-for-api-responses">How to Build Zod Schemas for API Responses</h2>
<p>Building on our understanding of Zod’s core concepts, let’s create Zod schemas specifically for data received from API calls. We’ll leverage data from <a target="_blank" href="https://jsonplaceholder.typicode.com/posts">JSONPlaceholder</a>, which offers information about posts.  </p>
<p>Here’s a sample JSON response representing a post from JSONPlaceholder:</p>
<pre><code class="lang-typescript">{
  <span class="hljs-string">"userId"</span>: <span class="hljs-number">1</span>,
  <span class="hljs-string">"id"</span>: <span class="hljs-number">3</span>,
  <span class="hljs-string">"title"</span>: <span class="hljs-string">"ea molestias quasi exercitationem repellat qui ipsa sit aut"</span>,
  <span class="hljs-string">"body"</span>: <span class="hljs-string">"et iusto sed quo iure\nvoluptatem occaecati omnis eligendi aut"</span>
}
</code></pre>
<p>Create a React component (give it a name that fits your project structure) to demonstrate building and utilizing Zod schemas for API validation. In this article, for illustrative purposes, we’ll call it the <code>ZodApi</code> component.</p>
<pre><code class="lang-typescript"> <span class="hljs-keyword">import</span> { z } <span class="hljs-keyword">from</span> <span class="hljs-string">'zod'</span>;

  <span class="hljs-keyword">const</span> postSchema = z.object({
  userId: z.number().positive().int(),
  id: z.number().positive().int(),
  title: z.string(),
  body: z.string()
});

<span class="hljs-keyword">const</span> postSchemaArray = z.array(postSchema); <span class="hljs-comment">// Schema for array of posts</span>
</code></pre>
<p>This code defines the expected structure of a single post object (<code>postSchema</code>) and an array of posts (<code>postSchemaArray</code>).</p>
<p>The following sections will explore integrating Zod with React components for API call handling and error management.</p>
<h2 id="heading-how-to-integrate-zod-with-react-api-calls">How to Integrate Zod with React API Calls</h2>
<p>Let's bridge the gap between your defined Zod schemas and real API interactions.</p>
<p>We’ll need to update the code we wrote in the previous section to achieve our desired result in this section.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { z } <span class="hljs-keyword">from</span> <span class="hljs-string">'zod'</span>;
<span class="hljs-keyword">import</span> { useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">const</span> postSchema = z.object({
  userId: z.number().positive().int(),
  id: z.number().positive().int(),
  title: z.string(),
  body: z.string()
});

<span class="hljs-keyword">const</span> postSchemaArray = z.array(postSchema); <span class="hljs-comment">// schema for an array of posts</span>

<span class="hljs-keyword">type</span> Posts = z.infer&lt;<span class="hljs-keyword">typeof</span> postSchemaArray&gt;; <span class="hljs-comment">// type of the posts</span>

<span class="hljs-keyword">const</span> ZodApi = <span class="hljs-function">() =&gt;</span> {
  useEffect(<span class="hljs-function">() =&gt;</span> {
    fetch(<span class="hljs-string">"https://jsonplaceholder.typicode.com/posts"</span>)
      .then(<span class="hljs-function">(<span class="hljs-params">response</span>) =&gt;</span> response.json())
      .then(<span class="hljs-function">(<span class="hljs-params">posts: Posts</span>) =&gt;</span> {
        <span class="hljs-keyword">const</span> validatedPosts = postSchemaArray.safeParse(posts); <span class="hljs-comment">// remember to use safeParse instead of parse</span>

        <span class="hljs-keyword">if</span> (validatedPosts.success === <span class="hljs-literal">false</span>) {
          <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Validation Error:"</span>validatedPosts.error);
          <span class="hljs-keyword">return</span>;
        }

        <span class="hljs-comment">// we can now safely use the posts</span>
        <span class="hljs-built_in">console</span>.log(validatedPosts.data);
      });
  }, []);
  <span class="hljs-keyword">return</span> &lt;div&gt;ZodApi&lt;/div&gt;;
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> ZodApi;
</code></pre>
<p>The <code>ZodApi</code> component demonstrates:</p>
<ul>
<li><p>Fetching data: Uses <code>useEffect</code> and <code>fetch</code> to get data from the API.</p>
</li>
<li><p>Type safety: <code>type Posts = z.infer&lt;typeof postSchemaArray&gt;;</code> ensures type safety by defining the <code>Posts type</code> inferred from the schema <code>postSchemaArray</code>.</p>
</li>
<li><p>Parsing with Zod: Validates the fetched data against the <code>postSchemaArray</code> using <code>safeParse</code>.</p>
</li>
<li><p>Handling success: If validation succeeds, it provides access to clean data in <code>validatedPosts.data</code> for use in your component (UI, state, and so on).</p>
</li>
</ul>
<p>Error handling: The <code>if</code> statement showcases a simple approach to Zod error handling. In a case where the validation is not successful (<code>validatedPosts.success === false</code>), a ZodError message is logged to the console.  </p>
<p>Here’s a snapshot showing the resulting output in the console.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740504815204/45ce101a-4e8c-475b-ad4d-783b0940710b.png" alt="45ce101a-4e8c-475b-ad4d-783b0940710b" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h2 id="heading-how-to-render-the-user-interface-ui-and-handle-errors-in-react">How to Render the User Interface (UI) and Handle Errors in React</h2>
<p>In this section, you’ll learn how to render the UI based on our validated data and implement the error-handling mechanism using React states.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { z } <span class="hljs-keyword">from</span> <span class="hljs-string">"zod"</span>;
<span class="hljs-keyword">import</span> { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">const</span> postSchema = z.object({
  userId: z.number().positive().int(),
  id: z.number().positive().int(),
  title: z.string(),
  body: z.string(),
});

<span class="hljs-keyword">const</span> postSchemaArray = z.array(postSchema); <span class="hljs-comment">// schema for an array of posts</span>

<span class="hljs-keyword">type</span> Posts = z.infer&lt;<span class="hljs-keyword">typeof</span> postSchemaArray&gt;; <span class="hljs-comment">// type of the posts</span>

<span class="hljs-keyword">const</span> ZodApi = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> [posts, setPosts] = useState&lt;Posts&gt;([]); <span class="hljs-comment">// State to store validated posts</span>
  <span class="hljs-keyword">const</span> [error, setError] = useState(<span class="hljs-string">""</span>); <span class="hljs-comment">// State to store any errors</span>
  useEffect(<span class="hljs-function">() =&gt;</span> {
    fetch(<span class="hljs-string">"https://jsonplaceholder.typicode.com/posts"</span>)
      .then(<span class="hljs-function">(<span class="hljs-params">response</span>) =&gt;</span> response.json())
      .then(<span class="hljs-function">(<span class="hljs-params">posts: Posts</span>) =&gt;</span> {
        <span class="hljs-keyword">const</span> validatedPosts = postSchemaArray.safeParse(posts); <span class="hljs-comment">// remember to use safeParse instead of parse</span>

        <span class="hljs-keyword">if</span> (validatedPosts.success === <span class="hljs-literal">false</span>) {
          <span class="hljs-built_in">console</span>.log(validatedPosts.error.name);
          setError(validatedPosts.error.message); <span class="hljs-comment">// set error state</span>
          <span class="hljs-keyword">return</span>;
        }

        <span class="hljs-comment">// we can now safely use the validatedPosts </span>
        <span class="hljs-built_in">console</span>.log(validatedPosts.data);
        setPosts(validatedPosts.data)
      });
  }, []);

  <span class="hljs-comment">// Handle loading state (optional)</span>
  <span class="hljs-keyword">if</span> (!posts.length &amp;&amp; !error) {
    <span class="hljs-keyword">return</span> &lt;div&gt;Loading posts...&lt;/div&gt;;
  }

  <span class="hljs-comment">// Handle error state</span>
  <span class="hljs-keyword">if</span> (error) {
    <span class="hljs-keyword">return</span> &lt;div&gt;<span class="hljs-built_in">Error</span> fetching Data&lt;<span class="hljs-regexp">/div&gt;; /</span><span class="hljs-regexp">/ Display user-friendly error message
  }

  return (
    &lt;div&gt;
      &lt;h1&gt;Posts&lt;/</span>h1&gt;
      &lt;ol&gt;
        {posts.map(<span class="hljs-function">(<span class="hljs-params">post</span>) =&gt;</span> (
          &lt;li key={post.id}&gt;
            {post.title}
          &lt;/li&gt;
        ))}
      &lt;/ol&gt;
    &lt;/div&gt;
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> ZodApi;
</code></pre>
<p>In the code above, we’ve updated the <code>ZodApi</code> component to perform the following tasks:</p>
<ul>
<li><p>State declaration: The <code>posts</code> and <code>error</code> states hold the data and error (if any) gotten from the fetch request.</p>
</li>
<li><p>Error handling: We use the <code>posts</code> and <code>error</code> state to show a “Loading posts…” message when the posts are being fetched and no error occurs, and display an error message when an error occurs.</p>
</li>
<li><p>Rendering posts: It maps through the fetched posts and renders them on the UI.</p>
</li>
</ul>
<p>Output:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740506104138/f2afac53-e312-4a40-af01-500e0dd349f7.gif" alt="f2afac53-e312-4a40-af01-500e0dd349f7" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>After fetching the results, you should see the 100 posts displayed on your screen. If you followed the steps correctly, you'll find all 100 posts visible. If you encounter any issues, make sure the fetching process was successful.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>By incorporating Zod into your React development workflow, you can build more robust and reliable applications.</p>
<p>Zod empowers you to catch mismatched data early on, preventing errors and saving valuable debugging time. Also, the user-friendly error messages given by Zod validation enhance your application’s overall user experience.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Use CSS to Improve Web Accessibility ]]>
                </title>
                <description>
                    <![CDATA[ Did you know that CSS can play a significant role in web accessibility? While CSS primarily handles the visual presentation of a webpage, when you use it properly it can enhance the user’s experience and improve accessibility. In this article, I'll s... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-use-css-to-improve-web-accessibility/</link>
                <guid isPermaLink="false">66eb0a282e5bd4267b92ca10</guid>
                
                    <category>
                        <![CDATA[ CSS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ frontend ]]>
                    </category>
                
                    <category>
                        <![CDATA[ a11y ]]>
                    </category>
                
                    <category>
                        <![CDATA[ webdev ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Design ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Elizabeth Lola ]]>
                </dc:creator>
                <pubDate>Wed, 18 Sep 2024 17:13:12 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1726577970240/02631676-6492-4b83-a057-b9c2048709ee.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Did you know that CSS can play a significant role in web accessibility? While CSS primarily handles the visual presentation of a webpage, when you use it properly it can enhance the user’s experience and improve accessibility.</p>
<p>In this article, I'll share some ways CSS can support accessibility so you can start using these techniques in your own projects.</p>
<h2 id="heading-prerequisites"><strong>Prerequisites</strong></h2>
<p>To follow along with this tutorial, you should have a basic understanding of HTML, CSS, and a little bit of Javascript.</p>
<h2 id="heading-update-focus-styles">Update Focus Styles</h2>
<p>The browser provides default focus styles for interactive elements like buttons or input fields. But sometimes these default focus styles might not be ideal for your design system – especially if the colors used in your design are too close to the default colors. This might make it difficult to notice.</p>
<p>Also, different browsers have different default focus styles and you might want to standardize the focus styles to ensure uniformity.</p>
<p>You can change the default focus style of an element in CSS using the <code>:focus</code> pseudo-class. For example, the default focus style for an input element is a blue outline in Chrome and a blue outline with outline offset in Firefox, to update the default focus styles of an input element you can do this:</p>
<pre><code class="lang-css"><span class="hljs-selector-tag">input</span><span class="hljs-selector-pseudo">:focus</span> {
  <span class="hljs-attribute">outline</span>: <span class="hljs-number">2px</span> solid <span class="hljs-number">#007BFF</span>;
  <span class="hljs-attribute">outline-offset</span>: <span class="hljs-number">2px</span>;
  <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">1rem</span>;
}
</code></pre>
<h2 id="heading-avoid-content-shifts">Avoid Content Shifts</h2>
<p>Content shifts can happen when you’re lazy loading images, where images load progressively as the user scrolls down the page. Sometimes the image pushes the content around it downwards, shifting the text you're reading out of place.</p>
<p>Content shifts can also happen during dynamic content fetching, especially when new content like text, images, or ads is added to the page without reserving space for it in advance.</p>
<p>Content shifts can be frustrating, especially for users:</p>
<ul>
<li><p>With cognitive disabilities who may lose track of where they are in the content.</p>
</li>
<li><p>Using screen magnifiers, where the shift can cause them to lose their zoomed-in focus.</p>
</li>
<li><p>Navigating with a keyboard, as it can mess up the natural tab order and make navigation confusing.</p>
</li>
</ul>
<p>You can pre-allocate space for content to prevent shifts by using the <code>min-height</code> or <code>aspect-ratio</code> properties. Here's how you can allocate space for an image to prevent content shift before the image has fully loaded.</p>
<pre><code class="lang-css"><span class="hljs-selector-tag">img</span> {
    <span class="hljs-attribute">width</span>: <span class="hljs-number">100%</span>;
    <span class="hljs-attribute">height</span>: auto;
    <span class="hljs-attribute">aspect-ratio</span>: <span class="hljs-number">16</span>/<span class="hljs-number">9</span>;
    <span class="hljs-attribute">object-fit</span>: cover; <span class="hljs-comment">/* Ensures the image fits well within the allocated space */</span>
}
</code></pre>
<p>You can also use animations or transitions when dynamically loading content to add smooth transitions for new content. So, instead of a sudden shift, the content slides in gracefully, reducing the perception of disruption.</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.new-content</span> {
    <span class="hljs-attribute">transition</span>: margin <span class="hljs-number">0.3s</span> ease-in-out, opacity <span class="hljs-number">0.3s</span> ease-in-out;
}
</code></pre>
<h2 id="heading-reduce-motion">Reduce Motion</h2>
<p>Rapid animations or really complex transitions can be disorienting for users with motion sensitivity, which could lead to discomfort like headaches, dizziness, or vertigo (for users with vestibular disorders).</p>
<p>You can use CSS’s <code>prefers-reduced-motion</code> media query to reduce or disable animations for users.</p>
<p>Personally, instead of disabling animations completely, I replace complex, distracting animations with more subtle ones to maintain functionality while respecting user preferences.</p>
<p>Here's how to use <code>prefers-reduced-motion</code> to create a simpler animation:</p>
<pre><code class="lang-css"><span class="hljs-comment">/* Default animation */</span>
<span class="hljs-keyword">@keyframes</span> complexAnimation {
    0% { <span class="hljs-attribute">transform</span>: <span class="hljs-built_in">translateX</span>(<span class="hljs-number">0</span>); <span class="hljs-attribute">opacity</span>: <span class="hljs-number">0</span>; }
    50% { <span class="hljs-attribute">transform</span>: <span class="hljs-built_in">translateX</span>(<span class="hljs-number">100px</span>); <span class="hljs-attribute">opacity</span>: <span class="hljs-number">0.5</span>; }
    100% { <span class="hljs-attribute">transform</span>: <span class="hljs-built_in">translateX</span>(<span class="hljs-number">0</span>); <span class="hljs-attribute">opacity</span>: <span class="hljs-number">1</span>; }
}

<span class="hljs-selector-class">.element</span> {
    <span class="hljs-attribute">animation</span>: complexAnimation <span class="hljs-number">2s</span> ease-in-out;
}

<span class="hljs-comment">/* Simpler animation for reduced motion preference */</span>
<span class="hljs-keyword">@media</span> (<span class="hljs-attribute">prefers-reduced-motion:</span> reduce) {
    <span class="hljs-keyword">@keyframes</span> simpleAnimation {
        0% { <span class="hljs-attribute">opacity</span>: <span class="hljs-number">0</span>; }
        100% { <span class="hljs-attribute">opacity</span>: <span class="hljs-number">1</span>; }
    }

    <span class="hljs-selector-class">.element</span> {
        <span class="hljs-attribute">animation</span>: simpleAnimation <span class="hljs-number">1s</span> ease-in-out;
    }
}
</code></pre>
<p>Here’s an example from the code above. If you have reduced motion enabled you’ll see a fading ball instead of a moving ball:</p>
<div class="embed-wrapper">
        <iframe width="100%" height="350" src="https://codepen.io/leezee/embed/preview/PorrrQW?default-tab=result&amp;editable=true" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="CodePen embed" scrolling="no" allowtransparency="true" allowfullscreen="true" loading="lazy"></iframe></div>
<p> </p>
<p><strong>Note</strong>: If you want to see the reduced motion in action, you can enable it in the <a target="_blank" href="https://developer.chrome.com/docs/devtools/rendering">rendering tab on Google Chrome</a>.</p>
<h2 id="heading-focus-within-for-nested-elements">Focus Within for Nested Elements</h2>
<p>You can highlight or style a parent element when any of its child elements receive focus to make it clear which group (like form inputs or dropdown menus) is currently being interacted with.</p>
<p>To do this, you can use CSS’s <code>:focus-within</code> pseudo-class which is used to style an element when any of its descendants receive focus either through keyboard navigation or user interaction.</p>
<p>For example, to highlight a fieldset when any item in the group is focused in a grouped control, you can do this:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span><span class="css">
 <span class="hljs-selector-tag">fieldset</span> {
   <span class="hljs-attribute">padding</span>: <span class="hljs-number">10px</span>;
   <span class="hljs-attribute">border</span>: <span class="hljs-number">1px</span> solid <span class="hljs-number">#ccc</span>;
 }

 <span class="hljs-selector-tag">fieldset</span><span class="hljs-selector-pseudo">:focus-within</span> {
   <span class="hljs-attribute">border-color</span>: <span class="hljs-number">#007BFF</span>; <span class="hljs-comment">/* highlight the fieldset when a user focuses on any input */</span>
 }
</span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">fieldset</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">legend</span>&gt;</span>Choose a color:<span class="hljs-tag">&lt;/<span class="hljs-name">legend</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">label</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"radio"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"color"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"red"</span>&gt;</span> Red<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">label</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"radio"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"color"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"green"</span>&gt;</span> Green<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">label</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"radio"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"color"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"blue"</span>&gt;</span> Blue<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">fieldset</span>&gt;</span>
</code></pre>
<h2 id="heading-customize-contrast-options">Customize Contrast Options</h2>
<p>Sometimes you may be working on a design that uses lots of colors and might not maintain high contrast between text and background to fit an aesthetic. Or perhaps you're working on a design with lots of bright colors. In these cases, you should consider how your application renders for different users.</p>
<p>Some users with low vision or certain types of color blindness might need high contrast mode to differentiate text from the background more clearly. Other users sensitive to bright colors might prefer a softer, less jarring visual experience.</p>
<p>Some of these users might have their systems set to high or low contrast to help improve their experience. To customize their experience, you can use the CSS <code>prefers-contrast</code> media query.</p>
<p>The <code>prefers-contrast</code> media query allows you to tailor the contrast of your website or application based on the user's system settings.</p>
<p>Here's an example of using <code>prefers-contrast</code>:</p>
<pre><code class="lang-css"><span class="hljs-comment">/* default styling preference */</span>
<span class="hljs-selector-tag">body</span> {
    <span class="hljs-attribute">background-color</span>: white;
    <span class="hljs-attribute">color</span>: black;
}

<span class="hljs-comment">/* high contrast preference */</span>
<span class="hljs-keyword">@media</span> (<span class="hljs-attribute">prefers-contrast:</span> more) {
    <span class="hljs-selector-tag">body</span> {
        <span class="hljs-attribute">background-color</span>: black;
        <span class="hljs-attribute">color</span>: white;
    }
    <span class="hljs-selector-tag">a</span> {
        <span class="hljs-attribute">color</span>: yellow;
    }
}
<span class="hljs-comment">/* low contrast preference */</span>

<span class="hljs-keyword">@media</span> (<span class="hljs-attribute">prefers-contrast:</span> less) {
    <span class="hljs-selector-tag">body</span> {
        <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#f0f0f0</span>;
        <span class="hljs-attribute">color</span>: <span class="hljs-number">#333</span>;
    }
    <span class="hljs-selector-tag">a</span> {
        <span class="hljs-attribute">color</span>: <span class="hljs-number">#555</span>;
    }
}
</code></pre>
<div class="embed-wrapper">
        <iframe width="100%" height="350" src="https://codepen.io/leezee/embed/preview/dyBBxgV?default-tab=result&amp;editable=true" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="CodePen embed" scrolling="no" allowtransparency="true" allowfullscreen="true" loading="lazy"></iframe></div>
<p> </p>
<p>In the example above, the <code>prefers-contrast: more</code> option ensures that when a user prefers high contrast, the background is black and the text is white, with yellow links for better visibility.</p>
<p>The <code>prefers-contrast: less</code> adjusts the color scheme to a softer color for users who prefer less contrast. The default style is used if the user has no specific contrast preference or if their preference is not detected.</p>
<p><strong>Note</strong>: If your design uses minimal colors and maintains high contrast between text and background or you're working with a design where text is minimal and the focus is on visual content (like image galleries or video players), you might not need <code>prefers-contrast</code> as much. But it's still good practice to consider contrasts.</p>
<h2 id="heading-enable-dark-mode">Enable Dark Mode</h2>
<p>You can use CSS to accommodate users’ preferences for dark or light modes. You can achieve this through the CSS <code>prefers-color-scheme</code> media query. The browser can detect the user's color preference and apply the style if provided in CSS.</p>
<p>Here's an example of how you can add a dark mode style to your site using CSS variables:</p>
<pre><code class="lang-css"><span class="hljs-selector-pseudo">:root</span> {
  <span class="hljs-attribute">--background-color</span>: <span class="hljs-number">#ffffff</span>;
  <span class="hljs-attribute">--text-color</span>: <span class="hljs-number">#000000</span>;
}

<span class="hljs-keyword">@media</span> (<span class="hljs-attribute">prefers-color-scheme:</span> dark) {
  <span class="hljs-selector-pseudo">:root</span> {
    <span class="hljs-attribute">--background-color</span>: <span class="hljs-number">#000000</span>;
    <span class="hljs-attribute">--text-color</span>: <span class="hljs-number">#ffffff</span>;
  }
}

<span class="hljs-selector-tag">body</span> {
  <span class="hljs-attribute">background-color</span>: <span class="hljs-built_in">var</span>(--background-color);
  <span class="hljs-attribute">color</span>: <span class="hljs-built_in">var</span>(--text-color);
}
</code></pre>
<p>In the example above, the variables get updated if the browser detects a dark color scheme preference.</p>
<p>If you want to allow users to toggle between modes manually, you can use JavaScript for this:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span><span class="css">
 <span class="hljs-comment">/* Default light mode styles */</span>
  <span class="hljs-selector-tag">body</span> {
   <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#ffffff</span>;
   <span class="hljs-attribute">color</span>: <span class="hljs-number">#000000</span>;
  }
 <span class="hljs-comment">/* Dark mode styles */</span>
  <span class="hljs-selector-tag">body</span><span class="hljs-selector-class">.dark-mode</span> {
   <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#000000</span>;
   <span class="hljs-attribute">color</span>: <span class="hljs-number">#ffffff</span>;
  }
</span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"toggle-theme"</span>&gt;</span>Toggle Theme<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
  <span class="hljs-keyword">const</span> toggleButton = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'toggle-theme'</span>);
  toggleButton.addEventListener(<span class="hljs-string">'click'</span>, <span class="hljs-function">() =&gt;</span> {
   <span class="hljs-built_in">document</span>.body.classList.toggle(<span class="hljs-string">'dark-mode'</span>);
  });
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
<h2 id="heading-use-rem-units-for-responsive-typography">Use <code>rem</code> Units for Responsive Typography</h2>
<p>Using <code>rem</code> units for responsive typography can help enhance accessibility to adapt more dynamically to a user's preference. Since <code>rem</code> is relative to the root font size (typically set by the browser or user), it scales with changes in the base font size. This helps ensure that text remains readable without breaking layouts.</p>
<p>Users can set a preferred font size in their browser or operating system for better readability. When you use <code>rem</code>, the website content scales according to this setting which ensures that the text is not too small or too large for the users (which can happen when using fixed units like <code>px</code>).</p>
<p>When users zoom in using browser settings or increase their preferred text size, the <code>rem</code>-based text will scale appropriately.</p>
<p>The default root font size (usually 16px) is typically inherited from the browser, but you can set it explicitly if needed:</p>
<pre><code class="lang-css"><span class="hljs-selector-tag">html</span> {
  <span class="hljs-attribute">font-size</span>: <span class="hljs-number">100%</span>; <span class="hljs-comment">/* Default 16px */</span>
}
</code></pre>
<p>After setting the root font size, you can use <code>rem</code> unit for the rest of your content. For example:</p>
<pre><code class="lang-css"><span class="hljs-selector-tag">h1</span> {
  <span class="hljs-attribute">font-size</span>: <span class="hljs-number">2.5rem</span>; <span class="hljs-comment">/* Equivalent to 40px if root is 16px */</span>
}

<span class="hljs-selector-tag">p</span> {
  <span class="hljs-attribute">font-size</span>: <span class="hljs-number">1rem</span>; <span class="hljs-comment">/* Equivalent to 16px */</span>
}
</code></pre>
<h2 id="heading-use-animations-to-enhance-ux">Use Animations to Enhance UX</h2>
<p>CSS animations can enhance accessibility when used thoughtfully. They can help create an engaging and understandable experience for users.</p>
<p>Here are some ways that animations can help improve accessibility:</p>
<ul>
<li><p>You can use animations to indicate loading state to visually communicate to users that the system is working on a task.</p>
</li>
<li><p>Using animated text effects, like fades or scaling on headlines or important sections, can help guide users' eyes to important content. This can be useful for people with cognitive disabilities who benefit from clear visual hierarchies.</p>
</li>
<li><p>Subtle transitions for state change instead of having abrupt changes (like a modal popping up instantly) can create smoother transitions between different interface states.</p>
</li>
<li><p>Using animated highlights or shaking effects on form fields can provide visual feedback to users about input errors. You should pair these animations with labels or ARIA attributes to make it clear what the user needs to correct.</p>
</li>
<li><p>Animations can help users track focus, especially keyboard users or those with visual impairments. CSS transitions that highlight focused elements (for example by enlarging buttons or changing the border) assist users in understanding where they are within the page.</p>
</li>
</ul>
<h3 id="heading-best-practices">Best Practices:</h3>
<ul>
<li><p>Ensure animations are used purposefully, not just for aesthetic reasons.</p>
</li>
<li><p>Avoid overly long or continuous animations that can distract or annoy users.</p>
</li>
<li><p>Combine animations with other accessible features, such as screen reader announcements, to ensure all users understand content changes.</p>
</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>When considering accessibility, well-structured HTML forms the foundation of an accessible page – but CSS also plays a vital role in enhancing that structure.</p>
<p>CSS alone cannot fix poorly structured HTML. But when it’s applied thoughtfully to a solid foundation, it ensures a more inclusive and engaging experience by improving visual hierarchy, readability, and interaction for users of all abilities.</p>
<p>Combining accessible HTML with CSS not only improves the user interface but also provides support for assistive technologies.</p>
<p>Thank you so much for reading this article. If you found it helpful, consider sharing. Happy coding!</p>
<p>You can connect with me on <a target="_blank" href="https://www.linkedin.com/in/elizabeth-meshioye/">LinkedIn</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build an Accessible Modal – with Example Code ]]>
                </title>
                <description>
                    <![CDATA[ We often use modals or popups to display important information or prompt users to take action. Unlike regular pop-ups that can be opened in new windows or tabs, these dialogues keep the user on the same page by overlaying the existing content. This e... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-an-accessible-modal-with-example-code/</link>
                <guid isPermaLink="false">66cdec7e3bcefa4703267811</guid>
                
                    <category>
                        <![CDATA[ a11y ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Accessibility ]]>
                    </category>
                
                    <category>
                        <![CDATA[ frontend ]]>
                    </category>
                
                    <category>
                        <![CDATA[ HTML5 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Elizabeth Lola ]]>
                </dc:creator>
                <pubDate>Tue, 27 Aug 2024 15:10:54 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1724337698676/aa23c219-2ffb-4424-bb34-3195a905d973.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>We often use modals or popups to display important information or prompt users to take action. Unlike regular pop-ups that can be opened in new windows or tabs, these dialogues keep the user on the same page by overlaying the existing content. This ensures that users remain focused on the task at hand.</p>
<p>Modals are common and sometimes required. And if they're not implemented correctly, they can be a significant barrier. Ensuring that modals are accessible means they are usable by everyone, including people who rely on assistive technologies.</p>
<p>In this article, we'll <strong>build a modal</strong> and follow the guidelines to make it accessible.</p>
<h2 id="heading-prerequisites"><strong>Prerequisites</strong></h2>
<p>To follow along with this tutorial, you should have:</p>
<ol>
<li><p><strong>Basic HTML knowledge:</strong> Understand how HTML elements and attributes work.</p>
</li>
<li><p><strong>Basic JavaScript knowledge:</strong> Familiarity with basic JavaScript concepts like functions, event handling, and DOM manipulation is helpful.</p>
</li>
<li><p><strong>Understanding of ARIA:</strong> While the tutorial explains ARIA roles and attributes, having a basic understanding of accessibility concepts can be beneficial.</p>
</li>
</ol>
<h2 id="heading-when-should-you-use-a-modal">When Should You Use a Modal?</h2>
<p>Using modals effectively requires careful consideration of the user experience. Here are some guidelines to help you decide if you should use a modal or not:</p>
<ul>
<li><p>You should use modals when the user needs to make a critical decision, such as confirming a potentially destructive action (for example, deleting an item) or agreeing to terms and conditions</p>
</li>
<li><p>You can use a modal when a task requires the user’s complete focus and does not rely on information from the rest of the page (for example, filling out a form or completing a payment process).</p>
</li>
<li><p>You can use a modal for displaying temporary or transient information that doesn’t need to be permanently visible on the page (for example, alerts, notifications, or brief messages).</p>
</li>
<li><p>You should avoid using modals for tasks that require extensive interaction or input, such as lengthy forms or complex workflows. These can be frustrating in a modal because of limited space and navigation constraints.</p>
</li>
<li><p>You should avoid using modals for actions a user will need to perform frequently, as this can become repetitive and annoying. Inline options or tooltips might be better for repetitive actions.</p>
</li>
<li><p>You should not use modals if they interrupt the user’s natural flow on the site, especially if the content or action in the modal is not urgent or important.</p>
</li>
</ul>
<h2 id="heading-modal-accessibility-guidelines">Modal Accessibility Guidelines</h2>
<p>Here are some tips to help you build useful and accessible modals:</p>
<ul>
<li>Provide a descriptive <code>aria-labelledby</code> attribute that points to the modal's title or heading. If there is no title, use <code>aria-label</code> to provide a short, descriptive label.</li>
</ul>
<ul>
<li><p>Always include a visible and easily accessible close button within the modal, usually in the top-right corner. Label this button clearly, for example, with the text "Close" or an icon with <code>aria-label="Close"</code>.</p>
</li>
<li><p>When the modal opens, move the keyboard focus to the first interactive element within the modal (usually a close button). When the modal closes, return the focus to the element that triggered the modal.</p>
</li>
<li><p>Keep the keyboard focus within the modal while it is open.</p>
</li>
<li><p>Allow users to close the modal by pressing the <code>Escape</code> key.</p>
</li>
</ul>
<p>Following these guidelines, let's build a modal.</p>
<p>I prefer using the right HTML tags to build components, and in this case I'll be doing exactly that using the <code>dialog</code> tag.</p>
<h2 id="heading-how-to-build-a-modal-using-the-dialog-tag">How to Build a Modal Using the <code>dialog</code> Tag</h2>
<p>In case you're not familiar with the <code>dialog</code> tag, it was introduced in HTML5. You use it to create dialog boxes like popups, alerts, and modals. It offers built-in methods and attributes that make it easier to manage dialog behavior without needing extensive JavaScript. The javascript built-in methods are <code>show()</code>, <code>showModal()</code>, and <code>close()</code>.</p>
<h3 id="heading-show-and-showmodal"><code>show()</code> and <code>showModal()</code></h3>
<p>The <code>show()</code> method is useful for a non-blocking dialog. This means that the dialog appears on top of the current content, but users can still interact with other parts of the webpage (clicking buttons, links, and so on) while the dialog is open.</p>
<p>This is useful in situations where the dialog is providing information that doesn’t require the user’s immediate attention. Here's an example:</p>
<pre><code class="lang-xml"><span class="hljs-comment">&lt;!-- Previous content here --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">dialog</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"dialog-box"</span>&gt;</span>
<span class="hljs-comment">&lt;!-- More content here --&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">dialog</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
    <span class="hljs-keyword">const</span> dialog = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'dialog-box'</span>);
    dialog.show();
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
<p>Result:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1723822375653/a592c09a-747c-4248-84e2-9cd76c8f6498.png" alt="a non-modal dialog" class="image--center mx-auto" width="1988" height="724" loading="lazy"></p>
<p>The <code>showModal()</code> method opens the dialog in a modal mode. This means that the dialog takes focus, and interaction with the rest of the webpage is blocked until the dialog is closed. The user cannot click on or interact with any other part of the page.</p>
<p>Depending on the browser, a semi-transparent backdrop appears behind the dialog, visually indicating that the rest of the page is not interactable.</p>
<p>When a dialog is opened with <code>showModal()</code>, focus is automatically trapped within the dialog. The user can only tab through elements inside the dialog, and the focus will loop within the dialog’s content until it is closed. Here's an example:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">dialog</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"dialog-box"</span>&gt;</span>
<span class="hljs-comment">&lt;!-- More content here --&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">dialog</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
    <span class="hljs-keyword">const</span> dialog = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'dialog-box'</span>);
    dialog.showModal();
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
<p>Result:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1723823970852/1ca30713-199d-4b94-b12a-ad1a27a9063f.png" alt="a modal dialog" class="image--center mx-auto" width="1724" height="1060" loading="lazy"></p>
<p>The <code>&lt;dialog&gt;</code> element has default styles but can be customized using CSS to match your design. You can style the dialog box, add animations, or modify the backdrop. The backdrop can be styled using the <code>::backdrop</code> selector. For example:</p>
<pre><code class="lang-css"><span class="hljs-selector-tag">dialog</span><span class="hljs-selector-pseudo">::backdrop</span> {
    <span class="hljs-attribute">background</span>: <span class="hljs-built_in">rgba</span>(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0.7</span>);
}
</code></pre>
<p>The dialog also comes with some built-in accessibility features like focus management, backdrop, automatic announcement on open, and pressing the <code>ESC</code> key will close the dialog.</p>
<p>You can add the <code>autofocus</code> attribute to the first interactive element in the modal, such as the first input in a form or the close button. Alternatively, you can rely on the <code>&lt;dialog&gt;</code> element's native focus management.</p>
<p>Avoid using <code>tabindex</code> on the <code>&lt;dialog&gt;</code> element, as it is not an interactive element like a button or link. The <code>&lt;dialog&gt;</code> serves as a container for interactive content, and it is not intended to receive direct user focus.</p>
<p>The <code>&lt;dialog&gt;</code> element provides a native way to create modals. If you're building a custom modal, make sure its accessibility features match those of the native <code>&lt;dialog&gt;</code> element.</p>
<p>Bringing it all together:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span><span class="css">
    <span class="hljs-selector-tag">dialog</span><span class="hljs-selector-pseudo">::backdrop</span> {
        <span class="hljs-attribute">background</span>: <span class="hljs-built_in">rgba</span>(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0.7</span>);
    }
</span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"open-dialog"</span>&gt;</span>Open Dialog<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">dialog</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"dialog-box"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>Modal title<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
          <span class="hljs-comment">&lt;!-- More content here --&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"close-dialog"</span> <span class="hljs-attr">autofocus</span>&gt;</span>Close<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">dialog</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
    <span class="hljs-keyword">const</span> dialog = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"dialog-box"</span>);
    <span class="hljs-keyword">const</span> openButton = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"open-dialog"</span>);
    <span class="hljs-keyword">const</span> closeButton = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"close-dialog"</span>);

    openButton.addEventListener(<span class="hljs-string">"click"</span>, <span class="hljs-function">() =&gt;</span> {
      dialog.showModal();
    });
    closeButton.addEventListener(<span class="hljs-string">"click"</span>, <span class="hljs-function">() =&gt;</span> {
      dialog.close();
    });
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
</code></pre>
<p>You'll notice that I didn't use the <code>aria-label</code> attribute on the dialog as I listed in the guidelines. Well, that's because the dialog element, if well-structured, doesn't necessarily need one. In this case, there's a visible label in the dialog element (the <code>h2</code> element).</p>
<p>If there are no visible labels present then you need to add one. Like in this example:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">dialog</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"confimation-dialog"</span> <span class="hljs-attr">aria-label</span>=<span class="hljs-string">"Confirmation Dialog"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Are you sure you want to proceed?<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"close-dialog"</span> <span class="hljs-attr">autofocus</span>&gt;</span>Close<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">dialog</span>&gt;</span>
</code></pre>
<h2 id="heading-what-is-the-inert-attribute">What is the <code>inert</code> Attribute?</h2>
<p>When a modal is open, a screen reader might still navigate to and around content outside the modal. You would generally want the user's focus to be restricted to the modal itself, or stop the user from accidentally clicking on elements outside the modal to prevent confusion and errors. In these cases, you'll need the <code>inert</code> attribute.</p>
<p>The <code>inert</code> attribute makes an element and all of its descendants non-interactive and inaccessible to assistive technologies. When a modal is open, using the <code>inert</code> attribute on the rest of the page content will ensure that only the modal content can be accessed, making the dialog experience clearer.</p>
<h3 id="heading-how-to-use-the-inert-attribute">How to Use the <code>inert</code> Attribute</h3>
<p>When a modal is opened, you can apply the <code>inert</code> attribute to the rest of the page content (typically the <code>&lt;main&gt;</code> element). When the modal is closed, you remove the <code>inert</code> attribute.</p>
<p>Here's an example showing how to use <code>inert</code> with a modal dialog:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">header</span>&gt;</span>Site Header<span class="hljs-tag">&lt;/<span class="hljs-name">header</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">main</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"main-content"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"open-dialog"</span>&gt;</span>Open modal<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>This is the main content of the page.<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
        <span class="hljs-comment">&lt;!-- More content here --&gt;</span>

    <span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- Move the dialog outside the main element --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">dialog</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"dialog"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>Modal Title<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>This is the content inside the modal.<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"close-dialog"</span> <span class="hljs-attr">autofocus</span>&gt;</span>Close<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">dialog</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
        <span class="hljs-keyword">const</span> dialog = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'dialog'</span>);
        <span class="hljs-keyword">const</span> mainContent = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'main-content'</span>);
        <span class="hljs-keyword">const</span> openButton = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'open-dialog'</span>);
        <span class="hljs-keyword">const</span> closeButton = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'close-dialog'</span>);

        openButton.addEventListener(<span class="hljs-string">"click"</span>, <span class="hljs-function">() =&gt;</span> {
           mainContent.setAttribute(<span class="hljs-string">'inert'</span>, <span class="hljs-string">''</span>);
          dialog.showModal();
        });

        closeButton.addEventListener(<span class="hljs-string">"click"</span>, <span class="hljs-function">() =&gt;</span> {
           dialog.close();
        });

        <span class="hljs-comment">// the dialog elemnt has a close event, which is called when a user calls the close() method or presses the esc key</span>
        dialog.addEventListener(<span class="hljs-string">"close"</span>, <span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> {
           mainContent.removeAttribute(<span class="hljs-string">"inert"</span>);
        });
    </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
</code></pre>
<h2 id="heading-how-to-animate-the-open-and-close-states">How to Animate the Open and Close States</h2>
<p>When a modal appears (open state) or disappears (close state), it can be jarring for users if this transition happens abruptly. Animating these states can create a smoother user experience by gradually introducing or removing the modal, making it feel more natural.</p>
<h4 id="heading-why-animate-the-open-and-close-states">Why Animate the Open and Close States?</h4>
<p>Animating the open and close states of a modal can:</p>
<ul>
<li><p><strong>Enhance User Experience</strong>: A smooth animation can make the transition less abrupt and more engaging.</p>
</li>
<li><p><strong>Draw Attention</strong>: Subtle animations can help guide the user's focus to the modal content when it appears.</p>
</li>
<li><p><strong>Maintain Consistency</strong>: Consistent animations across your UI can create a cohesive and professional feel.</p>
</li>
</ul>
<p>By default, the dialog is set to <code>display:none</code> when closed and <code>display:block</code> when open. You cannot transition from <code>none</code> to <code>block</code> in CSS, but you can combine the display properties with <code>transform</code> or <code>opacity</code>. The <code>transform</code> property can be used to scale or move the modal, while <code>opacity</code> controls its transparency.</p>
<p>Here’s an example of how you might animate a modal:</p>
<pre><code class="lang-css"><span class="hljs-selector-tag">dialog</span> {
  <span class="hljs-attribute">animation</span>: zoom-out <span class="hljs-number">0.5s</span> ease-out;
}

<span class="hljs-comment">/* an open attribute is added to the dialog when it is open  */</span>
<span class="hljs-selector-tag">dialog</span><span class="hljs-selector-attr">[open]</span> {
    <span class="hljs-attribute">animation</span>: zoom-in <span class="hljs-number">0.5s</span> ease-out;
}

<span class="hljs-comment">/* The display property in the keyframes is critical 
because it toggles the modal’s visibility on and off.  */</span>
<span class="hljs-keyword">@keyframes</span> zoom-in {
    0% {
      <span class="hljs-attribute">opacity</span>: <span class="hljs-number">0</span>;
      <span class="hljs-attribute">transform</span>: <span class="hljs-built_in">scale</span>(<span class="hljs-number">0.1</span>);
      <span class="hljs-attribute">display</span>:  none;
    }
    100% {
      <span class="hljs-attribute">opacity</span>: <span class="hljs-number">1</span>;
      <span class="hljs-attribute">transform</span>: <span class="hljs-built_in">scale</span>(<span class="hljs-number">1</span>);
      <span class="hljs-attribute">display</span>: block;
    }
}

<span class="hljs-keyword">@keyframes</span> zoom-out {
    0% {
      <span class="hljs-attribute">opacity</span>: <span class="hljs-number">1</span>;
      <span class="hljs-attribute">transform</span>: <span class="hljs-built_in">scale</span>(<span class="hljs-number">1</span>);
      <span class="hljs-attribute">display</span>: block;
    }
    100% {
      <span class="hljs-attribute">opacity</span>: <span class="hljs-number">0</span>;
      <span class="hljs-attribute">transform</span>: <span class="hljs-built_in">scale</span>(<span class="hljs-number">0</span>);
      <span class="hljs-attribute">display</span>: none;
    }
}
</code></pre>
<p>Final result:</p>
<div class="embed-wrapper">
        <iframe width="100%" height="350" src="https://codepen.io/leezee/embed/preview/XWLVBgp?default-tab=result&amp;editable=true" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="CodePen embed" scrolling="no" allowtransparency="true" allowfullscreen="true" loading="lazy"></iframe></div>
<p> </p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>The <code>&lt;dialog&gt;</code> element is the native way to create modals. It provides built-in accessible features for both keyboard and screen reader users</p>
<p>The <code>&lt;dialog&gt;</code> element is of two types, modal and non-modal. You can create a non-modal dialog using the <code>show()</code> method, and the <code>showModal()</code> method will create a modal dialog.</p>
<p>When you're not using the native dialog element, ensure your custom modal matches the native dialog in terms of accessibility to ensure a uniform experience for all users</p>
<p>You should also always remember to autofocus the most immediate interactive element, the dialog can do this by default.</p>
<p>Finally, you can use the <code>inert</code> attribute on other elements to prevent those elements from being accessed when the modal is open.</p>
<h3 id="heading-resources">Resources:</h3>
<ul>
<li><p><a target="_blank" href="https://w3c.github.io/aria/#dialog">W3c Dialog role</a></p>
</li>
<li><p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog">MDN: The Dialog element</a></p>
</li>
</ul>
<p>Thank you so much for reading this article. If you found it helpful, consider sharing. Happy coding!</p>
<p>You can connect with me on <a target="_blank" href="https://www.linkedin.com/in/elizabeth-meshioye/">LinkedIn</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Frontend Web Development: In-Depth Project Tutorial ]]>
                </title>
                <description>
                    <![CDATA[ Are you looking to improve your frontend web development skills with HTML, CSS, JavaScript, TypeScript, and React? If so, we have the perfect video course for you! We just published a 10-hour project-based, frontend web development course on the free... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/frontend-web-development-in-depth-project-tutorial/</link>
                <guid isPermaLink="false">66b2026d25ef0bb2c5a5172e</guid>
                
                    <category>
                        <![CDATA[ frontend ]]>
                    </category>
                
                    <category>
                        <![CDATA[ youtube ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Beau Carnes ]]>
                </dc:creator>
                <pubDate>Tue, 04 Apr 2023 18:29:33 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/04/tictAC.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Are you looking to improve your frontend web development skills with HTML, CSS, JavaScript, TypeScript, and React? If so, we have the perfect video course for you!</p>
<p>We just published a 10-hour project-based, frontend web development course on the freeCodeCamp.org YouTube channel.</p>
<p>Zach Gollwitzer published this course. Zach is a full stack developer and his Frontend Developer Bootcamp course we published a few months ago has over 1,000,000 views. This new course is a sequel to that previous course. However, you can jump straight to this course if you already have a basic understanding of HTML, CSS, and JavaScript.</p>
<p>This video course is divided into four sections, with each section building on the previous one. In Section 1, you will learn how to build a Tic Tac Toe game with Vanilla HTML/CSS/JS. This will be the foundation for the rest of the course.</p>
<p>In this first section, you will learn how to set up your project and the necessary VSCode extensions. Then, you will build the UI with HTML and CSS and add JavaScript interactivity to the project.</p>
<p>In Section 2, you will refactor the Tic Tac Toe game to use the Model-View-Controller (MVC) pattern. You will explore why this pattern is helpful and how it can improve the structure of your code.</p>
<p>Section 3 will introduce you to TypeScript and teach you how to set it up from scratch. You will learn the benefits of using TypeScript and how it can improve the reliability and maintainability of your code.</p>
<p>Finally, in Section 4, you will refactor the Tic Tac Toe game to use React and TypeScript. You will learn how to set up a React app from scratch and explore why React is useful and what problems it solves over a Vanilla approach. You will also learn how to initialize TypeScript in a React app from scratch and refactor your Vanilla app to a React app.</p>
<p>This video course is perfect for anyone looking to improve their frontend web development skills, from beginners to intermediate developers.</p>
<p>Watch the full course on <a target="_blank" href="https://www.youtube.com/watch?v=MsnQ5uepIaE">the freeCodeCamp.org YouTube channel</a> (10-hour watch).</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/MsnQ5uepIaE" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Add Search Functionality to a Frontend Application ]]>
                </title>
                <description>
                    <![CDATA[ By Njoku Samson Ebere As a software developer, part of your job is to deliver the best user experience possible to those using your site or product.  And building a helpful and efficient search function is one way you can do this. So if you are looki... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-add-search-to-frontend-app/</link>
                <guid isPermaLink="false">66d84f9b5e1ebbdb6468edcd</guid>
                
                    <category>
                        <![CDATA[ Front-end Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ frontend ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ search ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Tue, 04 Jan 2022 16:28:58 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/01/pexels-ketut-subiyanto-4126712.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Njoku Samson Ebere</p>
<p>As a software developer, part of your job is to deliver the best user experience possible to those using your site or product. </p>
<p>And building a helpful and efficient search function is one way you can do this. So if you are looking for the right way to build out search functionality on the front end of your site, you're in the right place. </p>
<p>Some time ago, I thought that search functionality had to be built in the back end and called from the front end. </p>
<p>But as I continued building applications, I learned that sometimes, you might just have to search among the data retrieved from a public endpoint where there is no <em>search</em> endpoint. Other times, frontend search might be necessary to improve a website’s speed and user experience in general.</p>
<p>This tutorial will first go through the "wrong way" of setting up search which many of us have adopted. And then we'll learn a much better way of doing it. So stick with me and let me take you on this ride.</p>
<h3 id="heading-prerequisites">Prerequisites</h3>
<p>It will be easy to follow this tutorial if you have basic knowledge of:</p>
<ul>
<li><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/First_steps/What_is_JavaScript">JavaScript</a></li>
<li><a target="_blank" href="https://reactjs.org/">React</a></li>
</ul>
<h2 id="heading-starter-project">Starter Project</h2>
<p>I have cooked up a little application to give you a head-start if you want to code along with me. Just clone <a target="_blank" href="https://github.com/EBEREGIT/search-tutorial/tree/starter-code">this repository</a>. The branch of interest is the <strong>starter-code</strong> branch. </p>
<p>Follow the instructions in the <a target="_blank" href="https://github.com/EBEREGIT/search-tutorial/blob/starter-code/README.md">ReadMe</a> file to setup the project and you should have the following screen:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/12/Screenshot-2021-12-29-at-20.17.12.png" alt="Image" width="600" height="400" loading="lazy">
<em>Starter Project Screen</em></p>
<p>In the project you now have, we are fetching COVID-19 updates for every country in the <code>src/context/hatchways.js</code> file courtesy of <a target="_blank" href="https://api.coronatracker.com/">coronatracker</a>. </p>
<p>In our <code>src/App.js</code> file, we display the results we have gotten. A <em>search</em> input box is situated above the list of results. For each of these result, the <code>src/components/Country.js</code> file is rendered.</p>
<p>As a user types into the input box, the <code>filterCountryByName</code> function is called to search through the countries we collected earlier. This function is being built in the <code>src/Helpers/HatchHelper.js</code> file.</p>
<p>All styles are in the <code>src/styles/App.scss</code> file.</p>
<p>You should now be able to navigate around the project and find your way. Let's begin with how you shouldn't build out your search functionality.</p>
<h2 id="heading-how-not-to-build-search-functionality">How NOT to Build Search Functionality</h2>
<p>We will focus on the <code>src/Helpers/HatchHelper.js</code> file to build out the search function.</p>
<p>Already we have the following code:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// search countries by name</span>
<span class="hljs-keyword">const</span> filterCountryByName = <span class="hljs-function">(<span class="hljs-params">name, countries, setResults</span>) =&gt;</span> {
  <span class="hljs-comment">// clear search result if the search field is empty</span>
  <span class="hljs-keyword">if</span> (name === <span class="hljs-string">""</span>) {
    setResults([]);
  }

  <span class="hljs-comment">// discontinue if there is no search yet</span>
  <span class="hljs-keyword">if</span> (name === <span class="hljs-literal">null</span> || name === <span class="hljs-string">""</span> || countries === []) <span class="hljs-keyword">return</span>;
};
</code></pre>
<p>Next, we need to empty the previous search array so that we don't add the new search result to it. This is just in case we already made a search and want to do another.</p>
<pre><code class="lang-javascript">    <span class="hljs-comment">// empty the previous search array if any</span>
    <span class="hljs-keyword">const</span> searchResult = [];
</code></pre>
<p>Convert the <em>search string</em> to lower case for consistency's sake. This will make the search case insensitive.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> data = name.toLowerCase();
</code></pre>
<p>Now, loop through the <strong>countries</strong> like so:</p>
<pre><code class="lang-javascript">  <span class="hljs-comment">// loop through all countries</span>
  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> country <span class="hljs-keyword">of</span> countries) {

  }
</code></pre>
<p>Next, collect each country name and make it lower case to ensure that the search will be case insensitive like so:</p>
<pre><code class="lang-javascript">    <span class="hljs-keyword">const</span> countryName = country.countryName.toLowerCase();
</code></pre>
<p>Below that, check if the search string matches one character in the country name (<code>[...countryName].includes(data)</code>), one word in the country name (<code>countryName.split(" ").includes(data)</code>) or the full country name (<code>countryName === data</code>) and collect the country details like so:</p>
<pre><code class="lang-javascript">    <span class="hljs-comment">// check if the search word or character matches</span>
    <span class="hljs-keyword">if</span> (
      [...countryName].includes(data) ||
      countryName === data ||
      countryName.split(<span class="hljs-string">" "</span>).includes(data)
    ) {
      searchResult.push(country);
    }
</code></pre>
<p>When the loop is done, update the search Result with the following line of code:</p>
<pre><code class="lang-javascript">setResults(searchResult);
</code></pre>
<p>The <code>filterCountryByName</code> function now looks like this:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// search countries by name</span>
<span class="hljs-keyword">const</span> filterCountryByName = <span class="hljs-function">(<span class="hljs-params">name, countries, setResults</span>) =&gt;</span> {
  <span class="hljs-comment">// clear search result if the search field is empty</span>
  <span class="hljs-keyword">if</span> (name === <span class="hljs-string">""</span>) {
    setResults([]);
  }

  <span class="hljs-comment">// discontinue if there is no search yet</span>
  <span class="hljs-keyword">if</span> (name === <span class="hljs-literal">null</span> || name === <span class="hljs-string">""</span> || countries === []) <span class="hljs-keyword">return</span>;

  <span class="hljs-comment">// empty the previous search array if any</span>
  <span class="hljs-keyword">const</span> searchResult = [];
  <span class="hljs-keyword">const</span> data = name.toLowerCase();

  <span class="hljs-comment">// loop through all countries</span>
  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> country <span class="hljs-keyword">of</span> countries) {
    <span class="hljs-keyword">const</span> countryName = country.countryName.toLowerCase();

    <span class="hljs-comment">// check if the search word or character matches</span>
    <span class="hljs-keyword">if</span> (
      [...countryName].includes(data) ||
      countryName === data ||
      countryName.split(<span class="hljs-string">" "</span>).includes(data)
    ) {
      searchResult.push(country);
    }
  }

  setResults(searchResult);
};
</code></pre>
<p>Replace the <strong>main</strong> element in the <code>src/App.js</code> file with the following code to ensure proper feedback during search:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">main</span>&gt;</span>
    {filterByNameResults &amp;&amp; filterByNameResults.length
    ? filterByNameResults.map((country) =&gt; (
    <span class="hljs-tag">&lt;<span class="hljs-name">Country</span> <span class="hljs-attr">country</span>=<span class="hljs-string">{country}</span> /&gt;</span>
    ))
    : filterByName &amp;&amp; !filterByNameResults.length
    ? "No Result Found!"
    : hatchLoading === "processing"
    ? "Fetching Data..."
    : hatchLoading === "found" &amp;&amp; hatches &amp;&amp; hatches.length
    ? hatches.map((country) =&gt; <span class="hljs-tag">&lt;<span class="hljs-name">Country</span> <span class="hljs-attr">country</span>=<span class="hljs-string">{country}</span> /&gt;</span>)
    : "No country Found! Check your Internet Connection!"}
<span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span>
</code></pre>
<h3 id="heading-how-to-test-your-search-function">How to Test Your Search Function</h3>
<p>Let's now make a search and see what we get:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/12/ezgif.com-gif-maker.gif" alt="Image" width="600" height="400" loading="lazy">
<em>Testing How to Make Frontend Search the Wrong Way</em></p>
<p>Here's the code for the <a target="_blank" href="https://github.com/EBEREGIT/search-tutorial/tree/wrong-way">wrong way to code a search function</a>.</p>
<h3 id="heading-what-is-the-problem-with-the-search-method-above">What is the problem with the search method above?</h3>
<p>You will notice that the search string must satisfy at least one of the 3 conditions that we specified for a result to be returned. </p>
<p>So how about a user who isn't sure of the spelling but knows a couple of characters contained in the country name? </p>
<p>Do you notice that the user will take more time to search certain words because the words must me typed completely to get a match?</p>
<p><strong>Think about this</strong>: ITA- should be able to return ITALY, NIG- should be able to return NIGER and NIGERIA, and so on.</p>
<p>So while our search works, these issues make it difficult to use and negatively impact the user experience. This now takes us to the right way to make this search functionality.</p>
<h2 id="heading-how-to-build-a-search-feature-the-right-way">How to Build a Search Feature the Right Way</h2>
<p>We need to create another search just below the current one. </p>
<p>Start by setting 2 initial states to hold the <strong>search string</strong> and the <strong>search results</strong> for this new search like so:</p>
<pre><code class="lang-javascript">  <span class="hljs-keyword">const</span> [searchString, setSearchString] = useState(<span class="hljs-string">""</span>);
  <span class="hljs-keyword">const</span> [searchResult, setSearchResult] = useState([]);
</code></pre>
<p>Next, make another input box just below the first one like so:</p>
<pre><code class="lang-javascript">          {<span class="hljs-comment">/* search by name the right way*/</span>}
          &lt;input
            name=<span class="hljs-string">"searchString"</span>
            value={searchString}
            placeholder=<span class="hljs-string">"Search by name (Right Way)"</span>
            onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> setSearchString(e.target.value)}
            onKeyUp={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span>
              searchCountryByName(
                e.target.value,
                hatches,
                setSearchResult
              )
            }
          /&gt;
</code></pre>
<p>Go to the <code>src/Helpers/HatchHelper.js</code> file and create the <strong><code>searchCountryByName</code></strong> function below the <strong><code>filterCountryByName</code></strong> function:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// search countries by name the right way</span>
<span class="hljs-keyword">const</span> searchCountryByName = <span class="hljs-function">(<span class="hljs-params">
  searchString,
  countries,
  setSearchResult
</span>) =&gt;</span> {

};
</code></pre>
<p>Include it in the export like this:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> { filterCountryByName, searchCountryByName };
</code></pre>
<p>You can now import it in the <code>src/App.js</code> file like so:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { filterCountryByName, searchCountryByName } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Helpers/HatchHelper"</span>;
</code></pre>
<p>You should now have a second input box that doesn’t do anything just yet:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/12/Screenshot-2021-12-30-at-07.38.27.png" alt="Image" width="600" height="400" loading="lazy">
<em>Screen showing a second input box that doesn’t do anything just yet</em></p>
<h4 id="heading-fleshing-out-the-function">Fleshing out the function</h4>
<p>We will now build out the function to work as we desire. </p>
<p>Begin by adding the following lines of code:</p>
<pre><code class="lang-javascript">    <span class="hljs-comment">// clear search result if the search field is empty</span>
    <span class="hljs-keyword">if</span> (searchString === <span class="hljs-string">""</span>) {
      setSearchResult([]);
    }

    <span class="hljs-comment">// discontinue if there is no search yet</span>
    <span class="hljs-keyword">if</span> (searchString === <span class="hljs-literal">null</span> || searchString === <span class="hljs-string">""</span> || countries === []) <span class="hljs-keyword">return</span>;
</code></pre>
<p>Next, empty the previous search array if any like this:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// empty the previous search array if any</span>
  setSearchResult([]);
</code></pre>
<p>Then create a variable that will hold our search results while searching:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">let</span> results = [];
</code></pre>
<p>Create a regular expression pattern for the search string like so:</p>
<pre><code class="lang-javascript">  <span class="hljs-comment">// create a regular expression pattern for the search string</span>
  <span class="hljs-keyword">const</span> pattern = <span class="hljs-keyword">new</span> <span class="hljs-built_in">RegExp</span>(searchString, <span class="hljs-string">"gi"</span>);
</code></pre>
<blockquote>
<p>In the code above, we are saying that we want to use this <strong>searchString</strong> for something. While using it, we want it to be case-insensitive and we want all possible results. You can learn more about <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp">regular expressions here</a>.</p>
</blockquote>
<p>Now loop through countries and collect each country name like so:</p>
<pre><code class="lang-javascript">  <span class="hljs-comment">// loop through all countries</span>
  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> country <span class="hljs-keyword">of</span> countries) {
    <span class="hljs-keyword">const</span> countryName = country.countryName;

  }
</code></pre>
<p>Still in the loop, test if the regular expression pattern matches the <strong><code>countryName</code></strong> that we just collected. If it is <strong>true</strong>, then add the country details to the <strong>results</strong> array like so:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// check if the search word or character matches</span>
<span class="hljs-keyword">if</span> (pattern.test(countryName)) {
    results.push(country);
}
</code></pre>
<p>Finish by updating the search result using the following code:</p>
<pre><code class="lang-javascript">setSearchResult(results)
</code></pre>
<p>The <code>searchCountryByName</code> function now looks like this:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// search countries by name the right way</span>
<span class="hljs-keyword">const</span> searchCountryByName = <span class="hljs-function">(<span class="hljs-params">
  searchString,
  countries,
  setSearchResult
</span>) =&gt;</span> {
  <span class="hljs-comment">// clear search result if the search field is empty</span>
  <span class="hljs-keyword">if</span> (searchString === <span class="hljs-string">""</span>) {
    setSearchResult([]);
  }

  <span class="hljs-comment">// discontinue if there is no search yet</span>
  <span class="hljs-keyword">if</span> (searchString === <span class="hljs-literal">null</span> || searchString === <span class="hljs-string">""</span> || countries === []) <span class="hljs-keyword">return</span>;

  <span class="hljs-comment">// empty the previous search array if any</span>
  setSearchResult([]);
  <span class="hljs-keyword">let</span> results = [];

  <span class="hljs-comment">// create a regular expression pattern for the search string</span>
  <span class="hljs-keyword">const</span> pattern = <span class="hljs-keyword">new</span> <span class="hljs-built_in">RegExp</span>(searchString, <span class="hljs-string">"gi"</span>);

  <span class="hljs-comment">// loop through all countries</span>
  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> country <span class="hljs-keyword">of</span> countries) {
    <span class="hljs-keyword">const</span> countryName = country.countryName;

    <span class="hljs-comment">// check if the search word or character matches</span>
    <span class="hljs-keyword">if</span> (pattern.test(countryName)) {
      results.push(country);
    }
  }

  setSearchResult(results)
};
</code></pre>
<p>Return to the <code>src/App.js</code> file and replace the main element with the following code:</p>
<pre><code class="lang-html">        <span class="hljs-tag">&lt;<span class="hljs-name">main</span>&gt;</span>
          {filterByNameResults &amp;&amp; filterByNameResults.length
            ? filterByNameResults.map((country) =&gt; (
                <span class="hljs-tag">&lt;<span class="hljs-name">Country</span> <span class="hljs-attr">country</span>=<span class="hljs-string">{country}</span> /&gt;</span>
              ))
            : filterByName &amp;&amp; !filterByNameResults.length
            ? "No Result Found!"
            : searchResult &amp;&amp; searchResult.length
            ? searchResult.map((country) =&gt; <span class="hljs-tag">&lt;<span class="hljs-name">Country</span> <span class="hljs-attr">country</span>=<span class="hljs-string">{country}</span> /&gt;</span>)
            : searchString &amp;&amp; !searchResult.length
            ? "No Result Found!"
            : hatchLoading === "processing"
            ? "Fetching Data..."
            : hatchLoading === "found" &amp;&amp; hatches &amp;&amp; hatches.length
            ? hatches.map((country) =&gt; <span class="hljs-tag">&lt;<span class="hljs-name">Country</span> <span class="hljs-attr">country</span>=<span class="hljs-string">{country}</span> /&gt;</span>)
            : "No country Found! Check your Internet Connection!"}
        <span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span>
</code></pre>
<p>Now, the results for the second search box are included above.</p>
<h3 id="heading-testing-your-search-function-the-right-way">Testing your search function (the right way)</h3>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/12/ezgif.com-gif-maker--1-.gif" alt="Image" width="600" height="400" loading="lazy">
<em>Testing How to Make Frontend Search the Right Way</em></p>
<p>Walah! You just learned the right way to create a search on the front end. 😊</p>
<p>Here's the code for the <a target="_blank" href="https://github.com/EBEREGIT/search-tutorial/tree/right-way">right way to build a search function</a>.</p>
<h2 id="heading-how-to-optimize-your-search-functionality">How to Optimize Your Search Functionality</h2>
<p>We are actually done. So you can skip this if you are busy, but it will just take a moment if you want to improve your search function.</p>
<p>You will notice that when you make a search the wrong way and do not refresh the page, you will be stuck with the results of the wrong way. It would be better to get fresh results when the second search box is used for the right way. </p>
<p>To achieve that, we will need to clear all search results for every search being made – whether it is the <strong>Wrong</strong> or <strong>Right</strong> Way. Let's do the following:</p>
<p>In the <code>src/App.js</code>, replace the <em>onkey</em> event of the first search box with the following<em>:</em></p>
<pre><code class="lang-javascript">            onKeyUp={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span>
              filterCountryByName(
                e.target.value,
                hatches,
                setFilterByNameResults,
                setSearchString,
                setSearchResult
              )
            }
</code></pre>
<p>Replace the <em>onkey</em> event of the second search box with the following<em>:</em></p>
<pre><code class="lang-javascript">            onKeyUp={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span>
              searchCountryByName(
                e.target.value,
                hatches,
                setSearchResult,
                setFilterByName,
                setFilterByNameResults
              )
            }
</code></pre>
<p>In the <code>src/Helpers/HatchHelper.js</code> file, add the 2 parameters we just passed into the <strong><code>filterCountryByName</code></strong> like so:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// search countries by name</span>
<span class="hljs-keyword">const</span> filterCountryByName = <span class="hljs-function">(<span class="hljs-params">
  name,
  countries,
  setResults,
  setSearchString,
  setSearchResult
</span>) =&gt;</span> {...}
</code></pre>
<p>Next, just before clearing the initial search results, clear the other search field and results like so:</p>
<pre><code class="lang-javascript">  <span class="hljs-comment">// clear the other search field and results if any</span>
  setSearchString(<span class="hljs-string">""</span>);
  setSearchResult([]);
</code></pre>
<p>Now do the same for the <strong><code>searchCountryByName</code></strong> function.</p>
<h4 id="heading-when-you-are-done-you-should-have-the-following-result">When you are done, you should have the following result:</h4>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/12/ezgif.com-gif-maker--2--1.gif" alt="Image" width="600" height="400" loading="lazy">
<em>Our application after we have optimised the functionality</em></p>
<p>Awesome! 👍🏾👍🏾👍🏾</p>
<p>Here's the <a target="_blank" href="https://github.com/EBEREGIT/search-tutorial/tree/optimization">optimisation code</a>.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>It has been an awesome ride with you as we saw the mistakes many of us have made and how to correct them by creating a search function that offers the best experience to the user.</p>
<p>I believe the code can be improved even more. So I encourage to take a look at the code again and see how you can make it even better.</p>
<p>All the code is <a target="_blank" href="https://github.com/EBEREGIT/search-tutorial">here</a>. Thanks for reading!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Giphy API Tutorial – How to Generate Animated Text GIFs with ReactJS ]]>
                </title>
                <description>
                    <![CDATA[ By Charles M. In this tutorial you will create an app that generates dynamic animated text using Giphy's API with ReactJS. After that I'll go over some of the other API features Giphy provides that you can use to make other interesting projects. You ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/giphy-api-tutorial/</link>
                <guid isPermaLink="false">66d45dd73dce891ac3a967bc</guid>
                
                    <category>
                        <![CDATA[ api ]]>
                    </category>
                
                    <category>
                        <![CDATA[ CSS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ frontend ]]>
                    </category>
                
                    <category>
                        <![CDATA[ gif ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Wed, 26 May 2021 21:44:01 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2021/05/giphy-API-tutorial.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Charles M.</p>
<p>In this tutorial you will create an app that generates dynamic animated text using Giphy's API with ReactJS.</p>
<p>After that I'll go over some of the other API features Giphy provides that you can use to make other interesting projects.</p>
<p>You can find the <a target="_blank" href="https://github.com/renaissanceengineer/reactjs-giphy-api-tutorial">code for the tutorial here</a>.</p>
<h2 id="heading-video-tutorial">Video Tutorial</h2>
<p>To see a preview of the finished product in action, you can watch the start of this video. If you prefer to follow a video tutorial instead of reading (or in addition to reading), you can also follow along for the rest of the video. </p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/H8JpzxRoS18" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<h2 id="heading-getting-started">Getting Started</h2>
<p>To get started you'll need a basic development environment for ReactJS. I'll be using create-react-app as the starting project template. </p>
<p>Next you'll need to visit <a target="_blank" href="https://developers.giphy.com">Giphy's developers page</a> and create an account so you can get your API key. Once you've created your account you'll see a dashboard like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/05/giphy-dashboard.PNG" alt="Image" width="600" height="400" loading="lazy"></p>
<p>You need to click "create an App" and choose the SDK option for your app. Your dashboard will then present you with an API key you will use to make calls to the Giphy API. </p>
<h3 id="heading-how-to-setup-the-app-file-and-folder">How to Setup the App File and Folder</h3>
<p>The structure for this tutorial will be standard for ReactJS projects. Inside the <code>src</code> directory, create a <code>components</code> directory and create two files, <code>Error.js</code> and <code>TextList.js</code> </p>
<p>You also need to create a <code>.env</code> file in the root of the project that you'll use to store your API key. Whatever you name your variable, you need to append REACT_APP in front of it, like this:</p>
<p><code>REACT_APP_GIPHY_KEY=apikeyhere</code></p>
<h3 id="heading-install-giphy-js-fetch">Install Giphy JS-fetch</h3>
<p>The final thing you need to do is install Giphy's API helper library which you can do using the following command:</p>
<p><code>npm install @giphy/js-fetch-api</code></p>
<h2 id="heading-giphy-api-call">Giphy API Call</h2>
<p>The first task in making this app is creating an input form to accept the text you want to generate from the Giphy API. You will then use that text input and send it as an API request. </p>
<p>Before displaying this response data, let's test it out by simply making the API request and then logging the response. Write the following code in your <code>App.js</code> file:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { GiphyFetch } <span class="hljs-keyword">from</span> <span class="hljs-string">'@giphy/js-fetch-api'</span>
<span class="hljs-keyword">import</span> {useState} <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>
<span class="hljs-keyword">import</span> TextList <span class="hljs-keyword">from</span> <span class="hljs-string">'./components/TextList'</span>
<span class="hljs-keyword">import</span> <span class="hljs-built_in">Error</span> <span class="hljs-keyword">from</span> <span class="hljs-string">'./components/Error'</span>
<span class="hljs-keyword">import</span> <span class="hljs-string">'./App.css'</span>;

<span class="hljs-keyword">const</span> giphy = <span class="hljs-keyword">new</span> GiphyFetch(process.env.REACT_APP_GIPHY_KEY)

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [text, setText] = useState(<span class="hljs-string">''</span>)
  <span class="hljs-keyword">const</span> [results, setResults] = useState([])
  <span class="hljs-keyword">const</span> [err, setErr] = useState(<span class="hljs-literal">false</span>)

  <span class="hljs-keyword">const</span> handleInput = <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
    setText(e.target.value)
  }

  <span class="hljs-keyword">const</span> handleSubmit = <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
    <span class="hljs-keyword">if</span>(text.length === <span class="hljs-number">0</span>) {

      <span class="hljs-comment">//set error state to true</span>
      setErr(<span class="hljs-literal">true</span>)
      <span class="hljs-keyword">return</span>
    }

    <span class="hljs-built_in">console</span>.log(text)

    <span class="hljs-keyword">const</span> apiCall = <span class="hljs-keyword">async</span> () =&gt; {
      <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> giphy.animate(text, {<span class="hljs-attr">limit</span>: <span class="hljs-number">20</span>})
      <span class="hljs-built_in">console</span>.log(res.data)
      setResults(res.data)
    }

    apiCall()
    setText(<span class="hljs-string">''</span>)
    setErr(<span class="hljs-literal">false</span>)

  }

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"App"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Animated Text Generator<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h3</span>&gt;</span>Type text into the form and hit submit<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'input-field'</span> <span class="hljs-attr">value</span>=<span class="hljs-string">{text}</span> <span class="hljs-attr">onChange</span>=<span class="hljs-string">{handleInput}</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'submit-btn'</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{handleSubmit}</span>&gt;</span>Submit<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<p>Let's take a look at what's happening in this code:</p>
<p><code>const giphy = new GiphyFetch(process.env.REACT_APP_GIPHY_KEY)</code> is where you use the Giphy helper library to create the object you'll use for interacting with the Giphy API. </p>
<p><code>process.env.REACT_APP_GIPHY_KEY</code> is how your API key is passed as an argument from the <code>.env</code> file. You can also pass your API key as a string, but you won't want to do this in production because somebody could steal and use your key.</p>
<p>Inside the main App component, you create three pieces of state using hooks. The 1st is <code>text</code> which will be what stores the user input. This is what will be passed to the API as an argument to generate text. </p>
<p><code>err</code> will be used to conditionally render an error later if the user attempts to submit an empty string. </p>
<p>And <code>results</code> is an empty array that will be used to store the results from the API response. </p>
<p>If you run the code and check your developer console, you should see that the Giphy API returned an array with 20 objects.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/05/console-results.PNG" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-how-to-display-the-data-with-react">How to Display the Data with React</h2>
<p>Now that the data is being properly stored in state, all you need to do is display that data with JSX.  To handle that, we'll finish those two components we created earlier. </p>
<p>First we'll make a simple error component that can display a custom message. Place the following code into <code>Error.js</code> inside your components folder:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> <span class="hljs-built_in">Error</span> = <span class="hljs-function">(<span class="hljs-params">props</span>) =&gt;</span> {
    <span class="hljs-keyword">if</span>(!props.isError) {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>
    }

    <span class="hljs-keyword">return</span> (
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'error'</span>&gt;</span>{props.text}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span></span>
    )
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-built_in">Error</span>
</code></pre>
<p>The <code>Error</code> component is very simple. It takes the <code>err</code> state and a text string as props, and if the value is true it will render the text. If <code>err</code> is false, it returns null.</p>
<p>Next is the TextList component which will take the <code>results</code> state as props and then display the data in your app:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> TextList = <span class="hljs-function">(<span class="hljs-params">props</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> items = props.gifs.map(<span class="hljs-function">(<span class="hljs-params">itemData</span>) =&gt;</span> {
    <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Item</span> <span class="hljs-attr">url</span>=<span class="hljs-string">{itemData.url}</span> /&gt;</span></span>;
  });
  <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-container"</span>&gt;</span>{items}<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>;
};
<span class="hljs-keyword">const</span> Item = <span class="hljs-function">(<span class="hljs-params">props</span>) =&gt;</span> {
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"gif-item"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">src</span>=<span class="hljs-string">{props.url}</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
};
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> TextList;
</code></pre>
<p>This component is a little more complicated, so here's what is happening:</p>
<p>The <code>Item</code> component accepts the URL value which is inside each value returned from the API. It uses this URL as the source for the image element.</p>
<p>The <code>results</code> state array from the App component is passed to the TextList component as <code>gifs</code>. The array is mapped over to generate all the <code>Item</code> components for all the results and assigned to the <code>items</code> variable and then returned inside a container div. We'll style this container later to create a grid layout.</p>
<h3 id="heading-how-to-import-the-components-into-the-main-app">How to Import the Components into the Main App</h3>
<p>Now you just need to use those finished components in your JSX. The final code of your <code>App.js</code> file should look like this:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> TextList <span class="hljs-keyword">from</span> <span class="hljs-string">'./components/TextList'</span>
<span class="hljs-keyword">import</span> <span class="hljs-built_in">Error</span> <span class="hljs-keyword">from</span> <span class="hljs-string">'./components/Error'</span>
<span class="hljs-keyword">import</span> { GiphyFetch } <span class="hljs-keyword">from</span> <span class="hljs-string">'@giphy/js-fetch-api'</span>
<span class="hljs-keyword">import</span> {useState} <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>
<span class="hljs-keyword">import</span> <span class="hljs-string">'./App.css'</span>;

<span class="hljs-keyword">const</span> giphy = <span class="hljs-keyword">new</span> GiphyFetch(process.env.REACT_APP_GIPHY_KEY)

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [text, setText] = useState(<span class="hljs-string">''</span>)
  <span class="hljs-keyword">const</span> [results, setResults] = useState([])
  <span class="hljs-keyword">const</span> [err, setErr] = useState(<span class="hljs-literal">false</span>)

  <span class="hljs-keyword">const</span> handleInput = <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
    setText(e.target.value)
  }

  <span class="hljs-keyword">const</span> handleSubmit = <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
    <span class="hljs-keyword">if</span>(text.length === <span class="hljs-number">0</span>) {

      <span class="hljs-comment">//set error state to true</span>
      setErr(<span class="hljs-literal">true</span>)
      <span class="hljs-keyword">return</span>
    }

    <span class="hljs-built_in">console</span>.log(text)

    <span class="hljs-keyword">const</span> apiCall = <span class="hljs-keyword">async</span> () =&gt; {
      <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> giphy.animate(text, {<span class="hljs-attr">limit</span>: <span class="hljs-number">20</span>})

      setResults(res.data)
    }

    apiCall()
    <span class="hljs-comment">//change error state back to false</span>
    setText(<span class="hljs-string">''</span>)
    setErr(<span class="hljs-literal">false</span>)

  }

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"App"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Animated Text Generator<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h3</span>&gt;</span>Type text into the form and hit submit<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'input-field'</span> <span class="hljs-attr">value</span>=<span class="hljs-string">{text}</span> <span class="hljs-attr">onChange</span>=<span class="hljs-string">{handleInput}</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'submit-btn'</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{handleSubmit}</span>&gt;</span>Submit<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Error</span> <span class="hljs-attr">isError</span>=<span class="hljs-string">{err}</span> <span class="hljs-attr">text</span>=<span class="hljs-string">'need length longer than 0 for input'</span>/&gt;</span>
      {results &amp;&amp; <span class="hljs-tag">&lt;<span class="hljs-name">TextList</span> <span class="hljs-attr">gifs</span>=<span class="hljs-string">{results}</span>  /&gt;</span>}
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<p>The only changes here are the bottom two lines added in the return statement:</p>
<p>The <code>Error</code> component is passed the <code>err</code> state and a <code>text</code> prop which will only be rendered if an error occurs. </p>
<p>In this app there is only one error condition in case the input is empty, but you could add additional checks with custom error messages as well.</p>
<p>Then we use conditional rendering with the <code>&amp;&amp;</code> logical operator. This causes the <code>TextList</code> component to render only if the results array is not empty, which means the API response returned successfully with our gifs.</p>
<p>If you run your code at this point, you should see an ugly but functional app. If you use the input field and click the submit button, the gifs should be returned and displayed in your app.</p>
<h2 id="heading-how-to-add-styling-with-css">How to Add Styling with CSS</h2>
<p>The last thing to do is make the app look a little bit prettier. Feel free to customize any of these styles if you want to adjust how things look. Place this code into your <code>App.css</code> file:</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.App</span> {
  <span class="hljs-attribute">text-align</span>: center;
}

<span class="hljs-selector-class">.error</span> {
  <span class="hljs-attribute">color</span>: <span class="hljs-number">#b50000</span>;
  <span class="hljs-attribute">font-size</span>: <span class="hljs-number">20px</span>;
  <span class="hljs-attribute">font-weight</span>: <span class="hljs-number">500</span>;
}


<span class="hljs-selector-class">.input-field</span> {
  <span class="hljs-attribute">font-size</span>: <span class="hljs-number">20px</span>;
  <span class="hljs-attribute">vertical-align</span>: middle;
  <span class="hljs-attribute">transition</span>: .<span class="hljs-number">5s</span>;
  <span class="hljs-attribute">border-width</span>: <span class="hljs-number">2px</span>;
  <span class="hljs-attribute">margin</span>: <span class="hljs-number">5px</span>;
}

<span class="hljs-selector-class">.input-field</span><span class="hljs-selector-pseudo">:focus</span> {
  <span class="hljs-attribute">box-shadow</span>: <span class="hljs-number">0</span> <span class="hljs-number">14px</span> <span class="hljs-number">28px</span> <span class="hljs-built_in">rgba</span>(<span class="hljs-number">0</span>,<span class="hljs-number">0</span>,<span class="hljs-number">0</span>,<span class="hljs-number">0.25</span>), <span class="hljs-number">0</span> <span class="hljs-number">10px</span> <span class="hljs-number">10px</span> <span class="hljs-built_in">rgba</span>(<span class="hljs-number">0</span>,<span class="hljs-number">0</span>,<span class="hljs-number">0</span>,<span class="hljs-number">0.22</span>);
  <span class="hljs-attribute">outline</span>: none;
}

<span class="hljs-selector-class">.input-field</span><span class="hljs-selector-pseudo">:hover</span> {
  <span class="hljs-attribute">box-shadow</span>: <span class="hljs-number">0</span> <span class="hljs-number">14px</span> <span class="hljs-number">28px</span> <span class="hljs-built_in">rgba</span>(<span class="hljs-number">0</span>,<span class="hljs-number">0</span>,<span class="hljs-number">0</span>,<span class="hljs-number">0.25</span>), <span class="hljs-number">0</span> <span class="hljs-number">10px</span> <span class="hljs-number">10px</span> <span class="hljs-built_in">rgba</span>(<span class="hljs-number">0</span>,<span class="hljs-number">0</span>,<span class="hljs-number">0</span>,<span class="hljs-number">0.22</span>);

}

<span class="hljs-selector-class">.submit-btn</span> {
  <span class="hljs-attribute">background-color</span>: <span class="hljs-built_in">rgb</span>(<span class="hljs-number">19</span>, <span class="hljs-number">209</span>, <span class="hljs-number">235</span>);
  <span class="hljs-attribute">color</span>: <span class="hljs-number">#fff</span>;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">6px</span> <span class="hljs-number">30px</span>;
  <span class="hljs-attribute">vertical-align</span>: middle;
  <span class="hljs-attribute">outline</span>: none;
  <span class="hljs-attribute">border</span>: none;
  <span class="hljs-attribute">font-size</span>: <span class="hljs-number">16px</span>;
  <span class="hljs-attribute">transition</span>: .<span class="hljs-number">3s</span>;
  <span class="hljs-attribute">cursor</span>: pointer;
}

<span class="hljs-selector-class">.submit-btn</span><span class="hljs-selector-pseudo">:hover</span> {
  <span class="hljs-attribute">background-color</span>: <span class="hljs-built_in">rgb</span>(<span class="hljs-number">10</span>, <span class="hljs-number">130</span>, <span class="hljs-number">146</span>);
}

<span class="hljs-selector-class">.text-container</span> {
  <span class="hljs-attribute">display</span>: flex;
  <span class="hljs-attribute">flex-wrap</span>: wrap;
  <span class="hljs-attribute">justify-content</span>: center;
}

<span class="hljs-selector-class">.gif-item</span> {
  <span class="hljs-attribute">flex-basis</span>: <span class="hljs-number">19%</span>;
}

<span class="hljs-selector-tag">img</span> {
  <span class="hljs-attribute">max-width</span>: <span class="hljs-number">100%</span>;
}

<span class="hljs-keyword">@media</span> screen <span class="hljs-keyword">and</span> (<span class="hljs-attribute">max-width:</span> <span class="hljs-number">992px</span>) {
  <span class="hljs-selector-class">.gif-item</span> {
    <span class="hljs-attribute">flex-basis</span>: <span class="hljs-number">31%</span>;
  }
}

<span class="hljs-keyword">@media</span> screen <span class="hljs-keyword">and</span> (<span class="hljs-attribute">max-width:</span> <span class="hljs-number">600px</span>) {
  <span class="hljs-selector-class">.gif-item</span> {
    <span class="hljs-attribute">flex-basis</span>: <span class="hljs-number">48%</span>;
  }
}
</code></pre>
<p>Nothing crazy going on here with the CSS. Just some styling for the submit button and some box shadow for the input field. </p>
<p>There are also a few media queries for some responsive design that changes the column count depending on the screen size.</p>
<h2 id="heading-other-giphy-api-features">Other Giphy API features</h2>
<p>The animated text API is just one feature available in the Giphy API. I'll go over a few other features that could be useful as part of a project or as a solo project.</p>
<h3 id="heading-animated-emoji">Animated Emoji</h3>
<p>The Emoji endpoint is very straightforward in terms of use. It returns a bunch of animated emoji just like the animated text API you used above, except you don't need to pass any arguments to it. An example API call:</p>
<p><code>const data = await gf.emoji()</code></p>
<p>This endpoint could be useful if you are building a chat application and want to make it easy for users to use Emoji in their messages.</p>
<h3 id="heading-pre-built-ui-components">Pre-Built UI components</h3>
<p>If you don't feel like messing around with a ton of custom code like we did in this tutorial, Giphy actually provides components for both ReactJS and regular JavaScript. </p>
<p>You can make a grid very similar to what we created in this tutorial with just a few lines of code:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { Grid } <span class="hljs-keyword">from</span> <span class="hljs-string">'@giphy/react-components'</span>
<span class="hljs-keyword">import</span> { GiphyFetch } <span class="hljs-keyword">from</span> <span class="hljs-string">'@giphy/js-fetch-api'</span>

<span class="hljs-comment">// use @giphy/js-fetch-api to fetch gifs</span>
<span class="hljs-comment">// apply for a new Web SDK key. Use a separate key for every platform (Android, iOS, Web)</span>
<span class="hljs-keyword">const</span> gf = <span class="hljs-keyword">new</span> GiphyFetch(<span class="hljs-string">'your Web SDK key'</span>)

<span class="hljs-comment">// fetch 10 gifs at a time as the user scrolls (offset is handled by the grid)</span>
<span class="hljs-keyword">const</span> fetchGifs = <span class="hljs-function">(<span class="hljs-params">offset: number</span>) =&gt;</span> gf.trending({ offset, <span class="hljs-attr">limit</span>: <span class="hljs-number">10</span> })

<span class="hljs-comment">// React Component</span>
ReactDOM.render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Grid</span> <span class="hljs-attr">width</span>=<span class="hljs-string">{800}</span> <span class="hljs-attr">columns</span>=<span class="hljs-string">{3}</span> <span class="hljs-attr">gutter</span>=<span class="hljs-string">{6}</span> <span class="hljs-attr">fetchGifs</span>=<span class="hljs-string">{fetchGifs}</span> /&gt;</span></span>, target)
</code></pre>
<p>You get some additional bonus features like automatic dynamic updates to fetch more content when users scroll to the bottom of the Grid. </p>
<p>You can choose between templates which handle almost everything or just a Grid component which gives you a little more control.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/05/giphy-ui-kits.PNG" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Here's an <a target="_blank" href="https://codesandbox.io/s/giphyreact-components-hbmcf?from-embed">interactive demo</a> provided by Giphy. </p>
<h3 id="heading-trending-api">Trending API</h3>
<p>This endpoint returns a list of constantly updated content based on user engagement and what is currently popular on Giphy.</p>
<h3 id="heading-search-api">Search API</h3>
<p>This endpoint is similar to the animated text endpoint, you just need to pass a search query as a parameter and you'll get an array of gifs that match.</p>
<p>There are many more API endpoints available. You can see the rest in <a target="_blank" href="https://developers.giphy.com/docs/api/endpoint">Giphy's API documentation</a>.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>That's it for this tutorial! I hope you found it interesting and you make some cool projects using the Giphy API.</p>
<p>If you are interested in a bunch of other cool APIs that you can use for making portfolio projects, you can check out this video as well which goes over 8 more APIs that I think are really cool.</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/3ZRBDIA8C6E" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Add Search to a React App with Fuse.js ]]>
                </title>
                <description>
                    <![CDATA[ Search is a powerful way help people visiting your site find the content that's most important to them. But often it's really challenging to figure out the rules and logic to make that happen. In this article, we'll see how can we can use fuse.js to ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-add-search-to-a-react-app-with-fuse-js/</link>
                <guid isPermaLink="false">66b8e33e6a98b2a27ee1f348</guid>
                
                    <category>
                        <![CDATA[ fuse.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ front end ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Front-end Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ frontend ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ json ]]>
                    </category>
                
                    <category>
                        <![CDATA[ General Programming ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ react hooks ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ search ]]>
                    </category>
                
                    <category>
                        <![CDATA[ tech  ]]>
                    </category>
                
                    <category>
                        <![CDATA[ technology ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Colby Fayock ]]>
                </dc:creator>
                <pubDate>Tue, 26 May 2020 14:45:00 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2020/05/fusejs-1.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Search is a powerful way help people visiting your site find the content that's most important to them. But often it's really challenging to figure out the rules and logic to make that happen. In this article, we'll see how can we can use fuse.js to add search to our apps.</p>
<ul>
<li><a class="post-section-overview" href="#heading-what-is-fusejs">What is fuse.js?</a></li>
<li><a class="post-section-overview" href="#heading-why-is-search-important">Why is search important?</a></li>
<li><a class="post-section-overview" href="#heading-what-are-we-going-to-build">What are we going to build?</a></li>
<li><a class="post-section-overview" href="#heading-step-0-bootstrapping-our-app">Step 0: Bootstrapping our app</a></li>
<li><a class="post-section-overview" href="#heading-step-1-installing-fusejs">Step 1: Installing Fuse.js</a></li>
<li><a class="post-section-overview" href="#heading-step-2-creating-a-new-fuse-search-instance">Step 2: Creating a new Fuse search instance</a></li>
<li><a class="post-section-overview" href="#heading-step-3-setting-up-dynamic-search-based-on-user-input">Step 3: Setting up dynamic search based on user input</a></li>
</ul>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/GZl-yEz4_qw" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<h2 id="heading-what-is-fusejs">What is fuse.js?</h2>
<p><a target="_blank" href="https://fusejs.io/">Fuse.js</a> is a JavaScript library that provides fuzzy search capabilities for applications and websites. It's nice and easy to use out of the box, but also includes configuration options that allow you to tweak and create powerful solutions.</p>
<h2 id="heading-why-is-search-important">Why is search important?</h2>
<p>Whether you're a content creator or are trying to sell a product with your website, it's important to help your visitors actually find what they're looking for. </p>
<p>If you're building an ecommerce website, you want someone to be able to easily find your Bender vinyl figures rather than having to dig through the entire catalog first.</p>
<h2 id="heading-what-are-we-going-to-build">What are we going to build?</h2>
<p>We're going to start off with a basic Create React App example. It's going to include some character info as structured data for one of my favorite shows Futurama that's simply dumped out into an HTML list.</p>
<p>With that list, we're going to use fuse.js to provide client-side search capabilities, allowing us to demonstrate searching for the character we're looking for by their name and other details.</p>
<h2 id="heading-step-0-bootstrapping-our-app">Step 0: Bootstrapping our app</h2>
<p>To get started, we're going to need content to work with. I got started by building a list of characters from Futurama as structured json data that I put in a list with a fresh Create React App.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/futurama-character-search-demo.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Futurama character search demo</em></p>
<p>You'll also notice I've already added an input for our search. It's not yet functional but we'll use that to get started.</p>
<p>If you'd like to start off at the same place, I created a branch with my demo repo that you can clone locally to walk through the project with me!</p>
<pre><code class="lang-shell">git clone --single-branch --branch start git@github.com:colbyfayock/my-futurama-characters.git
</code></pre>
<p><a target="_blank" href="https://github.com/colbyfayock/my-futurama-characters/tree/start">Git branch "start"</a></p>
<p>Or <a target="_blank" href="https://github.com/colbyfayock/my-futurama-characters/commit/20d4e42aaf69e214b63e684e012cd2f8c95d427b">follow along with the commit</a>.</p>
<h2 id="heading-step-1-installing-fusejs">Step 1: Installing Fuse.js</h2>
<p>First thing we'll want to do is actually add Fuse.js to our app. In your project, run:</p>
<pre><code class="lang-shell">yarn add fuse.js
# or
npm install --save fuse.js
</code></pre>
<p>This will save the dependency to our project so that we'll be able to use it in our project.</p>
<p>Next we'll want to import the dependency to our app so that we can start building with it. At the top of your file, in our case <code>src/App.js</code> if you're following along with me in a new Create React App project, add:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> Fuse <span class="hljs-keyword">from</span> <span class="hljs-string">'fuse.js'</span>;
</code></pre>
<p>If you want to test that it's working, you can <code>console.log(Fuse)</code> and see our <code>Fuse</code> class we'll use to create our search capabilities.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/fusejs-class.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Imported fuse.js class</em></p>
<p>And with that, we're ready to get started!</p>
<p><a target="_blank" href="https://github.com/colbyfayock/my-futurama-characters/commit/54720daffa6ff415997c319b12f8f44d7ec8b748">Follow along with the commit</a></p>
<h2 id="heading-step-2-creating-a-new-fuse-search-instance">Step 2: Creating a new Fuse search instance</h2>
<p>To use Fuse.js, we'll want to first create a new instance of it.</p>
<p>At the top of your component, add:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> fuse = <span class="hljs-keyword">new</span> Fuse(characters, {
  <span class="hljs-attr">keys</span>: [
    <span class="hljs-string">'name'</span>,
    <span class="hljs-string">'company'</span>,
    <span class="hljs-string">'species'</span>
  ]
});
</code></pre>
<p>With this does:</p>
<ul>
<li>Creates a new instance of Fuse</li>
<li>Passes in our <code>characters</code> array of objects</li>
<li>Specifies the 3 keys in our data that we want to search on</li>
</ul>
<p>Next, to perform the search, we can add:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> results = fuse.search(<span class="hljs-string">'bender'</span>);
</code></pre>
<p>And if we console log out the results, we can see:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/basic-fusejs-search-results.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Basic fuse.js search results</em></p>
<p>You'll notice that we have more results than our friend Bender though. Fuse.js provides a "fuzzy search" meaning it tries to help you in case you're not sure what you're looking for or if you're misspelling your query.</p>
<p>To get an idea of how this works, let's add the <code>includeScore</code> option to our search:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> fuse = <span class="hljs-keyword">new</span> Fuse(characters, {
  <span class="hljs-attr">keys</span>: [
    <span class="hljs-string">'name'</span>,
    <span class="hljs-string">'company'</span>,
    <span class="hljs-string">'species'</span>
  ],
  <span class="hljs-attr">includeScore</span>: <span class="hljs-literal">true</span>
});
</code></pre>
<p>Now we can see the <code>score</code> attribute in our results object.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/fusejs-search-results-with-score.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Fuse.js search results with score</em></p>
<p>You'll notice that our first result has a really low score. With fuse.js, a lower score means it's closer to an exact match.</p>
<p>A score of 0 indicates a perfect match, while a score of 1 indicates a complete mismatch.</p>
<p>It's saying that is incredibly likely that the first result is what we're looking for, but it's not confident in the others.</p>
<p>So with our results, we want to actually connect that to our UI. If you notice our array output is different than what we are mapping through for the HTML list, so let's create a new variable that we can change it to:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> results = fuse.search(<span class="hljs-string">'bender'</span>);
<span class="hljs-keyword">const</span> characterResults = results.map(<span class="hljs-function"><span class="hljs-params">character</span> =&gt;</span> character.item);
</code></pre>
<p>What this is doing is creating a new array using the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map">map</a> method that will only include the <code>item</code> property from each array object.</p>
<p>Then if we replace our <code>characters</code> map inside of our list with <code>characterResults.map</code>:</p>
<pre><code class="lang-jsx">&lt;ul className=<span class="hljs-string">"characters"</span>&gt;
  {characterResults.map(<span class="hljs-function"><span class="hljs-params">character</span> =&gt;</span> {
    <span class="hljs-keyword">const</span> { name, company, species, thumb } = character;
</code></pre>
<p>We can now see that our page only shows the results for "bender"!</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/futurama-character-search-filtered-results.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Demo with filtered results</em></p>
<p><a target="_blank" href="https://github.com/colbyfayock/my-futurama-characters/commit/adbf30a872fa134cfca4e142ba479877b9665e9a">Follow along with the commit!</a></p>
<h2 id="heading-step-3-setting-up-dynamic-search-based-on-user-input">Step 3: Setting up dynamic search based on user input</h2>
<p>Now that we have a hard-coded search working, we want someone to actually be able to use the search input to search!</p>
<p>To achieve this, we're going to use the <code>useState</code> hook and listen for changes to the <code>input</code> field, which will dynamically create a search for our data.</p>
<p>First, import the <code>useState</code> hook from React:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> React, { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
</code></pre>
<p>Next, let's use that hook to create a state instance:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> [query, updateQuery] = useState(<span class="hljs-string">''</span>);
</code></pre>
<p>Here, we're creating a new state of <code>query</code> that we can update with <code>updateQuery</code> that defaults to an empty string (<code>''</code>).</p>
<p>With that, let's tell our search input to use that <code>query</code> value as it's value:</p>
<pre><code class="lang-jsx">&lt;input type=<span class="hljs-string">"text"</span> value={query} /&gt;
</code></pre>
<p>At this point, nothing should be different, as we are using a blank query.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/futurama-character-search-filtered-results.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Demo with filtered results - nothing changed</em></p>
<p>Now let's add an event handler to our input that we can use to update our state:</p>
<pre><code class="lang-jsx">&lt;input type=<span class="hljs-string">"text"</span> value={query} onChange={onSearch} /&gt;
</code></pre>
<p>And we'll want to create that function so we can use it:</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">onSearch</span>(<span class="hljs-params">{ currentTarget }</span>) </span>{
  updateQuery(currentTarget.value);
}
</code></pre>
<p>This will update our <code>query</code> with the input's value any time it changes.</p>
<p>Now that our <code>query</code>  will have what we want to search for, we can update our search instance:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> results = fuse.search(query);
</code></pre>
<p>And now if you reload the page, it's blank! ?</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/futurama-character-search-no-results.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Demo with no results</em></p>
<p>That's because by default, Fuse sees our empty query and doesn't match it to anything. If we now search for something like <code>slurms</code>, we can see our search dynamically update with results!</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/futurama-character-search-results.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Demo with results for "slurms"</em></p>
<p>If we wanted to fix this though so that all of our results show when there's no query, we can do so with an <code>if</code> statement or in my example, a ternary, that will show all of the characters if there is no query:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> characterResults = query ? results.map(<span class="hljs-function"><span class="hljs-params">character</span> =&gt;</span> character.item) : characters;
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/futurama-character-search-demo.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Demo with all results</em></p>
<p>And with that, we have our basic search!</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/futurama-character-search-results-query.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Demo with filtered results for "zoidberg"</em></p>
<p><a target="_blank" href="https://github.com/colbyfayock/my-futurama-characters/commit/1b8918fc56f31517686a6c73f1969787728736ac">Follow along with the commit!</a></p>
<h2 id="heading-what-can-i-do-next">What can I do next?</h2>
<h3 id="heading-tuning-your-search">Tuning your search</h3>
<p>Fuse.js comes with a lot of options that you can use to tune your search to however you'd like. Want to only show confident results? Use the <code>threshold</code> option! Want case sensitive queries? Use the <code>isCaseSensitive</code> option!</p>
<p><a target="_blank" href="https://fusejs.io/api/options.html">https://fusejs.io/api/options.html</a></p>
<h3 id="heading-setting-the-default-query-with-url-parameters">Setting the default query with URL parameters</h3>
<p>Sometimes you want someone to be able to link to a particular set of results. To do this, we might want to be able to add a new URL parameter like <code>?q=bender</code>.</p>
<p>To make this work, you can grab that URL parameter with javascript and use that value to set our <code>query</code> state.</p>
<h2 id="heading-join-the-conversation">Join the conversation!</h2>
<div class="embed-wrapper">
        <blockquote class="twitter-tweet">
          <a href="https://twitter.com/colbyfayock/status/1265298322891378688"></a>
        </blockquote>
        <script defer="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></div>
<div id="colbyfayock-author-card">
  <p>
    <a href="https://twitter.com/colbyfayock">
      <img src="https://res.cloudinary.com/fay/image/upload/w_2000,h_400,c_fill,q_auto,f_auto/w_1020,c_fit,co_rgb:007079,g_north_west,x_635,y_70,l_text:Source%20Sans%20Pro_64_line_spacing_-10_bold:Colby%20Fayock/w_1020,c_fit,co_rgb:383f43,g_west,x_635,y_6,l_text:Source%20Sans%20Pro_44_line_spacing_0_normal:Follow%20me%20for%20more%20JavaScript%252c%20UX%252c%20and%20other%20interesting%20things!/w_1020,c_fit,co_rgb:007079,g_south_west,x_635,y_70,l_text:Source%20Sans%20Pro_40_line_spacing_-10_semibold:colbyfayock.com/w_300,c_fit,co_rgb:7c848a,g_north_west,x_1725,y_68,l_text:Source%20Sans%20Pro_40_line_spacing_-10_normal:colbyfayock/w_300,c_fit,co_rgb:7c848a,g_north_west,x_1725,y_145,l_text:Source%20Sans%20Pro_40_line_spacing_-10_normal:colbyfayock/w_300,c_fit,co_rgb:7c848a,g_north_west,x_1725,y_222,l_text:Source%20Sans%20Pro_40_line_spacing_-10_normal:colbyfayock/w_300,c_fit,co_rgb:7c848a,g_north_west,x_1725,y_295,l_text:Source%20Sans%20Pro_40_line_spacing_-10_normal:colbyfayock/v1/social-footer-card" alt="Follow me for more Javascript, UX, and other interesting things!" width="2000" height="400" loading="lazy">
    </a>
  </p>
  <ul>
    <li>
      <a href="https://twitter.com/colbyfayock">? Follow Me On Twitter</a>
    </li>
    <li>
      <a href="https://youtube.com/colbyfayock">?️ Subscribe To My Youtube</a>
    </li>
    <li>
      <a href="https://www.colbyfayock.com/newsletter/">✉️ Sign Up For My Newsletter</a>
    </li>
  </ul>
</div>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ What is Tailwind CSS and How Can I Add it to my Website or React App? ]]>
                </title>
                <description>
                    <![CDATA[ CSS is a technology that can be your best or worst friend. While it's incredibly flexible and can produce what seems like magic, without the proper care and attention, it can become hard to manage like any other code.  How can Tailwind CSS help us to... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/what-is-tailwind-css-and-how-can-i-add-it-to-my-website-or-react-app/</link>
                <guid isPermaLink="false">66b8e39e0196ee8e3efce546</guid>
                
                    <category>
                        <![CDATA[ CSS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ CSS Framework ]]>
                    </category>
                
                    <category>
                        <![CDATA[ CSS3 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Developer Tools ]]>
                    </category>
                
                    <category>
                        <![CDATA[ framework ]]>
                    </category>
                
                    <category>
                        <![CDATA[ front end ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Front-end Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ frontend ]]>
                    </category>
                
                    <category>
                        <![CDATA[ HTML ]]>
                    </category>
                
                    <category>
                        <![CDATA[ PostCSS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ tailwind ]]>
                    </category>
                
                    <category>
                        <![CDATA[ tools ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Design ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Colby Fayock ]]>
                </dc:creator>
                <pubDate>Tue, 19 May 2020 14:45:00 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2020/05/tailwind-1.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>CSS is a technology that can be your best or worst friend. While it's incredibly flexible and can produce what seems like magic, without the proper care and attention, it can become hard to manage like any other code. </p>
<p>How can Tailwind CSS help us to take control of our styles?</p>
<ul>
<li><a class="post-section-overview" href="#heading-what-is-tailwind">What is Tailwind?</a></li>
<li><a class="post-section-overview" href="#heading-so-what-makes-tailwind-great">So what makes Tailwind great?</a></li>
<li><a class="post-section-overview" href="#heading-part-1-adding-tailwind-css-to-a-static-html-page">Part 1: Adding Tailwind CSS to a static HTML page</a></li>
<li><a class="post-section-overview" href="#heading-part-2-adding-tailwind-css-to-a-react-app">Part 2: Adding Tailwind CSS to a React app</a></li>
</ul>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/7KeZcRMltP0" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<h2 id="heading-what-is-tailwind">What is Tailwind?</h2>
<p><a target="_blank" href="https://tailwindcss.com/">Tailwind CSS</a> is a "utility-first" CSS framework that provides a deep catalog of CSS classes and tools that lets you easily get started styling your website or application.</p>
<p>The underlying goal is that as you're building your project, you don't need to deal with cascading styles and worrying about how to override that 10-selector pileup that's been haunting your app for the last 2 years.</p>
<h2 id="heading-so-what-makes-tailwind-great">So what makes Tailwind great?</h2>
<p>Taildwind's solution is to provide a wide variety of CSS classes that each have their own focused use. Instead of a class called <code>.btn</code> that is created with a bunch of CSS attributes directly, in Tailwind, you would either apply a bunch of classes like <code>bg-blue-500 py-2 px-4 rounded</code> to the button element or build a <code>.btn</code> class by <a target="_blank" href="https://tailwindcss.com/docs/functions-and-directives/#apply">applying</a> those utility class to that selector.</p>
<p>While Tailwind has a lot more going for it, we're going to focus on these basics for this tutorial, so let's dig in!</p>
<h2 id="heading-part-1-adding-tailwind-css-to-a-static-html-page">Part 1: Adding Tailwind CSS to a static HTML page</h2>
<p>We're going to start off by applying Tailwind CSS straight to a static HTML page. The hope is that by focusing on Tailwind and not the app, we can get a better understanding of what's actually happening with the framework.</p>
<h3 id="heading-step-1-creating-a-new-page">Step 1: Creating a new page</h3>
<p>You can get started by simply creating a new HTML file. For the content, you can use whatever you want, but I'm going to use <a target="_blank" href="http://fillerama.io/">fillerama.io</a> so the filler content is a bit more fun.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/html-page-with-content.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>New HTML page with content</em></p>
<p>If you want to simplify this step, you can just <a target="_blank" href="https://github.com/colbyfayock/my-tailwind-static/commit/c7db11899c9cd193cdd666fd228cfaefe75623f2#diff-eacf331f0ffc35d4b482f1d15a887d3b">copy the file I created</a> to get started.</p>
<p><a target="_blank" href="https://github.com/colbyfayock/my-tailwind-static/commit/c7db11899c9cd193cdd666fd228cfaefe75623f2">Follow along with the commit!</a></p>
<h3 id="heading-step-2-adding-tailwind-css-via-cdn">Step 2: Adding Tailwind CSS via CDN</h3>
<p>Tailwind typically recommends that you install through <a target="_blank" href="https://www.npmjs.com/package/tailwindcss">npm</a> to get the full functionality, but again, we're just trying to understand how this works first.</p>
<p>So let's add a link to the CDN file in the <code>&lt;head&gt;</code> of our document:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css"</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span>&gt;</span>
</code></pre>
<p>Once you save and reload the page, the first thing you'll notice is that all of the styles were stripped!</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/html-page-tailwind-base.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>HTML page with the Tailwind CSS base</em></p>
<p>This is expected. Tailwind includes a set of <a target="_blank" href="https://tailwindcss.com/docs/preflight">preflight styles</a> to fix cross-browser inconsistencies. For one, they include the popular <a target="_blank" href="https://github.com/necolas/normalize.css/">normalize.css</a> which they build upon with their own styles.</p>
<p>But we're going to learn how to use Tailwind to add back our styles and set things up how we want!</p>
<p><a target="_blank" href="https://github.com/colbyfayock/my-tailwind-static/commit/b431b75cee0a03154a70b194b6dfcf028bc65942">Follow along with the commit!</a></p>
<h3 id="heading-step-3-using-tailwind-css-to-add-styles-to-your-page">Step 3: Using Tailwind CSS to add styles to your page</h3>
<p>Now that we have Tailwind installed, we've added the ability to make use of their huge library of utility classes that we'll now use to add styles back to our page.</p>
<p>Let's start off by adding some margin to all of our paragraphs (<code>&lt;p&gt;</code>) and our list elements (<code>&lt;ol&gt;</code>, <code>&lt;ul&gt;</code>). We can do this by adding the <code>.my-5</code> class to each element like so:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"my-5"</span>&gt;</span>
  Bender, quit destroying the universe! Yeah, I do that with my stupidness. I never loved you. Moving along…
  Belligerent and numerous.
<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
</code></pre>
<p>The class name follows a pattern that you'll notice with the rest of the utility classes – <code>.my-5</code> stands for margin (m) applied to the y-axis (y) with a value of 5 which in Tailwind's case, it uses <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Values_and_units">rem</a>, so the value is 5rem.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/html-page-paragraph-styles.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>HTML page with basic paragraph styles</em></p>
<p>Next, let's make our headers look like actual headers. Starting with our <code>h1</code> tag, let's add:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"text-2xl font-bold mt-8 mb-5"</span>&gt;</span>
</code></pre>
<p>Here's what's happening:</p>
<ul>
<li><code>text-2xl</code>: set the text size (font-size) to 2xl. In Tailwind, that 2xl will equate to 1.5rem</li>
<li><code>font-bold</code>: set the weight of the text (font-weight) to bold</li>
<li><code>mt-8</code>: Similar to <code>my-5</code>, this will set the margin top (t) to 8rem</li>
<li><code>mb-5</code>: And this will set the margin bottom (b) to 5rem</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/html-page-header-styles.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>HTML page with styled H1</em></p>
<p>With those classes added to the <code>h1</code>, let's apply those same exact classes to the rest of our header elements, but as we go down the list, reduce the size of the font size, so it will look like:</p>
<ul>
<li>h2: <code>text-xl</code></li>
<li>h3: <code>text-lg</code></li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/html-page-all-headers-style.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>HTML page with all headers styled</em></p>
<p>Now let's make our list elements look like lists. Starting with our unordered list (<code>&lt;ul&gt;</code>), let's add these classes:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">ul</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"list-disc list-inside my-5 pl-2"</span>&gt;</span>
</code></pre>
<p>Here's what we're adding:</p>
<ul>
<li><code>list-disc</code>: set the list-style-stype to disc (the markers on each line item)</li>
<li><code>list-inside</code>: sets the position of the list markers using  relative to the list items and the list itself with list-style-position to inside</li>
<li><code>my-5</code>: set the margin of the y axis to 5rem</li>
<li><code>pl-2</code>: set the left padding to 2rem</li>
</ul>
<p>Then we can apply the exact same classes to our ordered list (<code>&lt;ol&gt;</code>), except instead of <code>list-disc</code>, we want to change our style type to <code>list-decimal</code>, so that we can see numbers given it's an ordered list.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">ol</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"list-decimal list-inside my-5 pl-2"</span>&gt;</span>
</code></pre>
<p>And we have our lists!</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/html-page-styled-lists.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>HTML page with styled lists</em></p>
<p>Finally, let's make our content a little easier to read by setting a max width and centering the content. On the <code>&lt;body&gt;</code> tag, add the following:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">body</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"max-w-4xl mx-auto"</span>&gt;</span>
</code></pre>
<p>/Note: Typically you wouldn't want to apply these classes to the <code>&lt;body&gt;</code> itself, rather, you can wrap all of your content with a <code>&lt;main&gt;</code> tag, but since we're just trying to get an idea of how this works, we'll roll with this. Feel free to add the <code>&lt;main&gt;</code> tag with those classes instead if you prefer!/</p>
<p>And with that, we have our page!</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/html-page-content-centered.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>HTML page with centered content</em></p>
<p><a target="_blank" href="https://github.com/colbyfayock/my-tailwind-static/commit/06fd719c98d17e2242b61ec2ab7034436c1c2ba6">Follow along with the commit!</a></p>
<h3 id="heading-step-4-adding-a-button-and-other-components">Step 4: Adding a button and other components</h3>
<p>For the last part of our static example, let's add a button.</p>
<p>The trick with Tailwind, is they intentionally don't provide pre-baked component classes with the idea being that likely people would need to override these components anyways to make them look how they wanted.</p>
<p>So that means, we're going to have to create our own using the utility classes!</p>
<p>First, let's add a new button. Somewhere on the page, add the following snippet. I'm going to add it right below the first paragraph (<code>&lt;p&gt;</code>) tag:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">button</span>&gt;</span>Party with Slurm!<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/html-page-unstyled-button.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>HTML page with unstyled button</em></p>
<p>You'll notice just like all of the other elements, that it's unstyled, however, if you try clicking it, you'll notice it still has the click actions. So let's make it look like a button.</p>
<p>Let's add the following classes:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"text-white font-bold bg-purple-700 hover:bg-purple-800 py-2 px-4 rounded"</span>&gt;</span>
  Party with Slurm!
<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
</code></pre>
<p>Here's a breakdown of what's happening:</p>
<ul>
<li><code>text-white</code>: we're setting our text color to white</li>
<li><code>font-bold</code>: set the weight of the text to bold (font-weight)</li>
<li><code>bg-purple-700</code>: set the background color of the button to purple with a shade of 700. The 700 is relative to the other colors defined as purple, you can find these values on their <a target="_blank" href="https://tailwindcss.com/docs/customizing-colors#default-color-palette">palette documentation page</a></li>
<li><code>hover:bg-purple-800</code>: when someone hovers over the button, set the background color to purple shade 800. Tailwind provides these helper classes that allow us to easily define interactive stiles with things like <a target="_blank" href="https://tailwindcss.com/course/hover-focus-and-active-styles/">hover, focus, and active modifiers</a></li>
<li><code>py-2</code>: set the padding of the y-axis to 2rem</li>
<li><code>px-4</code>: set the padding of the  x-axis to 4rem</li>
<li><code>rounded</code>: round the corners of the element by setting the border radius. With tailwind, it sets the border-radius value to .25rem</li>
</ul>
<p>And with all of that, we have our button!</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/html-page-styled-button.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>HTML page with a styled button</em></p>
<p>You can apply this methodology to any other component that you'd like to build. Though it's a manual process, we'll find out how we can make this process easier when building in more dynamic projects like those based on React.</p>
<p><a target="_blank" href="https://github.com/colbyfayock/my-tailwind-static/commit/09312336dce316a75e8007d6c935133490f16c25">Follow along with the commit!</a></p>
<h2 id="heading-part-2-adding-tailwind-css-to-a-react-app">Part 2: Adding Tailwind CSS to a React app</h2>
<p>For more of a real-world use case, we're going to add Tailwind CSS to an app created with <a target="_blank" href="https://reactjs.org/docs/create-a-new-react-app.html">Create React App</a>.</p>
<p>First, we'll walk through the steps you need to take to add tailwind to your project using a fresh install of Create React App, then we'll use our content from our last example to recreate it in React.</p>
<h3 id="heading-step-1-spinning-up-a-new-react-app">Step 1: Spinning up a new React app</h3>
<p>I'm not going to detail this step out too much. The gist is we'll bootstrap a new React app using Create React App.</p>
<p>To get started, you can follow along <a target="_blank" href="https://reactjs.org/docs/create-a-new-react-app.html">with the directions</a> from the official React documentation:</p>
<p><a target="_blank" href="https://reactjs.org/docs/create-a-new-react-app.html">https://reactjs.org/docs/create-a-new-react-app.html</a></p>
<p>And once you start your development server, you should now see an app!</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/create-react-app-starting-page.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Create React App starting page</em></p>
<p>Finally, let's migrate all of our old content to our app. To do this, copy everything inside of the <code>&lt;body&gt;</code> tag of our static example and paste it inside of the wrapper <code>&lt;div className="App"&gt;</code> in the new React project.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/code-migrating-content.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Migrating code to React app</em></p>
<p>Next, change all <code>class="</code> attributes from the content we pasted in to <code>className="</code> so that it's using proper React attributes:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/code-fixing-class-attribute.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Fixing class attribute in content</em></p>
<p>And lastly, replace the className <code>App</code> on our wrapper <code>&lt;div&gt;</code> to the classes we used on our static <code>&lt;body&gt;</code>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/code-wrapper-styles.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Adding wrapper styles to the app</em></p>
<p>Once you save your changes and spin back up your server, it will look deceivingly okay.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/react-app-basic-content.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>React app with basic content</em></p>
<p>React includes some basic styles itself, so while it looks okay, we're not actually using Tailwind yet. So let's get started by installing it!</p>
<p><a target="_blank" href="https://github.com/colbyfayock/my-tailwind-dynamic/commit/57993883c77739f71072bcc02ed2398543efc2fd">Follow along with the commit!</a></p>
<h3 id="heading-step-2-installing-tailwind-in-your-react-app">Step 2: Installing Tailwind in your React app</h3>
<p>There are a few steps we'll need to go through in order to get Tailwind up and running on our app. Make sure you follow these steps carefully to ensure it's properly configured.</p>
<p>First, let's add our dependencies:</p>
<pre><code>yarn add tailwindcss postcss-cli autoprefixer
# or
npm install tailwindcss postcss-cli autoprefixer
</code></pre><p><a target="_blank" href="https://tailwindcss.com/docs/installation#4-process-your-css-with-tailwind">Per Tailwind's documentation</a>, we need to be able to process our styles so that they can be properly added to our pipeline. So in the above, we're adding:</p>
<ul>
<li><a target="_blank" href="https://tailwindcss.com/">tailwindcss</a>: the core Tailwind package</li>
<li><a target="_blank" href="https://github.com/postcss/postcss">postcss-cli</a>: Create React App already uses postcss, but we need to configure Tailwind to be part of that build process and run it's own processing</li>
<li><a target="_blank" href="https://github.com/postcss/autoprefixer">autoprefixer</a>: Tailwind doesn't include vendor prefixes, so we want to add autoprefixer to handle this for us. This runs as part of our postcss configuration</li>
</ul>
<p>We're also going to add two dev dependencies that make it easier to work with our code:</p>
<pre><code>yarn add concurrently chokidar-cli -D
# or
npm install concurrently chokidar-cli --save-dev
</code></pre><ul>
<li><a target="_blank" href="https://github.com/kimmobrunfeldt/concurrently">concurrently</a>: a package that lets us set up the ability to run multiple commands at once. This is helpful since we'll need to watch both the styles and React app itself.</li>
<li><a target="_blank" href="https://github.com/kimmobrunfeldt/chokidar-cli">chokidar-cli</a>: let's us watch files and run a command when changed. We'll use this to watch our CSS file and run the build process of the CSS on cahnge</li>
</ul>
<p>Next, let's configure postcss, so create a new file in the root of your project called <code>postcss.config.js</code> and include the following:</p>
<pre><code class="lang-js"><span class="hljs-comment">// Inside postcss.config.js</span>
<span class="hljs-built_in">module</span>.exports = {
    <span class="hljs-attr">plugins</span>: [
        <span class="hljs-built_in">require</span>(<span class="hljs-string">'tailwindcss'</span>),
        <span class="hljs-built_in">require</span>(<span class="hljs-string">'autoprefixer'</span>)
    ],
};
</code></pre>
<p>This will add the Tailwindcss and Autoprefixer plugins to our postcss config.</p>
<p>With our configuration, we need to include it as part of the build and watch processes. Inside <code>package.json</code>, add the following to definitions to your <code>scripts</code> property:</p>
<pre><code class="lang-json"><span class="hljs-string">"build:css"</span>: <span class="hljs-string">"tailwind build src/App.css -o src/index.css"</span>,
<span class="hljs-string">"watch:css"</span>: <span class="hljs-string">"chokidar 'src/App.css' -c 'npm run build:css'"</span>,
</code></pre>
<p>Additionally, modify the <code>start</code> and <code>build</code> scripts to now include those commands:</p>
<pre><code class="lang-json"><span class="hljs-string">"start"</span>: <span class="hljs-string">"concurrently -n Tailwind,React 'npm run watch:css' 'react-scripts start'"</span>,
<span class="hljs-string">"build"</span>: <span class="hljs-string">"npm run build:css &amp;&amp; react-scripts build"</span>,
</code></pre>
<p>With our configuration ready to go, let's try our styles back to where they were when we left off from the static example.</p>
<p>Inside the <code>App.css</code> file, replace the entire content with:</p>
<pre><code class="lang-css"><span class="hljs-keyword">@tailwind</span> base;
<span class="hljs-keyword">@tailwind</span> components;
<span class="hljs-keyword">@tailwind</span> utilities;
</code></pre>
<p>This is going to import Tailwind's base styles, components, and utility classes that allow Tailwind to work as you would expect it to.</p>
<p>We can also remove the <code>App.css</code> import from our <code>App.js</code> file because it's now getting injected directly into our <code>index.css</code> file. So remove this line:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> <span class="hljs-string">'./App.css'</span>;
</code></pre>
<p>Once you're done, you can start back up your development server! If it was already started, make sure to restart it so all of the configuration changes take effect.</p>
<p>And now the page should look exactly like it did in our static example!</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/react-app-with-styled-content.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>React app with content styled</em></p>
<p><a target="_blank" href="https://github.com/colbyfayock/my-tailwind-dynamic/commit/5f50cc218ef58f469dad7f09bdad31f36b58a896">Follow along with the commit!</a></p>
<h3 id="heading-step-3-creating-a-new-button-component-class-with-tailwind">Step 3: Creating a new button component class with Tailwind</h3>
<p>Tailwind doesn't ship with prebaked component classes, but it does make it easy to create them!</p>
<p>We're going to use our button that we already created as an example of creating a new component. We'll create a new class <code>btn</code> as well as a color modifier <code>btn-purple</code> to accomplish this.</p>
<p>The first thing we'll want to do is open up our App.css file where we'll create our new class. Inside that file, let's add:</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.btn</span> {
  @apply font-bold py-2 px-4 rounded;
}
</code></pre>
<p>If you remember from our HTML, we're already including those same classes to our <code>&lt;button&gt;</code> element.  Tailwind let's us "apply" or include the styles that make up these utility classes to another class, in this case, the <code>.btn</code> class.</p>
<p>And now that we're creating that class, let's apply it to our button:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"btn text-white bg-purple-700 hover:bg-purple-800"</span>&gt;</span>
  Party with Slurm!
<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
</code></pre>
<p>And if we open up our page, we can see our button still looks the same. If we inspect the element, we can see our new <code>.btn</code> class generated with those styles.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/react-tailwind-button-class.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>.btn class in a React app with Tailwind</em></p>
<p>Next, let's create a color modifier. Similar to what we just did, we're going to add the following rules:</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.btn-purple</span> {
  @apply bg-purple-700 text-white;
}

<span class="hljs-selector-class">.btn-purple</span><span class="hljs-selector-pseudo">:hover</span> {
  @apply bg-purple-800;
}
</code></pre>
<p>Here, we're adding our background color and our text color to our button class. We're also applying a darker button color when someone hovers over the button.</p>
<p>We'll also want to update our HTML button to include our new class and remove the ones we just applied:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"btn btn-purple"</span>&gt;</span>
  Party with Slurm!
<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
</code></pre>
<p>And with that change, we can still see that nothing has changed and we have our new button class!</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/react-tailwind-styled-button.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Styled button in React with Tailwind</em></p>
<p><a target="_blank" href="https://github.com/colbyfayock/my-tailwind-dynamic/commit/7a76e8a4583b0a4c523ea902d73e889c7b86f437">Follow along with the commit!</a></p>
<h2 id="heading-applying-these-concepts-to-more-components">Applying these concepts to more components</h2>
<p>Through this walkthrough, we learned how to create a new component class using the Tailwind apply directive. This allowed us to create reusable classes that represent a component like a button.</p>
<p>We can apply this to any number of components in our design system. For instance, if we wanted to always show our lists the way we set them up here, we could create a <code>.list-ul</code> class that represented an unordered list with the Tailwind utilities <code>list-disc list-inside my-5 pl-2</code> applied.</p>
<h2 id="heading-what-tips-and-tricks-do-you-like-to-use-with-tailwind">What tips and tricks do you like to use with Tailwind?</h2>
<p>Share with me on <a target="_blank" href="https://twitter.com/colbyfayock">Twitter</a>!</p>
<div id="colbyfayock-author-card">
  <p>
    <a href="https://twitter.com/colbyfayock">
      <img src="https://res.cloudinary.com/fay/image/upload/w_2000,h_400,c_fill,q_auto,f_auto/w_1020,c_fit,co_rgb:007079,g_north_west,x_635,y_70,l_text:Source%20Sans%20Pro_64_line_spacing_-10_bold:Colby%20Fayock/w_1020,c_fit,co_rgb:383f43,g_west,x_635,y_6,l_text:Source%20Sans%20Pro_44_line_spacing_0_normal:Follow%20me%20for%20more%20JavaScript%252c%20UX%252c%20and%20other%20interesting%20things!/w_1020,c_fit,co_rgb:007079,g_south_west,x_635,y_70,l_text:Source%20Sans%20Pro_40_line_spacing_-10_semibold:colbyfayock.com/w_300,c_fit,co_rgb:7c848a,g_north_west,x_1725,y_68,l_text:Source%20Sans%20Pro_40_line_spacing_-10_normal:colbyfayock/w_300,c_fit,co_rgb:7c848a,g_north_west,x_1725,y_145,l_text:Source%20Sans%20Pro_40_line_spacing_-10_normal:colbyfayock/w_300,c_fit,co_rgb:7c848a,g_north_west,x_1725,y_222,l_text:Source%20Sans%20Pro_40_line_spacing_-10_normal:colbyfayock/w_300,c_fit,co_rgb:7c848a,g_north_west,x_1725,y_295,l_text:Source%20Sans%20Pro_40_line_spacing_-10_normal:colbyfayock/v1/social-footer-card" alt="Follow me for more Javascript, UX, and other interesting things!" width="2000" height="400" loading="lazy">
    </a>
  </p>
  <ul>
    <li>
      <a href="https://twitter.com/colbyfayock">? Follow Me On Twitter</a>
    </li>
    <li>
      <a href="https://youtube.com/colbyfayock">?️ Subscribe To My Youtube</a>
    </li>
    <li>
      <a href="https://www.colbyfayock.com/newsletter/">✉️ Sign Up For My Newsletter</a>
    </li>
  </ul>
</div>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Use Pure CSS to Create a Beautiful Loading Animation for your App ]]>
                </title>
                <description>
                    <![CDATA[ If you've been around the internet lately, you've most likely seen a nice subtle loading animation that fills page content before gracefully loading in.  Some of the social giants like Facebook even use this approach to give page loading a better exp... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-use-css-to-create-a-beautiful-loading-animation-for-your-app/</link>
                <guid isPermaLink="false">66b8e3730cedc1f2a4f7069f</guid>
                
                    <category>
                        <![CDATA[ animation ]]>
                    </category>
                
                    <category>
                        <![CDATA[ animations ]]>
                    </category>
                
                    <category>
                        <![CDATA[ CSS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ CSS3 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ front end ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Front-end Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ frontend ]]>
                    </category>
                
                    <category>
                        <![CDATA[ HTML ]]>
                    </category>
                
                    <category>
                        <![CDATA[ HTML5 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Inspiration ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Pure CSS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Applications ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Design ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Colby Fayock ]]>
                </dc:creator>
                <pubDate>Tue, 05 May 2020 14:45:00 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2020/05/loading-animation.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>If you've been around the internet lately, you've most likely seen a nice subtle loading animation that fills page content before gracefully loading in. </p>
<p>Some of the social giants like Facebook even use this approach to give page loading a better experience. How can we do that with just some simple CSS?</p>
<ul>
<li><a class="post-section-overview" href="#heading-what-are-we-going-to-build">What are we going to build?</a></li>
<li><a class="post-section-overview" href="#heading-just-want-the-snippet">Just want the snippet?</a></li>
<li><a class="post-section-overview" href="#heading-part-1-creating-our-loading-animation">Part 1: Creating our loading animation</a></li>
<li><a class="post-section-overview" href="#heading-part-2-using-our-loading-animation-in-a-dynamic-app">Part 2: Using our loading animation in a dynamic app</a></li>
</ul>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/auyZWWjXJCo" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<h2 id="heading-what-are-we-going-to-build">What are we going to build?</h2>
<p>We're going to create a loading animation using a CSS class that you can apply to pretty much any element you want (within reason).</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/loading-animation.gif" alt="Image" width="600" height="400" loading="lazy">
<em>Loading animation preview</em></p>
<p>This gives you great flexibility to use it and makes the solution nice and simple with only CSS.</p>
<p>While the snippet is pretty small and you could just copy and paste it, I'll walk you through what's happening and an example of using it dynamically when loading data.</p>
<h2 id="heading-just-want-the-snippet">Just want the snippet?</h2>
<p>You can grab it here!</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="d155418975d1e0e04b2805e285296033">
        <script src="https://gist.github.com/colbyfayock/d155418975d1e0e04b2805e285296033.js"></script></div><h2 id="heading-do-i-need-to-know-how-to-animate-before-this-tutorial">Do I need to know how to animate before this tutorial?</h2>
<p>No! We'll walk through in detail exactly what you need to do. In fact, the animation in this tutorial is relatively simple, so let's dig in!</p>
<h2 id="heading-part-1-creating-our-loading-animation">Part 1: Creating our loading animation</h2>
<p>This first part is going to focus on getting the loading animation together and seeing it on a static HTML website. The goal is to walk through actually creating the snippet. We'll only use HTML and CSS for this part.</p>
<h3 id="heading-step-1-creating-some-sample-content">Step 1: Creating some sample content</h3>
<p>To get started, we'll want a little sample content. There's really no restrictions here, you can create this with basic HTML and CSS or you can add this to your Create React App!</p>
<p>For the walk through, I'm going to use HTML and CSS with a few examples of content that will allow us to see this in effect.</p>
<p>To get started, create a new HTML file. Inside that HTML file, fill it with some content that will give us the ability to play with our animation. I'm going to use <a target="_blank" href="http://fillerama.io/">fillerama</a> which uses lines from my favorite TV show <a target="_blank" href="https://en.wikipedia.org/wiki/Futurama">Futurama</a>!</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/static-html-css-website-fillerama.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Static HTML &amp; CSS webpage with content from fillerama.io</em></p>
<p>If you're going to follow along with me, here's what my project looks like:</p>
<pre><code>my-css-loading-animation-<span class="hljs-keyword">static</span>
- index.html
- main.css
</code></pre><p><a target="_blank" href="https://github.com/colbyfayock/my-css-loading-animation-static/commit/9aa7925f7048fa1b73fef74d0d56380c29fc5d73">Follow along with the commit!</a></p>
<h3 id="heading-step-2-starting-with-a-foundation-loading-class">Step 2: Starting with a foundation loading class</h3>
<p>For our foundation, let's create a new CSS class. Inside our CSS file, let's add:</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.loading</span> {
  <span class="hljs-attribute">background</span>: <span class="hljs-number">#eceff1</span>;
}
</code></pre>
<p>With that class, let's add it to a few or all of our elements. I added it to a few paragraphs, headings, and lists.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"loading"</span>&gt;</span>For example...
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/static-html-css-gray-background.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Static HTML &amp; CSS webpage with a gray background for the content</em></p>
<p>That gives us a basic background, but we'd probably want to hide that text. When it's loading, we won't have that text yet, so most likely we would want to use filler text or a fixed height. Either way, we can set the color to transparent:</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.loading</span> {
  <span class="hljs-attribute">color</span>: transparent;
  <span class="hljs-attribute">background</span>: <span class="hljs-number">#eceff1</span>;
}
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/static-html-css-gray-background-hidden-text.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Static HTML &amp; CSS webpage with a gray background and transparent color for the content</em></p>
<p>If you notice with list elements, whether you apply the class to the top level list element (<code>&lt;ol&gt;</code> or <code>&lt;ul&gt;</code>) vs the list item itself (<code>&lt;li&gt;</code>), it looks like one big block. If we add a little margin to the bottom of all list elements, we can see a different in how they display:</p>
<pre><code class="lang-css"><span class="hljs-selector-tag">li</span> {
  <span class="hljs-attribute">margin-bottom</span>: .<span class="hljs-number">5em</span>;
}
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/static-html-css-gray-background-different-lists.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Style difference between applying to the top level list or the list items</em></p>
<p>And now it's starting to come together, but it kind of just looks like placeholders. So let's animate this to look like it's actually loading.</p>
<p><a target="_blank" href="https://github.com/colbyfayock/my-css-loading-animation-static/commit/f68cdef36be11311a5cc11a1d39e52ea7e7bb48d">Follow along with the commit!</a></p>
<h3 id="heading-step-3-styling-and-animating-our-loading-class">Step 3: Styling and animating our loading class</h3>
<p>Before actually animating our class, we need something to animate, so let's add a gradient to our <code>.loading</code> class:</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.loading</span> {
  <span class="hljs-attribute">color</span>: transparent;
  <span class="hljs-attribute">background</span>: <span class="hljs-built_in">linear-gradient</span>(<span class="hljs-number">100deg</span>, #eceff1 <span class="hljs-number">30%</span>, #f6f7f8 <span class="hljs-number">50%</span>, #eceff1 <span class="hljs-number">70%</span>);
}
</code></pre>
<p>This is saying that we want a <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/linear-gradient">linear gradient</a> that's tilted at 100 degrees, where we start with <code>#eceff1</code> and fade to <code>#f6f7f8</code> at 30% and back to <code>#eceff1</code> at 70%;</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/static-html-css-gray-background-subtle-gradient.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Subtle gradient background that might look like a glare</em></p>
<p>It's hard to see initially when it's still, it might just look like a glare on your computer! If you'd like to see it before moving on, feel free to play with the colors above to see the gradient.</p>
<p>Now that we have something to animate, we'll first need to create a <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/@keyframes">keyframes</a> rule:</p>
<pre><code class="lang-css"><span class="hljs-keyword">@keyframes</span> loading {
  0% {
    <span class="hljs-attribute">background-position</span>: <span class="hljs-number">100%</span> <span class="hljs-number">50%</span>;
  }
  100% {
    <span class="hljs-attribute">background-position</span>: <span class="hljs-number">0</span> <span class="hljs-number">50%</span>;
  }
}
</code></pre>
<p>This rule when applied will change the background position from starting at 100% of the x-axis to 0% of the x-axis.</p>
<p>With the rule, we can add our <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Animations/Using_CSS_animations">animation</a> property to our <code>.loading</code> class:</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.loading</span> {
  <span class="hljs-attribute">color</span>: transparent;
  <span class="hljs-attribute">background</span>: <span class="hljs-built_in">linear-gradient</span>(<span class="hljs-number">100deg</span>, #eceff1 <span class="hljs-number">30%</span>, #f6f7f8 <span class="hljs-number">50%</span>, #eceff1 <span class="hljs-number">70%</span>);
  <span class="hljs-attribute">animation</span>: loading <span class="hljs-number">1.2s</span> ease-in-out infinite;
}
</code></pre>
<p>Our animation line is setting the keyframe to <code>loading</code>, telling it to last for 1.2 seconds, setting the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/animation-timing-function">timing function</a> to <code>ease-in-out</code> to make it smooth, and tell it to loop forever with <code>infinite</code>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/static-html-css-gray-background-subtle-gradient.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>No change – it's not animating</em></p>
<p>If you notice though after saving that, it's still not doing anything. The reason for this is we're setting our gradient from one end of the DOM element to the other, so there's nowhere to move!</p>
<p>So let's try also setting a <code>background-size</code> on our <code>.loading</code> class.</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.loading</span> {
  <span class="hljs-attribute">color</span>: transparent;
  <span class="hljs-attribute">background</span>: <span class="hljs-built_in">linear-gradient</span>(<span class="hljs-number">100deg</span>, #eceff1 <span class="hljs-number">30%</span>, #f6f7f8 <span class="hljs-number">50%</span>, #eceff1 <span class="hljs-number">70%</span>);
  <span class="hljs-attribute">background-size</span>: <span class="hljs-number">400%</span>;
  <span class="hljs-attribute">animation</span>: loading <span class="hljs-number">1.2s</span> ease-in-out infinite;
}
</code></pre>
<p>Now, since our background expands beyond our DOM element (you can't see that part), it has some space to animate with and we get our animation!</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/static-html-css-loading-animation.gif" alt="Image" width="600" height="400" loading="lazy">
<em>Our loading animation!</em></p>
<p><a target="_blank" href="https://github.com/colbyfayock/my-css-loading-animation-static/commit/bc4b5ec955a0906fea032edbbaf90f037f76c91b">Follow along with the commit!</a></p>
<h2 id="heading-part-2-using-our-loading-animation-in-a-dynamic-app">Part 2: Using our loading animation in a dynamic app</h2>
<p>Now that we have our loading animation, let's put it into action with a basic example where we fake a loading state.</p>
<p>The trick with actually using this is typically we don't have the actual content available, so in most cases, we have to fake it.</p>
<p>To show you how we can do this, we're going to build a simple <a target="_blank" href="https://reactjs.org/">React</a> app with <a target="_blank" href="https://nextjs.org/">Next.js</a>.</p>
<h3 id="heading-step-1-creating-an-example-react-app-with-nextjs">Step 1: Creating an example React app with Next.js</h3>
<p>Navigate to the directory you want to create your new project in and run:</p>
<pre><code class="lang-shell">yarn create next-app
# or
npm init next-app
</code></pre>
<p>It will prompt you with some options, particularly a name which will determine the directory the project is created in and the type of project. I'm using <code>my-css-loading-animation-dynamic</code> and the "Default Starter App".</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/nextjs-new-project.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Creating a new project with Next.js</em></p>
<p>Once installed, navigate into your new directory and start up your development server:</p>
<pre><code>cd [directory]
yarn dev
# or 
npm run dev
</code></pre><p><img src="https://www.freecodecamp.org/news/content/images/2020/05/nextjs-starting-dev-server.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Starting development server with Next.js</em></p>
<p>Next, let's replace the content in our <code>pages/index.js</code> file. I'm going to derive the content from the previous example, but we'll create it similar to how we might expect it to come from an API. First, let's add our  content as an object above our return statement:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> content = {
  <span class="hljs-attr">header</span>: <span class="hljs-string">`So, how 'bout them Knicks?`</span>,
  <span class="hljs-attr">intro</span>: <span class="hljs-string">`What are their names? I'm Santa Claus! This opera's as lousy as it is brilliant! Your lyrics lack subtlety. You can't just have your characters announce how they feel. That makes me feel angry! Good news, everyone! I've taught the toaster to feel love!`</span>,
  <span class="hljs-attr">list</span>: [
    <span class="hljs-string">`Yes! In your face, Gandhi!`</span>,
    <span class="hljs-string">`So I really am important? How I feel when I'm drunk is correct?`</span>,
    <span class="hljs-string">`Who are those horrible orange men?`</span>
  ]
}
</code></pre>
<p>To display that content, inside <code>&lt;main&gt;</code>, let's replace the content with:</p>
<pre><code class="lang-jsx">&lt;main&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>{ content.header }<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span></span>
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>{ content.intro }<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span></span>
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>
    { content.list.map((item, i) =&gt; {
      return (
        <span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{i}</span>&gt;</span>{ item }<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
      )
    })}
  <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span></span>
&lt;/main&gt;
</code></pre>
<p>And for the styles, you can copy and paste everything from our Part 1 <code>main.css</code> file into the <code>&lt;style&gt;</code> tags at the bottom of our index page. That will leave us with:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/basic-content-with-nextjs.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Basic content with Next.js</em></p>
<p>With that, we should be back to a similar point we finished at in Part 1 except we're not actively using any of the loading animations yet.</p>
<p><a target="_blank" href="https://github.com/colbyfayock/my-css-loading-animation-dynamic/commit/365e081522ec07b1754bf360a95b0bc373476c95">Follow along with the commit!</a></p>
<h3 id="heading-step-2-faking-loading-data-from-an-api">Step 2: Faking loading data from an API</h3>
<p>The example we're working with is pretty simple. You'd probably see this coming pre-generated statically, but this helps us create a realistic demo that we can test our loading animation with.</p>
<p>To fake our loading state, we're going to use React's <code>useState</code>, <code>useEffect</code>, and an old fashioned <code>setTimeout</code> to preload some "loading" content, and after the <code>setTimeout</code> finishes, update that content with our actual data. In the meantime, we'll know that we're in a loading state with a separate instance of <code>useState</code>.</p>
<p>First, we need to import our dependencies. At the top of our <code>pages/index.js</code> file, add:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">import</span> { useState, useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
</code></pre>
<p>Above our <code>content</code> object, let's add some state:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">const</span> [loadingState, updateLoadingState] = useState(<span class="hljs-literal">true</span>);
<span class="hljs-keyword">const</span> [contentState, updateContentState] = useState({})
</code></pre>
<p>And in our content, we can update the instances to use that state:</p>
<pre><code class="lang-jsx">&lt;h1&gt;{ contentState.header }&lt;/h1&gt;
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>{ contentState.intro }<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span></span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>
  { contentState.list.map((item, i) =&gt; {
    return (
      <span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{i}</span>&gt;</span>{ item }<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
    )
  })}
<span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span></span>
</code></pre>
<p>Once you save and load that, you'll first notice we get an error because our <code>list</code> property doesn't exist on our <code>contentState</code>, so we can first fix that:</p>
<pre><code class="lang-jsx">{ <span class="hljs-built_in">Array</span>.isArray(contentState.list) &amp;&amp; contentState.list.map(<span class="hljs-function">(<span class="hljs-params">item, i</span>) =&gt;</span> {
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{i}</span>&gt;</span>{ item }<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span></span>
  )
})}
</code></pre>
<p>And after that's ready, let's add our <code>setTimeout</code> inside of a <code>useEffect</code> hook to simulate our data loading. Add this under our <code>content</code> object:</p>
<pre><code class="lang-jsx">useEffect(<span class="hljs-function">() =&gt;</span> {
  <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> {
    updateContentState(content);
    updateLoadingState(<span class="hljs-literal">false</span>)
  }, <span class="hljs-number">2000</span>);
}, [])
</code></pre>
<p>Once you save and open up your browser, you'll notice that for 2 seconds you don't have any content and then it loads in, basically simulating loading that data asynchronously.</p>
<p><a target="_blank" href="https://github.com/colbyfayock/my-css-loading-animation-dynamic/commit/f0cada8d696ffe3e983f5efc03dc9d75a2245fe1">Follow along with the commit!</a></p>
<h3 id="heading-step-3-adding-our-loading-animation">Step 3: Adding our loading animation</h3>
<p>Now we can finally add our loading animation. So to do this, we're going to use our loading state we set up using <code>useState</code> and if the content is loading, add our <code>.loading</code>  class to our elements.</p>
<p>Before we do that, instead of individually adding this class to each item in the DOM, it might make more sense to do so using CSS and adding the class to the parent, so let's do that first.</p>
<p>First, update the <code>.loading</code> class to target our elements:</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.loading</span> <span class="hljs-selector-tag">h1</span>,
<span class="hljs-selector-class">.loading</span> <span class="hljs-selector-tag">p</span>,
<span class="hljs-selector-class">.loading</span> <span class="hljs-selector-tag">li</span> {
  <span class="hljs-attribute">color</span>: transparent;
  <span class="hljs-attribute">background</span>: <span class="hljs-built_in">linear-gradient</span>(<span class="hljs-number">100deg</span>, #eceff1 <span class="hljs-number">30%</span>, #f6f7f8 <span class="hljs-number">50%</span>, #eceff1 <span class="hljs-number">70%</span>);
  <span class="hljs-attribute">background-size</span>: <span class="hljs-number">400%</span>;
  <span class="hljs-attribute">animation</span>: loading <span class="hljs-number">1.2s</span> ease-in-out infinite;
}
</code></pre>
<p>Then we can dynamically add our class to our <code>&lt;main&gt;</code> tag:</p>
<pre><code class="lang-jsx">&lt;main className={loadingState ? <span class="hljs-string">'loading'</span> : <span class="hljs-string">''</span>}&gt;
</code></pre>
<p><em>Note: if you use <a target="_blank" href="https://sass-lang.com/">Sass</a>,  you can manage your loading styles by <a target="_blank" href="https://sass-lang.com/documentation/at-rules/extend">extending</a> the <code>.loading</code> class in the instances you want to use it or create a <a target="_blank" href="https://sass-lang.com/documentation/style-rules/placeholder-selectors">placeholder</a> and extend that!</em></p>
<p>And if you refresh the page, you'll notice it's still just a blank page for 2 seconds!</p>
<p>The issue, is when we load our content, nothing exists inside of our tags that can that would allow the line-height of the elements to give it a height.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/html-css-collapsed-content.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>No height when there's no content</em></p>
<p>But we can fix that! Because our <code>.loading</code> class sets our text to transparent, we can simply add the word <code>Loading</code> for each piece of content:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">const</span> [contentState, updateContentState] = useState({
  <span class="hljs-attr">header</span>: <span class="hljs-string">'Loading'</span>,
  <span class="hljs-attr">intro</span>: <span class="hljs-string">'Loading'</span>,
  <span class="hljs-attr">list</span>: [
    <span class="hljs-string">'Loading'</span>,
    <span class="hljs-string">'Loading'</span>,
    <span class="hljs-string">'Loading'</span>
  ]
})
</code></pre>
<p><em>Note: We can't use an empty space here because that alone won't provide us with a height when rendered in the DOM.</em></p>
<p>And once you save and reload the page, our first 2 seconds will have a loading state that reflects our content!</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/html-css-loading-animation-1.gif" alt="Image" width="600" height="400" loading="lazy">
<em>HTML &amp; CSS loading animation</em></p>
<p><a target="_blank" href="https://github.com/colbyfayock/my-css-loading-animation-dynamic/commit/5b7b1c40d1eebf97f65c966bb771a5f6787073ea">Follow along with the commit!</a></p>
<h2 id="heading-some-additional-thoughts">Some additional thoughts</h2>
<p>This technique can be used pretty broadly. Being a CSS class makes it nice and easy to add where every you want.</p>
<p>If you're not a fan of setting the <code>Loading</code> text for the loading state, another option is to set a fixed height. The only issue with that is it requires more maintenance for tweaking the CSS to match what the content loading in will look like.</p>
<p>Additionally, this won't be perfect. More often than not, you won't know exactly how much copy you have on a page. The goal is to simulate and hint that there will be content and that it's currently loading.</p>
<h2 id="heading-whats-your-favorite-loading-animation">What's your favorite loading animation?</h2>
<p>Let me know on <a target="_blank" href="https://twitter.com/colbyfayock">Twitter</a>!</p>
<h2 id="heading-join-in-on-the-conversation">Join in on the conversation!</h2>
<div class="embed-wrapper">
        <blockquote class="twitter-tweet">
          <a href="https://twitter.com/freeCodeCamp/status/1264557769547493376"></a>
        </blockquote>
        <script defer="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></div>
<div id="colbyfayock-author-card">
  <p>
    <a href="https://twitter.com/colbyfayock">
      <img src="https://res.cloudinary.com/fay/image/upload/w_2000,h_400,c_fill,q_auto,f_auto/w_1020,c_fit,co_rgb:007079,g_north_west,x_635,y_70,l_text:Source%20Sans%20Pro_64_line_spacing_-10_bold:Colby%20Fayock/w_1020,c_fit,co_rgb:383f43,g_west,x_635,y_6,l_text:Source%20Sans%20Pro_44_line_spacing_0_normal:Follow%20me%20for%20more%20JavaScript%252c%20UX%252c%20and%20other%20interesting%20things!/w_1020,c_fit,co_rgb:007079,g_south_west,x_635,y_70,l_text:Source%20Sans%20Pro_40_line_spacing_-10_semibold:colbyfayock.com/w_300,c_fit,co_rgb:7c848a,g_north_west,x_1725,y_68,l_text:Source%20Sans%20Pro_40_line_spacing_-10_normal:colbyfayock/w_300,c_fit,co_rgb:7c848a,g_north_west,x_1725,y_145,l_text:Source%20Sans%20Pro_40_line_spacing_-10_normal:colbyfayock/w_300,c_fit,co_rgb:7c848a,g_north_west,x_1725,y_222,l_text:Source%20Sans%20Pro_40_line_spacing_-10_normal:colbyfayock/w_300,c_fit,co_rgb:7c848a,g_north_west,x_1725,y_295,l_text:Source%20Sans%20Pro_40_line_spacing_-10_normal:colbyfayock/v1/social-footer-card" alt="Follow me for more Javascript, UX, and other interesting things!" width="2000" height="400" loading="lazy">
    </a>
  </p>
  <ul>
    <li>
      <a href="https://twitter.com/colbyfayock">? Follow Me On Twitter</a>
    </li>
    <li>
      <a href="https://youtube.com/colbyfayock">?️ Subscribe To My Youtube</a>
    </li>
    <li>
      <a href="https://www.colbyfayock.com/newsletter/">✉️ Sign Up For My Newsletter</a>
    </li>
  </ul>
</div>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to add Coronavirus (COVID-19) case statistics to your React map dashboard with Gatsby ]]>
                </title>
                <description>
                    <![CDATA[ Previously, we walked through creating a map that shows an interactive look at Coronavirus (COVID-19) cases per country. How can we extend this with some case statistics to show recent data about the impacts on our world? Author's note: Similar to be... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-add-coronavirus-covid-19-case-statistics-to-your-map-dashboard-in-gatsby-and-react-leaflet/</link>
                <guid isPermaLink="false">66bee8e6bc07dbcebef938dc</guid>
                
                    <category>
                        <![CDATA[ coronavirus ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Covid-19 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ data analytics ]]>
                    </category>
                
                    <category>
                        <![CDATA[ front end ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Front-end Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ frontend ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Gatsby ]]>
                    </category>
                
                    <category>
                        <![CDATA[ GatsbyJS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ leaflet ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Mapping ]]>
                    </category>
                
                    <category>
                        <![CDATA[ maps ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ react-leaflet ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Colby Fayock ]]>
                </dc:creator>
                <pubDate>Wed, 22 Apr 2020 14:45:00 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2020/04/coronavirus-mapping-app-2600x1000.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Previously, we walked through creating a map that shows an interactive look at Coronavirus (COVID-19) cases per country. How can we extend this with some case statistics to show recent data about the impacts on our world?</p>
<p><em>Author's note: Similar to before, this dashboard is meant to be a demo and proof of concept for using real world data to build a dashboard. While this data should be accurate per the NovelCOVID API, I would recommend using tools like the <a target="_blank" href="https://www.arcgis.com/apps/opsdashboard/index.html#/bda7594740fd40299423467b48e9ecf6">Johns Hopkins University dashboard</a> for complete and accurate analysis. Stay home and be safe! ❤️</em></p>
<ul>
<li><a class="post-section-overview" href="#heading-what-are-we-going-to-build">What are we going to build?</a></li>
<li><a class="post-section-overview" href="#heading-what-do-we-need-before-we-get-started">What do we need before we get started?</a></li>
<li><a class="post-section-overview" href="#heading-step-1-update-how-we-fetch-our-data-and-fetch-the-statistics">Step 1: Update how we fetch our data and fetch the statistics</a></li>
<li><a class="post-section-overview" href="#heading-step-2-adding-statistics-to-our-dashboard">Step 2: Adding statistics to our dashboard</a></li>
<li><a class="post-section-overview" href="#heading-step-3-make-the-data-human-friendly">Step 3: Make the data human friendly</a></li>
<li><a class="post-section-overview" href="#heading-step-4-add-the-last-updated-date">Step 4: Add the Last Updated date</a></li>
<li><a class="post-section-overview" href="#heading-what-can-i-do-next">What can I do next?</a></li>
</ul>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/9bfxeod27fU" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<h2 id="heading-what-are-we-going-to-build">What are we going to build?</h2>
<p>We're going to be extending our <a target="_blank" href="https://www.colbyfayock.com/2020/03/how-to-create-a-coronavirus-covid-19-dashboard-map-app-with-gatsby-and-leaflet">original map demo</a> with some basic statistics that we can retrieve from the <a target="_blank" href="https://github.com/NovelCOVID/API">NovelCOVID API</a>. To get an idea, here's <a target="_blank" href="https://coronavirus-map-dashboard.netlify.app/">my demo</a> I'm basing this off of.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/04/coronavirus-covid-19-dashboard-map-stats.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Coronavirus (COVID-19) map demo with dashboard statistics</em></p>
<p>While you're not required to have completed <a target="_blank" href="https://www.colbyfayock.com/2020/03/how-to-create-a-coronavirus-covid-19-dashboard-map-app-with-gatsby-and-leaflet/">Part 1</a> to apply these concepts, it definitely helps, and it lets you set up a map for your dashboard. If you'd like to start there, which I recommend, check out <a target="_blank" href="https://www.colbyfayock.com/2020/03/how-to-create-a-coronavirus-covid-19-dashboard-map-app-with-gatsby-and-leaflet/">How to create a Coronavirus (COVID-19) Dashboard &amp; Map App with Gatsby and Leaflet</a> first.</p>
<h2 id="heading-woah-a-mapping-app">Woah, a mapping app?</h2>
<p>Yup. If you haven't played with maps before, don't be discouraged! It's not as bad as you probably think. If you'd rather start with mapping basics, you can  <a target="_blank" href="https://www.freecodecamp.org/news/easily-spin-up-a-mapping-app-in-react-with-leaflet/">read more about how mapping works</a>  first.</p>
<h2 id="heading-what-do-we-need-before-we-get-started">What do we need before we get started?</h2>
<p>For this walkthrough, you pretty much need a React app in some form. I'll be working with the dashboard we previously built in my last walkthrough that includes a <a target="_blank" href="https://www.colbyfayock.com/2020/03/how-to-create-a-coronavirus-covid-19-dashboard-map-app-with-gatsby-and-leaflet/">map of the cases of the Coronavirus (COVID-19) per country</a>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/04/coronavirus-map-tutorial-country-markers.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Coronavirus (COVID-19) map dashboard</em></p>
<p>I recommend starting with the previous tutorial, but if you want to skip the map and start fresh, the easiest way would probably be to use <a target="_blank" href="https://github.com/facebook/create-react-app">Create React App</a>, <a target="_blank" href="https://www.gatsbyjs.org/">Gatsby</a>, or <a target="_blank" href="https://nextjs.org/">Next.js</a>.</p>
<h2 id="heading-step-1-update-how-we-fetch-our-data-and-fetch-the-statistics">Step 1: Update how we fetch our data and fetch the statistics</h2>
<p>To get started with our statistics dashboard, we're going to do a little prep work by changing how we're fetching the data. The goal here, is we're going to wrap our request logic in a reusable way so that we can use it for both our countries data and our new statistics data.</p>
<h3 id="heading-creating-a-new-react-hook-to-fetch-data">Creating a new React hook to fetch data</h3>
<p>Diving in, the first we'll do is create a new <a target="_blank" href="https://reactjs.org/docs/hooks-reference.html">React hook</a> that will serve as how we fetch the data. To get started, create a new file in your hooks directory called <code>useTracker.js</code>  and add a line inside of <code>hooks/index.js</code> to export it:</p>
<pre><code class="lang-js"><span class="hljs-comment">// New file src/hooks/useTracker.js</span>
<span class="hljs-comment">// This will be empty for now</span>
</code></pre>
<pre><code class="lang-js"><span class="hljs-comment">// Inside hooks/index.js</span>
<span class="hljs-keyword">export</span> { <span class="hljs-keyword">default</span> <span class="hljs-keyword">as</span> useTracker } <span class="hljs-keyword">from</span> <span class="hljs-string">'./useTracker'</span>;
</code></pre>
<p>Inside of our <code>useTracker.js</code> file, we're going to set up our request logic. This is a long file, so make sure you copy and paste the entire thing before we walk through what it does:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> axios <span class="hljs-keyword">from</span> <span class="hljs-string">'axios'</span>;

<span class="hljs-keyword">const</span> API_HOST = <span class="hljs-string">'https://corona.lmao.ninja/v2'</span>;

<span class="hljs-keyword">const</span> ENDPOINTS = [
  {
    <span class="hljs-attr">id</span>: <span class="hljs-string">'all'</span>,
    <span class="hljs-attr">path</span>: <span class="hljs-string">'/all'</span>,
    <span class="hljs-attr">isDefault</span>: <span class="hljs-literal">true</span>
  },
  {
    <span class="hljs-attr">id</span>: <span class="hljs-string">'countries'</span>,
    <span class="hljs-attr">path</span>: <span class="hljs-string">'/countries'</span>
  }
]

<span class="hljs-keyword">const</span> defaultState = {
  <span class="hljs-attr">data</span>: <span class="hljs-literal">null</span>,
  <span class="hljs-attr">state</span>: <span class="hljs-string">'ready'</span>
}

<span class="hljs-keyword">const</span> useTracker = <span class="hljs-function">(<span class="hljs-params">{ api = <span class="hljs-string">'all'</span> }</span>) =&gt;</span> {

  <span class="hljs-keyword">const</span> [tracker = {}, updateTracker] = useState(defaultState)

  <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">fetchTracker</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">let</span> route = ENDPOINTS.find(<span class="hljs-function">(<span class="hljs-params">{ id } = {}</span>) =&gt;</span> id === api);

    <span class="hljs-keyword">if</span> ( !route ) {
      route = ENDPOINTS.find(<span class="hljs-function">(<span class="hljs-params">{ isDefault } = {}</span>) =&gt;</span> !!isDefault);
    }

    <span class="hljs-keyword">let</span> response;

    <span class="hljs-keyword">try</span> {
      updateTracker(<span class="hljs-function">(<span class="hljs-params">prev</span>) =&gt;</span> {
        <span class="hljs-keyword">return</span> {
          ...prev,
          <span class="hljs-attr">state</span>: <span class="hljs-string">'loading'</span>
        }
      });
      response = <span class="hljs-keyword">await</span> axios.get(<span class="hljs-string">`<span class="hljs-subst">${API_HOST}</span><span class="hljs-subst">${route.path}</span>`</span>);
    } <span class="hljs-keyword">catch</span>(e) {
      updateTracker(<span class="hljs-function">(<span class="hljs-params">prev</span>) =&gt;</span> {
        <span class="hljs-keyword">return</span> {
          ...prev,
          <span class="hljs-attr">state</span>: <span class="hljs-string">'error'</span>,
          <span class="hljs-attr">error</span>: e
        }
      });
      <span class="hljs-keyword">return</span>;
    }

    <span class="hljs-keyword">const</span> { data } = response;

    updateTracker(<span class="hljs-function">(<span class="hljs-params">prev</span>) =&gt;</span> {
      <span class="hljs-keyword">return</span> {
        ...prev,
        <span class="hljs-attr">state</span>: <span class="hljs-string">'ready'</span>,
        data
      }
    });

  }

  useEffect(<span class="hljs-function">() =&gt;</span> {
    fetchTracker()
  }, [api])

  <span class="hljs-keyword">return</span> {
    fetchTracker,
    ...tracker
  }
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> useTracker;
</code></pre>
<p>Starting from the top:</p>
<ul>
<li>We import our dependencies: we're going to use Reacts <code>useEffect</code>  and <code>useState</code> hooks to manage our requests</li>
<li>We define default constants: we have a base API endpoint for our data, a list of the available endpoints we'll use, and a state object that will store our data</li>
<li>We define our <code>useTracker</code> hook:  our hook includes one argument <code>api</code>  that will allow us to specify which endpoint we'll use to make our request</li>
<li>We set up a state instance: we'll want to keep track of our fetched data, so we create a <code>tracker</code> state instance that we'll be able to update</li>
<li>We created an asynchronous <code>fetchTracker</code> function: we'll use this to make our actual request</li>
<li>Inside our function: we first find the API route and create our URL, update our state instance to a "loading" state, try to make our request, catch any errors if there are any, and finally if the request is successful, we update our state with that data</li>
<li>We trigger our function: using a <code>useEffect</code> hook, we trigger our <code>fetchTracker</code> function to make the request. We only have one dependency of <code>api</code>. This means the function will only fire the first time and any time the <code>api</code> value we pass in changes. We won't be changing that value, but it may be helpful in other instances if you're dynamically changing the API used</li>
<li>We return our tracker: the returned object includes both our <code>tracker</code> data as well as our <code>fetchTracker</code> function that we could use to refetch the data if we'd like</li>
</ul>
<p>And with all of that, we have a brand new hook that will fetch data from the NovelCOVID API.</p>
<h3 id="heading-using-our-new-tracker-hook">Using our new tracker hook</h3>
<p>To make use of this hook, let's jump over to <code>src/pages/index.js</code>, remove our <code>axios</code> import if it's there, and instead import our hook:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { useTracker } <span class="hljs-keyword">from</span> <span class="hljs-string">'hooks'</span>;
</code></pre>
<p>With our hook, let's replace our original country data request.  First, add the following to the top of the <code>IndexPage</code> component:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> { <span class="hljs-attr">data</span>: countries = [] } = useTracker({
  <span class="hljs-attr">api</span>: <span class="hljs-string">'countries'</span>
});

<span class="hljs-keyword">const</span> hasCountries = <span class="hljs-built_in">Array</span>.isArray(countries) &amp;&amp; countries.length &gt; <span class="hljs-number">0</span>;
</code></pre>
<p>This will let us fetch our country data and let us know if we have any results. Next, let's replace our original request.</p>
<p>Inside of our <code>mapEffect</code> function, let's remove the <code>axios</code> request in addition to the response, the destructured data object, and the <code>hasData</code> constant.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/04/code-diff-map-effect-countries-data.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Code diff showing update to map effect</em></p>
<p>Then, replace <code>hasData</code> with <code>hasCountries</code>:</p>
<pre><code class="lang-js"><span class="hljs-keyword">if</span> ( !hasCountries ) <span class="hljs-keyword">return</span>;
</code></pre>
<p>And replace <code>data</code> with <code>countries</code> in the <code>geoJson</code> object where we map our features:</p>
<pre><code class="lang-js">features: countries.map(<span class="hljs-function">(<span class="hljs-params">country = {}</span>) =&gt;</span> {
</code></pre>
<p>At this point, if you hit save and refresh, you shouldn't notice any difference to what you previously had.</p>
<h3 id="heading-add-a-request-for-our-stats">Add a request for our stats</h3>
<p>Now that we are using our <code>useTracker</code> hook to fetch our country data, let's also use that to fetch our stats.</p>
<p>Right next to where we set up our <code>useTracker</code> hook before, let's add another request:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> { <span class="hljs-attr">data</span>: stats = {} } = useTracker({
  <span class="hljs-attr">api</span>: <span class="hljs-string">'all'</span>
});
</code></pre>
<p>And if we add a <code>console.log</code> statement under to see what's inside <code>stats</code>:</p>
<pre><code class="lang-js"><span class="hljs-built_in">console</span>.log(<span class="hljs-string">'stats'</span>, stats);
</code></pre>
<p>We should see our <code>stats</code> data object logged out!</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/04/console-log-coronavirus-stats-1.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Using console.log to show Coronavirus (COVID-19) statistics</em></p>
<p><a target="_blank" href="https://github.com/colbyfayock/my-coronavirus-map/commit/fe9d85e57f7474a86d38213676bf62df4b6168a4">Follow along with the commit!</a></p>
<h2 id="heading-step-2-adding-statistics-to-our-dashboard">Step 2: Adding statistics to our dashboard</h2>
<p>Now that we have our data available to use, let's use it!</p>
<p>To get started adding our statistics to the dashboard, let's create a data structure that will allow us to easily configure the data we want to use.</p>
<p>To do this, let's first create a new array called <code>dashboardStats</code> below <code>hasCountries</code> at the top of the page component:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> dashboardStats = [];
</code></pre>
<p>Inside this array, let's add some new objects that specify our data that we're pulling from the <code>stats</code> object we requested. To start, let's try to add:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> dashboardStats = [
  {
    <span class="hljs-attr">primary</span>: {
      <span class="hljs-attr">label</span>: <span class="hljs-string">'Total Cases'</span>,
      <span class="hljs-attr">value</span>: stats?.cases
    },
    <span class="hljs-attr">secondary</span>: {
      <span class="hljs-attr">label</span>: <span class="hljs-string">'Per 1 Million'</span>,
      <span class="hljs-attr">value</span>: stats?.casesPerOneMillion
    }
  },
  {
    <span class="hljs-attr">primary</span>: {
      <span class="hljs-attr">label</span>: <span class="hljs-string">'Total Deaths'</span>,
      <span class="hljs-attr">value</span>: stats?.deaths
    },
    <span class="hljs-attr">secondary</span>: {
      <span class="hljs-attr">label</span>: <span class="hljs-string">'Per 1 Million'</span>,
      <span class="hljs-attr">value</span>: stats?.deathsPerOneMillion
    }
  },
  {
    <span class="hljs-attr">primary</span>: {
      <span class="hljs-attr">label</span>: <span class="hljs-string">'Total Tests'</span>,
      <span class="hljs-attr">value</span>: stats?.tests
    },
    <span class="hljs-attr">secondary</span>: {
      <span class="hljs-attr">label</span>: <span class="hljs-string">'Per 1 Million'</span>,
      <span class="hljs-attr">value</span>: stats?.testsPerOneMillion
    }
  }
]
</code></pre>
<p>The reason we're splitting this up into <code>primary</code> and <code>secondary</code> keys, is we're going to use that to differentiate between logically similar stats that we want to style a little bit differently.</p>
<p>_Note: if you're not familiar with the <code>?.</code> syntax, it's called <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining">Optional Chaining</a>. This allows us to chain our properties without worrying about if the objects exist. If <code>stats</code> is undefined, it will simply return undefined instead of throwing an error._</p>
<p>With our stats data, let's add the tracker to our map. Let's remove our current <code>&lt;Map&gt;</code> component and include it nested inside our tracker div in the following:</p>
<pre><code class="lang-jsx">&lt;div className=<span class="hljs-string">"tracker"</span>&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Map</span> {<span class="hljs-attr">...mapSettings</span>} /&gt;</span></span>
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"tracker-stats"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>
      { dashboardStats.map(({ primary = {}, secondary = {} }, i) =&gt; {
        return (
          <span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{</span>`<span class="hljs-attr">Stat-</span>${<span class="hljs-attr">i</span>}`} <span class="hljs-attr">className</span>=<span class="hljs-string">"tracker-stat"</span>&gt;</span>
            { primary.value &amp;&amp; (
              <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"tracker-stat-primary"</span>&gt;</span>
                { primary.value }
                <span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span>{ primary.label }<span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span>
              <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
            )}
            { secondary.value &amp;&amp; (
              <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"tracker-stat-secondary"</span>&gt;</span>
                { secondary.value }
                <span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span>{ secondary.label }<span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span>
              <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
            )}
          <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
        );
      })}
    <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
&lt;/div&gt;
</code></pre>
<p>This code should be immediately following the <code>&lt;Helmet&gt;</code> component if you're following along.</p>
<p>To explain what we're doing:</p>
<ul>
<li>We're creating a "tracker" div that will organize our stats</li>
<li>We move our <code>&lt;Map</code> component inside of this tracker</li>
<li>We create a separate section called "tracker-stats"</li>
<li>Inside of this, we create an unordered list (<code>ul</code>)</li>
<li>Inside of our list, we loop through all of our stats inside <code>dashboardStats</code></li>
<li>For each stat, we create a new list element (<code>li</code>) and include 2 optional paragraphs that includes our primary stat data and our secondary stat data</li>
</ul>
<p>Once we reload our page, we should now see a few stats:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/04/adding-coronavirus-stats-to-page.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Adding the first statistics to the page</em></p>
<p>Now that we have our stats on our page, let's make them look like they're in a dashboard.</p>
<p>Let's create a new file called <code>_tracker.scss</code> inside of our <code>src/assets/stylesheets/components</code> directory. Once that file is created, additionally add it to the <code>src/assets/stylesheets/components/__components.scss</code> file:</p>
<pre><code class="lang-scss"><span class="hljs-keyword">@import</span> <span class="hljs-string">"tracker"</span>;
</code></pre>
<p>With our new component style file ready to go, let's add some styles into <code>_tracker.scss</code>:</p>
<pre><code class="lang-scss"><span class="hljs-selector-class">.tracker-stats</span> {

  <span class="hljs-attribute">color</span>: white;
  <span class="hljs-attribute">background-color</span>: <span class="hljs-variable">$blue-grey-900</span>;
  <span class="hljs-attribute">border-top</span>: solid <span class="hljs-number">1px</span> darken(<span class="hljs-variable">$blue-grey-900</span>, <span class="hljs-number">5</span>);

  <span class="hljs-selector-tag">ul</span> {
    <span class="hljs-attribute">display</span>: grid;
    grid-template-<span class="hljs-attribute">columns</span>: <span class="hljs-number">1</span>fr <span class="hljs-number">1</span>fr <span class="hljs-number">1</span>fr;
    <span class="hljs-attribute">list-style</span>: none;
    <span class="hljs-attribute">padding</span>: <span class="hljs-number">0</span>;
    <span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span>;
  }

}

<span class="hljs-selector-class">.tracker-stat</span> {

  <span class="hljs-attribute">font-size</span>: <span class="hljs-number">2em</span>;
  <span class="hljs-attribute">text-align</span>: center;
  <span class="hljs-attribute">padding</span>: .<span class="hljs-number">5em</span>;
  <span class="hljs-attribute">border-right</span>: solid <span class="hljs-number">1px</span> darken(<span class="hljs-variable">$blue-grey-900</span>, <span class="hljs-number">5</span>);
  <span class="hljs-attribute">border-bottom</span>: solid <span class="hljs-number">1px</span> darken(<span class="hljs-variable">$blue-grey-900</span>, <span class="hljs-number">5</span>);

  <span class="hljs-selector-tag">strong</span> {
    <span class="hljs-attribute">font-weight</span>: normal;
    <span class="hljs-attribute">color</span>: <span class="hljs-variable">$blue-grey-300</span>;
  }

}

<span class="hljs-selector-class">.tracker-stat-primary</span> {

  <span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span>;

  <span class="hljs-selector-tag">strong</span> {
    <span class="hljs-attribute">display</span>: block;
    <span class="hljs-attribute">font-size</span>: .<span class="hljs-number">5em</span>;
  }

}

<span class="hljs-selector-class">.tracker-stat-secondary</span> {

  <span class="hljs-attribute">font-size</span>: .<span class="hljs-number">5em</span>;
  <span class="hljs-attribute">margin</span>: .<span class="hljs-number">8em</span> <span class="hljs-number">0</span> <span class="hljs-number">0</span>;

  <span class="hljs-selector-tag">strong</span> {
    <span class="hljs-attribute">font-size</span>: .<span class="hljs-number">8em</span>;
    <span class="hljs-attribute">margin-left</span>: .<span class="hljs-number">4em</span>;
  }

}
</code></pre>
<p>Above – we're adding colors and organizational effects, such as using <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout">CSS Grid</a>, to allow our data to be organized in an easy to read way and to look good! We're also making use of some pre-existing colors variables that are used within the project to keep the color use consistent.</p>
<p>Once you save those styles and reload the page, it should look much better:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/04/adding-coronavirus-case-statistics-to-map-dashboard.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Adding case statistics to the dashboard</em></p>
<p>From here, feel free to add more stats or adjust them to your liking. In the demo I created, I added the stats for active cases, critical cases, and recovered cases. If you'd like to do the same, you can <a target="_blank" href="https://github.com/colbyfayock/my-coronavirus-map/commit/eb8a28c9e46dc2327ada0df21b250422e55d304c">check out the commit</a>.</p>
<p><a target="_blank" href="https://github.com/colbyfayock/my-coronavirus-map/commit/eb8a28c9e46dc2327ada0df21b250422e55d304c">Follow along with the commit!</a></p>
<h2 id="heading-step-3-make-the-data-human-friendly">Step 3: Make the data human friendly</h2>
<p>Now the rest of this walkthrough could be considered optional, but ultimately we want people to be able to read these statistics, right? So let's make the numbers a little more easy to read.</p>
<p>First, let's open our <code>src/lib/util.js</code> file and add this function:</p>
<pre><code class="lang-js"><span class="hljs-comment">/**
 * commafy
 * <span class="hljs-doctag">@description </span>Applies appropriate commas to large numbers
 */</span>

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">commafy</span>(<span class="hljs-params">value</span>) </span>{
  <span class="hljs-keyword">let</span> numberString = <span class="hljs-string">`<span class="hljs-subst">${value}</span>`</span>;

  numberString = numberString.split(<span class="hljs-string">''</span>);

  numberString.reverse();

  numberString = numberString.reduce(<span class="hljs-function">(<span class="hljs-params">prev, current, index</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> shouldComma = (index + <span class="hljs-number">1</span>) % <span class="hljs-number">3</span> === <span class="hljs-number">0</span> &amp;&amp; index + <span class="hljs-number">1</span> &lt; numberString.length;
    <span class="hljs-keyword">let</span> updatedValue = <span class="hljs-string">`<span class="hljs-subst">${prev}</span><span class="hljs-subst">${current}</span>`</span>;
    <span class="hljs-keyword">if</span> ( shouldComma ) {
      updatedValue = <span class="hljs-string">`<span class="hljs-subst">${updatedValue}</span>,`</span>;
    }
    <span class="hljs-keyword">return</span> updatedValue;
  }, <span class="hljs-string">''</span>);

  numberString = numberString.split(<span class="hljs-string">''</span>);
  numberString.reverse()
  numberString = numberString.join(<span class="hljs-string">''</span>);

  <span class="hljs-keyword">return</span> numberString;
}
</code></pre>
<p>This function will take a number and turn it into a string with commas. To walk through what it does:</p>
<ul>
<li>Takes in a value as an argument. For our use, this value will most likely be a number.</li>
<li>It converts the value into a string. We'll use this to work with adding commas to our number.</li>
<li>We split that string into an array and reverse it. We want to reverse it because it makes it easier to add our commas depending on the index.</li>
<li>We use the javascript <code>reduce</code> function to recreate our number-string. After every 3 numbers, we want to add a comma.</li>
<li>Once we have our new value with the commas, we want to re-reverse it. So we split it again, reverse the array of characters, and re-join it, which is what we return</li>
</ul>
<p>And now that we have our <code>commafy</code> function, let's use it. Back inside <code>src/pages/index.js</code>, let's import our function at the top of the page:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { commafy } <span class="hljs-keyword">from</span> <span class="hljs-string">'lib/util'</span>;
</code></pre>
<p>Then, in our <code>dashboardStats</code> array, let's replace every number value with a ternary expression and function that will convert our number if it's available:</p>
<pre><code class="lang-js">value: stats ? commafy(stats?.cases) : <span class="hljs-string">'-'</span>
</code></pre>
<p>This line checks to see if <code>stats</code> exists. If it does, we <code>commafy</code> the <code>cases</code> value. If it doesn't exist, we return a <code>-</code> to show it's unavailable.</p>
<p>Once we repeat that process for all of our numbers, we can save, reload the page, and see our human friendly numbers!</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/04/coronavirus-dashboard-stats-with-readable-stats.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Formatting the statistics to be human readable</em></p>
<p><a target="_blank" href="https://github.com/colbyfayock/my-coronavirus-map/commit/90f266c17815239d9d3356d9b9d660915fdc26c2">Follow along with the commit!</a></p>
<h2 id="heading-step-4-add-the-last-updated-date">Step 4: Add the Last Updated date</h2>
<p>Finally, we want to make sure people are staying informed and understand the last time this data was updated. Luckily, our API provides a Last Updated date for us, so let's use it!</p>
<p>At the bottom of our "tracker" <code>div</code> under <code>tracker-stats</code>, let's add the following:</p>
<pre><code class="lang-jsx">&lt;div className=<span class="hljs-string">"tracker-last-updated"</span>&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>
    Last Updated: { stats?.updated }
  <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span></span>
&lt;/div&gt;
</code></pre>
<p>This creates a new section where we simply include the <code>updated</code> property from our stats. And if we save and reload the page, we can see the last updated date!</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/04/coronvirus-dashboard-last-updated.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Adding last updated to the dashboard</em></p>
<p>But how could we even understand what that number is, unless you're the computer crawling this blog post? So let's change it to a human readable format like we did with our numbers.</p>
<p>Inside of our <code>src/lib/util.js</code> file, let's add another function:</p>
<pre><code class="lang-js"><span class="hljs-comment">/**
 * friendlyDate
 * <span class="hljs-doctag">@description </span>Takes in a date value and returns a friendly version
 */</span>

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">friendlyDate</span>(<span class="hljs-params">value</span>) </span>{
  <span class="hljs-keyword">const</span> date = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(value);
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Intl</span>.DateTimeFormat(<span class="hljs-string">'en'</span>, {
    <span class="hljs-attr">year</span>: <span class="hljs-string">'numeric'</span>,
    <span class="hljs-attr">month</span>: <span class="hljs-string">'short'</span>,
    <span class="hljs-attr">day</span>: <span class="hljs-string">'2-digit'</span>,
    <span class="hljs-attr">hour</span>: <span class="hljs-string">'numeric'</span>,
    <span class="hljs-attr">minute</span>: <span class="hljs-string">'numeric'</span>
  }).format(date);
}
</code></pre>
<p>This function creates a new <code>Date</code> object, then uses the javascript <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat">International DateTimeFormat API</a> to convert it into a friendly readable format!</p>
<p>Once that's saved, let's import it next to our <code>commafy</code> function at the top of <code>src/pages/index.js</code>:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { commafy, friendlyDate } <span class="hljs-keyword">from</span> <span class="hljs-string">'lib/util'</span>;
</code></pre>
<p>Then we can update our code similar to how we updated our numbers:</p>
<pre><code class="lang-jsx">Last Updated: { stats ? friendlyDate(stats?.updated) : <span class="hljs-string">'-'</span> }
</code></pre>
<p>And if we save and reload, we see it in a human readable way!</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/04/coronvirus-dashboard-last-updated-formatted-1.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Formatting the last updated date</em></p>
<p>Finally for our "last updated" should look like it fits in with the rest of the dashboard, so let's add a few more styles. Inside of our <code>_tracker.scss</code> file we were working with earlier:</p>
<pre><code class="lang-scss"><span class="hljs-selector-class">.tracker-last-updated</span> {

  <span class="hljs-attribute">color</span>: white;
  <span class="hljs-attribute">background-color</span>: <span class="hljs-variable">$blue-grey-900</span>;
  <span class="hljs-attribute">padding</span>: .<span class="hljs-number">8em</span> <span class="hljs-number">0</span>;

  <span class="hljs-selector-tag">p</span> {
    <span class="hljs-attribute">color</span>: <span class="hljs-variable">$blue-grey-300</span>;
    <span class="hljs-attribute">font-size</span>: .<span class="hljs-number">8em</span>;
    <span class="hljs-attribute">text-align</span>: center;
    <span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span>;
  }

}
</code></pre>
<p>And once we hit save and refresh the browser, we have our dashboard statistics with the last updated time! ?</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/04/coronavirus-dashboard-formatted-styled.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Final dashboard with formatted lasted updated date</em></p>
<p><a target="_blank" href="https://github.com/colbyfayock/my-coronavirus-map/commit/408286aecb32223c8782eb1539f5563135c75dfb">Follow along with the commit!</a></p>
<h2 id="heading-what-can-i-do-next">What can I do next?</h2>
<h3 id="heading-make-the-marker-tooltip-data-human-friendly">Make the marker tooltip data human friendly</h3>
<p>Now that we have our handy <code>commafy</code> and <code>friendlyDate</code> functions, we can reuse those functions to clean up the data in our country marker popups!</p>
<h3 id="heading-use-the-fetchtracker-function-to-poll-for-updates">Use the fetchTracker function to poll for updates</h3>
<p>Inside of the <code>useTracker</code> hook we created, we exported a function called <code>fetchTracker</code>. This allows us to force a request to the API to fetch new data. To make sure our map stays current even when somebody doesn't refresh the page, we can create a <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout">timer</a> in javascript to regularly invoke that function to update our dashboard data.</p>
<h3 id="heading-clear-the-map-layers-before-re-adding-the-new-ones">Clear the map layers before re-adding the new ones</h3>
<p>One thing we're currently not doing is cleaning up old layers before adding a new one. The way the map is set up, it just keeps layering them on top. What we can do is before we add all of our new layers, we can clear out the old ones. <a target="_blank" href="https://github.com/colbyfayock/my-coronavirus-map/commit/cad3b5a6e31a6ae090549c12e40a08fee4db4aa5">Check out this commit</a> to get started!</p>
<h2 id="heading-want-to-learn-more-about-maps">Want to learn more about maps?</h2>
<p>You can check out a few of my other resources to get started:</p>
<ul>
<li><a target="_blank" href="https://www.colbyfayock.com/2020/03/how-to-create-a-coronavirus-covid-19-dashboard-map-app-with-gatsby-and-leaflet">How to create a Coronavirus (COVID-19) Dashboard &amp; Map App in React with Gatsby and Leaflet</a> (Part 1 of this post)</li>
<li><a target="_blank" href="https://www.colbyfayock.com/2020/04/how-to-set-up-a-custom-mapbox-basemap-style-with-react-leaflet-and-leaflet-gatsby-starter/">How to set up a custom Mapbox basemap style with React Leaflet and Leaflet Gatsby Starter</a></li>
<li><a target="_blank" href="https://www.colbyfayock.com/2020/03/anyone-can-map-inspiration-and-an-introduction-to-the-world-of-mapping">Anyone Can Map! Inspiration and an introduction to the world of mapping</a></li>
<li><a target="_blank" href="https://www.colbyfayock.com/2020/03/how-to-create-a-summer-road-trip-mapping-app-with-gatsby-and-leaflet">How to Create a Summer Road Trip Mapping App with Gatsby and Leaflet</a></li>
<li><a target="_blank" href="https://www.colbyfayock.com/2019/12/create-your-own-santa-tracker-with-gatsby-and-react-leaflet/">How to Create your own Santa Tracker with Gatsby and React Leaflet</a></li>
<li><a target="_blank" href="https://www.freecodecamp.org/news/easily-spin-up-a-mapping-app-in-react-with-leaflet/">How to build a mapping app in React the easy way with Leaflet</a></li>
</ul>
<div id="colbyfayock-author-card">
  <p>
    <a href="https://twitter.com/colbyfayock">
      <img src="https://res.cloudinary.com/fay/image/upload/w_2000,h_400,c_fill,q_auto,f_auto/w_1020,c_fit,co_rgb:007079,g_north_west,x_635,y_70,l_text:Source%20Sans%20Pro_64_line_spacing_-10_bold:Colby%20Fayock/w_1020,c_fit,co_rgb:383f43,g_west,x_635,y_6,l_text:Source%20Sans%20Pro_44_line_spacing_0_normal:Follow%20me%20for%20more%20JavaScript%252c%20UX%252c%20and%20other%20interesting%20things!/w_1020,c_fit,co_rgb:007079,g_south_west,x_635,y_70,l_text:Source%20Sans%20Pro_40_line_spacing_-10_semibold:colbyfayock.com/w_300,c_fit,co_rgb:7c848a,g_north_west,x_1725,y_68,l_text:Source%20Sans%20Pro_40_line_spacing_-10_normal:colbyfayock/w_300,c_fit,co_rgb:7c848a,g_north_west,x_1725,y_145,l_text:Source%20Sans%20Pro_40_line_spacing_-10_normal:colbyfayock/w_300,c_fit,co_rgb:7c848a,g_north_west,x_1725,y_222,l_text:Source%20Sans%20Pro_40_line_spacing_-10_normal:colbyfayock/w_300,c_fit,co_rgb:7c848a,g_north_west,x_1725,y_295,l_text:Source%20Sans%20Pro_40_line_spacing_-10_normal:colbyfayock/v1/social-footer-card" alt="Follow me for more Javascript, UX, and other interesting things!" width="2000" height="400" loading="lazy">
    </a>
  </p>
  <ul>
    <li>
      <a href="https://twitter.com/colbyfayock">? Follow Me On Twitter</a>
    </li>
    <li>
      <a href="https://youtube.com/colbyfayock">?️ Subscribe To My Youtube</a>
    </li>
    <li>
      <a href="https://www.colbyfayock.com/newsletter/">✉️ Sign Up For My Newsletter</a>
    </li>
  </ul>
</div>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Create a Custom React Hook and Publish it to npm ]]>
                </title>
                <description>
                    <![CDATA[ Hooks are a handy addition to the React API that allow us to organize some of our logic and state in function components. How can we build a custom hook and share it with the rest of the world? What are hooks? Why are custom hooks cool? What are we ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-create-a-custom-react-hook-and-publish-it-to-npm/</link>
                <guid isPermaLink="false">66b8e34f682e4a25eed261a0</guid>
                
                    <category>
                        <![CDATA[ front end ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Front-end Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ frontend ]]>
                    </category>
                
                    <category>
                        <![CDATA[ hooks ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ npm ]]>
                    </category>
                
                    <category>
                        <![CDATA[ npm scripts ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ react hooks ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Yarn ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Colby Fayock ]]>
                </dc:creator>
                <pubDate>Tue, 14 Apr 2020 14:45:00 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/07/Custom-React-Hooks-Book-Cover--1-.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Hooks are a handy addition to the React API that allow us to organize some of our logic and state in function components. How can we build a custom hook and share it with the rest of the world?</p>
<ul>
<li><a class="post-section-overview" href="#heading-what-are-hooks">What are hooks?</a></li>
<li><a class="post-section-overview" href="#heading-why-are-custom-hooks-cool">Why are custom hooks cool?</a></li>
<li><a class="post-section-overview" href="#heading-what-are-we-going-to-make">What are we going to make?</a></li>
<li><a class="post-section-overview" href="#heading-step-0-naming-your-hook">Step 0: Naming your hook</a></li>
<li><a class="post-section-overview" href="#heading-step-1-setting-up-your-project">Step 1: Setting up your project</a></li>
<li><a class="post-section-overview" href="#heading-step-2-writing-your-new-react-hook">Step 2: Writing your new React Hook</a></li>
<li><a class="post-section-overview" href="#heading-step-3-using-your-react-hook-in-an-example">Step 3: Using your React hook in an example</a></li>
<li><a class="post-section-overview" href="#heading-step-4-compiling-your-react-hook-and-example">Step 4: Compiling your React hook and Example</a></li>
<li><a class="post-section-overview" href="#heading-step-5-publishing-your-react-hook-to-npm">Step 5: Publishing your React hook to npm</a></li>
<li><a class="post-section-overview" href="#heading-more-resources-about-hooks">More resources about hooks</a></li>
</ul>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/Q0xVnRanXVk" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<h2 id="heading-what-are-hooks">What are hooks?</h2>
<p>React <a target="_blank" href="https://reactjs.org/docs/hooks-intro.html">hooks</a> in simple terms are functions. When you include them in your component or within another hook, they allow you to make use of React internals and parts of the React lifecycle with native hooks like <code>useState</code> and <code>useEffect</code>.</p>
<p>I don’t plan on doing a deep dive about hooks, but you can <a target="_blank" href="https://www.freecodecamp.org/news/how-to-destructure-the-fundamentals-of-react-hooks-d13ff6ea6871/">check out a quick introduction</a> with an example of <code>useState</code> as well as <a target="_blank" href="https://reactjs.org/docs/hooks-intro.html">the intro from the React team</a>.</p>
<h2 id="heading-why-are-custom-hooks-cool">Why are custom hooks cool?</h2>
<p>The great thing about creating custom hooks is they allow you to abstract logic for your components making it easier to reuse across multiple components in your app.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/04/hook-example-use-counter-1.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Hook diagram example for useCounter</em></p>
<p>For instance, if you wanted to create a simple counter where you use React’s state to manage the current count. Instead of having the same <code>useState</code> hook in each component file, you can create that logic once in a <code>useCounter</code> hook, making it easier to maintain, extend, and squash bugs if they come up.</p>
<h2 id="heading-what-are-we-going-to-make">What are we going to make?</h2>
<p>For the purposes of this article, we’re going to keep it simple with a basic hook. Typically, you might use a hook because rather than a typical function, you use other native hooks that are required to be used within React function components. We’re going to stick with some basic input and output to keep things simple.</p>
<p>We’re going to recreate this custom <a target="_blank" href="https://github.com/colbyfayock/use-placecage">Placecage hook</a> I made, that allows you to easily generate image URLs that you can use as placeholder images.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/04/nic-cage-excited.gif" alt="Image" width="600" height="400" loading="lazy">
<em>Nic Cage excited</em></p>
<p>If you’re not familiar, <a target="_blank" href="https://www.placecage.com/">Placecage</a> is an API that allows you to generate pictures of Nic Cage as placeholder images for your website. Silly? Yes. Fun? Absolutely!</p>
<p>But if you’re not a fan of Nic's work, you can just as easily swap in the URL for <a target="_blank" href="https://placeholder.com/">Fill Murray</a> which uses pictures of Bill Murray or <a target="_blank" href="https://placeholder.com/">placeholder.com</a> which generates simple solid color background with text that shows the size of the image.</p>
<h2 id="heading-step-0-naming-your-hook">Step 0: Naming your hook</h2>
<p>Before we jump in to our actual code, our ultimate goal is to publish this hook. If that’s not your goal, you can skip this step, but for publishing, we’ll want to create a name for our hook.</p>
<p>In our case, our hook name will be <code>usePlaceCage</code>. Now with that in mind, we have 2 formats of our name — one in camelCase format and one in snake-case format.</p>
<ul>
<li><strong>camelCase:</strong> usePlaceCage</li>
<li><strong>snake-case:</strong> use-placecage</li>
</ul>
<p>The camelCase format will be used for the actual hook function, where the snake-case name will be used for the package name and some of the folders. When creating the name, keep in mind that the package name must be unique. If a package with the same name exists on <a target="_blank" href="https://www.npmjs.com/">npmjs.com</a> already, you won't be able to use it.</p>
<p>If you don’t already have a name, it's okay! You can just use your own name or something you can think of, it doesn’t really matter too much as really we're just trying to learn how to do this. If it were me for instance, I would use:</p>
<ul>
<li><strong>camelCase:</strong> useColbysCoolHook</li>
<li><strong>snake-case:</strong> use-colbyscoolhook</li>
</ul>
<p>But just to clarify, for the rest of our example, we’re going to stick with <code>usePlaceCage</code> and <code>use-placecage</code>.</p>
<h2 id="heading-step-1-setting-up-your-project">Step 1: Setting up your project</h2>
<p>Though you can set up your project however you’d like, we’re going to walk through building a new hook from <a target="_blank" href="https://github.com/colbyfayock/use-custom-hook">this template</a> I created.</p>
<p>The hope here, is that we can take out some of the painful bits of the process and immediately get productive with our custom hook. Don’t worry though, I’ll explain what’s going on along the way.</p>
<p>The requirements here are <a target="_blank" href="https://git-scm.com/">git</a> and <a target="_blank" href="https://yarnpkg.com/">yarn</a> as it helps provide tools that make it easy to scaffold this template, such as using the workspaces feature to allow easy npm scripts to manage the code from the root of the project. If either of those are a dealbreaker, you can try downloading the repo via the download link and update it as needed.</p>
<h3 id="heading-cloning-the-hook-template-from-git">Cloning the hook template from git</h3>
<p>To start, let’s clone the repository from Github. In the command below, you should replace <code>use-my-custom-hook</code> with the name of your hook, such as <code>use-cookies</code> or <code>use-mooncake</code>.</p>
<pre><code class="lang-shell">git clone https://github.com/colbyfayock/use-custom-hook use-my-custom-hook
cd use-my-custom-hook
</code></pre>
<p>Once you clone and navigate to that folder, you should now see 2 directories – an <code>example</code> directory and a <code>use-custom-hook</code> directory.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/04/cloning-use-custom-hook-1.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Cloning use-custom-hook</em></p>
<p>This will give you a few things to get started:</p>
<ul>
<li>A hook directory that will include the source for our hook</li>
<li>Build scripts that compile our hook with <a target="_blank" href="https://babeljs.io/">babel</a></li>
<li>An example page that imports our hook and creates a simple demo page with <a target="_blank" href="https://nextjs.org/">next.js</a></li>
</ul>
<h3 id="heading-running-the-hook-setup-scripts">Running the hook setup scripts</h3>
<p>After we successfully clone the repo, we want to run the setup scripts which install dependencies and update the hook to the name we want.</p>
<pre><code class="lang-shell">yarn install &amp;&amp; yarn setup
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/04/setting-up-new-hook-template.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Setting up a new hook from the use-custom-hook template</em></p>
<p>When the setup script runs, it will do a few things:</p>
<ul>
<li>It will ask you for your name – this is used to update the LICENSE and the package's author name</li>
<li>It will ask you for your hook's name in 2 variations – camelCase and snake-case - this will be used to update the name of the hook throughout the template and move files with that name to the correct location</li>
<li>It will reset git – it will first remove the local .git folder, which contains the history from my template and reinitialize git with a fresh commit to start your new history in</li>
<li>Finally, it will remove the setup script directory and remove the package dependencies that were only being used by those scripts</li>
</ul>
<h3 id="heading-starting-the-development-server">Starting the development server</h3>
<p>Once the setup scripts finish running, you'll want to run:</p>
<pre><code class="lang-shell">yarn develop
</code></pre>
<p>This runs a watch process on the hook source, building the hook locally each time a source file is changed, and running the example app server, where you can test the hook and make changes to the example pages.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/04/custom-hook-development-server.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Starting up the use-custom-hook development server</em></p>
<p>With this all ready, we can get started!</p>
<p><a target="_blank" href="https://github.com/colbyfayock/use-my-custom-hook/commits/master">Follow along with the commit!</a></p>
<h2 id="heading-step-2-writing-your-new-react-hook">Step 2: Writing your new React Hook</h2>
<p>At this point, you should now have a new custom hook where you can make it do whatever you'd like. But since we're going to walk through rebuilding the <a target="_blank" href="https://github.com/colbyfayock/use-placecage">usePlaceCage</a> hook, let's start there.</p>
<p>The usePlaceCage hook does 1 simple thing from a high level view – it takes in a configuration object and returns a number of image URLs that you can then use for your app.</p>
<p>Just as a reminder, any time I mention <code>usePlaceCage</code> or <code>use-placecage</code>, you should use the hook name that you set up before.</p>
<h3 id="heading-a-little-bit-about-placecagecom">A little bit about placecage.com</h3>
<p>Placecage.com is a placeholder image service that does 1 thing. It takes a URL with a simple configuration and returns an image... of Nic Cage.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/04/placecage-website.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>placecage.com</em></p>
<p>From the simplest use, the service uses a URL pattern as follows:</p>
<pre><code>https:<span class="hljs-comment">//www.placecage.com/200/300</span>
</code></pre><p>This would return an image with a width of 200 and height of 300.</p>
<p>Optionally, you can pass an additional URL parameter that defines the type of image:</p>
<pre><code>https:<span class="hljs-comment">//www.placecage.com/gif/200/300</span>
</code></pre><p>In this particular instance, our type is <code>gif</code>, so we'll receive a gif.</p>
<p>The different types available to use are:</p>
<ul>
<li>Nothing: calm</li>
<li><code>g</code>: gray</li>
<li><code>c</code>: crazy</li>
<li><code>gif</code>: gif</li>
</ul>
<p>We'll use this to define how we set up configuration for our hook.</p>
<h3 id="heading-defining-our-core-generator-function">Defining our core generator function</h3>
<p>To get started, we're going to copy over a function at the bottom of our <code>use-placecage/src/usePlaceCage.js</code> file, which allows us to generate an image URL, as well as a few constant definitions that we'll use in that function.</p>
<p>First, let's copy over our constants to the top of our <code>usePlaceCage.js</code> file:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> PLACECAGE_HOST = <span class="hljs-string">'https://www.placecage.com/'</span>;
<span class="hljs-keyword">const</span> TYPES = {
  <span class="hljs-attr">calm</span>: <span class="hljs-literal">null</span>,
  <span class="hljs-attr">gray</span>: <span class="hljs-string">'g'</span>,
  <span class="hljs-attr">crazy</span>: <span class="hljs-string">'c'</span>,
  <span class="hljs-attr">gif</span>: <span class="hljs-string">'gif'</span>
};
<span class="hljs-keyword">const</span> DEFAULT_TYPE = <span class="hljs-string">'calm'</span>;
<span class="hljs-keyword">const</span> ERROR_BASE = <span class="hljs-string">'Failed to place Nick'</span>;
</code></pre>
<p>Here we:</p>
<ul>
<li>Define a host, which is the base URL of our image service.</li>
<li>Define the available types, which we'll use in the configuration API. We set <code>calm</code> to <code>null</code>, because it's the default value which you get by not including it at all</li>
<li>Our default type will be <code>calm</code></li>
<li>And we set an error base which is a consistent message when throwing an error</li>
</ul>
<p>Then for our function, let's copy this at the bottom of our <code>usePlaceCage.js</code> file:</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">generateCage</span>(<span class="hljs-params">settings</span>) </span>{
  <span class="hljs-keyword">const</span> { type = DEFAULT_TYPE, width = <span class="hljs-number">200</span>, height = <span class="hljs-number">200</span>, count = <span class="hljs-number">1</span> } = settings;
  <span class="hljs-keyword">const</span> config = [];

  <span class="hljs-keyword">if</span> ( type !== DEFAULT_TYPE &amp;&amp; TYPES[type] ) {
    config.push(TYPES[type]);
  }

  config.push(width, height);

  <span class="hljs-keyword">if</span> ( <span class="hljs-built_in">isNaN</span>(count) ) {
    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">`<span class="hljs-subst">${ERROR_BASE}</span>: Invalid count <span class="hljs-subst">${count}</span>`</span>);
  }

  <span class="hljs-keyword">return</span> [...new <span class="hljs-built_in">Array</span>(count)].map(<span class="hljs-function">() =&gt;</span> <span class="hljs-string">`<span class="hljs-subst">${PLACECAGE_HOST}</span><span class="hljs-subst">${config.join(<span class="hljs-string">'/'</span>)}</span>`</span>);
}
</code></pre>
<p>Walking through this code:</p>
<ul>
<li>We define a <code>generateCage</code> function which we'll use to generate our image URL</li>
<li>We take in a settings object as an argument, which defines the configuration of our image URL. We'll be using the same parameters as we saw in our placecage.com URL</li>
<li>We destructure those settings to make them available for us to use</li>
<li>We have a few defaults here just to make it easier. Our default <code>type</code> will be defined by <code>DEFAULT_TYPE</code> along with a default width, height, and number of results we want to return</li>
<li>We create a <code>config</code> array. We'll use this  to append all of the different configuration objects in our URL and finally join them together with a <code>/</code> essentially making a URL</li>
<li>Before we push our config to that array, we check if it's a valid argument, by using the <code>TYPES</code> object to check against it. If it's valid, we push it to our config array</li>
<li>We then push our width and height</li>
<li>We do some type checking, if we don't have a valid number as the <code>count</code>, we throw an error, otherwise we'll get incorrect results</li>
<li>Finally, we return a new array with the number of results requested, mapped to a URL creator, which uses <code>PLACECAGE_HOST</code> as our defined base URL, and with our config array joined by <code>/</code></li>
</ul>
<p>And if we were to test this function, it would look like this:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> cage = generateCage({
  <span class="hljs-attr">type</span>: <span class="hljs-string">'gif'</span>,
  <span class="hljs-attr">width</span>: <span class="hljs-number">500</span>,
  <span class="hljs-attr">height</span>: <span class="hljs-number">500</span>,
  <span class="hljs-attr">count</span>: <span class="hljs-number">2</span>
});

<span class="hljs-built_in">console</span>.log(cage); <span class="hljs-comment">// ['https://www.placecage.com/gif/500/500', 'https://www.placecage.com/gif/500/500']</span>
</code></pre>
<h3 id="heading-using-our-function-in-the-hook">Using our function in the hook</h3>
<p>So now that we have our generator function, let's actually use it in our hook!</p>
<p>Inside of the <code>usePlaceCage</code> function in the <code>use-placecage/src/usePlaceCage.js</code> file, we can add:</p>
<pre><code class="lang-js"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">usePlaceCage</span> (<span class="hljs-params">settings = {}</span>) </span>{
  <span class="hljs-keyword">return</span> generateCage(settings);
}
</code></pre>
<p>What this does it uses our generator function, takes in the settings that were passed into the hook, and returns that value from the hook.</p>
<p>Similar to our previous use example, if we were to use our hook, it would look like this:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> cage = usePlaceCage({
  <span class="hljs-attr">type</span>: <span class="hljs-string">'gif'</span>,
  <span class="hljs-attr">width</span>: <span class="hljs-number">500</span>,
  <span class="hljs-attr">height</span>: <span class="hljs-number">500</span>,
  <span class="hljs-attr">count</span>: <span class="hljs-number">2</span>
});

<span class="hljs-built_in">console</span>.log(cage); <span class="hljs-comment">// ['https://www.placecage.com/gif/500/500', 'https://www.placecage.com/gif/500/500']</span>
</code></pre>
<p>At this point, it does the same thing!</p>
<p>So now we have our hook, it serves as a function to generate image URLs for the placecage.com service. How can we actually use it?</p>
<p><a target="_blank" href="https://github.com/colbyfayock/use-my-custom-hook/commit/a4d4d3c3565759031c29d00faf731ac4c236a1fd">Follow along with the commit!</a></p>
<h2 id="heading-step-3-using-your-react-hook-in-an-example">Step 3: Using your React hook in an example</h2>
<p>The good news about our template, is it already includes an example app that we can update to easily make use of our hook to both test and provide documentation for those who want to use it.</p>
<h3 id="heading-setting-up-the-hook">Setting up the hook</h3>
<p>To get started, let's open up our <code>example/pages/index.js</code> file. Inside of this file you'll see the following:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> hookSettings = {
  <span class="hljs-attr">message</span>: <span class="hljs-string">'Hello, custom hook!'</span>
}

<span class="hljs-keyword">const</span> { message } = usePlaceCage(hookSettings);
</code></pre>
<p>This snippet is what was used by default in the template just for a proof of concept, so let's update that. We're going to use the same exact configuration as we did in Step 2:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> hookSettings = {
  <span class="hljs-attr">type</span>: <span class="hljs-string">'gif'</span>,
  <span class="hljs-attr">width</span>: <span class="hljs-number">500</span>,
  <span class="hljs-attr">height</span>: <span class="hljs-number">500</span>,
  <span class="hljs-attr">count</span>: <span class="hljs-number">2</span>
}

<span class="hljs-keyword">const</span> cage = usePlaceCage(hookSettings);
</code></pre>
<p>Again, we set up our settings object with the configuration for our hook and invoke our hook and set the value to the <code>cage</code> constant.</p>
<p>If we now console log that value our to our dev tools, we can see it working!</p>
<pre><code class="lang-js"><span class="hljs-built_in">console</span>.log(<span class="hljs-string">'cage'</span>, cage);
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/04/console-log-cage-array.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Using console.log to show the cage value</em></p>
<p><em>Note: If you get an error here about <code>message</code>, you  can comment that our or remove it under the Examples section.</em></p>
<h3 id="heading-updating-the-example-with-our-new-hook-configuration">Updating the example with our new hook configuration</h3>
<p>If you scroll down to the Examples section, you'll notice that we have the same default <code>hookSettings</code> as above, so let's update that again to make sure our example is accurate.</p>
<pre><code class="lang-jsx">{<span class="hljs-string">`const hookSettings = {
  type: 'gif',
  width: 500,
  height: 500,
  count: 2
}

const cage = usePlaceCage(hookSettings);`</span>}
</code></pre>
<p>You'll also notice that we're no longer using the <code>message</code> variable. If you didn't remove it in the last step, we can now replace it under the Output heading with:</p>
<pre><code class="lang-jsx">&lt;p&gt;
  { <span class="hljs-built_in">JSON</span>.stringify(cage) }
&lt;/p&gt;
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>
  { cage.map((img, i) =&gt; <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{</span>`<span class="hljs-attr">img-</span>${<span class="hljs-attr">i</span>}`} <span class="hljs-attr">width</span>=<span class="hljs-string">{200}</span> <span class="hljs-attr">src</span>=<span class="hljs-string">{img}</span> /&gt;</span>)}
<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span></span>
</code></pre>
<p>We're doing 2 things here:</p>
<ul>
<li>Instead of showing the variable itself, we wrap it with <code>JSON.stringify</code> so that we can show the contents of the array</li>
<li>We also use the <code>map</code> function to loop over our image URLs in the <code>cage</code> constant and create a new image element for each. This let's us preview the output instead of just seeing the values</li>
</ul>
<p>And once you save and open your browser, you should now see your updated examples and output!</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/04/custom-hook-example-page.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Custom hook example page</em></p>
<h3 id="heading-other-things-you-can-do-on-that-page">Other things you can do on that page</h3>
<p>Before moving on, you can also update a few other things that will be important for your hooks page:</p>
<ul>
<li>Update the <strong>How to use</strong> section with instructions</li>
<li>Add additional examples to make it easier for people to know what to do</li>
</ul>
<p>A  few things are also automatically pulled in from the <code>use-placecage/package.json</code> file. You can either update them there to make it easier to maintain or you can replace them in the example page:</p>
<ul>
<li><code>name</code>: Is used at the <code>&lt;h1&gt;</code> of the page</li>
<li><code>description</code>: Is used at the description under the <code>&lt;h1&gt;</code></li>
<li><code>repository.url</code>: Used to include a link to the repository</li>
<li><code>author</code>: The <code>name</code> and <code>url</code> are used to include a link at the bottom of the page</li>
</ul>
<p><a target="_blank" href="https://github.com/colbyfayock/use-my-custom-hook/commit/71ae57b562ad814d0ce862c22e247aa8c450b6cf">Follow along with the commit!</a></p>
<h2 id="heading-step-4-compiling-your-react-hook-and-example">Step 4: Compiling your React hook and Example</h2>
<p>The way we can make our hook work easily as an npm module is to compile it for others to use. We're using babel to do this.</p>
<p>Though the publish process already does this for us automatically with the <code>prepublishOnly</code> script in <code>use-placecage/package.json</code>, we can manually compile our hook using the <code>yarn build</code> command from the root of the project.</p>
<p>Along with compiling the hook, running <code>yarn build</code> will also compile the example page, allowing you to upload it wherever you'd like. After running that command, you should see an output of static HTML files in the <code>example/out</code> directory.</p>
<p>If you're looking for a recommendation, <a target="_blank" href="https://www.netlify.com/">Netlify</a> makes it easy to connect your <a target="_blank" href="https://github.com/">Github</a> account and deploy the static site.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/04/netlify-deployment-setup.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Deployment setup in Netlify</em></p>
<p><a target="_blank" href="https://use-my-custom-hook.netlify.com/">See the demo site deployed to Netlify!</a></p>
<h2 id="heading-step-5-publishing-your-react-hook-to-npm">Step 5: Publishing your React hook to npm</h2>
<p>Finally, if you're happy with your hook, it's time to publish!</p>
<p>npm makes this part really easy. The only prerequisite you need to have an npm account. With that account, let's log in:</p>
<pre><code class="lang-shell">npm login
</code></pre>
<p>Which will prompt you for your login credentials.</p>
<p>Next, let's navigate to our hook's directory, as our package configuration is there under <code>use-placecage/package.json</code>:</p>
<pre><code class="lang-shell">cd use-placecage
</code></pre>
<p>Then, we can simply publish!</p>
<pre><code class="lang-shell">npm publish
</code></pre>
<p>Keep in mind, that each package name needs to be unique. If you used <code>use-placecage</code>, it's already taken... by me. ?</p>
<p>But if you're successful, npm should build your hook and upload it to the package registry!</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/04/publishing-npm-package.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Publishing an npm package</em></p>
<p>It will then be available on npm with the following pattern:</p>
<pre><code>https:<span class="hljs-comment">//www.npmjs.com/package/[package-name]</span>
</code></pre><p>So for <code>use-placeage</code>, it's available here: <a target="_blank" href="https://www.npmjs.com/package/use-placecage">https://www.npmjs.com/package/use-placecage</a></p>
<h2 id="heading-we-now-have-a-custom-hook">We now have a custom hook!</h2>
<p>Yay ? if you followed along, you should now have created a custom hook and published it to npm.</p>
<p>Though this was a silly example using placecage.com, it gives us a good idea of how we can easily set this up.</p>
<p>You'll also notice that this specific example wasn't the best use case for a hooks, where we could have simply used a function. Typically, we'll want to use custom hooks to wrap functionality that can only live inside a React component, such as <code>useState</code>. To learn more about that, you can read one of my other <a target="_blank" href="https://www.freecodecamp.org/news/how-to-destructure-the-fundamentals-of-react-hooks-d13ff6ea6871/">articles about custom hooks</a>.</p>
<p>However, this gave us a good basis to talk through the creation and configuration of our new hook!</p>
<h2 id="heading-more-resources-about-hooks">More resources about hooks</h2>
<ul>
<li><a target="_blank" href="https://www.freecodecamp.org/news/how-to-destructure-the-fundamentals-of-react-hooks-d13ff6ea6871/">How to destructure the fundamentals of React Hooks</a> (freecodecamp.org)</li>
<li><a target="_blank" href="https://reactjs.org/docs/hooks-intro.html">Introducing Hooks</a> (reactjs.org)</li>
<li><a target="_blank" href="https://reactjs.org/docs/hooks-reference.html">Hooks API Reference</a> (reactjs.org)</li>
</ul>
<div id="colbyfayock-author-card">
  <p>
    <a href="https://twitter.com/colbyfayock">
      <img src="https://res.cloudinary.com/fay/image/upload/w_2000,h_400,c_fill,q_auto,f_auto/w_1020,c_fit,co_rgb:007079,g_north_west,x_635,y_70,l_text:Source%20Sans%20Pro_64_line_spacing_-10_bold:Colby%20Fayock/w_1020,c_fit,co_rgb:383f43,g_west,x_635,y_6,l_text:Source%20Sans%20Pro_44_line_spacing_0_normal:Follow%20me%20for%20more%20JavaScript%252c%20UX%252c%20and%20other%20interesting%20things!/w_1020,c_fit,co_rgb:007079,g_south_west,x_635,y_70,l_text:Source%20Sans%20Pro_40_line_spacing_-10_semibold:colbyfayock.com/w_300,c_fit,co_rgb:7c848a,g_north_west,x_1725,y_68,l_text:Source%20Sans%20Pro_40_line_spacing_-10_normal:colbyfayock/w_300,c_fit,co_rgb:7c848a,g_north_west,x_1725,y_145,l_text:Source%20Sans%20Pro_40_line_spacing_-10_normal:colbyfayock/w_300,c_fit,co_rgb:7c848a,g_north_west,x_1725,y_222,l_text:Source%20Sans%20Pro_40_line_spacing_-10_normal:colbyfayock/w_300,c_fit,co_rgb:7c848a,g_north_west,x_1725,y_295,l_text:Source%20Sans%20Pro_40_line_spacing_-10_normal:colbyfayock/v1/social-footer-card" alt="Follow me for more Javascript, UX, and other interesting things!" width="2000" height="400" loading="lazy">
    </a>
  </p>
  <ul>
    <li>
      <a href="https://twitter.com/colbyfayock">? Follow Me On Twitter</a>
    </li>
    <li>
      <a href="https://youtube.com/colbyfayock">?️ Subscribe To My Youtube</a>
    </li>
    <li>
      <a href="https://www.colbyfayock.com/newsletter/">✉️ Sign Up For My Newsletter</a>
    </li>
  </ul>
</div>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ The Best Way to Learn Frontend Web Development ]]>
                </title>
                <description>
                    <![CDATA[ By Mehul Mohan Why web development? Web development is a field that is not going anywhere anytime soon. The web is moving quickly, and there are regular improvements to the devices many people use daily.  Cloud-based applications have allowed us to d... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/learn-frontend-web-development/</link>
                <guid isPermaLink="false">66d46065e39d8b5612bc0ddf</guid>
                
                    <category>
                        <![CDATA[ frontend ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Thu, 09 Apr 2020 22:48:37 +0000</pubDate>
                <media:content url="https://cdn-media-2.freecodecamp.org/w1280/5f9c9bb1740569d1a4ca2d56.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Mehul Mohan</p>
<h2 id="heading-why-web-development">Why web development?</h2>
<p>Web development is a field that is not going anywhere anytime soon. The web is moving quickly, and there are regular improvements to the devices many people use daily. </p>
<p>Cloud-based applications have allowed us to do the heavy lifting in the cloud and use the browser as a bridge to connect powerful backend servers with less capable and powerful frontends. </p>
<p>Now is the best time to learn web development if you're looking to learn something.</p>
<p>Not only does web development open a plethora of opportunities for you as an individual, but you'll also be able to code and deploy complete projects end-to-end which can be consumed by almost everyone on the planet. </p>
<p>Since most people have access to a browser, every person is just a single URL away from the web-app you've coded. If you compare this with a desktop application or a hardware-based product, you'll see that it's just not that scalable compared to the web. </p>
<p>Now that we've established the fact that the web is the best way to go right now, let's see how to learn web development in the best way possible.</p>
<h2 id="heading-the-web-is-vast-start-small-and-keep-it-small">The web is vast – start small and keep it small</h2>
<p>You have probably seen a large number of JavaScript frameworks and libraries out there like React, Angular, Vue, Ember, jQuery, XYZ, and so on.</p>
<p>Some very common questions I get as a YouTuber posting web development content are the following:</p>
<p>Should I learn X?</p>
<p>Is X better than Y?</p>
<p>What is the scope of Z?</p>
<p>Always remember two things about technology, especially about the web:</p>
<ol>
<li>Your tech stack almost always never matters. If you're good enough with your language/framework, you'll win every time.</li>
<li>You never need to learn everything. Never. You can be a jack of all trades, but make sure you're a master of one.</li>
</ol>
<p>To summarize this section, <strong>you need to think very small when you start</strong>. It is super easy to get carried away with that hot framework that was just released. Just don't try to race ahead by dropping old tech and adapting new tech without doing proper research.</p>
<h2 id="heading-start-with-html-and-css">Start with HTML and CSS</h2>
<p>I cannot stress this enough. It pains me to see a ton of people asking me questions about their React projects when the answer lies in basic HTML or CSS.</p>
<p>JavaScript was only a browser scripting language in the beginning. And although it's much more than that now, it doesn't change the fact that on the web it complements HTML and CSS.</p>
<p>With advancements in web standards, there's so much you can achieve in CSS alone. For a lot of things, JavaScript is not even required. </p>
<p>Animations? You got it. Drag-and-drop? HTML can handle that. Layouts? Checkout the Flexbox or Grid CSS APIs! </p>
<p>There are so many things you can learn about HTML and CSS! Don't just rush into JavaScript because all the cool kids on the block are into Angular. Those libraries and frameworks are not going anywhere. <strong>Take your time</strong>.</p>
<h2 id="heading-go-wide-or-go-deep">Go wide or go deep?</h2>
<p>There are two approaches when you learn web development – go wide or go deep. </p>
<p>Going wide means you start learning many things together. This might work for some people, but not for others. You might overwhelm yourself with tutorials, exercises, videos, and blogs, then eventually give up. </p>
<p>Going deep means you start learning one thing and try to learn as much as you can about it. This has its own set of pros and cons. You might get bored, or give up because you don't see results.</p>
<p>So what's the solution? The answer is, surprisingly, doing neither. Choose a small tech stack like HTML/CSS/JavaScript, and go moderately deep into all three. This has a couple of advantages:</p>
<ol>
<li>You won't get bored, as all three have relatively different purposes and you write them differently.</li>
<li>You can blend all three, and create and see something meaningful quickly without months of effort (which is usually required by other languages like C/C++). This will keep you motivated to keep going.</li>
</ol>
<h2 id="heading-do-not-choose-react-or-angular-or-vue">Do not choose React, or Angular, or Vue</h2>
<p>When people start to work with JavaScript, there's an urge to pick up a library like React, a UI system like Material UI, and dive into building awesome stuff. </p>
<p>Unfortunately, this leads to nothing more than shooting yourself in the foot. You can never, ever, create intermediate to advanced projects with these frameworks if you don't understand the basics of JavaScript. And applying JavaScript takes some time. </p>
<p>Notice that I used the word applying, and not learning, in the previous sentence. There's a huge difference between learning something and applying something you've learned. </p>
<p>Create a small but working project with HTML/CSS/JavaScript. And when you do, create one more, and then, create another project. </p>
<p>Every step step along the way, keep increasing the complexity of the project, and the expectations too, until your codebase becomes unmanageable. At that point you've arrived at what I like to call <em>the boundary of superior learning</em>.</p>
<p>You see, frameworks exist to offload repetitive work from you. They do not exist so that you ignore what's really going on under the hood and rely on the fact that it's all magic. </p>
<p>The first time you choose a framework like React or Angular for your projects should be <strong>when you're confident that you can create that project without React or Angular</strong>.</p>
<h2 id="heading-master-of-one-trade">Master of one trade</h2>
<p>Now, when you're good with HTML/CSS/JavaScript, it's time to move on to industry standards. The truth is, no matter how good of a JavaScript developer you may be, you will often need to work with modern frameworks like React – and just with your JavaScript knowledge, you cannot immediately master it. </p>
<p>It'll take time to learn and understand the terminology, the concepts, and how that framework works under the hood. So take up a framework and master it. </p>
<p>How, you might ask? The answer is simple – it's up to you. Create a simple project in all the major frameworks (Angular, Vue, and React), and see which one vibes the most with you. Choose one and don't look back</p>
<p>Then create all your projects with that framework. Aim to do something which you don't already know how to do. This is the best way to <strong>push yourself out of your comfort zone and learn new things</strong>.</p>
<h2 id="heading-but-dont-forget-about-the-others">But don't forget about the others</h2>
<p>It's equally important to keep an eye on other candidates too. This not only involves direct competitors of your "favorite" framework out there, but also some worthy web development tools too. </p>
<p>You don't have to "master" these other tools because a lot of times they just require a one-time setup with regular maintenance. But it's always nice to keep them on your checklist of skills.</p>
<p>This could include several things - like unit testing with Jest, end-to-end testing with Cypress, Webpack, Babel, or Parcel, a bit of devops, Linux shell scripting, server deployments, and so on.</p>
<p>You can always learn the basics of these skills quickly as they've been around for a long time, so they have a lot of helpful material available with them.</p>
<h2 id="heading-move-on">Move on</h2>
<p>Trust me, it's a funny feeling – but once you develop many projects and write a lot of code with your favorite stack, you start feeling like you can do anything. At this point, you can get a job and just keep doing the same thing - that's completely fine. Or, you can level up your skills through the roof and move on. </p>
<p>The web. Is. Vast. </p>
<p>So if you're done with HTML/CSS/JavaScript/React/Angular/Vue, move on to servers. Learn about server deployment, NGiNX, HAProxy, load balancing, setting up cloud infrastructure, firewalls, and autoscaling.</p>
<p>Done with that too? Move on to WebAssembly, the language for the next iteration of the web. </p>
<p>Finished learning WebAssembly? Err, I don't know then. Maybe move on to other programming areas, or cooking? :)</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In today's world, it's so easy for you to not repeat the mistakes I made while learning web development myself. Because all those mistakes are now public for everyone to read! </p>
<p>You have so many resources available to you, and I've been working on one for web developers to learn! Here's my <a target="_blank" href="https://youtube.com/codedamn">YouTube channel</a> where I teach you how to program awesome things. And here's the <a target="_blank" href="https://codedamn.com">codedamn platform</a> – the actual platform which allows you to become a great developer by blending a bunch of videos, articles, and exercises.</p>
<p>Let me know what you think about this article, and web development in general, through my <a target="_blank" href="https://twitter.com/mehulmpt">twitter</a> and <a target="_blank" href="https://instagram.com/mehulmpt">Instagram</a> handles. I would love to connect with you out there!</p>
<p>Peace!</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
