<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Menard Maranan</title>
    <description>The latest articles on DEV Community by Menard Maranan (@menard_codes).</description>
    <link>https://dev.to/menard_codes</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F645742%2F97aa5eab-56cf-4594-9d46-7141dcfa6f65.jpg</url>
      <title>DEV Community: Menard Maranan</title>
      <link>https://dev.to/menard_codes</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/menard_codes"/>
    <language>en</language>
    <item>
      <title>Why Alt Text Matters for Shopify Seo</title>
      <dc:creator>Menard Maranan</dc:creator>
      <pubDate>Mon, 09 Mar 2026 11:04:45 +0000</pubDate>
      <link>https://dev.to/menard_codes/why-alt-text-matters-for-shopify-seo-4kl9</link>
      <guid>https://dev.to/menard_codes/why-alt-text-matters-for-shopify-seo-4kl9</guid>
      <description>&lt;p&gt;Most Shopify merchants spend hours obsessing over the right keywords, building backlinks, and tweaking meta descriptions — yet they leave one of the easiest SEO wins completely untouched: image alt text.&lt;/p&gt;

&lt;p&gt;If your store has dozens or hundreds of product images with no alt text, you're leaving rankings, traffic, and sales on the table. Here's why alt text matters more than most merchants realize, and what you can do about it.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Is Alt Text?
&lt;/h2&gt;

&lt;p&gt;Alt text (short for "alternative text") is a short written description attached to an image. It lives in the HTML behind your storefront and is invisible to shoppers under normal circumstances — but it's read by search engines and screen readers.&lt;/p&gt;

&lt;p&gt;In Shopify, you can add alt text to product images directly from the product editor.&lt;/p&gt;

&lt;p&gt;&lt;a href="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%2Fff3e535ehf8zx8dn82jz.webp" class="article-body-image-wrapper"&gt;&lt;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%2Fff3e535ehf8zx8dn82jz.webp" alt="Editing alt text in Shopify" width="800" height="401"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In HTML, it looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"blue-running-shoes.jpg"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Men's blue lightweight running shoes with cushioned sole"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simple enough. But that small attribute has a surprisingly large impact.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Google Cares About Alt Text
&lt;/h2&gt;

&lt;p&gt;Google can't see images the way humans do. Instead, it relies on text signals to understand what an image contains — and alt text is the most direct signal available.&lt;/p&gt;

&lt;p&gt;When Google crawls your Shopify store, it reads the alt text on every image to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Understand the content of the page.&lt;/strong&gt; Alt text reinforces the topic of the surrounding page, adding relevance context for your target keywords.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Index images in Google Image Search.&lt;/strong&gt; Product images that rank in image search can drive significant discovery traffic — especially in fashion, home decor, and lifestyle categories.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Determine ranking relevance.&lt;/strong&gt; Pages with well-optimized alt text tend to rank better for their target queries because the text signals align with what the page is about.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In short, every untagged image is a missed opportunity to communicate with Google about what your page is selling.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Shopify-Specific Opportunity
&lt;/h2&gt;

&lt;p&gt;Here's something worth knowing: the majority of Shopify stores have incomplete or entirely missing alt text across their product catalog.&lt;/p&gt;

&lt;p&gt;That's not an insult — it's just the reality of how stores are built. When you upload product photos, Shopify doesn't automatically generate alt text. The default is either blank or pulled from the image filename (more on why that's a problem shortly). Most merchants never go back to fix it.&lt;/p&gt;

&lt;p&gt;This creates a real competitive edge for the stores that do. If you sell running shoes and your product images have descriptive, keyword-relevant alt text while your competitors' images are blank, your pages have a meaningful advantage in Google's eyes.&lt;/p&gt;

&lt;p&gt;Beyond product images, there are other alt text fields merchants often overlook:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Collection page images&lt;/strong&gt; — often set once and forgotten&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blog post images&lt;/strong&gt; — a missed opportunity on an already SEO-focused asset&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Homepage banners and lifestyle images&lt;/strong&gt; — high-visibility images that frequently go untagged&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every image on your store is an opportunity. Most stores leave them all blank.&lt;/p&gt;




&lt;h2&gt;
  
  
  Alt Text and Accessibility — A Bonus Win
&lt;/h2&gt;

&lt;p&gt;Alt text wasn't originally invented for SEO. It was created for accessibility — specifically, to help visually impaired shoppers who use screen readers navigate the web.&lt;/p&gt;

&lt;p&gt;When a screen reader encounters an image with no alt text, it might read out the filename ("IMG_4823.jpg") or skip it entirely. For a visually impaired shopper trying to evaluate your product, that's a frustrating and exclusionary experience.&lt;/p&gt;

&lt;p&gt;Writing descriptive alt text means those shoppers can understand what your product looks like, which builds trust and increases the likelihood of a purchase.&lt;/p&gt;

&lt;p&gt;There's also a growing connection between accessibility and SEO. Google has indicated that user experience signals matter for rankings, and an accessible store is a better experience for everyone. Beyond rankings, accessibility is increasingly a legal and ethical consideration for online businesses.&lt;/p&gt;

&lt;p&gt;Good alt text serves your SEO and your shoppers at the same time.&lt;/p&gt;




&lt;h2&gt;
  
  
  Common Mistakes Shopify Merchants Make
&lt;/h2&gt;

&lt;p&gt;If you've started thinking about alt text, here are the pitfalls to avoid:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Leaving it blank.&lt;/strong&gt; The most common mistake. No alt text means no signal — Google and screen readers get nothing useful from your images.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Using raw file names.&lt;/strong&gt; Shopify will sometimes use the image filename as a fallback. "DSC_00142.jpg" or "product-image-v3-FINAL.png" tells Google absolutely nothing about your product.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keyword stuffing.&lt;/strong&gt; Cramming as many keywords as possible into alt text ("buy cheap blue running shoes men running shoes discount free shipping") looks spammy to Google and is a poor experience for screen reader users. Google can and does penalize this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Generic descriptions.&lt;/strong&gt; "A shoe" or "product image" technically isn't blank, but it's nearly as useless. Vague descriptions don't help Google understand what's unique about your product.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Good Alt Text Looks Like
&lt;/h2&gt;

&lt;p&gt;Good alt text is descriptive, specific, and naturally includes relevant keywords without forcing them. Here are some before-and-after examples:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Image&lt;/th&gt;
&lt;th&gt;Bad Alt Text&lt;/th&gt;
&lt;th&gt;Good Alt Text&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A pair of blue running shoes&lt;/td&gt;
&lt;td&gt;&lt;em&gt;(blank)&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;Men's blue lightweight running shoes with cushioned sole&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A ceramic coffee mug&lt;/td&gt;
&lt;td&gt;"mug"&lt;/td&gt;
&lt;td&gt;Handmade ceramic coffee mug in matte terracotta glaze, 12oz&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A floral summer dress&lt;/td&gt;
&lt;td&gt;"IMG_8821.jpg"&lt;/td&gt;
&lt;td&gt;Women's floral wrap midi dress in yellow and white, sleeveless&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A leather wallet&lt;/td&gt;
&lt;td&gt;"product"&lt;/td&gt;
&lt;td&gt;Slim minimalist leather bifold wallet in tan, holds 6 cards&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The formula is straightforward: &lt;strong&gt;what it is + key descriptive details + material or context where relevant&lt;/strong&gt;. Write it the way a customer would describe the product to someone who can't see it. Keywords tend to appear naturally when you do this well.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Scale Problem — Why Most Stores Don't Do This
&lt;/h2&gt;

&lt;p&gt;Here's the honest reality: if you understand why alt text matters, you probably want to fix it across your entire store. But that's easier said than done.&lt;/p&gt;

&lt;p&gt;A Shopify store with 200 products, each with 4–5 images, means 800–1,000 images to write alt text for. Doing that manually — opening each product, writing a description, saving, moving to the next — would take days of tedious, repetitive work. And that's before you consider new products being added regularly.&lt;/p&gt;

&lt;p&gt;This is exactly why most stores never get it done, even when the owner knows they should. The intent is there; the bandwidth isn't.&lt;/p&gt;

&lt;p&gt;This is the problem Syncor Alt was built to solve: AI-powered bulk alt text generation for Shopify stores, so merchants can optimize their entire image catalog in minutes rather than days.&lt;/p&gt;




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

&lt;p&gt;Alt text is one of the highest-leverage, lowest-effort SEO improvements available to Shopify merchants.&lt;/p&gt;

&lt;p&gt;Your alt texts — properly written, SEO-optimized, and WCAG-compliant is an easy and quick SEO win. &lt;a href="https://apps.shopify.com/syncor-alt" rel="noopener noreferrer"&gt;Syncor Alt&lt;/a&gt; takes the manual work off your plate, generating SEO-optimized and WCAG compliant alt text that is loved by Search Engines.&lt;/p&gt;

&lt;p&gt;Get started with 50 free credits — enough to see the difference on 50 of your product images right away. Syncor Alt doesn’t have subscriptions, so it’s 100% risk free.&lt;/p&gt;

&lt;p&gt;Try Syncor Alt - &lt;a href="https://apps.shopify.com/syncor-alt" rel="noopener noreferrer"&gt;https://apps.shopify.com/syncor-alt&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Or watch this demo to see how it works: 🎥 &lt;a href="https://www.youtube.com/watch?v=09YwpX8VCnc" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=09YwpX8VCnc&lt;/a&gt;&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>seo</category>
      <category>a11y</category>
      <category>ai</category>
    </item>
    <item>
      <title>Traffic Light - vanilla HTML, CSS, &amp; JavaScript</title>
      <dc:creator>Menard Maranan</dc:creator>
      <pubDate>Thu, 05 Mar 2026 03:47:10 +0000</pubDate>
      <link>https://dev.to/menard_codes/traffic-light-vanilla-html-css-javascript-10em</link>
      <guid>https://dev.to/menard_codes/traffic-light-vanilla-html-css-javascript-10em</guid>
      <description>&lt;p&gt;I created this fun little project as a refresher.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🌐 Demo: &lt;a href="https://menard-codes.github.io/traffic-light/" rel="noopener noreferrer"&gt;https://menard-codes.github.io/traffic-light/&lt;/a&gt;&lt;br&gt;
👨‍💻 Code: &lt;a href="https://github.com/menard-codes/traffic-light" rel="noopener noreferrer"&gt;https://github.com/menard-codes/traffic-light&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;I haven't touched pure vanilla HTML, CSS, and JavaScript for a while.&lt;/p&gt;

&lt;p&gt;React, Tailwind, and TypeScript is what I use everyday in the frontend, so I want to challenge myself if I still got the basics right without relying on modern conveniences of frameworks and tools.&lt;/p&gt;

&lt;p&gt;And since there's no React to help me manage component state, I have to rely on the &lt;a href="https://refactoring.guru/design-patterns/observer" rel="noopener noreferrer"&gt;observer pattern&lt;/a&gt; if I want a similar functionality.&lt;/p&gt;

&lt;p&gt;So I decided to try and build a Traffic Light simulator, only using HTML, CSS, and JavaScript.&lt;/p&gt;

&lt;h2&gt;
  
  
  Markpup
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!doctype html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Document&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"style.css"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;main&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"traffic-light"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"timer-box"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"timer"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"box"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"light red"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"light yellow"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"light green"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"./script.js"&lt;/span&gt; &lt;span class="na"&gt;defer&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Stylesheet
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="o"&gt;*,&lt;/span&gt;
&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nd"&gt;::before&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nd"&gt;::after&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;box-sizing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;border-box&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;main&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;min-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100vh&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;place-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.traffic-light&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.traffic-light&lt;/span&gt; &lt;span class="nc"&gt;.box&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#1d1e22&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.5rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;flex-direction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.2rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;justify-content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;align-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.light&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;aspect-ratio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.red&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#500&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.red.on&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#f00&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.yellow&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#550&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.yellow.on&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#ff0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.green&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#050&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.green.on&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#0f0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.timer-box&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#555&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="m"&gt;#333&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.5rem&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;#timer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;"Courier New"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Courier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;monospace&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;700&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#0f0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;#timer&lt;/span&gt;&lt;span class="nc"&gt;.warn&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#f00&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Traffic Light logic
&lt;/h2&gt;

&lt;p&gt;I created 3 classes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Timer&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Light&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TrafficLight&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;TrafficLight&lt;/code&gt; and &lt;code&gt;Light&lt;/code&gt; classes follow the &lt;a href="https://refactoring.guru/design-patterns/observer" rel="noopener noreferrer"&gt;observer pattern&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;TrafficLight.run()&lt;/code&gt; method handles switching the currently active light, notifying the subscribers, and running/resetting the timer on every light switch.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DOMContentLoaded&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Timer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;_currentCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;_interval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;_timer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#timer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_timer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_currentCount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * @param {{ maxCount: number; currentColor: TrafficLightColor }}
     */&lt;/span&gt;
    &lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;maxCount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;currentColor&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Reset count and clear any existing interval before starting fresh&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_currentCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;maxCount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_setTimerDisplay&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;currentColor&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_interval&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;clearInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_interval&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_interval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setInterval&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_currentCount&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_currentCount&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_setTimerDisplay&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;currentColor&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_interval&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;clearInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_interval&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_interval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_currentCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_timer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;warn&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * @param {{ currentColor: TrafficLightColor }} param0
     */&lt;/span&gt;
    &lt;span class="nf"&gt;_setTimerDisplay&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;currentColor&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_timer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_currentCount&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;padStart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentColor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;green&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_timer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;go&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_timer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;wait&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_timer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stop&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;red&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_timer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stop&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_timer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;wait&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_timer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;go&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;yellow&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_timer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;wait&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_timer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;go&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_timer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stop&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="cm"&gt;/**
   * @typedef {'red' | 'yellow' | 'green'} TrafficLightColor
   */&lt;/span&gt;
  &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Light&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cm"&gt;/**
     * @param {TrafficLightColor} color
     */&lt;/span&gt;
    &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_dom&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * @param {TrafficLightColor} currentColor
     */&lt;/span&gt;
    &lt;span class="nf"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentColor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentColor&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_turnOn&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_turnOff&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;_turnOn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_dom&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;on&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;_turnOff&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_dom&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;on&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TrafficLight&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;_listeners&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Light&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;red&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Light&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;yellow&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Light&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;green&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     *
     * @param {{ timeInterval: number }} {timeInterval} - Time interval in seconds for switching the lights.
     */&lt;/span&gt;
    &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;timeInterval&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_timeInterval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;timeInterval&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Timer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;listenersLength&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_listeners&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;currentPointer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;listenersLength&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nextPointer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;currentPointer&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;currentPointer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;nextPointer&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;listenersLength&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;nextPointer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;colors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_listeners&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currentColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;currentPointer&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_listeners&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentColor&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

        &lt;span class="c1"&gt;// Start timer AFTER switching the light, so count is in sync with current light&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sleepInMS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
          &lt;span class="nx"&gt;currentColor&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;yellow&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_timeInterval&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_timeInterval&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;
            &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_timeInterval&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="nx"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;maxCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sleepInMS&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;currentColor&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sleepInMS&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nx"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;_sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;trafficLight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TrafficLight&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;timeInterval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;trafficLight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;Unintentionally, I also had to refresh my understanding of the browser &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Execution_model" rel="noopener noreferrer"&gt;event loop&lt;/a&gt;. I ran through several bugs because of improper use of intervals, timeouts, and promises. That reminded me of the time when I was first learning about the event loop, pulling my hair out of frustration trying to understand it, lol.&lt;/p&gt;

</description>
      <category>html</category>
      <category>css</category>
      <category>javascript</category>
      <category>designpatterns</category>
    </item>
    <item>
      <title>Developer-Focused Resume Builder: An MVP in Progress</title>
      <dc:creator>Menard Maranan</dc:creator>
      <pubDate>Tue, 08 Apr 2025 16:38:40 +0000</pubDate>
      <link>https://dev.to/menard_codes/developer-focused-resume-builder-an-mvp-in-progress-41f9</link>
      <guid>https://dev.to/menard_codes/developer-focused-resume-builder-an-mvp-in-progress-41f9</guid>
      <description>&lt;p&gt;I've been thinking about a problem I've noticed in the tech job search process.&lt;/p&gt;

&lt;p&gt;Well, there are a lot of problems in that matter, but something that I've always wanted is a more developer-focused resume builder.&lt;/p&gt;

&lt;p&gt;I don't really build my resumes manually. It's time-consuming, tedious, and too boring for me so I just use a resume builder.&lt;/p&gt;

&lt;p&gt;While there are plenty of resume builders out there, most are designed for any profession giving you a generic, cookie-cutter resume.&lt;/p&gt;

&lt;p&gt;I just don't like how they don't really cater to the unique aspects of showcasing developer skills.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem I'm Trying to Solve
&lt;/h2&gt;

&lt;p&gt;Generic resume builders often miss what makes developers stand out. Some things I noticed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Many aren't structured to showcase technical skills effectively&lt;/li&gt;
&lt;li&gt;Rarely highlights developer-specific achievements (PRs, commits, hackathons)&lt;/li&gt;
&lt;li&gt;Don't emphasize GitHub contributions and open-source work&lt;/li&gt;
&lt;li&gt;Many of them don't even optimize for technical ATS systems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's just hard to make it appear on a resume how well you know a certain technology is. Sometimes those pesky algorithms and HRs who don't have much technical background just dump your resume just because it appears to them that you're not fit enough for the job (arrgghh).&lt;/p&gt;

&lt;p&gt;On the flip side, I've seen other people's resumes and what I often take an interest in is the skills section. You'll often see it sprinkled with a variety of languages, frameworks, and tools, without giving any context on how much expertise or experience they have on each of those technologies.&lt;/p&gt;

&lt;p&gt;Have they just created a Hello World program with this language or actually use it on a day-to-day basis?&lt;/p&gt;

&lt;p&gt;Anyway... We're moving away from the topic but I hope you get the idea. So going back...&lt;/p&gt;

&lt;h2&gt;
  
  
  My Idea: A Developer-Focused Resume Builder
&lt;/h2&gt;

&lt;p&gt;I'm working on a specialized resume builder that's designed specifically for developers. The goal is to help you create resumes that better represent your technical background and increase your chances of landing dev jobs.&lt;/p&gt;

&lt;p&gt;Some features I'm considering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Github integration to get to know your projects, PRs, commits, etc. and generate parts of the resume from that (skills and expertise, achievements, projects, soft skills, etc.).&lt;/li&gt;
&lt;li&gt;Templates designed with developer roles in mind&lt;/li&gt;
&lt;li&gt;Sections for GitHub projects, Stack Overflow contributions, and open-source work. Maybe with dev.to hackathons too?&lt;/li&gt;
&lt;li&gt;ATS optimization specific to tech job applications&lt;/li&gt;
&lt;li&gt;Quick skills section that understands the difference between "proficient in" vs "familiar with"&lt;/li&gt;
&lt;li&gt;And moar...&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The MVP Is Live!
&lt;/h2&gt;

&lt;p&gt;I've built a stupidly simple MVP (without any of the feature I mentioned, just yet) to get an idea if the dev community would even take an interest in this.&lt;/p&gt;

&lt;p&gt;It's very basic right now - just a multi-step form that collects your information and generates a downloadable PDF. No fancy templates and all those cool features I've thought of just yet, but the idea is there.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://dev-resume-orcin.vercel.app/" rel="noopener noreferrer"&gt;&lt;strong&gt;Dev Resume Builder&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://dev-resume-orcin.vercel.app/" rel="noopener noreferrer"&gt;&lt;strong&gt;https://dev-resume-orcin.vercel.app/&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I built it with React Router 7 for quick development. This is just a validation prototype - if there's enough interest, I plan to rebuild with a more robust tech stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  I Need Your Input
&lt;/h2&gt;

&lt;p&gt;I'm still in the ideation and validation stage, and I'd love to hear your thoughts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Would you use a resume builder specifically designed for developers?&lt;/li&gt;
&lt;li&gt;What features would make it worth using over generic tools?&lt;/li&gt;
&lt;li&gt;What's your biggest pain point when creating a developer resume?&lt;/li&gt;
&lt;li&gt;Any other feedback on the concept or the MVP?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're interested in updates as this idea develops, you can sign up for the mailing list on the MVP site.&lt;/p&gt;

&lt;p&gt;Thanks for reading, and I'm looking forward to your thoughts!&lt;/p&gt;

</description>
      <category>career</category>
      <category>discuss</category>
      <category>programming</category>
      <category>coding</category>
    </item>
    <item>
      <title>Secure File Share - A safer way to share sensitive files online</title>
      <dc:creator>Menard Maranan</dc:creator>
      <pubDate>Sun, 13 Oct 2024 23:37:40 +0000</pubDate>
      <link>https://dev.to/menard_codes/secure-file-transfer-a-safer-way-to-share-sensitive-files-online-2nnj</link>
      <guid>https://dev.to/menard_codes/secure-file-transfer-a-safer-way-to-share-sensitive-files-online-2nnj</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/pinata"&gt;The Pinata Challenge &lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;I've developed "Secure File Share", an open-source, self-hostable file sharing solution that addresses a common need in our digital world: the secure sharing of sensitive files without relying on third-party services.&lt;/p&gt;

&lt;h2&gt;
  
  
  Inspiration
&lt;/h2&gt;

&lt;p&gt;Being a developer working with a remote team, it is very common for us to share sensitive company files online, which is at risk of accidentally leaking to the outside world. Files have been scattered over email attachments, chat threads, and anywhere we communicate. For now, we adopted the password-protected files sharing feature of Google Drive but we're always looking for solutions without any 3rd party involvement and where we can have full control over our files, especially if we can self-host it. Our DevOps guy is working on a solution, but if I were to build that solution, this is how I imagine it to be.&lt;/p&gt;

&lt;p&gt;This is also heavily inspired by &lt;a href="https://onetimesecret.com/" rel="noopener noreferrer"&gt;Onetimesecret&lt;/a&gt;. This is like one time secret, but for files.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it Works
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Upload a file you want to share&lt;/li&gt;
&lt;li&gt;Set a passphrase and expiration, then click Create Share Link&lt;/li&gt;
&lt;li&gt;Send the share link to your intended recipient/s. It's recommended to share the passphrase separately.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The uploaded file as well as its records will all be deleted upon the specified expiration date. Upon viewing the file, the set expiration will be overwritten and will be set to 5 minutes before it's totally gone.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key features of Secure File Transfer include:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Disposable and password protected share links&lt;/li&gt;
&lt;li&gt;No sign-up required.&lt;/li&gt;
&lt;li&gt;Fully self-hostable&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;(&lt;strong&gt;Update&lt;/strong&gt;: I already took down the demo as I'm getting a lot of traffic and I don't wanna end up with a huge bill in fly.io or redis cloud😅 besides, this project is intended to be self-hosted, so that's what I encourage everyone intending to use this to do. There are hosting services out there with generous free-tier like fly.io. I highly recommend self hosting this if you're intending to use it for full security and control over your sensitive files. I'm taking down the demo but the source code will always be available. You're free to fork it, make your own modifications if you'd like, and self-host it. I left a docker-compose file there if you don't want a separate redis server, especially if you're hosting this in a VPS.&lt;/p&gt;

&lt;h3&gt;
  
  
  Quick Peek
&lt;/h3&gt;

&lt;p&gt;&lt;a href="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%2Ftetb720llyooui9dw57i.JPG" class="article-body-image-wrapper"&gt;&lt;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%2Ftetb720llyooui9dw57i.JPG" alt="Hero Image" width="800" height="391"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="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%2F6az2cv3t40frlg3lefzu.JPG" class="article-body-image-wrapper"&gt;&lt;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%2F6az2cv3t40frlg3lefzu.JPG" alt="Upload Page" width="800" height="391"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="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%2Ftrv00rcaf26wgtn1idg2.JPG" class="article-body-image-wrapper"&gt;&lt;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%2Ftrv00rcaf26wgtn1idg2.JPG" alt="Share Page" width="800" height="392"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="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%2Fn7qd5eny6nou68tv74ea.JPG" class="article-body-image-wrapper"&gt;&lt;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%2Fn7qd5eny6nou68tv74ea.JPG" alt="File View Request page" width="800" height="391"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="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%2Fx6ge9lc9l3vk7nfxhaly.JPG" class="article-body-image-wrapper"&gt;&lt;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%2Fx6ge9lc9l3vk7nfxhaly.JPG" alt="File View Page" width="800" height="391"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  My Code
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Here's the link to the Github Repo&lt;/strong&gt;: &lt;a href="https://github.com/menard-codes/secure-file-transfer" rel="noopener noreferrer"&gt;https://github.com/menard-codes/secure-file-transfer&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/menard-codes" rel="noopener noreferrer"&gt;
        menard-codes
      &lt;/a&gt; / &lt;a href="https://github.com/menard-codes/secure-file-transfer" rel="noopener noreferrer"&gt;
        secure-file-transfer
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Secure File Transfer&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/59591f15d5ee4f70d182f2b1e9256b29ab4f6ced9b577d9ad98c2eb06243315b/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f457870726573732532306a732d3030303030303f7374796c653d666f722d7468652d6261646765266c6f676f3d65787072657373266c6f676f436f6c6f723d7768697465"&gt;&lt;img src="https://camo.githubusercontent.com/59591f15d5ee4f70d182f2b1e9256b29ab4f6ced9b577d9ad98c2eb06243315b/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f457870726573732532306a732d3030303030303f7374796c653d666f722d7468652d6261646765266c6f676f3d65787072657373266c6f676f436f6c6f723d7768697465" alt="Express"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/b308ff9a6de632b94c933c0f27975188080f8cf88a115ae10338540f8d9ab8ab/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f547970655363726970742d3030374143433f7374796c653d666f722d7468652d6261646765266c6f676f3d74797065736372697074266c6f676f436f6c6f723d7768697465"&gt;&lt;img src="https://camo.githubusercontent.com/b308ff9a6de632b94c933c0f27975188080f8cf88a115ae10338540f8d9ab8ab/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f547970655363726970742d3030374143433f7374796c653d666f722d7468652d6261646765266c6f676f3d74797065736372697074266c6f676f436f6c6f723d7768697465" alt="TypeScript"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/8bc531eedbeb103d44ae3ee7e6e82bcabad9802c2710537d1bcb93264351500a/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f416c70696e652532304a532d3842433044303f7374796c653d666f722d7468652d6261646765266c6f676f3d616c70696e65646f746a73266c6f676f436f6c6f723d626c61636b"&gt;&lt;img src="https://camo.githubusercontent.com/8bc531eedbeb103d44ae3ee7e6e82bcabad9802c2710537d1bcb93264351500a/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f416c70696e652532304a532d3842433044303f7374796c653d666f722d7468652d6261646765266c6f676f3d616c70696e65646f746a73266c6f676f436f6c6f723d626c61636b" alt="AlpineJS"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/301183bc277b710d38a64b01c2b77fd7d72533925e00b5a8ffe1889001be655c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f507269736d612d3339383243453f7374796c653d666f722d7468652d6261646765266c6f676f3d507269736d61266c6f676f436f6c6f723d7768697465"&gt;&lt;img src="https://camo.githubusercontent.com/301183bc277b710d38a64b01c2b77fd7d72533925e00b5a8ffe1889001be655c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f507269736d612d3339383243453f7374796c653d666f722d7468652d6261646765266c6f676f3d507269736d61266c6f676f436f6c6f723d7768697465" alt="Prisma"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/44fda28383257633c3d9a53cab3269db3f3b330bad4bf88c49a2ed9c6394204b/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f72656469732d2532334444303033312e7376673f267374796c653d666f722d7468652d6261646765266c6f676f3d7265646973266c6f676f436f6c6f723d7768697465"&gt;&lt;img src="https://camo.githubusercontent.com/44fda28383257633c3d9a53cab3269db3f3b330bad4bf88c49a2ed9c6394204b/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f72656469732d2532334444303033312e7376673f267374796c653d666f722d7468652d6261646765266c6f676f3d7265646973266c6f676f436f6c6f723d7768697465" alt="Redis"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A &lt;code&gt;Dev x Pinata&lt;/code&gt; hackathon project for secure file sharing via password-protected disposable share links.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Overview&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;"Secure File Transfer"&lt;/strong&gt; is an open-source, self-hostable file sharing solution that allows users to generate secure, password-protected file share links with customizable expiration dates. This application doesn't require any logins or signups - it's designed for simple, secure file sharing.&lt;/p&gt;
&lt;p&gt;This app mainly solves the issue of sharing sensitive information online, without the need of signing up from any 3rd party app. Since this can be self-hosted, you can have full control over your files.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;How it Works&lt;/h2&gt;
&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Upload a file you want to share&lt;/li&gt;
&lt;li&gt;Set a passphrase and expiration, then click Create Share Link&lt;/li&gt;
&lt;li&gt;Send the share link to your intended recipient/s. It's recommended to share the passphrase separately.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Key Features&lt;/h3&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Generate secure, password-protected file share links&lt;/li&gt;
&lt;li&gt;Set custom expiration dates for shared links&lt;/li&gt;
&lt;li&gt;No user accounts or logins required&lt;/li&gt;
&lt;li&gt;…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/menard-codes/secure-file-transfer" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;(&lt;strong&gt;NOTE&lt;/strong&gt;: You can read the README for more info)&lt;/p&gt;

&lt;h3&gt;
  
  
  Tech Stack
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Backend: Express.js with TypeScript&lt;/li&gt;
&lt;li&gt;Frontend: EJS templates, Bulma CSS framework, Alpine.js&lt;/li&gt;
&lt;li&gt;Database: SQLite with Prisma ORM (easily replaceable if needed)&lt;/li&gt;
&lt;li&gt;File Storage: Pinata cloud&lt;/li&gt;
&lt;li&gt;Background Tasks: BullMQ with Redis&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I tried to be as light weight as possible when choosing the techstack and to not over engineer this. That said, I just have a simple express ts backend, ejs templates with Alpine.js (for minimal frontend interactivity) and Bulma as the CSS framework. The database is just an sqlite file since I don't see a need for a full blown database in this hackathon project, but this can be easily changed since I used Prisma ORM for it. Just modify the &lt;code&gt;datasource&lt;/code&gt;. I use BullMQ with Redis for background tasks (handling file expiration). And of course, Pinata cloud. I hosted this in fly.io as they have a generous free tier plan but this can be hosted anywhere, like in VPS (there's a docker compose you can use).&lt;/p&gt;

&lt;h2&gt;
  
  
  More Details
&lt;/h2&gt;

&lt;p&gt;Pinata plays a pivotal role in this project as all the shared files need to be stored somewhere, and that's where Pinata fits.&lt;/p&gt;

&lt;p&gt;Here's the &lt;code&gt;myPinata.ts&lt;/code&gt; file where I primarily interacted with Pinata via the Files SDK&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;FileObject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;PinataSDK&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pinata&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;dotenv&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dotenv&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;dotenv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pinata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PinataSDK&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;pinataJwt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PINATA_JWT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;pinataGateway&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PINATA_GATEWAY&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;uploadFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FileObject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;upload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;pinata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;upload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;upload&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getFileUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expires&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1800&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signedUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;pinata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gateways&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createSignedURL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="nx"&gt;cid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;expires&lt;/span&gt; &lt;span class="c1"&gt;// defaults to 30 minutes (1800 seconds)&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;signedUrl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;deleteFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fileId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;deleted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;pinata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;fileId&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;deleted&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I use these three functions in my endpoint handlers to handle file operations with Pinata. My background tasks also use the &lt;code&gt;deleteFile&lt;/code&gt; function, since these background tasks are used for dispatching deletion jobs at the set expiration of the files.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>pinatachallenge</category>
      <category>webdev</category>
      <category>api</category>
    </item>
    <item>
      <title>Star Wars Themed Solar System Guide - Glam Up My Markup</title>
      <dc:creator>Menard Maranan</dc:creator>
      <pubDate>Sun, 08 Sep 2024 08:04:50 +0000</pubDate>
      <link>https://dev.to/menard_codes/glam-up-my-markup-star-wars-themed-solar-system-1e0f</link>
      <guid>https://dev.to/menard_codes/glam-up-my-markup-star-wars-themed-solar-system-1e0f</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/challenges/frontend-2024-09-04"&gt;Frontend Challenge v24.09.04&lt;/a&gt;, Glam Up My Markup: Space&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Explore the Solar System: A Star Wars-Inspired Galactic Journey&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Imagine stumbling upon a legendary travel brochure from a galaxy far, far away—a guide to a mysterious star system known as the Solar System. In the spirit of the Star Wars universe, this project transforms our own celestial neighborhood into an intergalactic destination for adventurous travelers. From the shimmering orbits of the planets to the enigmatic asteroid belt and icy Kuiper Belt, this guide serves as your Jedi-approved travel companion. Packed with knowledge on the heavenly bodies, planets, moons, and cosmic wonders, it's like a holographic map leading you on a stellar tour through a system that feels as exotic as Tatooine, yet as familiar as home.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Demo&lt;/strong&gt;: &lt;a href="https://menard-codes.github.io/starwars-outer-space/" rel="noopener noreferrer"&gt;https://menard-codes.github.io/starwars-outer-space/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source Code (Github Repo)&lt;/strong&gt;: &lt;a href="https://github.com/menard-codes/starwars-outer-space" rel="noopener noreferrer"&gt;https://github.com/menard-codes/starwars-outer-space&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Journey
&lt;/h2&gt;

&lt;p&gt;Coming from the realm of frontend frameworks and web components, this was my first time diving into vanilla CSS and JavaScript without altering the HTML markup—a real challenge that pushed me to level up my skills in JS DOM manipulation and explore some cool CSS features. I hacked this together over a weekend, so it’s far from perfect and has a few UI quirks. If I had more time (I wish I did!), I’d smooth out all the rough edges.&lt;/p&gt;

&lt;p&gt;I also have some big ideas for future enhancements. Imagine orbiting moons, parallax effects, and an interactive experience that truly immerses you in the Solar System, Star Wars-style, but in 8-bit glory. With just two days to work on it, this was a quick project, but if I had months, I could take it to a whole new level of awesomeness.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;p&gt;I also want to credit these sources where I got the assets for this project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AI Image generation: &lt;a href="https://firefly.adobe.com/" rel="noopener noreferrer"&gt;https://firefly.adobe.com/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;AI-Generated 8-bit Pictures of the Planets, moons, and other heavenly bodies: Imagen 3 by Google Gemini &lt;a href="https://gemini.google.com/" rel="noopener noreferrer"&gt;https://gemini.google.com/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Death Star in 8-bit by FanManDan16: &lt;a href="https://www.newgrounds.com/art/view/fanmandan16/pixel-death-star" rel="noopener noreferrer"&gt;https://www.newgrounds.com/art/view/fanmandan16/pixel-death-star&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Star Destroyer pixel art: &lt;a href="https://www.pngwing.com/en/free-png-tzcob#google_vignette" rel="noopener noreferrer"&gt;https://www.pngwing.com/en/free-png-tzcob#google_vignette&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Star Wars Font by &lt;a href="https://www.dafont.com/boba-fonts.d150" rel="noopener noreferrer"&gt;Boba Fonts&lt;/a&gt;: &lt;a href="https://www.dafont.com/star-jedi.font" rel="noopener noreferrer"&gt;https://www.dafont.com/star-jedi.font&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The ITC Serif Gothic Font uploaded by Eladio Simonis: &lt;a href="https://font.download/font/itc-serif-gothic-2" rel="noopener noreferrer"&gt;https://font.download/font/itc-serif-gothic-2&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And this online tool:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To remove the background of images: &lt;a href="https://www.remove.bg/upload" rel="noopener noreferrer"&gt;https://www.remove.bg/upload&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>frontendchallenge</category>
      <category>css</category>
      <category>javascript</category>
    </item>
    <item>
      <title>My Pomodoro Clock Project</title>
      <dc:creator>Menard Maranan</dc:creator>
      <pubDate>Tue, 28 Nov 2023 15:50:47 +0000</pubDate>
      <link>https://dev.to/menard_codes/my-pomodoro-clock-project-1kmf</link>
      <guid>https://dev.to/menard_codes/my-pomodoro-clock-project-1kmf</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;: I'm learning state management in React and I built &lt;a href="https://pomodoro-react-71vv.vercel.app/" rel="noopener noreferrer"&gt;this Pomodoro Clock&lt;/a&gt;. No state management libraries used (i.e. Redux), just Context Managers, useRef, and useState. Also, this blog is more of a &lt;strong&gt;personal journal&lt;/strong&gt; rather than a technical guide or a tutorial.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Journal
&lt;/h2&gt;

&lt;p&gt;I'm using React.js for quite some time now, but lately I decided to step back and relearn the fundamental concepts.&lt;/p&gt;

&lt;p&gt;State Management is at the core of React.js which I barely focused on when I first started learning React. This gave me headaches down the road after I encountered &lt;strong&gt;deeply nested components with complex state logic&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is a common mistake committed by beginners (like me), which is &lt;strong&gt;not paying attention to the fundamentals&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Not knowing proper state management gave me problems like prop drilling and handling unnecessary states, while my code is messy, hard to read, and ill-structured. As a result, the app itself is buggy, inefficient, and have tons of unnecessary re-renders. Yikes!&lt;/p&gt;

&lt;p&gt;So with this problem, I decided to learn proper state management in React.&lt;/p&gt;

&lt;p&gt;My main resource for this is &lt;a href="https://react.dev/learn/managing-state" rel="noopener noreferrer"&gt;this section of the React docs about state management&lt;/a&gt;. After reading it, I decided to put my learnings to the test and try to build a project with complex state logic.&lt;/p&gt;

&lt;p&gt;For this, I decided to build a &lt;a href="https://en.wikipedia.org/wiki/Pomodoro_Technique" rel="noopener noreferrer"&gt;Pomodoro clock&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Project
&lt;/h2&gt;

&lt;p&gt;&lt;a href="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%2Fyaz79wqlkiczeh2mz7kq.JPG" class="article-body-image-wrapper"&gt;&lt;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%2Fyaz79wqlkiczeh2mz7kq.JPG" alt="Pomodoro Project Demo - Screenshot" width="800" height="403"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pomodoro-react-71vv.vercel.app/" rel="noopener noreferrer"&gt;Pomodoro Clock Demo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/menard-codes/pomodoro-react" rel="noopener noreferrer"&gt;Source Code&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: If you look at my code, you may still see some issues regarding proper state management and structuring my code, but this is an improvement as compared to my last projects with improper state management. I'll be kind to myself and take it as a good first step😉&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Learnings
&lt;/h2&gt;

&lt;p&gt;I learned a lot from what I read in the React docs as well as on that project, but I'll try to summarize some of my learnings.&lt;/p&gt;

&lt;h3&gt;
  
  
  Thinking in React
&lt;/h3&gt;

&lt;p&gt;This section in the React docs about &lt;a href="https://react.dev/learn/thinking-in-react" rel="noopener noreferrer"&gt;thinking in React&lt;/a&gt; resonates on me.&lt;/p&gt;

&lt;p&gt;I learned that when building React projects, you should start with a mockup and plan your project from there.&lt;/p&gt;

&lt;p&gt;By braking it down:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Have some kind of UI Wireframe or mockup of the UI&lt;/strong&gt;. This will become the basis of the rest of the plan.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Break the UI into a component hierarchy&lt;/strong&gt;. Identify which parts of the UI makes sense to be grouped together and define the components that will make up your UI based on your mockup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build a static version&lt;/strong&gt;. Start defining the markup first. State and props can be added later. You can use static data for the state and props so that you can check how your components reacts to these inputs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Find the minimal but complete representation of the UI State&lt;/strong&gt;. Identify all the data your components will rely on. Then from these data, identify which ones are dynamic (changing) and can't be calculated from other data/input. All of the data that fits this criteria is considered a state. If several components rely on a similar data, it makes sense to not give them their own separate states but rather let these components share this data so that they're in sync.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Identify where your state should live&lt;/strong&gt;. Now that you identified the states in your project, it's time to identify which components should hold these states and how shared states will be handled. If sibling components would rely on a state and they won't be deeply nested, just pass these data as props. If the components relying on this state exceeded more than 1 or two prop passings, it just makes sense to pass the state via Context Managers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Identify how to manage inverse data flow&lt;/strong&gt;. State trickles down the component tree, but for managing state changes made by child components, you need to pass down state setters as well, just like the state. Identifying where state setters should live and how to pass them is the same as how you identified where your states should live.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>react</category>
      <category>typescript</category>
    </item>
    <item>
      <title>I built a Snake Game in React</title>
      <dc:creator>Menard Maranan</dc:creator>
      <pubDate>Sun, 05 Nov 2023 09:46:44 +0000</pubDate>
      <link>https://dev.to/menard_codes/i-built-a-snake-game-in-react-48b6</link>
      <guid>https://dev.to/menard_codes/i-built-a-snake-game-in-react-48b6</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;: I built this classic "Snake Game" using React, HTML Canvas, and TypeScript. No third-party libraries are used.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="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%2Ft4cckb14n3ybopapbtqf.gif" class="article-body-image-wrapper"&gt;&lt;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%2Ft4cckb14n3ybopapbtqf.gif" alt="Snakes Game Demo" width="600" height="303"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Play the Game&lt;/strong&gt;: &lt;a href="https://snakes-game-nine.vercel.app/" rel="noopener noreferrer"&gt;https://snakes-game-nine.vercel.app/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source Code&lt;/strong&gt;: &lt;a href="https://github.com/menard-codes/snakes-game" rel="noopener noreferrer"&gt;https://github.com/menard-codes/snakes-game&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;



&lt;h2&gt;
  
  
  Background Story
&lt;/h2&gt;

&lt;p&gt;While I was bored and lying on the swing the other day, a random thought just went into my head. I remember back in the days that I used to play this Snakes game on a Nokia phone. So I wondered how could I build a game like this🤔.&lt;/p&gt;

&lt;p&gt;Web development is my thing, I'm not a game dev and I never built a game before, so this would be a challenge.&lt;/p&gt;

&lt;p&gt;But tackling a challenge is also my thing.&lt;/p&gt;

&lt;p&gt;So I sat there for a couple of hours conceptualizing this possibly &lt;em&gt;'new project'&lt;/em&gt; to see if I could pull this up. I wrote down my ideas and the structure of the project on my phone, as well as some pseudocode.&lt;/p&gt;

&lt;p&gt;After pondering about it for a while, I think I can build this thing and I have a rough concept in my phone, with all the game logic ready to be implemented and tested if this idea works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Development Time
&lt;/h2&gt;

&lt;p&gt;I was excited to start this project. So I brought my phone with my concept in it, went to my computer, and opened VS Code.&lt;/p&gt;

&lt;p&gt;I've thought of what tech should I use. Welp, I don't think I have lots of choices, I only know React (as I said, I'm just a web developer 😅). But since the game will be implemented primarily using HTML Canvas, I thought React would be optional as I can build this without a framework. But still, I pushed on as I hadn't built a React project before with this complex Canvas Element logic, so I gave it a try.&lt;/p&gt;

&lt;p&gt;Of course, I used TypeScript😉.&lt;/p&gt;

&lt;p&gt;And ohh, no CSS libraries. A plain 'ol CSS would do the trick since the UI won't be too complex and the components that I will style won't be that much.&lt;/p&gt;

&lt;p&gt;After several hours of coding (and tons of debugging), I finally managed to implement the game logic and my idea came to fruition. It's rough to the edges but the game is already working.&lt;/p&gt;

&lt;p&gt;This is the first game I made and the joy of making it work is priceless. It was just an idea moments ago and now I'm already playing it.&lt;/p&gt;

&lt;p&gt;Anyway, I'm also a bit tired at this moment so I continued the other day.&lt;/p&gt;

&lt;p&gt;So on the next day, I spent most of my hours fixing several bugs in the code. I also added some new features, like the scoring, high score, and pause/play.&lt;/p&gt;

&lt;p&gt;Then it's finished. I was so ecstatic. I showed it to my brothers and let them play for a while (and also to check if they might encounter some bugs, which they actually did).&lt;/p&gt;

&lt;p&gt;After fixing those minor bugs and making the game fully working, I hosted it on Vercel and wrote this blog.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;a href="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%2Ft4cckb14n3ybopapbtqf.gif" class="article-body-image-wrapper"&gt;&lt;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%2Ft4cckb14n3ybopapbtqf.gif" alt="Snakes Game Demo" width="600" height="303"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Play the Game&lt;/strong&gt;: &lt;a href="https://snakes-game-nine.vercel.app/" rel="noopener noreferrer"&gt;https://snakes-game-nine.vercel.app/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source Code&lt;/strong&gt;: &lt;a href="https://github.com/menard-codes/snakes-game" rel="noopener noreferrer"&gt;https://github.com/menard-codes/snakes-game&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Classic Snake gameplay&lt;/li&gt;
&lt;li&gt;Score tracking&lt;/li&gt;
&lt;li&gt;Saves HighScore&lt;/li&gt;
&lt;li&gt;Game over screen with the option to restart&lt;/li&gt;
&lt;li&gt;Keyboard controls for navigation&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Tech Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;React.js and HTML Canvas&lt;/li&gt;
&lt;li&gt;TypeScript for type safety&lt;/li&gt;
&lt;li&gt;No third-party libraries used&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Game Controls
&lt;/h2&gt;

&lt;p&gt;Use the arrow keys or &lt;code&gt;W&lt;/code&gt;,&lt;code&gt;A&lt;/code&gt;,&lt;code&gt;S&lt;/code&gt;,&lt;code&gt;D&lt;/code&gt; keys on your keyboard to control the snake's direction:&lt;/p&gt;

&lt;p&gt;↑ (Up) or &lt;code&gt;W&lt;/code&gt; - Move Up&lt;br&gt;
↓ (Down) or &lt;code&gt;S&lt;/code&gt; - Move Down&lt;br&gt;
← (Left) or &lt;code&gt;A&lt;/code&gt; - Move Left&lt;br&gt;
→ (Right) or &lt;code&gt;D&lt;/code&gt; - Move Right&lt;/p&gt;

&lt;p&gt;Others:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To Pause the game - Press &lt;code&gt;esc&lt;/code&gt; or click anywhere on the screen&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>react</category>
      <category>typescript</category>
      <category>javascript</category>
      <category>html</category>
    </item>
    <item>
      <title>React.js: Why you might need useReducer</title>
      <dc:creator>Menard Maranan</dc:creator>
      <pubDate>Wed, 05 Jul 2023 07:50:10 +0000</pubDate>
      <link>https://dev.to/menard_codes/reactjs-why-you-might-need-usereducer-3hei</link>
      <guid>https://dev.to/menard_codes/reactjs-why-you-might-need-usereducer-3hei</guid>
      <description>&lt;p&gt;Hooks in React.js is at the core of building functional components. Among the most used are the state management hooks, or simply the state hooks.&lt;/p&gt;

&lt;p&gt;In React, two hooks allow you to manage the state:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;useState&lt;/code&gt; - Gives you a state variable and a function to change the value of the state&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;useReducer&lt;/code&gt; - Gives you a state variable and a &lt;code&gt;dispatch&lt;/code&gt; function for updating the state (via another function called a &lt;code&gt;reducer&lt;/code&gt; function).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both hooks do the same thing, "to manage state", and what you can do with &lt;code&gt;useState&lt;/code&gt; can also be done with &lt;code&gt;useReducer&lt;/code&gt; and vice versa.&lt;/p&gt;

&lt;p&gt;So how do they differ?&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;useState&lt;/code&gt; hook is the simpler one and is more commonly used. Meanwhile, &lt;code&gt;useReducer&lt;/code&gt; (which is based on &lt;a href="https://redux.js.org/" rel="noopener noreferrer"&gt;Redux&lt;/a&gt; - a state management library) is often mocked to have tons of boilerplate code.&lt;/p&gt;

&lt;p&gt;But treating these hooks this way doesn't give &lt;code&gt;useReducer&lt;/code&gt; justice.&lt;/p&gt;

&lt;p&gt;There are use cases why &lt;code&gt;useReducer&lt;/code&gt; makes a better option than &lt;code&gt;useState&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In this article, I will discuss why you might want to consider using &lt;code&gt;useReducer&lt;/code&gt; in some cases over the &lt;code&gt;useState&lt;/code&gt; hook.&lt;/p&gt;

&lt;h2&gt;
  
  
  Organizing the logic for changing the state
&lt;/h2&gt;

&lt;p&gt;When managing a complex component state, your update logic might look messy, especially if you have different ways on changing the state.&lt;/p&gt;

&lt;p&gt;I'll give you a simple example - a counter component. Let's say you need to build this component with the following requirements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the component should have a state for tracking the &lt;code&gt;count&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;you should be able to increment, decrement, as well as reset the count.&lt;/li&gt;
&lt;li&gt;when the count value is 10 or more, the increment and decrement value must be 10.&lt;/li&gt;
&lt;li&gt;when the count value is -10 or less, the increment and decrement value must be 5.&lt;/li&gt;
&lt;li&gt;when the count value is between -9 and 9 (inclusive), the increment and decrement value is just 1&lt;/li&gt;
&lt;li&gt;Resetting the state reverts the &lt;code&gt;count&lt;/code&gt; value to &lt;code&gt;0&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So in this example component, we're required to have 3 update logics to the state (increment, decrement, reset) where both increment and decrement update logics have 3 different conditions.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;useState&lt;/code&gt;, you might do it this way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;


    &lt;span class="c1"&gt;// The count update logics:&lt;/span&gt;
    &lt;span class="c1"&gt;// handleIncrement, handleDecrement, and handleReset&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleIncrement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;incrementValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;incrementValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;incrementValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;incrementValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nf"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prevCount&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;prevCount&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;incrementValue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleDecrement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;decrementValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;decrementValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;decrementValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;decrementValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nf"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prevCount&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;prevCount&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;decrementValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleReset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;


    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Count: &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleIncrement&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; + &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleDecrement&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; - &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleReset&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Reset&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So in this simple component, you'll see how convoluted the component can get because of those update logics. You may reason out that you can simplify the state update logic by refactoring those three separate functions into one function that accepts a parameter to tell whether you want to increment, decrement, or reset. Sure that works, but how does that differ from creating a &lt;code&gt;reducer&lt;/code&gt; function?&lt;/p&gt;

&lt;p&gt;And this is why you might consider using &lt;code&gt;useReducer&lt;/code&gt;. With &lt;code&gt;useReducer&lt;/code&gt;, you can work with complex state management in a more organized way. The main reason why &lt;code&gt;useReducer&lt;/code&gt; would be a great choice is that you have a complex state where you have different update logics, and you want to keep things organized. This makes it easier to debug the update logics later (or update them when the requirements change).&lt;/p&gt;

&lt;p&gt;Alright, let's rewrite the same component but let's use the &lt;code&gt;useReducer&lt;/code&gt; hook here. Let's put the reducer logic in another file and just import it on the component.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// counterReducer.ts&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;CounterAction&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;increment&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;decrement&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;reset&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;CounterState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;counterReducer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CounterState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CounterAction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;changeValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
                            &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
                            &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;increment&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;changeValue&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;decrement&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;changeValue&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;reset&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="nl"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;_impossible&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Invalid action type: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Now, let's import this in the counter component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Counter.tsx&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useReducer&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;counterReducer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;CounterState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./counterReducer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;initialState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CounterState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useReducer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;counterReducer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;initialState&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Count: &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;increment&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; + &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;decrement&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; - &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;reset&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; Reset &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;So in this case, the reducer function handles all the update logic to the state. This way, you can abstract away from the nitty-gritty details of the state update logic and just specify in the component which update logic you want to take effect. And since we separated the reducer logic in another file, the &lt;code&gt;Counter&lt;/code&gt; component here no longer needs to concern itself with the update logic, making things more organized. If you need to debug the update logic, it's easier to know where to look. When requirements change and you want to update these state update logic, it's easier to go to what you're looking for and make changes.&lt;/p&gt;

&lt;p&gt;Now you might argue that this makes more boilerplate code (especially because I used TypeScript which adds more boilerplate), but eliminating boilerplate code is not the goal of &lt;code&gt;useReducer&lt;/code&gt; (because it's definitely more verbose). Rather, you should use &lt;code&gt;useReducer&lt;/code&gt; if you have to manage a complex state and you want the state update logic organized. And as mentioned earlier, organizing your state update logic this way makes it easier to debug and update later when requirements change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;Hopefully, I conveyed to you the importance of &lt;code&gt;useReducer&lt;/code&gt; and why you might want to use it in some cases over &lt;code&gt;useState&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For further reading, I suggest you to look at &lt;a href="https://react.dev/learn/extracting-state-logic-into-a-reducer" rel="noopener noreferrer"&gt;extracting state logic into a reducer&lt;/a&gt; from React documentation. There's also a challenge at the end to help you better understand &lt;code&gt;useReducer&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>react</category>
      <category>webdev</category>
      <category>frontend</category>
    </item>
    <item>
      <title>The Advantages of Medusa's Composable Architecture for Enterprise E-commerce Solutions</title>
      <dc:creator>Menard Maranan</dc:creator>
      <pubDate>Wed, 19 Apr 2023 03:30:50 +0000</pubDate>
      <link>https://dev.to/menard_codes/the-advantages-of-medusas-composable-architecture-for-enterprise-e-commerce-solutions-271n</link>
      <guid>https://dev.to/menard_codes/the-advantages-of-medusas-composable-architecture-for-enterprise-e-commerce-solutions-271n</guid>
      <description>&lt;p&gt;The e-commerce market has exploded in recent years, with more and more businesses turning to online sales to reach their customers.&lt;/p&gt;

&lt;p&gt;As the market grows, so does the need for businesses to choose the right e-commerce platform to meet their specific needs. For enterprise-level businesses, this decision is particularly crucial, as they require a solution that can handle high traffic, large product catalogs, and complex requirements.&lt;/p&gt;

&lt;p&gt;This is where Medusa comes in - a flexible, scalable, and customizable e-commerce platform that is built on a composable architecture.&lt;/p&gt;

&lt;p&gt;In this article, we will explore the advantages of Medusa's composable architecture for enterprise-level e-commerce solutions, and why it is the perfect choice for businesses looking for a comprehensive and flexible e-commerce solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Advantages of Medusa's Composable Architecture for Enterprise Solution
&lt;/h2&gt;

&lt;p&gt;Medusa's composable architecture offers several advantages for enterprise-level e-commerce solutions. It can be summed up with the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Customization and Flexibility&lt;/li&gt;
&lt;li&gt;Scalability&lt;/li&gt;
&lt;li&gt;Cost-effectiveness&lt;/li&gt;
&lt;li&gt;No Vendor Lock-In&lt;/li&gt;
&lt;li&gt;Integration with other tools&lt;/li&gt;
&lt;li&gt;Security and compliance&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Customization and Flexibility
&lt;/h3&gt;

&lt;p&gt;One of the key advantages of Medusa's composable architecture is its ability to offer a high level of customization and flexibility. This is made possible through Medusa's modular and composable architecture, which allows businesses to choose only the features and functionalities that they need, and to easily customize and extend their e-commerce solution.&lt;/p&gt;

&lt;p&gt;Medusa's modular architecture is made up of a series of interchangeable modules, each of which is responsible for a specific set of functionalities. This allows businesses to easily swap out modules, and to add or remove features as needed, without having to rebuild their entire platform.&lt;/p&gt;

&lt;p&gt;For example, a business that wants to offer a subscription service to its customers could easily add a subscription module to their Medusa e-commerce solution. This would allow customers to sign up for a subscription service, and for the business to manage and track subscriptions through the platform.&lt;/p&gt;

&lt;p&gt;In contrast, traditional e-commerce platforms like Shopify or BigCommerce offer a more rigid architecture, with limited customization options. Businesses using these platforms are often required to pay for features and functionalities that they may not need or want, and may be unable to customize the platform to meet their specific requirements.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scalability
&lt;/h3&gt;

&lt;p&gt;Another advantage of Medusa's composable architecture is its ability to scale and handle high traffic and large product catalogs. Medusa is designed with a focus on performance and speed, making it a great choice for enterprise-level businesses that need an e-commerce solution that can handle large volumes of traffic and sales.&lt;/p&gt;

&lt;p&gt;Medusa's scalable architecture is designed to handle high traffic and large product catalogs, with the ability to add and remove resources as needed. This is made possible through Medusa's modular and composable architecture, which allows businesses to easily add and remove modules as needed, and to scale their e-commerce solution as their needs grow.&lt;/p&gt;

&lt;p&gt;For example, a business that experiences a sudden spike in traffic during a holiday season can easily add additional resources to their Medusa e-commerce solution, ensuring that the platform can handle the increased traffic.&lt;/p&gt;

&lt;p&gt;In contrast, traditional e-commerce platforms like Shopify or BigCommerce may struggle to handle high traffic and large product catalogs, and may require businesses to pay for additional resources or upgrade to a more expensive plan in order to handle increased traffic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cost Effectiveness
&lt;/h3&gt;

&lt;p&gt;One of the biggest advantages of Medusa's composable architecture is its cost-effectiveness, particularly for enterprise-level businesses. Medusa is an open-source platform, which means that businesses can save money on licensing fees that they may otherwise need to pay for traditional e-commerce platforms like Shopify or BigCommerce.&lt;/p&gt;

&lt;p&gt;In addition, Medusa's modular and composable architecture allows businesses to choose only the features and functionalities that they need, rather than paying for a comprehensive e-commerce solution that includes features they may not need. This allows businesses to save money on unnecessary features and functionalities, and to focus their resources on building a customized e-commerce solution that meets their specific requirements.&lt;/p&gt;

&lt;p&gt;For example, a business that only sells physical products may not need a subscription module, and can choose not to install it, rather than paying for it as part of a larger e-commerce platform.&lt;/p&gt;

&lt;p&gt;In contrast, traditional e-commerce platforms like Shopify or BigCommerce often require businesses to pay for a comprehensive e-commerce solution, which may include features and functionalities that they do not need. This can result in higher costs for businesses, particularly for enterprise-level businesses that may require more customized e-commerce solutions.&lt;/p&gt;

&lt;h3&gt;
  
  
  No Vendor Lock In
&lt;/h3&gt;

&lt;p&gt;Another advantage of Medusa's composable architecture is that it helps businesses avoid vendor lock-in. Since Medusa is an open-source platform, businesses have full access to the codebase, which means that they are not tied to a single vendor or service provider. Businesses can choose to host their Medusa backend wherever they want along with their data.&lt;/p&gt;

&lt;p&gt;This is particularly important for enterprise-level businesses that require customized e-commerce solutions that may not be available through traditional e-commerce platforms. With Medusa's composable architecture, businesses have the flexibility to choose the vendors and service providers that best meet their needs, without being tied to a single e-commerce platform.&lt;/p&gt;

&lt;p&gt;In addition, Medusa's modular and composable architecture allows businesses to easily switch out modules and integrations, without having to rebuild their entire e-commerce solution. This is in contrast to traditional e-commerce platforms, where businesses may be tied to a specific set of integrations and may have difficulty switching to a different platform.&lt;/p&gt;

&lt;p&gt;Furthermore, Medusa's open-source nature allows businesses to take control of their own development and to build a customized e-commerce solution that meets their specific requirements. This gives businesses more control over their e-commerce solution, and allows them to respond quickly to changing market conditions and customer needs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Integration with Other Tools
&lt;/h3&gt;

&lt;p&gt;Another advantage of Medusa's composable architecture is its ability to integrate with third-party tools and services. Medusa's modular and composable architecture allows businesses to easily integrate their e-commerce solution with other tools and services in their technology stack, such as ERP systems, CRM systems, marketing automation platforms, and more.&lt;/p&gt;

&lt;p&gt;For example, a business may use an ERP system to manage their inventory. Medusa can easily integrate with the ERP system to keep track of inventory levels and to update product information. This integration allows businesses to manage their inventory more efficiently and to ensure that they have the right products in stock at all times.&lt;/p&gt;

&lt;p&gt;In addition, Medusa's composable architecture allows businesses to build a comprehensive e-commerce solution that is fully integrated with their existing technology stack. This can help businesses streamline their operations and improve their overall efficiency, by providing a single platform that can manage all aspects of their e-commerce operations.&lt;/p&gt;

&lt;p&gt;In contrast, traditional e-commerce platforms like Shopify or BigCommerce may not offer the same level of integration with other tools and services. This can result in businesses having to manage multiple systems and platforms, which can be time-consuming and costly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Security and Compliance
&lt;/h3&gt;

&lt;p&gt;One of the critical considerations for enterprise-level businesses when choosing an e-commerce platform is security and compliance. Medusa's composable architecture provides businesses with the flexibility to implement security and compliance measures that are tailored to their specific needs.&lt;/p&gt;

&lt;p&gt;Medusa allows businesses to choose which security and compliance measures to implement, such as adding PCI compliance features to their e-commerce solution.&lt;/p&gt;

&lt;p&gt;For example, businesses that handle credit card information need to ensure they are PCI compliant. Medusa can help businesses achieve PCI compliance by providing features such as secure credit card storage, fraud detection, and protection against data breaches.&lt;/p&gt;

&lt;p&gt;In contrast, traditional e-commerce platforms like Shopify or BigCommerce may offer a one-size-fits-all approach to security and compliance measures, which may not meet the specific needs of all businesses.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Medusa's composable architecture provides enterprise-level businesses with the flexibility, scalability, and customization they need to create a comprehensive e-commerce solution that meets their unique needs.&lt;/p&gt;

&lt;p&gt;Medusa offers several advantages over traditional e-commerce platforms like Shopify and BigCommerce, including customization and flexibility, scalability, cost-effectiveness, integration with other tools, and security and compliance. By choosing Medusa, businesses can tailor their e-commerce solution to their specific requirements and have greater control over their platform's functionality and features.&lt;/p&gt;

&lt;p&gt;For businesses that prioritize flexibility and customization, Medusa is an ideal choice. With its modular architecture, businesses can choose the features they need, integrate with their existing technology stack, and achieve compliance with relevant regulations and standards.&lt;/p&gt;

&lt;p&gt;Overall, Medusa offers enterprise-level businesses a powerful and flexible e-commerce platform that can meet their unique needs. We highly recommend Medusa for businesses looking for a scalable, customizable, and cost-effective e-commerce solution.&lt;/p&gt;

</description>
      <category>ecommerce</category>
      <category>medusajs</category>
      <category>architecture</category>
      <category>enterprise</category>
    </item>
    <item>
      <title>How to Build a Custom Field Plugin for Webiny Headless CMS</title>
      <dc:creator>Menard Maranan</dc:creator>
      <pubDate>Wed, 31 Aug 2022 14:10:21 +0000</pubDate>
      <link>https://dev.to/menard_codes/how-to-build-a-custom-field-plugin-for-webiny-headless-cms-4o75</link>
      <guid>https://dev.to/menard_codes/how-to-build-a-custom-field-plugin-for-webiny-headless-cms-4o75</guid>
      <description>&lt;p&gt;The concept of Headless CMS has gained a steady increase in popularity as the way people consume data evolves. Today, data and content are delivered not just through websites, but also in mobile apps, smart watches, VR, smart assistants, IoT devices, and more. As a result, more and more Headless CMS providers have stepped in to fulfill the demand, each offering common and unique features to fulfill your content and data management needs.&lt;/p&gt;

&lt;p&gt;Many of these Headless CMS platforms are proprietary. As a result, you’re locked into whatever feature and functionality there is your Headless CMS provider offers. If you need a certain feature or you want to customize something, you have no choice but to hope and wait for your provider to implement it (if they ever do).&lt;/p&gt;

&lt;p&gt;Alternatively, you can use open-source Headless CMS platforms like &lt;a href="https://github.com/webiny/webiny-js" rel="noopener noreferrer"&gt;Webiny&lt;/a&gt;. Webiny is an open-source framework that allows you to build serverless applications and websites, and as mentioned, it comes with a Headless CMS. Webiny’s composable architecture allows you to customize and extend its built-in features and functionalities, giving you full control over your entire software (such as your websites, GraphQL API’s resolvers, data types, schemas, Infrastructure as Code with Pulumi, etc) and data.&lt;/p&gt;

&lt;p&gt;Most of Webiny’s functionality is built with &lt;a href="https://www.webiny.com/docs/core-development-concepts/basics/plugins" rel="noopener noreferrer"&gt;plugins&lt;/a&gt;. When you need to extend Webiny’s built-in features, you’ll also need to use plugins (or build new ones that best fit your needs). So if you need a custom field type for your CMS content model, you can create a new one through plugins.&lt;/p&gt;

&lt;p&gt;To demonstrate, this article will guide you through building a custom field type for Webiny’s Headless CMS content model. And to build that custom field, we will use a set of built-in plugins in Webiny for constructing a new content model field type. We’ll do this through a step-by-step process with code samples and screenshots to help you along the way, so feel free to follow along.&lt;/p&gt;

&lt;p&gt;In this article, you’ll learn:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What are the plugins used to construct a new CMS content model field type&lt;/li&gt;
&lt;li&gt;How to create your custom field plugin&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Building a custom CMS model field type
&lt;/h2&gt;

&lt;p&gt;Like other Headless CMS platforms, Webiny comes with built-in content model fields. You can get pretty far with these built-in fields. But depending on your needs, you might need to have a content model that requires a property that doesn't come with Webiny's built-in field types. And in cases like this, you'll need to create your custom CMS content model field type.&lt;/p&gt;

&lt;h3&gt;
  
  
  Plugins for Creating a Custom CMS Field
&lt;/h3&gt;

&lt;p&gt;To build your custom field, you’ll need to use several plugins that all come with Webiny. These built-in plugins will help you define and build your new content model field type.&lt;/p&gt;

&lt;p&gt;There are five plugins related to building new content model field types, where three of which are required and two are optional:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/webiny/webiny-js/blob/83d6ff5be8de91cef9aca63ead199a8c00283957/packages/app-headless-cms/src/types.ts#L13" rel="noopener noreferrer"&gt;CmsEditorFieldTypePlugin&lt;/a&gt; - This is a &lt;strong&gt;required&lt;/strong&gt; plugin used to define some properties of your new content model field, from its name, type, description, label, the icon to associate with this new field, and more. Registering the plugin you created with &lt;code&gt;CmsEditorFieldTypePlugin&lt;/code&gt; will render your new field type to the content model UI editor.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/webiny/webiny-js/blob/83d6ff5be8de91cef9aca63ead199a8c00283957/packages/app-headless-cms/src/types.ts#L166" rel="noopener noreferrer"&gt;CmsEditorFieldRendererPlugin&lt;/a&gt; - This is another &lt;strong&gt;required&lt;/strong&gt; plugin used to define the renderer for your new field type. With this plugin, you’ll define the component to render the field you’re creating. It can be as simple as an input or as complex as a widget.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/webiny/webiny-js/blob/83d6ff5be8de91cef9aca63ead199a8c00283957/packages/api-headless-cms/src/types.ts#L383" rel="noopener noreferrer"&gt;CmsModelFieldToGraphQLPlugin&lt;/a&gt; - This is the third &lt;strong&gt;required&lt;/strong&gt; plugin used to define the new field’s schema, resolver, filters, etc. to the GraphQL API.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/webiny/webiny-js/blob/83d6ff5be8de91cef9aca63ead199a8c00283957/packages/api-headless-cms/src/types.ts#L1595" rel="noopener noreferrer"&gt;CmsModelFieldToStoragePlugin&lt;/a&gt; - This is an &lt;strong&gt;optional&lt;/strong&gt; plugin that sits in between your storage and API layer. With this plugin, you can manipulate the data before it’s sent to the storage layer (for example: censoring profanity in a blog post or comment) or after retrieving the data from the storage (and do things like decrypting an encrypted text).&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/webiny/webiny-js/blob/731966ba13e6d9c5c82828a89e0e9ef7d4a0a27d/packages/api-headless-cms-ddb-es/src/types.ts#L182" rel="noopener noreferrer"&gt;CmsModelFieldToElasticSearchPlugin&lt;/a&gt; - This is also an &lt;strong&gt;optional&lt;/strong&gt; plugin that is similar to the &lt;code&gt;CmsModelFieldToStoragePlugin&lt;/code&gt; for being an intermediary. But in this case, instead of the storage layer, this plugin sits in between the API layer and ElasticSearch. With this plugin, you can manipulate the data before it is sent to the index or after retrieving it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before we begin with the steps on building a custom content model field, make sure you have the following if you want to follow along the tutorial:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://nodejs.org/en/" rel="noopener noreferrer"&gt;Node.js 14.0.0 or higher&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Either of the two: &lt;a href="https://classic.yarnpkg.com/lang/en/" rel="noopener noreferrer"&gt;yarn 1.22.0 (classic) or higher&lt;/a&gt; || &lt;a href="https://yarnpkg.com/" rel="noopener noreferrer"&gt;yarn 2 (berry) or higher&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;An &lt;a href="https://aws.amazon.com/console/" rel="noopener noreferrer"&gt;AWS account&lt;/a&gt; and &lt;a href="https://www.webiny.com/docs/infrastructure/aws/configure-aws-credentials" rel="noopener noreferrer"&gt;pre-configured user credentials to your machine&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;And a pre-deployed Webiny project to AWS (&lt;a href="https://www.webiny.com/docs/get-started/install-webiny" rel="noopener noreferrer"&gt;Check this guide to learn how&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We’ll skip the Webiny app installation and deployment process in this article. So if you don’t have an up and running Webiny project yet, be sure to follow this &lt;a href="https://www.webiny.com/docs/get-started/install-webiny" rel="noopener noreferrer"&gt;installation and deployment guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step-by-Step: How To Build a Custom CMS Model Field Type
&lt;/h2&gt;

&lt;p&gt;Let’s say we have an e-commerce store selling mainly wrist watches. The products in this store are grouped according to their watch type, namely:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mechanical Watch&lt;/li&gt;
&lt;li&gt;Quartz Watch&lt;/li&gt;
&lt;li&gt;Digital Watch&lt;/li&gt;
&lt;li&gt;Smart Watch&lt;/li&gt;
&lt;li&gt;and Hybrid&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The content model needed for our PDP (Product Detail Page) requires an enumeration field type to select which kind of wrist watch a given product is. This new enumeration field will help categorize the products in the store. Since there is no built-in content model field in Webiny for our use case, we need to create a new one ourselves.&lt;/p&gt;

&lt;p&gt;So in this tutorial, we’ll build a simple dropdown menu as our new content model field type. This dropdown menu should contain all the mentioned watch types so that whenever we add a new product to the store, we can choose from the dropdown menu which category of watch it is.&lt;/p&gt;

&lt;p&gt;And with all that said, open your Webiny project in the code editor of your choice and let’s build this dropdown menu field type!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can check the &lt;a href="https://github.com/menard-codes/webiny-custom-field-plugin" rel="noopener noreferrer"&gt;full version of the code in this repo&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Step 1: Define a New Field Type
&lt;/h3&gt;

&lt;p&gt;When building a new content model field type, you should first create a plugin that defines this new field type you’re making. In this new plugin, you’ll give your new field a name, a field type, label, description, the icon for this new field (to be rendered on the list of content model fields in the content model UI editor), and more.&lt;/p&gt;

&lt;p&gt;To define our new field, we’ll be using the &lt;code&gt;CmsEditorFieldTypePlugin&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So first, create this new directory &lt;code&gt;fields/dropdown&lt;/code&gt; under the &lt;code&gt;apps/admin/src/plugins/headlessCMS&lt;/code&gt; directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# from the root of your Webiny project:&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; apps/admin/src/plugins/headlessCMS/fields/dropdown
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, create a new file called &lt;code&gt;productCategoryFieldPlugin.tsx&lt;/code&gt; under this new directory you created:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;touch &lt;/span&gt;apps/admin/src/plugins/headlessCMS/fields/dropdown/productCategoryFieldPlugin.tsx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this new file, we’ll write the plugin to define our new content model field type using the plugin &lt;code&gt;CmsEditorFieldTypePlugin&lt;/code&gt;. Copy the code below and paste it inside the file &lt;code&gt;productCategoryFieldPlugin.tsx&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// apps/admin/src/plugins/headlessCMS/fields/dropdown/productCategoryFieldPlugin.tsx&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CmsEditorFieldTypePlugin&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@webiny/app-headless-cms/types&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;DropdownIcon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FunctionComponent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;i&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;ICON&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;i&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;plugin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CmsEditorFieldTypePlugin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cms-editor-field-type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cms-editor-field-type-product-category&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;field&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;product-category&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Product Category&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Product Category&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;DropdownIcon&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;,&lt;/span&gt;
        &lt;span class="na"&gt;allowMultipleValues&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;allowPredefinedValues&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;multipleValuesLabel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Use as a list of multiple categories&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nf"&gt;createField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;product-category&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;validation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
                &lt;span class="na"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;plugin&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before we proceed, let’s first dissect this plugin and understand what it does.&lt;/p&gt;

&lt;p&gt;Under the &lt;code&gt;field&lt;/code&gt; property of the &lt;code&gt;CmsEditorFieldTypePlugin&lt;/code&gt;, we defined the properties of this new dropdown field we’re building. Here, we specified that the &lt;code&gt;type&lt;/code&gt; of this new plugin is &lt;code&gt;product-category&lt;/code&gt;, which we will use later to identify this new field type. We also defined its label, description, and icon. You can assign your values here if you want to. And the rest of the properties inside the &lt;code&gt;field&lt;/code&gt; property only relates to the rules regarding this new plugin.&lt;/p&gt;

&lt;p&gt;After defining our new field type, we need to include this in the list of content model field types of the headless CMS.&lt;/p&gt;

&lt;p&gt;Under the directory &lt;code&gt;apps/admin/src/plugins/&lt;/code&gt;, open the file &lt;code&gt;headlessCMS.ts&lt;/code&gt; and import our newly created plugin, then add this plugin to the exported array:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// apps/admin/src/plugins/headlessCMS.ts&lt;/span&gt;

&lt;span class="c1"&gt;// ... rest of the imports&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;richTextEditor&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./headlessCMS/richTextEditor&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// My custom plugins&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;productCategoryFieldPlugin&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./headlessCMS/fields/dropdown/productCategoryFieldPlugin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;- import the plugin&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;headlessCmsPlugins&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="nx"&gt;richTextEditor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;textField&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;// ...&lt;/span&gt;
        &lt;span class="nx"&gt;objectFieldRenderer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;productCategoryFieldPlugin&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;- the product-category field plugin&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now it’s time to check our new custom field type in the content model UI editor.&lt;/p&gt;

&lt;p&gt;Run this command to start a development server for your Webiny project’s admin workspace:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn webiny watch app/admin &lt;span class="nt"&gt;--env&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then, create a sample content model in Webiny’s Headless CMS (just name it &lt;code&gt;Product&lt;/code&gt;) and check out the field types list. You should be able to see the new field type named &lt;code&gt;PRODUCT CATEGORY&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.webiny.com%2Fstatic%2F31e9e0dcdb1b8fe0939d59a97813f338%2Fb17f8%2Fcreate-content-model.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.webiny.com%2Fstatic%2F31e9e0dcdb1b8fe0939d59a97813f338%2Fb17f8%2Fcreate-content-model.jpg" alt="create content model.JPG" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Define and Build the Renderer for the New Field
&lt;/h3&gt;

&lt;p&gt;Now that we defined our new field type, it’s time to build the renderer for it. The custom field’s renderer is what will be shown when you add or edit your content.&lt;/p&gt;

&lt;p&gt;For the renderer of our &lt;code&gt;product-category&lt;/code&gt; field type, we are going to define a dropdown menu. And to simplify things, we’ll use a third-party dependency called &lt;a href="https://www.npmjs.com/package/react-dropdown" rel="noopener noreferrer"&gt;react-dropdown&lt;/a&gt; to skip the part of implementing a dropdown menu from scratch.&lt;/p&gt;

&lt;p&gt;So with that said, open your terminal and install &lt;code&gt;react-dropdown&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn workspace admin add react-dropdown 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Take note of the &lt;code&gt;workspace admin&lt;/code&gt; since we only want to install &lt;code&gt;react-dropdown&lt;/code&gt; in the admin area. This is because every &lt;a href="https://www.webiny.com/docs/core-development-concepts/project-organization/monorepo-organization" rel="noopener noreferrer"&gt;Webiny project is a monorepo&lt;/a&gt; that has several workspaces in it (including the mentioned &lt;code&gt;admin&lt;/code&gt; workspace which we’re working on right now).&lt;/p&gt;

&lt;p&gt;After installing the dependency, we can now define and build the renderer for &lt;code&gt;product-category&lt;/code&gt; field.&lt;/p&gt;

&lt;p&gt;We will be using the &lt;code&gt;CmsEditorFieldRendererPlugin]&lt;/code&gt; to define our renderer’s properties like &lt;code&gt;rendererName&lt;/code&gt;, description, &lt;code&gt;canUse&lt;/code&gt; (or when should it be rendered), and most importantly, the component that will be rendered.&lt;/p&gt;

&lt;p&gt;Under the directory &lt;code&gt;apps/admin/src/plugins/headlessCMS/fields/dropdown&lt;/code&gt;, add another file named &lt;code&gt;productCategoryFieldRendererPlugin.tsx&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;touch &lt;/span&gt;apps/admin/src/plugins/headlessCMS/fields/dropdown/productCategoryFieldRendererPlugin.tsx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, write the renderer plugin inside this file using the &lt;code&gt;CmsEditorFieldRendererPlugin&lt;/code&gt;. Use the &lt;code&gt;react-dropdown&lt;/code&gt; to create the dropdown component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;//apps/admin/src/plugins/headlessCMS/fields/dropdown/productCategoryFieldRendererPlugin.tsx&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CmsEditorFieldRendererPlugin&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@webiny/app-headless-cms/types&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;ReactDropdown&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-dropdown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-dropdown/style.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default &lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;CmsEditorFieldRendererPlugin&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cms-editor-field-renderer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cms-editor-field-renderer-product-category&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;rendererName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;product-category&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// must be the same as the field type defined in CmsEditorFieldTypePlugin&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Product Category&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Product Category&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nf"&gt;canUse&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;field&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="c1"&gt;// only render this on fields with the type `product-category`&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;product-category&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
        &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="c1"&gt;// the component to render for the field type `product-category`&lt;/span&gt;
        &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;getBind&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Bind&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getBind&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

                        &lt;span class="c1"&gt;// the product categories in the e-commerce store&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Mechanical Watch&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Quartz Watch&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Digital Watch&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Smart Watch&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hybrid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;];&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;defaultOption&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
            &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Bind&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="si"&gt;{&lt;/span&gt;
                        &lt;span class="nx"&gt;bind&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ReactDropdown&lt;/span&gt;
                                &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
                                &lt;span class="na"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
                                &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;defaultOption&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
                                &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Select an option"&lt;/span&gt;
                            &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
                        &lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="si"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Bind&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the code above, the &lt;code&gt;product-category&lt;/code&gt; field’s renderer was defined in the &lt;code&gt;renderer&lt;/code&gt; property of the &lt;code&gt;CmsEditorFieldRendererPlugin&lt;/code&gt;. The &lt;code&gt;rendererName&lt;/code&gt; is the same with the custom field’s type, &lt;code&gt;product-category&lt;/code&gt;, which is required in order to associate the renderer to its corresponding field type. The &lt;code&gt;canUse&lt;/code&gt; property indicates that this renderer should only be used when the field type is &lt;code&gt;product-category&lt;/code&gt;. And finally, the &lt;code&gt;render&lt;/code&gt; property is where we defined the dropdown menu component, which will be rendered when creating or editing contents that has &lt;code&gt;product-category&lt;/code&gt; properties in them. Also, you might have noticed that our dropdown component is nested inside that &lt;code&gt;Bind&lt;/code&gt; component. This &lt;code&gt;Bind&lt;/code&gt; component is hooked into a React Context and helps abstract managing the state of our field renderer's component. That's why we don't have a &lt;code&gt;useState&lt;/code&gt; hook inside our renderer component. Instead, we pass the &lt;code&gt;value&lt;/code&gt; from the dropdown to &lt;code&gt;bind&lt;/code&gt; and use the &lt;code&gt;onChange&lt;/code&gt; handler of &lt;code&gt;bind&lt;/code&gt; to pass changed data from the component to the context object.&lt;/p&gt;

&lt;p&gt;After you defined the renderer, import this plugin to &lt;code&gt;headlessCMS.ts&lt;/code&gt; and add it to the exported list (next to the field type definition plugin from earlier). Remember to call this function upon adding it to the list.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// apps/admin/src/plugins/headlessCMS.ts&lt;/span&gt;

&lt;span class="c1"&gt;// ... rest of imports&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;richTextEditor&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./headlessCMS/richTextEditor&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// My custom plugins&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;productCategoryFieldPlugin&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./headlessCMS/fields/dropdown/productCategoryFieldPlugin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;productCategoryFieldRendererPlugin&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./headlessCMS/fields/dropdown/productCategoryFieldRendererPlugin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;- import the renderer plugin&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;headlessCmsPlugins&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="nx"&gt;richTextEditor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;textField&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;// ...&lt;/span&gt;
        &lt;span class="nx"&gt;objectFieldRenderer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;productCategoryFieldPlugin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nf"&gt;productCategoryFieldRendererPlugin&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;- the product-category field renderer plugin&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that you added this field renderer plugin, you can preview it in the content model UI editor and check the new dropdown field type we just defined:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.webiny.com%2Fstatic%2Fa326a50503c03756d0532087d76cf0c4%2Fb17f8%2Fcustom-field.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.webiny.com%2Fstatic%2Fa326a50503c03756d0532087d76cf0c4%2Fb17f8%2Fcustom-field.jpg" alt="custom field" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Define the Field in the GraphQL API
&lt;/h3&gt;

&lt;p&gt;After you defined the new field type and its renderer, it’s time to define this new field in the GraphQL API. For this, we’ll need to use the &lt;a href="https://github.com/webiny/webiny-js/blob/83d6ff5be8de91cef9aca63ead199a8c00283957/packages/api-headless-cms/src/types.ts#L383" rel="noopener noreferrer"&gt;CmsModelFieldToGraphQLPlugin&lt;/a&gt; to define our &lt;code&gt;product-category&lt;/code&gt; field type on the GraphQL API to handle all incoming requests from this field type.&lt;/p&gt;

&lt;p&gt;Since we will be working on the API layer, we’ll leave the admin area for now and switch to the Headless CMS API workspace.&lt;/p&gt;

&lt;p&gt;First off, add the directory &lt;code&gt;fields/dropdown&lt;/code&gt; under the directory &lt;code&gt;apps/api/headlessCMS/src/&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# from the root of your Webiny project:&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; apps/api/headlessCMS/src/fields/dropdown
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, create the file named &lt;code&gt;productCategoryFieldPlugin.ts&lt;/code&gt; under the directory you just created:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;touch &lt;/span&gt;apps/api/headlessCMS/src/fields/dropdown/productCategoryFieldPlugin.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside the file &lt;code&gt;productCategoryFieldPlugin.ts&lt;/code&gt;, define the &lt;code&gt;product-category&lt;/code&gt; field type to GraphQL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// apps/api/headlessCMS/src/fields/dropdown/productCategoryFieldPlugin.ts&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CmsModelFieldToGraphQLPlugin&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@webiny/api-headless-cms/types&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;plugin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CmsModelFieldToGraphQLPlugin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cms-model-field-to-graphql-product-category&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cms-model-field-to-graphql&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;fieldType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;product-category&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;- Must be the same as the field type defined in CmsEditorFieldTypePlugin&lt;/span&gt;
    &lt;span class="na"&gt;isSortable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;isSearchable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;read&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;createTypeField&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;field&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;multipleValues&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fieldId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: [String]`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fieldId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: String`&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="nf"&gt;createGetFilters&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;field&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fieldId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: String`&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="nf"&gt;createListFilters&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;field&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`
                &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fieldId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: String
                &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fieldId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_not: String
                &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fieldId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_in: [String]
                &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fieldId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_not_in: [String]
                &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fieldId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_contains: String
                &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fieldId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_not_contains: String
            `&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;manage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;createListFilters&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;field&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`
                &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fieldId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: String
                &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fieldId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_not: String
                &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fieldId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_in: [String]
                &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fieldId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_not_in: [String]
                &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fieldId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_contains: String
                &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fieldId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_not_contains: String
            `&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="nf"&gt;createTypeField&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;field&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;multipleValues&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fieldId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: [String]`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fieldId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: String`&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="nf"&gt;createInputField&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;field&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;multipleValues&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fieldId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: [String]`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fieldId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: String`&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;plugin&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In here, we defined the &lt;code&gt;fieldType&lt;/code&gt; as &lt;code&gt;product-category&lt;/code&gt; because the &lt;code&gt;fieldType&lt;/code&gt; in this plugin must match the field’s &lt;code&gt;type&lt;/code&gt; we defined in &lt;code&gt;CmsEditorFieldTypePlugin&lt;/code&gt;. That way, Webiny knows this specific plugin is for the &lt;code&gt;product-category&lt;/code&gt; field type.&lt;/p&gt;

&lt;p&gt;The properties &lt;code&gt;isSearchable&lt;/code&gt; and &lt;code&gt;isSortable&lt;/code&gt; are set to &lt;code&gt;true&lt;/code&gt; since we want this field to be searchable and sortable via the GraphQL API.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;read&lt;/code&gt; property, we defined the field type for &lt;code&gt;product-category&lt;/code&gt; in the Read API through &lt;code&gt;createTypeField&lt;/code&gt;, as well as some filters. Similarly in the &lt;code&gt;manage&lt;/code&gt; property, we defined the field type of &lt;code&gt;product-category&lt;/code&gt; in the Manage API through the &lt;code&gt;createTypeField&lt;/code&gt;. We also defined the input GraphQL field type through &lt;code&gt;createInputField&lt;/code&gt;, as well as a filter.&lt;/p&gt;

&lt;p&gt;Now that this plugin is defined, we can now import this to the &lt;code&gt;apps/api/headlessCMS/src/index.ts&lt;/code&gt; file and add the &lt;code&gt;productCategoryFieldPlugin&lt;/code&gt; plugin to the plugins used by the Headless CMS API.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// apps/api/headlessCMS/src/index.ts&lt;/span&gt;

&lt;span class="c1"&gt;// ... the rest of the imports&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;scaffoldsPlugins&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./plugins/scaffolds&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// My custom plugins&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;productCategoryFieldPlugin&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./fields/dropdown/productCategoryFieldPlugin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;- import the GraphQL plugin for the product-category field&lt;/span&gt;

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

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createHandler&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nf"&gt;createWcpContext&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="c1"&gt;// ...&lt;/span&gt;
                &lt;span class="nf"&gt;scaffoldsPlugins&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="nx"&gt;productCategoryFieldPlugin&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;- add here the field to GraphQL plugin of the product-category field&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;debug&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally, deploy the changes in the API and run the dev environment of your Headless CMS API using this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn webiny watch apps/api/headlessCMS &lt;span class="nt"&gt;--env&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If everything worked as expected, you should now be able to save your content model (that has the &lt;code&gt;product-category&lt;/code&gt; field type) and create new product entries using this new custom field. We will do all these in the next and final step of this tutorial.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Testing the New Field
&lt;/h3&gt;

&lt;p&gt;For the final step, we will test our new custom &lt;code&gt;product-category&lt;/code&gt; plugin by making a simple content model for the PDP (Product Detail Page) of our products, create entries or product page contents using the PDP content model, and finally, query the Headless CMS API for the product content entries. And if our custom plugin works properly, we should be able to use it on the PDP content model, use the dropdown menu to categorize the product entries, and get the product category when querying the products through the Headless CMS API.&lt;/p&gt;

&lt;p&gt;So to begin with, first go to Webiny’s Headless CMS and create a new content model (or use the &lt;code&gt;Product&lt;/code&gt; model from earlier) for our PDPs. You can use the following fields in your content model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Text - For the product name. Set it as the Title&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Product Category&lt;/strong&gt; - Our custom field type. Used for categorizing the watches on the store.&lt;/li&gt;
&lt;li&gt;File - This is where we’ll put the product images. Set it to accept multiple values and to only accept image files.&lt;/li&gt;
&lt;li&gt;Number - For the price of the product&lt;/li&gt;
&lt;li&gt;Rich Text - For the product description&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your &lt;code&gt;Product&lt;/code&gt; content model, including our very own &lt;code&gt;product-category&lt;/code&gt; field, should look something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.webiny.com%2Fstatic%2Fb55ff7f9f0387ea26c1617046adb7cbb%2Fb17f8%2Fproduct-category-field.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.webiny.com%2Fstatic%2Fb55ff7f9f0387ea26c1617046adb7cbb%2Fb17f8%2Fproduct-category-field.jpg" alt="product category field" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Save this content model, and create a new product entry for the &lt;code&gt;Products&lt;/code&gt; content model. For this example, I’ll add a mechanical watch and set the category using the dropdown menu we created earlier to &lt;code&gt;Mechanical Watch&lt;/code&gt;. When querying the products lists, we expect that the value we get for this product is &lt;code&gt;Mechanical Watch&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.webiny.com%2Fstatic%2F0541582dec812535feec5d63bb82cc40%2Fb17f8%2Fdropdown-field-in-action.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.webiny.com%2Fstatic%2F0541582dec812535feec5d63bb82cc40%2Fb17f8%2Fdropdown-field-in-action.jpg" alt="dropdown field in action" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Save and publish this product entry. Make sure you press the orange button from the top-right that says &lt;code&gt;Save &amp;amp; Publish&lt;/code&gt; and then click &lt;code&gt;Confirm&lt;/code&gt; so that we can query it from the Read API.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.webiny.com%2Fstatic%2F3cc67d974d214b2bc644b32dd7b42ca3%2Fb17f8%2Fsave-and-publish-content.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.webiny.com%2Fstatic%2F3cc67d974d214b2bc644b32dd7b42ca3%2Fb17f8%2Fsave-and-publish-content.jpg" alt="save and publish content" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If everything works well, you should be able to publish your first product content entry with the custom field type we created! Feel free to add as many product entries as you want.&lt;/p&gt;

&lt;p&gt;And now, for the final test, we will query the Headless CMS API for all our product entries. Make sure you’re on the Read API’s tab, or that your API endpoint is &lt;code&gt;cms/read/{locale}&lt;/code&gt;. You can also query the product entries in the &lt;code&gt;Preview&lt;/code&gt; API, but since we already have a published content entry, we can use the &lt;code&gt;Read&lt;/code&gt; API instead.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.webiny.com%2Fstatic%2F99ea865d8b29e3e68adfc0f047aeb93b%2Fb17f8%2Fheadless-cms-api.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.webiny.com%2Fstatic%2F99ea865d8b29e3e68adfc0f047aeb93b%2Fb17f8%2Fheadless-cms-api.jpg" alt="headless cms api" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, remove the pre-populated query and replace it with our products list query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;listProducts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;productName&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;productCategory&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;productPrice&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;productImages&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;productDescription&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, hit the play button to run the GraphQL query. And if our implementation works flawlessly and as expected, we should be able to retrieve the data we entered on the product content entries. Most importantly, we’ll see the value of the &lt;code&gt;product-category&lt;/code&gt; obtained from our custom &lt;code&gt;product-category&lt;/code&gt; dropdown field type!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.webiny.com%2Fstatic%2Fa040cbf9fbcd90cb5d780d13e7fefe54%2Fb17f8%2Fheadless-cms-field-in-graphql-response.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.webiny.com%2Fstatic%2Fa040cbf9fbcd90cb5d780d13e7fefe54%2Fb17f8%2Fheadless-cms-field-in-graphql-response.jpg" alt="headless cms field in graphql response" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And there you have it!&lt;/p&gt;

&lt;p&gt;You just created your own custom content model field type in Webiny’s Headless CMS. Feel free to play around and experiment with this new custom field type, or better yet, create your own field and use your imagination to build the field type you want.&lt;/p&gt;

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

&lt;p&gt;In this article, you learned how you can extend Webiny’s built-in features through plugins. You learned the different built-in plugins in Webiny for creating custom field types in Webiny’s Headless CMS, how to use these plugins, and how to define the field’s type, renderer, and field to GraphQL plugins to create the custom content model field type. We made a simple dropdown menu for our hypothetical e-commerce store to help categorize products. We also tested our custom field type by creating a content model with the dropdown menu, creating content entries using this field type, and querying the results in the API Playground.&lt;/p&gt;

&lt;p&gt;Webiny’s composable architecture allows you to have full control and freedom over your Headless CMS platform. Webiny’s plugins power most of its built-in features, and plugins are also what you need when you’re extending Webiny’s functionalities. With the world of technology rapidly changing year-over-year, having a dynamic and easy to scale Headless CMS platform like Webiny is crucial to the success of your business. If you want to have more freedom and control over your Headless CMS platform (and your software and data in general) be sure to &lt;a href="https://www.webiny.com/" rel="noopener noreferrer"&gt;check out Webiny&lt;/a&gt; and build your next website with them.&lt;/p&gt;

</description>
      <category>webiny</category>
      <category>webdev</category>
      <category>headlesscms</category>
      <category>typescript</category>
    </item>
    <item>
      <title>What is Unit Testing? How to Perform Unit Tests in Rust</title>
      <dc:creator>Menard Maranan</dc:creator>
      <pubDate>Thu, 28 Jul 2022 05:12:00 +0000</pubDate>
      <link>https://dev.to/menard_codes/unit-testing-in-rust-4cbh</link>
      <guid>https://dev.to/menard_codes/unit-testing-in-rust-4cbh</guid>
      <description>&lt;p&gt;Testing is an essential part of Software Development. Testing your code ensures that the software you develop works as expected and makes it less vulnerable to attackers.&lt;/p&gt;

&lt;p&gt;Software testing is a very broad topic. That's why in the software industry, there are separate professionals who specialize in just QA and testing alone. These professionals are often referred to as QA Engineers.&lt;/p&gt;

&lt;p&gt;Although QA is its own thing, that doesn't mean developers don't do testing at all.&lt;/p&gt;

&lt;p&gt;The most common tests that developers conduct are Unit Tests. Unit testing is a type of testing where you test small units of code (like functions) – hence the term, Unit Testing. You often do this by comparing the expected behavior of a unit of code against its actual behavior.&lt;/p&gt;

&lt;p&gt;Unit testing is such an integral part of the development workflow that some companies' whole development culture is centered around what's called &lt;strong&gt;&lt;a href="https://www.freecodecamp.org/news/test-driven-development-tutorial-how-to-test-javascript-and-reactjs-app/" rel="noopener noreferrer"&gt;Test-Driven Development (or TDD)&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In TDD, developers first write test cases (from the feature requirements, often called the &lt;strong&gt;user story&lt;/strong&gt;) and proceed to write the code that satisfies them. TDD shines mostly in projects where requirements are highly specific.&lt;/p&gt;

&lt;p&gt;You can implement unit testing in different ways across different programming languages. But at its core, Unit Testing is just about comparing the expected vs the actual behavior of the code.&lt;/p&gt;

&lt;p&gt;So regardless of how it's implemented in a particular language, the same principle generally applies when you work in any other language.&lt;/p&gt;

&lt;p&gt;In this tutorial, you'll learn unit testing in the Rust programming language. That said, you should know at least the &lt;a href="https://www.freecodecamp.org/news/rust-in-replit/" rel="noopener noreferrer"&gt;basics of programming in Rust&lt;/a&gt; although you don't need advanced knowledge of it.&lt;/p&gt;

&lt;p&gt;This article will cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How unit testing works in Rust&lt;/li&gt;
&lt;li&gt;How to write a unit test in Rust&lt;/li&gt;
&lt;li&gt;How to test a function&lt;/li&gt;
&lt;li&gt;Why failing tests are useful&lt;/li&gt;
&lt;li&gt;How to handle expected error behavior so that your tests won't fail&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, with all that said, let's proceed to learn unit testing with Rust!&lt;/p&gt;

&lt;h2&gt;
  
  
  How Unit Testing Works in Rust
&lt;/h2&gt;

&lt;p&gt;Rust is built with code safety at its core. Rust's strict type annotation rules help eliminate a ton of bugs early on in the development phase. But still, it's not foolproof.&lt;/p&gt;

&lt;p&gt;Like any other language, the business logic is on your shoulders and you have to help Rust understand what's acceptable in your code and what's not.&lt;/p&gt;

&lt;p&gt;And yes, that's why we do testing.&lt;/p&gt;

&lt;p&gt;You don't need to install a test suite to get started testing in Rust since it has built-in support for testing.&lt;/p&gt;

&lt;p&gt;To get started, create a new cargo project (take note of the &lt;code&gt;--lib&lt;/code&gt; flag) on your local machine and open it in the text editor or IDE of your choice. For this tutorial, I'll be using VS code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cargo new &lt;span class="nt"&gt;--lib&lt;/span&gt; rust_unit_testing
code rust_unit_testing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, open the &lt;code&gt;src/lib.rs&lt;/code&gt; file. This is where we'll spend the most time in this tutorial.&lt;/p&gt;

&lt;p&gt;&lt;a href="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%2F59n28tpbph3ul11qets7.JPG" class="article-body-image-wrapper"&gt;&lt;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%2F59n28tpbph3ul11qets7.JPG" alt="src/lib.rs file of a Rust Library Project" width="800" height="394"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In a newly created library project in Rust, you'll notice that the &lt;code&gt;lib.rs&lt;/code&gt; file is already pre-populated by a sample test code by default.&lt;/p&gt;

&lt;p&gt;The main purpose of this is for you to have a template to begin with when writing your tests. We'll dissect each part of this simple test and understand the basic testing concepts in Rust.&lt;/p&gt;

&lt;p&gt;So first, let's understand what those lines of test code are doing. In this example, you'll see a test module defined in &lt;code&gt;lib.rs&lt;/code&gt; with one test inside it which tests if 2 + 2 is equal to 4.&lt;/p&gt;

&lt;p&gt;If you don't know the concept of modules and attributes in Rust yet, that's fine and you can ignore them for now.&lt;/p&gt;

&lt;p&gt;But just to give you an idea, tests in Rust are written inside the &lt;code&gt;tests&lt;/code&gt; module (the &lt;code&gt;mod tests&lt;/code&gt; part says it's the tests module), and anything written inside this module tells cargo to run them only during testing (and that's essentially what the &lt;code&gt;#[cfg(test)]&lt;/code&gt; attribute implies).&lt;/p&gt;

&lt;p&gt;A test in Rust is essentially just a function annotated as a test. From the example above, you'll notice the &lt;code&gt;#[test]&lt;/code&gt; attribute above the function &lt;code&gt;it_works&lt;/code&gt;. This simply tells cargo that this function is a test, and should be invoked during testing.&lt;/p&gt;

&lt;p&gt;Inside the &lt;code&gt;it_works&lt;/code&gt; test function, it checks if the value of &lt;code&gt;result&lt;/code&gt; derived from 2 + 2 is equal to 4. It performs the checking using the &lt;code&gt;assert_eq!&lt;/code&gt; macro. The &lt;code&gt;assert_eq!&lt;/code&gt; macro compares the equality (&lt;code&gt;==&lt;/code&gt;) of the left and right values passed onto it.&lt;/p&gt;

&lt;p&gt;In most programming languages, there's a rule that the left values passed to the assert should be the expected values, while the actual value should be in the right. With Rust, there are no hard rules for that and you can pass on either side the expected and actual results.&lt;/p&gt;

&lt;p&gt;Now, try running your test using this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cargo &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's what the result should look like for the example above:&lt;/p&gt;

&lt;p&gt;&lt;a href="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%2Fi215ro7dfc60ylose6zg.JPG" class="article-body-image-wrapper"&gt;&lt;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%2Fi215ro7dfc60ylose6zg.JPG" alt="cargo test - results" width="800" height="394"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By running &lt;code&gt;cargo test&lt;/code&gt;, cargo will execute your test cases and output the report in your terminal. From that report, you'll see the tests run by cargo.&lt;/p&gt;

&lt;p&gt;The first line in the report says &lt;code&gt;running 1 test&lt;/code&gt; since we only have one test function &lt;code&gt;tests::it_works&lt;/code&gt;. Alongside the tested function, you'll see the message &lt;code&gt;ok&lt;/code&gt;, meaning, the test passed.&lt;/p&gt;

&lt;p&gt;You can also see the summary of the results below that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1 passed&lt;/li&gt;
&lt;li&gt;0 failed&lt;/li&gt;
&lt;li&gt;0 ignored&lt;/li&gt;
&lt;li&gt;0 measured&lt;/li&gt;
&lt;li&gt;0 filtered out&lt;/li&gt;
&lt;li&gt;and the status of the result that says &lt;code&gt;test result: ok&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;1 passed&lt;/code&gt; counter here represents that one test function (&lt;code&gt;tests::it_works&lt;/code&gt;) that passed the test, while the &lt;code&gt;failed&lt;/code&gt; counter tells how many failing tests we have. The same applies to the other counters as well.&lt;/p&gt;

&lt;p&gt;You'll also see the results of the &lt;strong&gt;Doc-tests&lt;/strong&gt; below. Since we don't have any doc tests here, you'll see &lt;code&gt;running 0 tests&lt;/code&gt;. You can ignore this for now and just focus on the unit tests. But if you want to learn more, you can check &lt;a href="https://doc.rust-lang.org/rust-by-example/testing/doc_testing.html" rel="noopener noreferrer"&gt;Rust's official documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to Write Tests in Rust
&lt;/h3&gt;

&lt;p&gt;When writing a test, you generally need to go through these three steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Mock the data or state needed for a test case. By this, I mean providing mock or sample data needed by the code you're testing (if necessary) and/or setting up the state or environment needed for the test case to run.&lt;/li&gt;
&lt;li&gt;Run the code that needs to be tested (passing the mock data necessary). An example is invoking a function you want to test.&lt;/li&gt;
&lt;li&gt;Check if the actual behavior of the code you're testing matches its expected behavior. For example, by passing an argument &lt;code&gt;x&lt;/code&gt; to a function, you assert if its returned value is the same as what you're expecting for it to return. Or check if a unit of code raises &lt;code&gt;panic!&lt;/code&gt;—which is the expected behavior, for example—if it's given a certain parameter.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In Rust, unit tests are written in the exact file where the code being tested is written. Test functions are then grouped inside the &lt;code&gt;tests&lt;/code&gt; module (which is named this way by convention).&lt;/p&gt;

&lt;h3&gt;
  
  
  How to test functions in Rust
&lt;/h3&gt;

&lt;p&gt;Now let's proceed to testing functions in Rust.&lt;/p&gt;

&lt;p&gt;To begin with, we need a simple function to test. But first, remove the &lt;code&gt;it_works&lt;/code&gt; test function since we no longer need it. Then, write this &lt;code&gt;adder&lt;/code&gt; function above the &lt;code&gt;tests&lt;/code&gt; module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/lib.rs&lt;/span&gt;

&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;adder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;i32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;i32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;i32&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[cfg(test)]&lt;/span&gt;
&lt;span class="k"&gt;mod&lt;/span&gt; &lt;span class="n"&gt;tests&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;adder&lt;/code&gt; function from above is a simple public function that just adds two numbers and returns the sum. To test if it works as expected, let's write a unit test for this function.&lt;/p&gt;

&lt;p&gt;From the three steps of writing unit tests we discussed earlier, the first two steps are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;set the data for the code to be tested&lt;/li&gt;
&lt;li&gt;run the code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So going back to the &lt;code&gt;tests&lt;/code&gt; module, first, bring the &lt;code&gt;adder&lt;/code&gt; function into its scope (using the &lt;code&gt;use&lt;/code&gt; keyword). Then write a function named &lt;code&gt;it_adds&lt;/code&gt; annotated with the &lt;code&gt;#[test]&lt;/code&gt; attribute.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/lib.rs&lt;/span&gt;

&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;adder&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;i32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;i32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;i32&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[cfg(test)]&lt;/span&gt;
&lt;span class="k"&gt;mod&lt;/span&gt; &lt;span class="n"&gt;tests&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// this brings everything from parent's scope into this scope&lt;/span&gt;
    &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;#[test]&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;it_adds&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside the &lt;code&gt;it_adds&lt;/code&gt; test function is where we'll write the tests. So within it, declare a variable named &lt;code&gt;sum&lt;/code&gt;, then call the function &lt;code&gt;adder&lt;/code&gt; and pass 4 and 5 as its parameters (which are our mock data).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/lib.rs&lt;/span&gt;

&lt;span class="c1"&gt;// --snip--&lt;/span&gt;

    &lt;span class="nd"&gt;#[test]&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;it_adds&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;adder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally, the third step in writing unit tests is to check the expected vs actual behavior of the code we're testing.&lt;/p&gt;

&lt;p&gt;So here, let's assert if the value of &lt;code&gt;sum&lt;/code&gt; as returned by the &lt;code&gt;adder&lt;/code&gt; function is equal to &lt;code&gt;9&lt;/code&gt; (which is our expected return value) using the &lt;code&gt;assert_eq!&lt;/code&gt; macro.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/lib.rs&lt;/span&gt;

&lt;span class="c1"&gt;// --snip--&lt;/span&gt;

    &lt;span class="nd"&gt;#[test]&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;it_adds&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;adder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nd"&gt;assert_eq!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's the final version of our code and test in the &lt;code&gt;lib.rs&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/lib.rs&lt;/span&gt;

&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;adder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;i32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;i32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;i32&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[cfg(test)]&lt;/span&gt;
&lt;span class="k"&gt;mod&lt;/span&gt; &lt;span class="n"&gt;tests&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// this brings everything from parent's scope into this scope&lt;/span&gt;
    &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;#[test]&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;it_adds&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;adder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nd"&gt;assert_eq!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you learned earlier, you can run this test using this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cargo &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If everything works well, we should get &lt;code&gt;test result: ok&lt;/code&gt; stating that our tests passed.&lt;/p&gt;

&lt;p&gt;&lt;a href="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%2Fg3xj477zhetc92oa4bqx.JPG" class="article-body-image-wrapper"&gt;&lt;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%2Fg3xj477zhetc92oa4bqx.JPG" alt="Running the cargo test" width="800" height="394"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can add more tests in the &lt;code&gt;tests&lt;/code&gt; module for the &lt;code&gt;adder&lt;/code&gt; function if you like (for example, adding negative numbers). Or better yet, create your own function and write a test (or tests) for it.&lt;/p&gt;

&lt;p&gt;Moreover, there are a lot more built-in assertion macros in Rust that you can use besides the &lt;code&gt;assert_eq!&lt;/code&gt; macro. Some of those include the &lt;code&gt;assert_ne!&lt;/code&gt; macro for asserting not equal values (&lt;code&gt;!=&lt;/code&gt;), and the &lt;code&gt;assert!&lt;/code&gt; macro which just asserts if the code you're testing returns a &lt;code&gt;true&lt;/code&gt; value.&lt;/p&gt;

&lt;p&gt;If you need more assertion macros (for example, comparison assertions that support &lt;code&gt;&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;&lt;/code&gt;, &lt;code&gt;&amp;gt;=&lt;/code&gt;, &lt;code&gt;&amp;lt;=&lt;/code&gt;), you can install external crates like this one: &lt;a href="https://crates.io/crates/claim" rel="noopener noreferrer"&gt;claim&lt;/a&gt;. You can check &lt;a href="https://docs.rs/claim/latest/claim/" rel="noopener noreferrer"&gt;claim's documentation here&lt;/a&gt; for more info.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Failing Tests Are Useful
&lt;/h3&gt;

&lt;p&gt;So far we're always getting passing results on our tests.&lt;/p&gt;

&lt;p&gt;Although it's good, the true power of unit tests comes from catching errors or bugs in our code and reporting them through failing tests. So for this time, let's intentionally write a 'buggy' code and see what happens.&lt;/p&gt;

&lt;p&gt;Back in the &lt;code&gt;lib.rs&lt;/code&gt; file, modify the &lt;code&gt;adder&lt;/code&gt; function by replacing the &lt;code&gt;+&lt;/code&gt; operator with &lt;code&gt;-&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/lib.rs&lt;/span&gt;

&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;adder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;i32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;i32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;i32&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// change the operator from '+' to '-'&lt;/span&gt;
    &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// --snip--&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now run the tests again using &lt;code&gt;cargo test&lt;/code&gt;. And as expected, you should see a failing test result like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="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%2Foayue6jc6r09s6voxcy5.JPG" class="article-body-image-wrapper"&gt;&lt;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%2Foayue6jc6r09s6voxcy5.JPG" alt="failing test by cargo" width="800" height="394"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First off, notice that the status of the test function &lt;code&gt;tests::it_adds&lt;/code&gt; is a big red &lt;code&gt;FAILED&lt;/code&gt;. This is what failing tests with cargo look like.&lt;/p&gt;

&lt;p&gt;Below that, you'll see the 'failures' report which lists out the failing tests and some information as to why they failed.&lt;/p&gt;

&lt;p&gt;From our example, the &lt;code&gt;tests::it_adds&lt;/code&gt; test failed and as the report said, the left and right values passed into the &lt;code&gt;assert_eq!&lt;/code&gt; macro aren't equal (&lt;code&gt;==&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;That's because the left value is &lt;code&gt;-1&lt;/code&gt; while the right value is &lt;code&gt;9&lt;/code&gt;. Remember that on our &lt;code&gt;assert_eq!&lt;/code&gt; assertion, the left value that we passed to it is the &lt;code&gt;sum&lt;/code&gt; variable that contains the return value of &lt;code&gt;adder(4, 5)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Since our operator is wrong, the &lt;code&gt;adder&lt;/code&gt; function performs &lt;code&gt;4 - 5&lt;/code&gt; instead of the expected &lt;code&gt;4 + 5&lt;/code&gt;. That's why instead of the expected value of &lt;code&gt;9&lt;/code&gt;, we got &lt;code&gt;-1&lt;/code&gt;. Cargo noticed this so it raised a failing test.&lt;/p&gt;

&lt;p&gt;Below the failed tests report is its summary (sort of), still under the &lt;code&gt;failures&lt;/code&gt; category, but just lists out the names of the test functions that failed.&lt;/p&gt;

&lt;p&gt;And lastly, the entire summary of the overall test:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Status is: &lt;code&gt;test result: FAILED&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;0 passed&lt;/li&gt;
&lt;li&gt;1 failed&lt;/li&gt;
&lt;li&gt;0 ignored&lt;/li&gt;
&lt;li&gt;0 measured&lt;/li&gt;
&lt;li&gt;0 filtered out&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This time, our &lt;code&gt;failed&lt;/code&gt; counter is &lt;code&gt;1&lt;/code&gt; (referring to our failed test function) while &lt;code&gt;passed&lt;/code&gt; is &lt;code&gt;0&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to Handle Expected Errors
&lt;/h3&gt;

&lt;p&gt;From the previous section, you learned that errors cause tests to fail.&lt;/p&gt;

&lt;p&gt;But what if you are expecting the code you're testing to fail (like for example, by giving it an invalid parameter). If it gets an error, cargo will flag this as a failing test even though you're actually expecting it to fail.&lt;/p&gt;

&lt;p&gt;Can you expect failing behaviors?&lt;/p&gt;

&lt;p&gt;The short answer is: yes, you can!&lt;/p&gt;

&lt;p&gt;To demonstrate this, let's go back to the &lt;code&gt;lib.rs&lt;/code&gt; file and modify our &lt;code&gt;adder&lt;/code&gt; function. This time, let's set a rule for it to only accept single-digit integers (positive, zero, and negative) – otherwise, it should 'panic'. And for readability purposes, let's rename our &lt;code&gt;adder&lt;/code&gt; function to &lt;code&gt;single_digit_adder&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/lib.rs&lt;/span&gt;

&lt;span class="c1"&gt;// modify the `adder` function from earlier&lt;/span&gt;
&lt;span class="c1"&gt;// and turn it into `single_digit_adder`&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;single_digit_adder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;i8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;i8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;i8&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;is_single_digit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;i8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;is_single_digit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;is_single_digit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nd"&gt;panic!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Only single digit integers are allowed!"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[cfg(test)]&lt;/span&gt;
&lt;span class="k"&gt;mod&lt;/span&gt; &lt;span class="n"&gt;tests&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="c1"&gt;// --snip--&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since we're expecting the &lt;code&gt;single_digit_adder&lt;/code&gt; function to 'panic' whenever it receives a non-single digit integer, we need to specify that on the test function that's responsible for testing exactly this behavior.&lt;/p&gt;

&lt;p&gt;To do that, we need to add another attribute to one of our test functions. And that is the &lt;code&gt;#[should_panic]&lt;/code&gt; attribute.&lt;/p&gt;

&lt;p&gt;Going back to the &lt;code&gt;tests&lt;/code&gt; module, first, edit the &lt;code&gt;it_adds&lt;/code&gt; test function by renaming the &lt;code&gt;adder&lt;/code&gt; function call into &lt;code&gt;single_digit_adder&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then, create a new test function named &lt;code&gt;it_should_only_accept_single_digits&lt;/code&gt; with both the &lt;code&gt;#[test]&lt;/code&gt; and the &lt;code&gt;#[should_panic]&lt;/code&gt; attribute.&lt;/p&gt;

&lt;p&gt;Inside this new test function, call the &lt;code&gt;single_digit_adder&lt;/code&gt; function with an invalid parameter (&lt;code&gt;11&lt;/code&gt;) in this case.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/lib.rs&lt;/span&gt;

&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;single_digit_adder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;i8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;i8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;i8&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[cfg(test)]&lt;/span&gt;
&lt;span class="k"&gt;mod&lt;/span&gt; &lt;span class="n"&gt;tests&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;#[test]&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;it_adds&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;single_digit_adder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nd"&gt;assert_eq!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// our new test function that expects `panic!` with invalid param&lt;/span&gt;
    &lt;span class="nd"&gt;#[test]&lt;/span&gt;
    &lt;span class="nd"&gt;#[should_panic]&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;it_should_only_accept_single_digits&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;single_digit_adder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;You don't need any assert macros in the &lt;code&gt;it_should_only_accept_single_digits&lt;/code&gt; test function since we just need &lt;code&gt;single_digit_adder&lt;/code&gt; to 'panic'. So simply calling the function is enough.&lt;/p&gt;

&lt;p&gt;By giving it an invalid parameter (&lt;code&gt;11&lt;/code&gt;, which isn't a single digit), we're expecting it to 'panic'. The &lt;code&gt;#[should_panic]&lt;/code&gt; attribute will then expect that something should panic inside the &lt;code&gt;it_should_only_accept_single_digits&lt;/code&gt; test function. If it didn't catch any panic, this test will fail. It will only pass if &lt;code&gt;single_digit_adder&lt;/code&gt; panics.&lt;/p&gt;

&lt;p&gt;So to test if it really works, try commenting the &lt;code&gt;#[should_panic]&lt;/code&gt; attribute first and then run &lt;code&gt;cargo test&lt;/code&gt;. You should expect it to fail.&lt;/p&gt;

&lt;p&gt;&lt;a href="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%2Fm4y272pq2e1in0jcy84w.JPG" class="article-body-image-wrapper"&gt;&lt;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%2Fm4y272pq2e1in0jcy84w.JPG" alt="Catching an error if an expected error behavior don't have #[should_panic]" width="800" height="394"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, uncomment the &lt;code&gt;#[should_panic]&lt;/code&gt; attribute and re-run the test. Your tests should all pass as expected:&lt;/p&gt;

&lt;p&gt;&lt;a href="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%2Fylg65hm3b1wlx299dmpl.JPG" class="article-body-image-wrapper"&gt;&lt;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%2Fylg65hm3b1wlx299dmpl.JPG" alt="The output of a test case expecting and actually catching a failing behavior" width="800" height="394"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice that on the test &lt;code&gt;tests::it_should_only_accept_single_digits&lt;/code&gt;, there's a &lt;code&gt;should panic&lt;/code&gt; alongside it, and that it passed the test. This means that this test function caught a panic as expected.&lt;/p&gt;

&lt;p&gt;And there you have it! You just learned what unit testing is and how to perform unit tests with the Rust programming language. Feel free to write your own tests using the knowledge you obtained from this article and use it in your future projects.&lt;/p&gt;

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

&lt;p&gt;In this article, you learned what unit testing is and its importance in the software development process. You also learned how to write unit tests through the simple three steps process and actually perform testing in the Rust programming language.&lt;/p&gt;

&lt;p&gt;We covered the structure of a test module in Rust and how to construct a test function, and then we wrote a simple Rust program and some test cases for it. We also covered failing tests and how to handle an expected failing behavior in the unit of code.&lt;/p&gt;

&lt;p&gt;Testing is an important part of the software development process. Testing your code helps ensure that the software works as expected. As a developer, it's important that you test your code to ensure the quality of the software you're shipping and that those silly bugs don't reach the end-user!&lt;/p&gt;

</description>
      <category>rust</category>
      <category>testing</category>
    </item>
    <item>
      <title>REPL-like environment for Rust (A Rust Playground Locally)</title>
      <dc:creator>Menard Maranan</dc:creator>
      <pubDate>Sat, 16 Jul 2022 22:40:00 +0000</pubDate>
      <link>https://dev.to/menard_codes/repl-like-environment-for-rust-a-rust-playground-locally-4a54</link>
      <guid>https://dev.to/menard_codes/repl-like-environment-for-rust-a-rust-playground-locally-4a54</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;: I made a simple REPL-like environment program (similar to Python's REPL or interactive interpreter) for Rust. In a sense, it's a Rust Playground, but locally, right through your terminal, so you can play along some Rust code without creating a new file and compiling it.&lt;/p&gt;

&lt;p&gt;Repo Link is here: &lt;a href="https://github.com/menard-codes/rust_playground_locally" rel="noopener noreferrer"&gt;https://github.com/menard-codes/rust_playground_locally&lt;/a&gt; (also, read the caution at the end of this blogpost)&lt;/p&gt;

&lt;p&gt;Check the short demo video below to see it in action.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So I'm currently learning Rust.&lt;/p&gt;

&lt;p&gt;As per my background, this is my first low-level language, and I'm still getting used to compiling my code. Coming from interpreted languages such as Python, I kinda miss the REPL (as it always come in handy when testing small pieces of code).&lt;/p&gt;

&lt;p&gt;And for a language like Rust, the closest thing is to use the &lt;a href="https://play.rust-lang.org/" rel="noopener noreferrer"&gt;Rust playground&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I admit, I'm a lazy dev. It's sometimes annoying to constantly open a browser, create a new tab, and search 'rust playground' just to test a small piece of code. Comparing it to Python's REPL which is more straightforward, I can just open the terminal and do my thing there.&lt;/p&gt;

&lt;p&gt;So I thought of making something similar for Rust, a REPL-like environment to run Rust code without the need to create a new file and compile it, all done right through the terminal.&lt;/p&gt;

&lt;p&gt;So I just did that.&lt;/p&gt;

&lt;p&gt;And to give you an idea, here's what it looks like:&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/1nkxZF0lvKA"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;So how did I made this?&lt;/p&gt;

&lt;p&gt;First off, I wondered around the &lt;a href="https://play.rust-lang.org/" rel="noopener noreferrer"&gt;Rust Playground&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I tried running a simple hello world program, and opened the dev tools and checked the networks tab to see what's what. From there, I noticed that the playground sends the code from the playground to an API which executes it.&lt;/p&gt;

&lt;p&gt;Then I thought if I can send my own request to that API and make it execute my code, then I can work on my idea around this. So I pulled out the API URL and the structure of the request payload, dropped them off into my Python script, wrote a sample Rust code, and tried sending it to this code execution API to see if it will work.&lt;/p&gt;

&lt;p&gt;As you might probably guess, it worked.&lt;/p&gt;

&lt;p&gt;"Gotcha!" I said.&lt;/p&gt;

&lt;p&gt;So then I preceded to write this stupidly simple program that looks like Python's REPL, wherein it takes the user's raw code input and sends it directly to that API to execute the Rust code for us, then print the stdout or stderr that's returned by the API (if any).&lt;/p&gt;

&lt;p&gt;Now, instead of opening the Rust playground, I can just pull out my terminal and run pieces of Rust code; without the need to create a new file and compiling it.&lt;/p&gt;

&lt;p&gt;And since I'm pretty much satisfied (at least) with it, I thought why not share it to everybody else. There might be someone out there who also came from a background like me, and who used to have (and love) REPL or the interactive environment of their languages to test out some pieces of code.&lt;/p&gt;

&lt;p&gt;So I decided to share it with the dev community.&lt;/p&gt;

&lt;p&gt;You can check the repo of this stupid simple program I made here: &lt;a href="https://github.com/menard-codes/rust_playground_locally" rel="noopener noreferrer"&gt;https://github.com/menard-codes/rust_playground_locally&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A little word of caution. I haven't tested this thing out, thus, there are potentially a number of edge-cases (as well as bugs) that'll make this not work properly.&lt;/p&gt;
&lt;/blockquote&gt;

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