<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ React - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ React - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Fri, 15 May 2026 22:29:53 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/reactjs/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Create Dynamic Emails in Go with React Email  ]]>
                </title>
                <description>
                    <![CDATA[ Backend applications are required to send emails to users to deliver notifications and maintain communication outside the application interface. These emails usually contain information specific to ea ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-create-dynamic-emails-in-go-with-react-email/</link>
                <guid isPermaLink="false">69e689acc9501dd0102dc758</guid>
                
                    <category>
                        <![CDATA[ Go Language ]]>
                    </category>
                
                    <category>
                        <![CDATA[ golang ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Orim Dominic Adah ]]>
                </dc:creator>
                <pubDate>Mon, 20 Apr 2026 20:16:44 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/62917f79-c4d8-40e2-8eb7-87b63560e546.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Backend applications are required to send emails to users to deliver notifications and maintain communication outside the application interface. These emails usually contain information specific to each user, such as the user's name or address, making them dynamic.</p>
<p>This article walks you through building a dynamic email template with React Email, converting it to HTML, and injecting data into it using Go templates. It also contains an optional section that shows you how to send and test the email delivery with MailHog.</p>
<p>To follow along with this article, you need to have Go and Node.js installed on your computer. You should also have a basic understanding of React and some familiarity with Go templates, though these aren't strict requirements because you can pick them up as you practise along.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-what-is-react-email">What is React Email?</a></p>
</li>
<li><p><a href="#heading-go-templates">Go Templates</a></p>
<ul>
<li><a href="#heading-go-template-delimiters">Go Template Delimiters</a></li>
</ul>
</li>
<li><p><a href="#heading-create-dynamic-emails-in-go-with-react-email">Create Dynamic Emails in Go with React Email</a></p>
<ul>
<li><p><a href="#heading-set-up-the-project">Set Up the Project</a></p>
</li>
<li><p><a href="#heading-set-up-react-email">Set Up React Email</a></p>
</li>
<li><p><a href="#heading-create-a-react-email-template">Create a React Email Template</a></p>
</li>
<li><p><a href="#heading-set-up-go-templates-from-html-files">Set Up Go Templates from HTML Files</a></p>
</li>
<li><p><a href="#heading-render-the-dynamic-email-in-the-browser">Render the Dynamic Email in the Browser</a></p>
</li>
<li><p><a href="#heading-send-and-test-email-with-go-mail-and-mailhog">Send and Test Email with go-mail and MailHog</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-what-is-react-email">What is React Email?</h2>
<p><a href="https://react.email/">React Email</a> is a JavaScript library that helps you build dynamic email templates with React. If you already know basic React, React Email provides a better developer experience for building dynamic email templates. Here are some reasons why:</p>
<ul>
<li><p><strong>Familiar syntax with React:</strong> If you know React already, React Email eliminates the hassle in learning a separate templating language, using inefficient drag-and-drop UIs, or writing emaiil templates from scratch with HTML tables.</p>
</li>
<li><p><strong>Reusable built-in components</strong>: React Email provides ready-to-use UI components like <a href="https://react.email/components/buttons">Buttons</a> and <a href="https://react.email/components/footers">Footers</a> so you don't have to start from scratch, making email development seamless and fast.</p>
</li>
<li><p><strong>Consistency across email clients</strong>: React Email generates email templates that have been tested and work well across popular email clients. This helps eliminate worries over emails rendering inconsistently across email clients.</p>
</li>
<li><p><strong>Email development tooling</strong>: React Email has features for previewing and assessing emails built with it. Some of these features include:</p>
<ul>
<li><p>A local development server that lets you preview mobile and desktop views of your emails in your web browser as you develop the emails in real time</p>
</li>
<li><p>An email delivery feature that sends your email to a real email address for preview</p>
</li>
<li><p>A compatibility checker that shows you how well your email is supported across popular email clients</p>
</li>
<li><p>A spam scorer that analyses your email to determine if it's likely to be marked as spam</p>
</li>
</ul>
</li>
<li><p><strong>Tailwind integration</strong>: <a href="https://tailwindcss.com/">Tailwind</a> is a popular CSS framework that provides classes for styling HTML and making it responsive. React Email integrates with Tailwind easily for creating beautiful emails.</p>
</li>
</ul>
<p>All these features are free to use.</p>
<p>In this article, you'll learn how to generate an HTML file from a React Email template, convert it to a Go template, and inject data into the template for previewing.</p>
<h2 id="heading-go-templates">Go Templates</h2>
<p>The Go <a href="https://pkg.go.dev/html/template">html/template</a> package allows you to define reusable HTML templates that can be populated with dynamic data. These templates contain placeholders (called actions) that are evaluated by Go's templating engine and replaced with actual values during execution.</p>
<img src="https://cdn.hashnode.com/uploads/covers/66e28b713f978a0e2cd2b763/7ff18217-2ff1-43fc-96b5-01681fbd0ac5.png" alt="Golang HTML template parsing and execution" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>First, you give the package HTML content that contains Go-specific annotations. It converts the HTML content to a Go HTML template and the Go-specific annotations to actions in the template. The template is then executed with data to produce HTML output that contains the data.</p>
<pre><code class="language-go">package main

import (
	"html/template"
	"os"
)

func main() {
	tmpl := template.New("hello")
	tmpl, _ = tmpl.Parse(`&lt;p&gt;Hello {{.}}&lt;/p&gt;`)
	tmpl.Execute(os.Stdout, "Gopher")
}

// Output: &lt;p&gt;Hello Gopher&lt;/p&gt;
// Playground: https://goplay.tools/snippet/KxbkWPIArz5
</code></pre>
<p>In the code snippet above:</p>
<ul>
<li><p><code>template.New</code> creates an empty template object with the name "hello"</p>
</li>
<li><p><code>tmpl.Parse(`&lt;p&gt;Hello {{.}}&lt;/p&gt;`)</code> parses the HTML string <code>&lt;p&gt;Hello {{.}}&lt;/p&gt;</code> to create a Go HTML template and saves it in <code>tmpl</code> . The <code>{{.}}</code> part of the HTML string is an action which acts as a placeholder for data. <code>{{</code> and <code>}}</code> are called delimiters and <code>.</code> is the data access identifier.</p>
</li>
<li><p><code>tmpl.Execute(os.Stdout, "Gopher")</code> populates the action with data - the "Gopher", string, and writes the resulting HTML output to the console.</p>
</li>
</ul>
<h3 id="heading-go-template-delimiters">Go Template Delimiters</h3>
<p>In the previous code snippet, you used double curly braces (<code>{{</code> and <code>}}</code>) as the delimiters in the Go template. Delimiters are symbols that Go uses to determine what parts of the input string represent an action – that is, a statement to be evaluated.</p>
<p>You can change the delimiters by using the <code>Delims</code> method on a template. An example is shown in the snippet below:</p>
<pre><code class="language-go">package main

import (
	"html/template"
	"os"
)

func main() {
	tmpl := template.New("hello")
	tmpl, _ = tmpl.Delims("((", "))").Parse(`&lt;p&gt;Hello ((.))&lt;/p&gt;`)
	tmpl.Execute(os.Stdout, "Gopher")
}

// Output: &lt;p&gt;Hello Gopher&lt;/p&gt;
// Playground: https://goplay.tools/snippet/00RuDzvZYwN
</code></pre>
<p>In the snippet above, <code>((</code> and <code>))</code> are used as the delimiters for the <code>hello</code> template.</p>
<p>This is important because you'll set your delimiters to prevent conflicts between Go's default delimiters and React's curly braces in React Email templates.</p>
<h2 id="heading-create-dynamic-emails-in-go-with-react-email">Create Dynamic Emails in Go with React Email</h2>
<p>The image below summarizes how the sample application you'll build in this article works:</p>
<img src="https://cdn.hashnode.com/uploads/covers/66e28b713f978a0e2cd2b763/289f0ea5-1de7-46f7-b463-32f6c61fa750.png" alt="From React Email templates to Email HTML with Dynamic Data" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>You'll create a React Email template that contains Go template annotations. Next, you'll use Node.js to create HTML files from it. Go will parse the HTML file to create a Go template, execute it, and send it.</p>
<p>Optionally, you'll use go-mail to send the email and MailHog, a local SMTP server, to preview it in your browser.</p>
<h3 id="heading-set-up-the-project">Set Up the Project</h3>
<p>First, make sure that you have Go and Node.js installed on your computer already. Clone this <a href="https://github.com/orimdominic/freeCodeCamp-go-react-email">freeCodeCamp-go-react-email</a> repository and checkout the <code>01-setup</code> branch using <code>git checkout 01-setup</code>.</p>
<p>The project contains a <code>main.go</code> file in the <code>cmd</code> directory and a <code>go.mod</code> file. It also contains a <code>.gitignore</code> file to instruct Git to ignore all <code>node_modules</code> directories.</p>
<p>Run <code>go run cmd/main.go</code> in the terminal of the project. If you see "It works!" logged to the console, you have set it up properly and you can continue to the next section.</p>
<h3 id="heading-set-up-react-email">Set Up React Email</h3>
<p>In the project root directory, create a <code>mailer</code> directory which will serve as the mailer package. It will hold all functionality related to creating and sending mails.</p>
<p>In the <code>mailer</code> directory, you'll create the <code>emails</code> Node.js project that will handle React Email functionality. To create the project:</p>
<ul>
<li><p>Create a directory called <code>_emails</code> in the <code>mailer</code> directory. The name of the directory starts with an underscore because it should be ignored when the <code>go build</code> command is run. So it won't be included in the Go compiled executable file.</p>
</li>
<li><p>Run <code>npm init -y</code> in the root terminal of the <code>_emails</code> directory to initialise the Node.js project in it. This will create a <code>package.json</code> file in the directory.</p>
</li>
<li><p>Update the value of the name field in <code>package.json</code> to "emails" to make the package name more conventional. This step is not compulsory.</p>
</li>
</ul>
<p>Next, install the required React Email libraries by running the following commands in the root terminal of the <code>_emails</code> directory:</p>
<pre><code class="language-shell">npm install @react-email/ui @types/react -D -E
npm install react-email react react-dom -E
</code></pre>
<p>After the installation is complete, replace the <code>scripts</code> field of the <code>package.json</code> file with the code snippet below:</p>
<pre><code class="language-json">  "scripts": {
    "dev": "email dev --dir ./src",
    "export": "email export --pretty --dir ./src --outDir ../templates"
  },
</code></pre>
<p>The <code>dev</code> script starts and runs the server for previewing the React Email templates in the browser. You will write the template with React and store it in the <code>src</code> directory under <code>_emails</code>. The <code>export</code> script transpiles the template files in the <code>src</code> directory from JSX (or TSX) to HTML and stores them in a directory called <code>templates</code>, a direct child of the <code>mailer</code> directory – not a child of the <code>_emails</code> directory.</p>
<p>The <code>templates</code> directory is stored as a child directory of the <code>mailer</code> directory because the Go project needs only the HTML output stored in the <code>templates</code> directory and not the contents of <code>_emails</code>.</p>
<p>If you've completed all these steps, you have set up React Email in the <code>emails</code> Node.js project. To view the current status of the project at this point, visit <a href="https://github.com/orimdominic/freeCodeCamp-go-react-email/tree/02-setup-react-email">freeCodeCamp-go-react-email/02-setup-react-email</a>.</p>
<p>In the next section, you'll create a React Email template and preview it in the browser.</p>
<h3 id="heading-create-a-react-email-template">Create a React Email Template</h3>
<p>In this section, you'll create a React Email template and preview it in the browser. You'll also build and export the template to HTML files.</p>
<p>Create a directory called <code>src</code> inside the <code>_emails</code> directory. Inside the <code>src</code> directory, create a file called <code>welcome.tsx</code>. Copy and paste the content of the snippet below into <code>welcome.tsx</code>.</p>
<pre><code class="language-typescript">import {
  Body,
  Button,
  Container,
  Head,
  Heading,
  Html,
  Img,
  Preview,
  Section,
  Tailwind,
  Text,
} from "react-email";

interface WelcomeEmailProps {
  username?: string;
  company?: string;
  gophers?: string[];
}

const WelcomeEmail = ({
  username = "Nicole",
  company = "GoWorld",
  gophers = ["Tinky Winky", "Dipsy", "Laa-Laa", "Po"],
}: WelcomeEmailProps) =&gt; {
  const previewText = `Welcome to \({company}, \){username}!`;

  return (
    &lt;Html&gt;
      &lt;Head /&gt;
      &lt;Preview&gt;{previewText}&lt;/Preview&gt;
      &lt;Tailwind&gt;
        &lt;Body className="m-auto font-sans"&gt;
          &lt;Container className="mb-10 mx-auto p-5 max-w-[465px]"&gt;
            &lt;Section className="mt-10"&gt;
              &lt;Img
                src={`https://storage.googleapis.com/gopherizeme.appspot.com/gophers/69428e5ec867c34bb4a49d5a063fdbc2a6633aed.png`}
                width="80"
                height="80"
                alt="Logo"
                className="my-0 mx-auto"
              /&gt;
            &lt;/Section&gt;
            &lt;Heading className="text-2xl font-normal text-center p-0 my-8 mx-0"&gt;
              Welcome to &lt;strong&gt;{company}&lt;/strong&gt;, {username}!
            &lt;/Heading&gt;
            &lt;Text className="text-start text-base"&gt;Hello {username},&lt;/Text&gt;
            &lt;Text className="text-start text-base leading-relaxed"&gt;
              We're excited to have you onboard at &lt;strong&gt;{company}&lt;/strong&gt;.
              We hope you enjoy your journey with us. If you have any questions
              or need assistance, feel free to reach out to any of the following
              gophers:
            &lt;/Text&gt;
            &lt;div className="text-start text-base leading-relaxed"&gt;
              &lt;ul className="pl-3"&gt;
                {gophers.map((gopher) =&gt; (
                  &lt;li&gt;{gopher}&lt;/li&gt;
                ))}
              &lt;/ul&gt;
            &lt;/div&gt;
            &lt;Section className="text-center mt-[32px] mb-[32px]"&gt;
              &lt;Button
                className="py-2.5 px-5 bg-white rounded-md text-base font-semibold no-underline text-center bg-black text-white"
                href={`https://go.dev`}
              &gt;
                Get Started
              &lt;/Button&gt;
            &lt;/Section&gt;
            &lt;Text className="text-start text-base text-white"&gt;
              Cheers,
              &lt;br /&gt;
              The {company} Team
            &lt;/Text&gt;
          &lt;/Container&gt;
        &lt;/Body&gt;
      &lt;/Tailwind&gt;
    &lt;/Html&gt;
  );
};

export default WelcomeEmail;
</code></pre>
<p>The code snippet above is the React Email template that you'll use in this article. To preview it, navigate to the terminal of the <code>_emails</code> root directory and run <code>npm run dev</code> . Use your web browser to visit the preview URL displayed on the terminal. Click on the "welcome" link on the left sidebar and you should see a UI similar to the one in the screenshot below:</p>
<img src="https://cdn.hashnode.com/uploads/covers/66e28b713f978a0e2cd2b763/9a1253d2-7502-40d5-9081-7974c7a83f36.png" alt="React Email preview UI" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>In the UI above, React Email renders the email with the default values supplied to the <code>welcome</code> email template.</p>
<p>Stop the server by clicking on the terminal that runs it by pressing <code>CTRL + C</code>. Build the HTML output of the <code>src</code> directory and export it by running <code>npm run export</code> in the terminal of the <code>_emails</code> root directory. This creates a <code>templates</code> directory within the <code>mailer</code> directory where the exported HTML files are stored. In the <code>templates</code> directory, you'll see a <code>welcome.html</code> file – the HTML output from <code>welcome.tsx</code>.</p>
<p>To see the current status of the project, visit <a href="https://github.com/orimdominic/freeCodeCamp-go-react-email/tree/03-create-react-email-template">freeCodeCamp-go-react-email/03-create-react-email-template</a>.</p>
<h3 id="heading-set-up-go-templates-from-html-files">Set Up Go Templates from HTML Files</h3>
<p>You have created a React Email template, previewed it, built it, and exported it as an HTML file. In this section, you'll update the React Email template to use the delimiters you set and not React's curly braces. You'll also create a Go template from the HTML file.</p>
<p>To get started, replace the content of <code>welcome.tsx</code> with the code snippet below to to use <code>((</code> and <code>))</code> as delimiters and remove TypeScript types:</p>
<pre><code class="language-typescript">import {
  Body,
  Button,
  Container,
  Head,
  Heading,
  Html,
  Img,
  Preview,
  Section,
  Tailwind,
  Text,
} from "react-email";

const WelcomeEmail = () =&gt; {
  const previewText = `Welcome to ((.Company)), ((.Username))!`;

  return (
    &lt;Html&gt;
      &lt;Head /&gt;
      &lt;Preview&gt;{previewText}&lt;/Preview&gt;
      &lt;Tailwind&gt;
        &lt;Body className="m-auto font-sans"&gt;
          &lt;Container className="mb-10 mx-auto p-5 max-w-[465px]"&gt;
            &lt;Section className="mt-10"&gt;
              &lt;Img
                src={`https://storage.googleapis.com/gopherizeme.appspot.com/gophers/69428e5ec867c34bb4a49d5a063fdbc2a6633aed.png`}
                width="80"
                height="80"
                alt="Gopher"
                className="my-0 mx-auto"
              /&gt;
            &lt;/Section&gt;
            &lt;Heading className="text-2xl font-normal text-center p-0 my-8 mx-0"&gt;
              Welcome to &lt;strong&gt;((.Company))&lt;/strong&gt;, ((.Username))!
            &lt;/Heading&gt;
            &lt;Text className="text-start text-base"&gt;Hello ((.Username)),&lt;/Text&gt;
            &lt;Text className="text-start text-base leading-relaxed"&gt;
              We're excited to have you onboard at &lt;strong&gt;((.Company))&lt;/strong&gt;
              . We hope you enjoy your journey with us. If you have any
              questions or need assistance, feel free to reach out to any of the
              following Gophers:
            &lt;/Text&gt;
            &lt;div className="text-start text-base leading-relaxed"&gt;
              &lt;ul className="pl-3"&gt;
                ((range .Gophers))
                &lt;li&gt;((.))&lt;/li&gt;
                ((end))
              &lt;/ul&gt;
            &lt;/div&gt;
            &lt;Section className="text-center mt-[32px] mb-[32px]"&gt;
              &lt;Button
                className="py-2.5 px-5 bg-white rounded-md border text-black text-base font-semibold no-underline text-center"
                href={`https://go.dev`}
              &gt;
                Get Started
              &lt;/Button&gt;
            &lt;/Section&gt;

            &lt;Text className="text-start text-base"&gt;
              Cheers,
              &lt;br /&gt;
              The ((.Company)), Team
            &lt;/Text&gt;
          &lt;/Container&gt;
        &lt;/Body&gt;
      &lt;/Tailwind&gt;
    &lt;/Html&gt;
  );
};

export default WelcomeEmail;
</code></pre>
<p>Run <code>npm run export</code> in the root terminal of the <code>_emails</code> directory to build and export this version of the React Email template to HTML. The HTML generated will contain Go template annotations that will become actions when parsed by Go to form a Go HTML template.</p>
<p>In the <code>mailer</code> directory, create a file named <code>fs.go</code>. The code in the file will be used to embed the files in the <code>templates</code> directory for use in the Go application. Copy and paste the content of the snippet below into <code>fs.go</code>:</p>
<pre><code class="language-go">package mailer

import (
	"embed"
	"io/fs"
)

//go:embed templates/*
var embedded embed.FS
var templateFS, _ = fs.Sub(embedded, "templates")
</code></pre>
<p><code>//go:embed templates/*</code> tells the Go compiler to embed files from the current directory (<code>mailer</code>) into the compiled binary of the Go application. You need this to access the HTML template files from the Go application. <code>templateFS</code> will be used to access the files in the <code>templates</code> subdirectory.</p>
<p>Create another file in the <code>mailer</code> directory and name it <code>mailer.go</code>. <code>mailer.go</code> will contain code used to parse HTML files to make Go HTML templates and also send emails. Copy the content of the code snippet below into <code>mailer.go</code>:</p>
<pre><code class="language-go">package mailer

import (
	"html/template"
	"io"
)

const (
	welcomeMailKey = "welcome_mail"
)

func setUpTemplates() (map[string]*template.Template, error) {
	templates := make(map[string]*template.Template)

	tmpl := template.New("welcome.html").Delims("((", "))")
	welcomeEmailTmpl, err := tmpl.ParseFS(templateFS, "welcome.html")
	if err != nil {
		return nil, err
	}

	templates[welcomeMailKey] = welcomeEmailTmpl

	return templates, nil
}

type Mailer struct {
	templates map[string]*template.Template
}

// NewMailer creates a new mailer
func NewMailer() (*Mailer, error) {
	tpls, err := setUpTemplates()
	if err != nil {
		return nil, err
	}

	return &amp;Mailer{
		templates: tpls,
	}, nil
}

type WelcomEmailData struct {
	Username string
	Company  string
	Gophers  []string
}

func (mailer *Mailer) WriteWelcomeMail(w io.Writer, data WelcomEmailData) error {
	tmpl := mailer.templates[welcomeMailKey]
	err := tmpl.Execute(w, data)

	return err
}
</code></pre>
<p>In the code snippet above:</p>
<ul>
<li><p><code>setUpTemplates</code> creates a template object, <code>tmpl</code>, and sets its delimiters. <code>tmpl</code> parses <code>welcome.html</code> to convert it to a Go template and stores the template with <code>welcomeEmailTmpl</code> as its identifier. After that, <code>welcomeEmailTmpl</code> is added to the <code>templates</code> map with <code>welcomeMailKey</code> as its key and <code>templates</code> is returned.</p>
</li>
<li><p><code>NewMailer</code> creates and returns a <code>Mailer</code> object which holds the templates map and methods to work with the mail templates.</p>
</li>
<li><p>WriteWelcomeMail is a method on <code>Mailer</code> that's used to execute the welcome email template with real data.</p>
</li>
</ul>
<p>To view the current status of the codebase at this point, visit <a href="https://github.com/orimdominic/freeCodeCamp-go-react-email/tree/04-create-golang-template">freeCodeCamp-go-react-email/04-create-golang-template</a>.</p>
<h3 id="heading-render-the-dynamic-email-in-the-browser">Render the Dynamic Email in the Browser</h3>
<p>In this section, you'll create a simple web server to view the rendered email template containing the dynamic values passed to it.</p>
<p>Replace the content of <code>main.go</code> with the code snippet below:</p>
<pre><code class="language-go">package main

import (
	"fmt"
	"net/http"
	"os"

	pkgMailer "github.com/orimdominic/freeCodeCamp-go-react-email/mailer"
)

func main() {
	mailer, err := pkgMailer.NewMailer()
	if err != nil {
		fmt.Fprint(os.Stderr, err)
		os.Exit(1)
	}

	http.HandleFunc("/mail", func(w http.ResponseWriter, r *http.Request) {
		username := r.URL.Query().Get("username")
		company := r.URL.Query().Get("company")
		gophers := []string{"Tinky Winky", "Dipsy", "Laa-Laa", "Po"}

		err := mailer.WriteWelcomeMail(w, pkgMailer.WelcomEmailData{
			Username: username,
			Company:  company,
			Gophers:  gophers,
		})
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
	})

	port := ":8888"
	err = http.ListenAndServe(port, nil)
	if err != nil {
		fmt.Fprint(os.Stderr, err)
		os.Exit(1)
	}
}
</code></pre>
<p>The code snippet above first creates a mailer object using the <code>NewMailer</code> function from <code>mailer.go</code>. After the error handling, it creates a simple web server running on port <code>8888</code> with a <code>GET /mail</code> route.</p>
<p>The <code>GET /mail</code> route accepts two query parameters: <code>username</code> and <code>company</code>, which will be used as the dynamic data for the email. The result of executing the template with <code>WriteWelcomeMail</code> is written as an HTML response on the browser. You'll use this route to test the functionality of the <code>mailer</code> package.</p>
<p>Before you start the server, you should build and export the React Email templates so that your HTML files always have the most recent changes from React Email templates. Instead of navigating between different directories to build, export and run the server, you can use a Makefile.</p>
<p>Navigate to the terminal of the root directory of the project and create a file called <code>Makefile</code>. Copy and paste the content of the code snippet below into it:</p>
<pre><code class="language-plaintext">run: email-build
	go run cmd/main.go

email-build: mailer/_emails
	npm --prefix mailer/_emails run export
</code></pre>
<p>The <code>run</code> script of the Makefile above builds and exports the React Email templates as HTML to the <code>mailer/templates</code> directory and then starts the Go application. Ensure that <code>Makefile</code> uses hard tabs, not spaces for indentation.</p>
<p>Run <code>make run</code> in the terminal of the root directory of the project and visit <code>http://localhost:8888/mail?username=Nicole&amp;company=GoWorld</code> in the browser. You'll see the email rendered on the browser UI.</p>
<img alt="Go template executed in the browser" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>Replace the values of <code>username</code> and <code>company</code> in the URL to test the email with different values.</p>
<p>With this setup, you can integrate the result of executing the template with your mail client and the email recipient will see the email as it's displayed in the browser.</p>
<p>To view the current status of the codebase at this point, visit <a href="https://github.com/orimdominic/freeCodeCamp-go-react-email/tree/05-render-dynamic-email">freeCodeCamp-go-react-email/05-render-dynamic-email</a>.</p>
<h3 id="heading-send-and-test-email-with-go-mail-and-mailhog">Send and Test Email with go-mail and MailHog</h3>
<p>In the previous section, you supplied data to execute your template, but it was rendered in the browser, not an email client. In this section, you'll use go-mail to send the email and MailHog to intercept and view it.</p>
<p>This section is optional. If you don't have MailHog installed locally, you'll need Docker Compose to set it up for this project. Make sure Docker Compose is installed on your computer before proceeding.</p>
<p>In your terminal, navigate to the root directory of the project and run <code>go get github.com/wneessen/go-mail</code> to install go-mail. Create a <code>compose.yml</code> file in the root directory of the project and paste the contents of the code snippet below into it:</p>
<pre><code class="language-yaml">services:
  mailhog:
    image: mailhog/mailhog
    restart: no
    logging:
      driver: "none" # disable saving logs
    ports:
      - 1025:1025 # smtp server
      - 8025:8025 # web ui
</code></pre>
<p>In your terminal, navigate to the project's root directory and run <code>docker compose up</code> to pull and start the MailHog SMTP server. MailHog listens for emails on port <code>1025</code> and exposes a web UI at <code>http://localhost:8025</code> where you can view intercepted emails. Depending on your internet connection, the initial image pull may take a few minutes.</p>
<p>Replace <code>mailer.go</code> with the content of the code snippet below:</p>
<pre><code class="language-go">package mailer

import (
	"html/template"
	"io"

	"github.com/wneessen/go-mail"
)

const (
	welcomeMailKey = "welcome_mail"
    sender = "noreply@localhost.com"
)

func setUpTemplates() (map[string]*template.Template, error) {
	templates := make(map[string]*template.Template)

	tmpl := template.New("welcome.html").Delims("((", "))")
	welcomeEmailTmpl, err := tmpl.ParseFS(templateFS, "welcome.html")
	if err != nil {
		return nil, err
	}

	templates[welcomeMailKey] = welcomeEmailTmpl

	return templates, nil
}

type Mailer struct {
	client    *mail.Client
	templates map[string]*template.Template
}

// NewMailer creates a new mailer
func NewMailer() (*Mailer, error) {
	tpls, err := setUpTemplates()
	if err != nil {
		return nil, err
	}

	c, err := mail.NewClient(
		"localhost",
		mail.WithPort(1025),
		mail.WithTLSPolicy(mail.NoTLS),
	)

	if err != nil {
		return nil, err
	}

	return &amp;Mailer{
		client:    c,
		templates: tpls,
	}, nil
}

type WelcomEmailData struct {
	Username string
	Company  string
	Gophers  []string
}

func (mailer *Mailer) WriteWelcomeMail(w io.Writer, data WelcomEmailData) error {
	tmpl := mailer.templates[welcomeMailKey]
	err := tmpl.Execute(w, data)

	return err
}

func (mailer *Mailer) SendWelcomeMail(to string, data WelcomEmailData) error {
	m := mail.NewMsg()
	m.From(sender)
	m.To(to)
	m.Subject("Welcome to " + data.Company)
	m.SetBodyHTMLTemplate(mailer.templates[welcomeMailKey], data)

	err := mailer.client.DialAndSend(m)
	return err
}
</code></pre>
<p>The new changes to <code>mailer.go</code> include:</p>
<ul>
<li><p>An import of the go-mail</p>
</li>
<li><p>The creation of a <code>sender</code> constant which represents the email of the sender</p>
</li>
<li><p>The creation of a mail client with go-mail</p>
</li>
<li><p>The creation of a <code>SendWelcomeMail</code> method on the <code>mailer</code> struct which creates an email with <code>welcomeEmailTmpl</code>, executes it, and sends it to a receiver.</p>
</li>
</ul>
<p>In <code>main.go</code>, update the <code>GET /mail</code> route to use <code>SendWelcomeMail</code> instead of <code>WriteWelcomeMail</code>. You can use any email address you want. The snippet below uses <code>fcc@go.dev</code>:</p>
<pre><code class="language-go">		err := mailer.SendWelcomeMail("fcc@go.dev", pkgMailer.WelcomEmailData{
			Username: username,
			Company:  company,
			Gophers:  gophers,
		})
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		fmt.Fprint(w, "Email sent")
</code></pre>
<p>Ensure that the mail server is running by visiting <a href="http://localhost:8025">http://localhost:8025</a> in your web browser. In another terminal, from the root directory of the project, run <code>make run</code> to start the server. Test the functionality of the route by visiting <code>http://localhost:8888/mail?username=Nicole&amp;company=GoWorld</code> once again. Next, check the email server by visiting <a href="http://localhost:8025">http://localhost:8025</a>. You should see a UI similar to the one in the screenshot below:</p>
<img src="https://cdn.hashnode.com/uploads/covers/66e28b713f978a0e2cd2b763/3cba1adf-f0e7-4539-8f65-931fef1aa73b.png" alt="MailHog UI for previewing mails" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>Click on "Welcome to Helix" to view the email.</p>
<p>To view the current status of the codebase at this point, visit <a href="https://github.com/orimdominic/freeCodeCamp-go-react-email/tree/06-send-email">freeCodeCamp-go-react-email/06-send-email</a>.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>By following along with this tutorial, you have:</p>
<ul>
<li><p>Learned how to create Go email templates from React Email templates</p>
</li>
<li><p>Learned how to use Makefiles to run custom scripts</p>
</li>
<li><p>Previewed your email in the browser and tested it using MailHog</p>
</li>
</ul>
<p>You can now skip the hassle of writing raw HTML email tables or learning a new templating language. With React Email and Go templates, you have a cleaner, more developer-friendly way to build and send beautiful emails.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ A Developer’s Guide to Lazy Loading in React and Next.js ]]>
                </title>
                <description>
                    <![CDATA[ Large JavaScript bundles can slow down your application. When too much code loads at once, users wait longer for the first paint and pages feel less responsive. Search engines may also rank slower sit ]]>
                </description>
                <link>https://www.freecodecamp.org/news/a-developers-guide-to-lazy-loading-in-react-and-nextjs/</link>
                <guid isPermaLink="false">69dea43f91716f3cfb762c99</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Next.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ David Aniebo ]]>
                </dc:creator>
                <pubDate>Tue, 14 Apr 2026 20:31:59 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/9e6d8733-23e7-4dab-8da2-98fbbc1c44e9.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Large JavaScript bundles can slow down your application. When too much code loads at once, users wait longer for the first paint and pages feel less responsive. Search engines may also rank slower sites lower in results.</p>
<p>Lazy loading helps solve this problem by splitting your code into smaller chunks and loading them only when they are needed</p>
<p>This guide walks you through lazy loading in React and Next.js. By the end, you'll know when to use <code>React.lazy</code>, <code>next/dynamic</code>, and <code>Suspense</code>, and you'll have working examples you can copy and adapt to your own projects.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-what-is-lazy-loading">What is Lazy Loading?</a></p>
</li>
<li><p><a href="#heading-prequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-how-to-use-reactlazy-for-code-splitting">How to Use React.lazy for Code Splitting</a></p>
</li>
<li><p><a href="#heading-how-to-use-suspense-with-reactlazy">How to Use Suspense with React.lazy</a></p>
</li>
<li><p><a href="#heading-how-to-handle-errors-with-error-boundaries">How to Handle Errors with Error Boundaries</a></p>
</li>
<li><p><a href="#heading-how-to-use-nextdynamic-in-nextjs">How to Use next/dynamic in Next.js</a></p>
</li>
<li><p><a href="#heading-reactlazy-vs-nextdynamic-when-to-use-each">React.lazy vs next/dynamic: When to Use Each</a></p>
</li>
<li><p><a href="#heading-real-world-examples">Real-World Examples</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-what-is-lazy-loading">What is Lazy Loading?</h2>
<p>Lazy loading is a performance technique that defers loading code until it's needed. Instead of loading your entire app at once, you split it into smaller chunks. The browser only downloads a chunk when the user navigates to that route or interacts with that feature.</p>
<p>Benefits include:</p>
<ul>
<li><p><strong>Faster initial load</strong>: Smaller first bundle means quicker time to interactive</p>
</li>
<li><p><strong>Better Core Web Vitals</strong>: Improves Largest Contentful Paint and Total Blocking Time</p>
</li>
<li><p><strong>Lower bandwidth</strong>: Users only download what they use</p>
</li>
</ul>
<p>In React, you achieve this with dynamic imports and <code>React.lazy()</code> or Next.js’s <code>next/dynamic</code>.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before you follow along, you should have:</p>
<ul>
<li><p>Basic familiarity with React (components, hooks, state)</p>
</li>
<li><p>Node.js installed (version 18 or later recommended)</p>
</li>
<li><p>A React app (Create React App or Vite) or a Next.js app (for the Next.js examples)</p>
</li>
</ul>
<p>For the React examples, you can use Create React App or Vite. For the Next.js examples, use the App Router (Next.js 13 or later).</p>
<h2 id="heading-how-to-use-reactlazy-for-code-splitting">How to Use <code>React.lazy</code> for Code Splitting</h2>
<p><code>React.lazy()</code> lets you define a component as a dynamic import. React will load that component only when it's first rendered.</p>
<p><code>React.lazy()</code> expects a function that returns a dynamic <code>import()</code>. The imported module must use a default export.</p>
<p>Here's a basic example:</p>
<pre><code class="language-jsx">import { lazy } from 'react';

const HeavyChart = lazy(() =&gt; import('./HeavyChart'));
const AdminDashboard = lazy(() =&gt; import('./AdminDashboard'));

function App() {
  return (
    &lt;div&gt;
      &lt;h1&gt;My App&lt;/h1&gt;
      &lt;HeavyChart /&gt;
      &lt;AdminDashboard /&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p>If you use named exports, you can map them to a default export:</p>
<pre><code class="language-jsx">const ComponentWithNamedExport = lazy(() =&gt;
  import('./MyComponent').then((module) =&gt; ({
    default: module.NamedComponent,
  }))
);
</code></pre>
<p>You can also name chunks for easier debugging in the browser:</p>
<pre><code class="language-jsx">const HeavyChart = lazy(() =&gt;
  import(/* webpackChunkName: "heavy-chart" */ './HeavyChart')
);
</code></pre>
<p><code>React.lazy()</code> alone isn't enough. You must wrap lazy components in <code>Suspense</code> so React knows what to show while they load.</p>
<h2 id="heading-how-to-use-suspense-with-reactlazy">How to Use <code>Suspense</code> with <code>React.lazy</code></h2>
<p><code>Suspense</code> is a React component that shows a fallback UI while its children are loading. It works with <code>React.lazy()</code> to handle the loading state of dynamically imported components.</p>
<p>Wrap your lazy components in <code>Suspense</code> and provide a <code>fallback</code> prop:</p>
<pre><code class="language-jsx">import { lazy, Suspense } from 'react';

const HeavyChart = lazy(() =&gt; import('./HeavyChart'));
const AdminDashboard = lazy(() =&gt; import('./AdminDashboard'));

function App() {
  return (
    &lt;div&gt;
      &lt;h1&gt;My App&lt;/h1&gt;
      &lt;Suspense fallback={&lt;div&gt;Loading chart...&lt;/div&gt;}&gt;
        &lt;HeavyChart /&gt;
      &lt;/Suspense&gt;
      &lt;Suspense fallback={&lt;div&gt;Loading dashboard...&lt;/div&gt;}&gt;
        &lt;AdminDashboard /&gt;
      &lt;/Suspense&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p>You can use a single <code>Suspense</code> boundary for multiple lazy components:</p>
<pre><code class="language-jsx">&lt;Suspense fallback={&lt;div&gt;Loading...&lt;/div&gt;}&gt;
  &lt;HeavyChart /&gt;
  &lt;AdminDashboard /&gt;
&lt;/Suspense&gt;
</code></pre>
<p>A more polished fallback improves perceived performance:</p>
<pre><code class="language-jsx">function LoadingSpinner() {
  return (
    &lt;div className="loading-container"&gt;
      &lt;div className="spinner" /&gt;
      &lt;p&gt;Loading...&lt;/p&gt;
    &lt;/div&gt;
  );
}

&lt;Suspense fallback={&lt;LoadingSpinner /&gt;}&gt;
  &lt;HeavyChart /&gt;
&lt;/Suspense&gt;
</code></pre>
<h2 id="heading-how-to-handle-errors-with-error-boundaries">How to Handle Errors with Error Boundaries</h2>
<p><code>React.lazy()</code> and <code>Suspense</code> don't handle loading errors (for example, network failures or missing chunks). For that, you need an Error Boundary.</p>
<p>Error Boundaries are class components that use <code>componentDidCatch</code> or <code>static getDerivedStateFromError</code> to catch errors in their child tree and render a fallback UI.</p>
<p>Here is a simple Error Boundary:</p>
<pre><code class="language-jsx">import { Component } from 'react';

class ErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    console.error('Error caught by boundary:', error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return this.props.fallback || &lt;div&gt;Something went wrong.&lt;/div&gt;;
    }
    return this.props.children;
  }
}
</code></pre>
<p>Wrap your <code>Suspense</code> boundary with an Error Boundary:</p>
<pre><code class="language-jsx">import { lazy, Suspense } from 'react';
import ErrorBoundary from './ErrorBoundary';

const HeavyChart = lazy(() =&gt; import('./HeavyChart'));

function App() {
  return (
    &lt;ErrorBoundary fallback={&lt;div&gt;Failed to load chart. Please try again.&lt;/div&gt;}&gt;
      &lt;Suspense fallback={&lt;div&gt;Loading chart...&lt;/div&gt;}&gt;
        &lt;HeavyChart /&gt;
      &lt;/Suspense&gt;
    &lt;/ErrorBoundary&gt;
  );
}
</code></pre>
<p>If the chunk fails to load, the Error Boundary catches it and shows your fallback instead of a blank screen or unhandled error.</p>
<h2 id="heading-how-to-use-nextdynamic-in-nextjs">How to Use <code>next/dynamic</code> in Next.js</h2>
<p>Next.js provides <code>next/dynamic</code>, which wraps <code>React.lazy()</code> and <code>Suspense</code> and adds options tailored for Next.js (including Server-Side Rendering).</p>
<p>Basic usage:</p>
<pre><code class="language-jsx">'use client';

import dynamic from 'next/dynamic';

const ComponentA = dynamic(() =&gt; import('../components/A'));
const ComponentB = dynamic(() =&gt; import('../components/B'));

export default function Page() {
  return (
    &lt;div&gt;
      &lt;ComponentA /&gt;
      &lt;ComponentB /&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<h3 id="heading-custom-loading-ui">Custom Loading UI</h3>
<p>Use the <code>loading</code> option to show a placeholder while the component loads:</p>
<pre><code class="language-jsx">const HeavyChart = dynamic(() =&gt; import('../components/HeavyChart'), {
  loading: () =&gt; &lt;p&gt;Loading chart...&lt;/p&gt;,
});
</code></pre>
<h3 id="heading-disable-server-side-rendering">Disable Server-Side Rendering</h3>
<p>For components that must run only on the client (for example, those using <code>window</code> or browser-only APIs), set <code>ssr: false</code>:</p>
<pre><code class="language-jsx">const ClientOnlyMap = dynamic(() =&gt; import('../components/Map'), {
  ssr: false,
  loading: () =&gt; &lt;p&gt;Loading map...&lt;/p&gt;,
});
</code></pre>
<p>Note: <code>ssr: false</code> works only for Client Components. Use it inside a <code>'use client'</code> file.</p>
<h3 id="heading-load-on-demand">Load on Demand</h3>
<p>You can load a component only when a condition is met:</p>
<pre><code class="language-jsx">'use client';

import { useState } from 'react';
import dynamic from 'next/dynamic';

const Modal = dynamic(() =&gt; import('../components/Modal'), {
  loading: () =&gt; &lt;p&gt;Opening modal...&lt;/p&gt;,
});

export default function Page() {
  const [showModal, setShowModal] = useState(false);

  return (
    &lt;div&gt;
      &lt;button onClick={() =&gt; setShowModal(true)}&gt;Open Modal&lt;/button&gt;
      {showModal &amp;&amp; &lt;Modal onClose={() =&gt; setShowModal(false)} /&gt;}
    &lt;/div&gt;
  );
}
</code></pre>
<h3 id="heading-named-exports">Named Exports</h3>
<p>For named exports, return the component from the dynamic import:</p>
<pre><code class="language-jsx">const Hello = dynamic(() =&gt;
  import('../components/hello').then((mod) =&gt; mod.Hello)
);
</code></pre>
<h3 id="heading-using-suspense-with-nextdynamic">Using Suspense with next/dynamic</h3>
<p>In React 18+, you can use <code>suspense: true</code> to rely on a parent <code>Suspense</code> boundary instead of the <code>loading</code> option:</p>
<pre><code class="language-jsx">const HeavyChart = dynamic(() =&gt; import('../components/HeavyChart'), {
  suspense: true,
});

// In your component:
&lt;Suspense fallback={&lt;div&gt;Loading...&lt;/div&gt;}&gt;
  &lt;HeavyChart /&gt;
&lt;/Suspense&gt;
</code></pre>
<p>Important: When using <code>suspense: true</code>, you can't use <code>ssr: false</code> or the <code>loading</code> option. Use the <code>Suspense</code> fallback instead.</p>
<h2 id="heading-reactlazy-vs-nextdynamic-when-to-use-each"><code>React.lazy</code> vs <code>next/dynamic</code>: When to Use Each</h2>
<table>
<thead>
<tr>
<th>Feature</th>
<th>React.lazy + Suspense</th>
<th>next/dynamic</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Framework</strong></td>
<td>Any React app (Create React App, Vite, etc.)</td>
<td>Next.js only</td>
</tr>
<tr>
<td><strong>Server-Side Rendering</strong></td>
<td>Not supported</td>
<td>Supported by default</td>
</tr>
<tr>
<td><strong>Disable SSR</strong></td>
<td>N/A</td>
<td><code>ssr: false</code> option</td>
</tr>
<tr>
<td><strong>Loading UI</strong></td>
<td><code>Suspense</code> fallback prop</td>
<td>Built-in <code>loading</code> option</td>
</tr>
<tr>
<td><strong>Error handling</strong></td>
<td>Requires Error Boundary</td>
<td>Requires Error Boundary</td>
</tr>
<tr>
<td><strong>Named exports</strong></td>
<td>Manual <code>.then()</code> mapping</td>
<td>Same <code>.then()</code> pattern</td>
</tr>
<tr>
<td><strong>Suspense mode</strong></td>
<td>Always uses Suspense</td>
<td>Optional via <code>suspense: true</code></td>
</tr>
</tbody></table>
<h3 id="heading-when-to-use-reactlazy">When to Use <code>React.lazy</code></h3>
<ul>
<li><p>You're building a <strong>pure React app</strong> (no Next.js)</p>
</li>
<li><p>You use Create React App, Vite, or a custom Webpack setup</p>
</li>
<li><p>You don't need Server-Side Rendering</p>
</li>
<li><p>You want a simple, framework-agnostic approach</p>
</li>
</ul>
<h3 id="heading-when-to-use-nextdynamic">When to <code>Use next/dynamic</code></h3>
<ul>
<li><p>You're building a <strong>Next.js app</strong></p>
</li>
<li><p>You need SSR for some components and want to disable it for others</p>
</li>
<li><p>You want built-in loading placeholders without manually adding <code>Suspense</code></p>
</li>
<li><p>You want Next.js-specific optimizations and defaults</p>
</li>
</ul>
<h2 id="heading-real-world-examples">Real-World Examples</h2>
<h3 id="heading-example-1-route-based-code-splitting-in-react">Example 1: Route-Based Code Splitting in React</h3>
<p>Split your app by route so each page loads only when the user navigates to it:</p>
<pre><code class="language-jsx">// App.jsx
import { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import ErrorBoundary from './ErrorBoundary';

const Home = lazy(() =&gt; import('./pages/Home'));
const Dashboard = lazy(() =&gt; import('./pages/Dashboard'));
const Settings = lazy(() =&gt; import('./pages/Settings'));

function App() {
  return (
    &lt;BrowserRouter&gt;
      &lt;ErrorBoundary fallback={&lt;div&gt;Failed to load page.&lt;/div&gt;}&gt;
        &lt;Suspense fallback={&lt;div&gt;Loading page...&lt;/div&gt;}&gt;
          &lt;Routes&gt;
            &lt;Route path="/" element={&lt;Home /&gt;} /&gt;
            &lt;Route path="/dashboard" element={&lt;Dashboard /&gt;} /&gt;
            &lt;Route path="/settings" element={&lt;Settings /&gt;} /&gt;
          &lt;/Routes&gt;
        &lt;/Suspense&gt;
      &lt;/ErrorBoundary&gt;
    &lt;/BrowserRouter&gt;
  );
}
</code></pre>
<h3 id="heading-example-2-lazy-loading-a-heavy-chart-library-in-nextjs">Example 2: Lazy Loading a Heavy Chart Library in Next.js</h3>
<p>Defer loading a chart library until the user opens the analytics section:</p>
<pre><code class="language-jsx">// app/analytics/page.jsx
'use client';

import { useState } from 'react';
import dynamic from 'next/dynamic';

const Chart = dynamic(() =&gt; import('../components/Chart'), {
  ssr: false,
  loading: () =&gt; (
    &lt;div className="chart-skeleton"&gt;
      &lt;div className="skeleton-bar" /&gt;
      &lt;div className="skeleton-bar" /&gt;
      &lt;div className="skeleton-bar" /&gt;
    &lt;/div&gt;
  ),
});

export default function AnalyticsPage() {
  const [showChart, setShowChart] = useState(false);

  return (
    &lt;div&gt;
      &lt;h1&gt;Analytics&lt;/h1&gt;
      &lt;button onClick={() =&gt; setShowChart(true)}&gt;Load Chart&lt;/button&gt;
      {showChart &amp;&amp; &lt;Chart /&gt;}
    &lt;/div&gt;
  );
}
</code></pre>
<h3 id="heading-example-3-lazy-loading-a-modal">Example 3: Lazy Loading a Modal</h3>
<p>Load a modal component only when the user clicks to open it:</p>
<pre><code class="language-jsx">// React (with React.lazy)
import { lazy, Suspense, useState } from 'react';

const Modal = lazy(() =&gt; import('./Modal'));

function ProductPage() {
  const [showModal, setShowModal] = useState(false);

  return (
    &lt;div&gt;
      &lt;button onClick={() =&gt; setShowModal(true)}&gt;Add to Cart&lt;/button&gt;
      {showModal &amp;&amp; (
        &lt;Suspense fallback={null}&gt;
          &lt;Modal onClose={() =&gt; setShowModal(false)} /&gt;
        &lt;/Suspense&gt;
      )}
    &lt;/div&gt;
  );
}
</code></pre>
<pre><code class="language-jsx">// Next.js (with next/dynamic)
'use client';

import { useState } from 'react';
import dynamic from 'next/dynamic';

const Modal = dynamic(() =&gt; import('./Modal'), {
  loading: () =&gt; null,
});

export default function ProductPage() {
  const [showModal, setShowModal] = useState(false);

  return (
    &lt;div&gt;
      &lt;button onClick={() =&gt; setShowModal(true)}&gt;Add to Cart&lt;/button&gt;
      {showModal &amp;&amp; &lt;Modal onClose={() =&gt; setShowModal(false)} /&gt;}
    &lt;/div&gt;
  );
}
</code></pre>
<h3 id="heading-example-4-lazy-loading-external-libraries">Example 4: Lazy Loading External Libraries</h3>
<p>Load a library only when the user needs it (for example, when they start typing in a search box):</p>
<pre><code class="language-jsx">'use client';

import { useState } from 'react';

const names = ['Alice', 'Bob', 'Charlie', 'Diana'];

export default function SearchPage() {
  const [results, setResults] = useState([]);
  const [query, setQuery] = useState('');

  const handleSearch = async (value) =&gt; {
    setQuery(value);
    if (!value) {
      setResults([]);
      return;
    }
    // Load fuse.js only when user searches
    const Fuse = (await import('fuse.js')).default;
    const fuse = new Fuse(names);
    setResults(fuse.search(value));
  };

  return (
    &lt;div&gt;
      &lt;input
        type="text"
        placeholder="Search..."
        value={query}
        onChange={(e) =&gt; handleSearch(e.target.value)}
      /&gt;
      &lt;ul&gt;
        {results.map((result) =&gt; (
          &lt;li key={result.refIndex}&gt;{result.item}&lt;/li&gt;
        ))}
      &lt;/ul&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Lazy loading improves performance by splitting your bundle and loading code only when needed. Here's what you learned:</p>
<ul>
<li><p><strong>React.lazy()</strong> – Use in plain React apps for code splitting. It requires a default export and works with dynamic <code>import()</code>.</p>
</li>
<li><p><strong>Suspense</strong> – Wrap lazy components in <code>Suspense</code> and provide a <code>fallback</code> for the loading state.</p>
</li>
<li><p><strong>Error Boundaries</strong> – Use them to catch chunk load failures and show a friendly error UI.</p>
</li>
<li><p><strong>next/dynamic</strong> – Use in Next.js for the same benefits plus SSR control and built-in loading options.</p>
</li>
</ul>
<p>Choose <code>React.lazy</code> for React-only projects and <code>next/dynamic</code> for Next.js. Combine them with <code>Suspense</code> and Error Boundaries for a solid lazy-loading setup.</p>
<p>Start by identifying your heaviest components (charts, modals, admin panels) and lazy load them. Measure your bundle size and Core Web Vitals before and after to see the impact.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Fashion App That Helps You Organize Your Wardrobe  ]]>
                </title>
                <description>
                    <![CDATA[ I used to spend too long deciding what to wear, even when my closet was full. That frustration made the problem feel very clear to me: it was not about having fewer clothes. It was about having better ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-a-fashion-app-to-organize-your-wardrobe/</link>
                <guid isPermaLink="false">69de6abf91716f3cfb5448a1</guid>
                
                    <category>
                        <![CDATA[ webdev ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ full stack ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Machine Learning ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker ]]>
                    </category>
                
                    <category>
                        <![CDATA[ MathJax ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Mokshita V P ]]>
                </dc:creator>
                <pubDate>Tue, 14 Apr 2026 16:26:39 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/bf593ff6-6de8-4b30-ab0a-700c3410ccb1.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>I used to spend too long deciding what to wear, even when my closet was full.</p>
<p>That frustration made the problem feel very clear to me: it was not about having fewer clothes. It was about having better organization, better visibility, and better guidance when making outfit decisions.</p>
<p>So I built a fashion web app that helps users organize their wardrobe, get outfit suggestions, evaluate shopping decisions, and improve recommendations over time using feedback.</p>
<p>In this article, I’ll walk through what the app does, how I built it, the decisions I made along the way, and the challenges that shaped the final result.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-table-of-contents">Table of Contents</a></p>
</li>
<li><p><a href="#heading-what-the-app-does">What the App Does</a></p>
</li>
<li><p><a href="#heading-why-i-built-it">Why I Built It</a></p>
</li>
<li><p><a href="#heading-tech-stack">Tech Stack</a></p>
</li>
<li><p><a href="#heading-product-walkthrough-what-users-see">Product Walkthrough (What Users See)</a></p>
</li>
<li><p><a href="#heading-how-i-built-it">How I Built It</a></p>
</li>
<li><p><a href="#heading-challenges-i-faced">Challenges I Faced</a></p>
</li>
<li><p><a href="#heading-what-i-learned">What I Learned</a></p>
</li>
<li><p><a href="#heading-what-i-want-to-improve-next">What I Want to Improve Next</a></p>
</li>
<li><p><a href="#heading-future-improvements">Future Improvements</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-what-the-app-does">What the App Does</h2>
<p>At a high level, the app combines six core capabilities:</p>
<ol>
<li><p>Wardrobe management</p>
</li>
<li><p>Outfit recommendations</p>
</li>
<li><p>Shopping suggestions</p>
</li>
<li><p>Discard recommendations</p>
</li>
<li><p>Feedback and usage tracking</p>
</li>
<li><p>Secure multi-user accounts</p>
</li>
</ol>
<p>Users can upload clothing items, explore suggested outfits, and mark recommendations as helpful or not helpful. They can also rate outfits and track whether items are worn, kept, or discarded.</p>
<p>That feedback becomes structured data for improving future recommendation quality.</p>
<h2 id="heading-why-i-built-it">Why I Built It</h2>
<p>I wanted to create something that felt personal and actually useful. A lot of fashion apps look polished, but they do not always help with everyday decisions. My goal was to build something that could make wardrobe management easier and outfit selection less overwhelming. The app needed to do three things well:</p>
<ul>
<li><p>store each user’s wardrobe data</p>
</li>
<li><p>personalize recommendations</p>
</li>
<li><p>learn from user feedback over time .</p>
</li>
</ul>
<p>That feedback loop mattered to me because it makes the app feel more alive instead of static.</p>
<h2 id="heading-tech-stack">Tech Stack</h2>
<p>Here are the tools I used to built the app:</p>
<ul>
<li><p>Frontend: React + Vite</p>
</li>
<li><p>Backend: FastAPI</p>
</li>
<li><p>Database: SQLite (local development)</p>
</li>
<li><p>Background jobs: Celery + Redis</p>
</li>
<li><p>Authentication: JWT (access + refresh token flow)</p>
</li>
<li><p>Deployment support: Docker and GitHub Codespaces</p>
</li>
</ul>
<p>This ended up giving me a pretty modular setup, which helped a lot as features started increasing: fast frontend iteration, clean API boundaries, and room to evolve recommendations separately from UI.</p>
<h2 id="heading-product-walkthrough-what-users-see">Product Walkthrough (What Users See)</h2>
<h3 id="heading-1-onboarding-and-account-setup">1. Onboarding and Account Setup</h3>
<p>To start using the app, a user needs to register, verify their email, and complete some profile basics.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68ab1274684dc97382d342ea/1ff4fb0d-dc97-4088-b720-db917b53ba5b.png" alt="Onboarding screen showing account creation, email verification, and profile fields for body shape, height, weight, and style preferences." style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>Each account is isolated, so wardrobe history and recommendations stay user-specific.</p>
<p>In this onboarding screen above, you can see account creation, email verification, and profile fields for body shape, height, weight, and style preferences.</p>
<h3 id="heading-2-wardrobe-upload">2. Wardrobe Upload</h3>
<p>Users can upload clothing images .</p>
<img src="https://cdn.hashnode.com/uploads/covers/68ab1274684dc97382d342ea/d69bf10b-b79b-4294-923c-5c9e5840098a.png" alt="Wardrobe upload form showing clothing image analysis results with category, dominant color, secondary color, and pattern details." style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>Image analysis labels each item and makes it searchable for recommendations. The wardrobe upload form shows image analysis results with category, dominant color, secondary color, and pattern details listed.</p>
<h3 id="heading-3-outfit-recommendations">3. Outfit Recommendations</h3>
<p>Users can request recommendations, then rate outputs.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68ab1274684dc97382d342ea/61527ddf-11e4-4284-92fd-2d0c948ae2db.png" alt="Outfit recommendation dashboard showing ranked outfit cards with feedback and rating actions." style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>Above you can see the outfit recommendation dashboard that shows ranked outfit cards with feedback and rating actions. Recommendations are ranked by a weighted scoring model.</p>
<h3 id="heading-4-shopping-and-discard-assistants">4. Shopping and Discard Assistants</h3>
<p>The app evaluates new items against existing wardrobe data and flags low-value wardrobe items that may be worth removing.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68ab1274684dc97382d342ea/88ed83c4-fdba-40e7-ad32-f77bdf21cb4d.png" alt="Shopping and discard analysis screen showing recommendation scores, written reasons, and styling guidance for each item." style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>You can see the recommendation scores, written reasons (not just a binary decision), and styling guidance for each item above. It also features a "how to style it" incase the user still wants to keep the item.</p>
<h2 id="heading-how-i-built-it">How I Built It</h2>
<h3 id="heading-1-frontend-setup-react-vite">1. Frontend Setup (React + Vite)</h3>
<p>I used React + Vite because I wanted fast iteration and a clean component structure.</p>
<p>The frontend is split into feature areas like onboarding, wardrobe management, outfits, shopping, and discarded-item suggestions. I also keep API calls in a service layer so the UI components stay focused on rendering and interaction.</p>
<p>The snippet below is a simplified example of the API service pattern used in the app. It is not meant to be copy-pasted as-is, but it shows the same structure the frontend uses when talking to the backend.</p>
<p>Example API client pattern:</p>
<pre><code class="language-javascript">export async function getOutfitRecommendations(userId, params = {}) {
  const query = new URLSearchParams(params).toString();
  const url = `/users/\({userId}/outfits/recommend\){query ? `?${query}` : ""}`;

  const response = await fetch(url, {
    headers: {
      Authorization: `Bearer ${localStorage.getItem("access_token")}`,
    },
  });

  if (!response.ok) {
    throw new Error("Failed to fetch outfit recommendations");
  }

  return response.json();
}
</code></pre>
<p>Here's what's happening in that snippet:</p>
<ul>
<li><p><code>URLSearchParams</code> builds optional query strings like <code>occasion</code>, <code>season</code>, or <code>limit</code>.</p>
</li>
<li><p>The request path is user-scoped, which keeps each user’s recommendations isolated.</p>
</li>
<li><p>The <code>Authorization</code> header sends the access token so the backend can verify the session.</p>
</li>
<li><p>The response is checked before parsing so the UI can surface a useful error if the request fails.</p>
</li>
</ul>
<p>This pattern kept the frontend simple and reusable as the number of API calls grew.</p>
<h3 id="heading-2-backend-architecture-with-fastapi">2. Backend Architecture with FastAPI</h3>
<p>The backend is organized around clear route groups:</p>
<ul>
<li><p>auth routes for register, login, refresh, logout, and sessions</p>
</li>
<li><p>user analysis routes</p>
</li>
<li><p>wardrobe CRUD routes</p>
</li>
<li><p>recommendation routes for outfits, shopping, and discard analysis</p>
</li>
<li><p>feedback routes for ratings and helpfulness signals</p>
</li>
</ul>
<p>One of the most important design choices was enforcing ownership checks on user-scoped resources. That prevented one user from accessing another user’s wardrobe or feedback data.</p>
<p>The backend snippet below is another simplified example from the app’s route layer. It shows the request validation and orchestration logic, while the actual scoring work stays in the recommendation service.</p>
<pre><code class="language-python">@app.get("/users/{user_id}/outfits/recommend")
def recommend_outfits(user_id: int, occasion: str | None = None, season: str | None = None, limit: int = 10):
    user = get_user_or_404(user_id)
    wardrobe_items = get_user_wardrobe(user_id)

    if len(wardrobe_items) &lt; 2:
        raise HTTPException(status_code=400, detail="Not enough wardrobe items")

    recommendations = outfit_generator.generate_outfit_recommendations(
        wardrobe_items=wardrobe_items,
        body_shape=user.body_shape,
        undertone=user.undertone,
        occasion=occasion,
        season=season,
        top_k=limit,
    )

    return {"user_id": user_id, "recommendations": recommendations}
</code></pre>
<p>Here's how to read that code:</p>
<ul>
<li><p><code>get_user_or_404</code> loads the profile data needed for personalization.</p>
</li>
<li><p><code>get_user_wardrobe</code> fetches only the current user’s items.</p>
</li>
<li><p>The minimum wardrobe check prevents the recommendation logic from running on incomplete data.</p>
</li>
<li><p><code>generate_outfit_recommendations</code> handles the scoring logic separately, which keeps the route handler small and easier to test.</p>
</li>
<li><p>The response returns the results in a shape the frontend can consume directly.</p>
</li>
</ul>
<p>That separation helped keep the API layer readable while the recommendation logic stayed isolated in its own service.</p>
<h3 id="heading-3-recommendation-logic">3. Recommendation Logic</h3>
<p>I intentionally started with deterministic rules before introducing heavy ML. That made behavior easier to debug and explain.</p>
<p>The outfit recommender scores combinations using weighted signals:</p>
<p>$$\text{outfit score} = 0.4 \cdot \text{color harmony} + 0.4 \cdot \text{body-shape fit} + 0.2 \cdot \text{undertone fit}$$</p>
<p>The snippet below is a simplified example from the recommendation engine. It shows how the app combines multiple signals into a single score:</p>
<pre><code class="language-python">def score_outfit(combo, user_context):
    color_score = color_harmony.score(combo)
    shape_score = body_shape_rules.score(combo, user_context.body_shape)
    undertone_score = undertone_rules.score(combo, user_context.undertone)

    total = 0.4 * color_score + 0.4 * shape_score + 0.2 * undertone_score
    return round(total, 3)
</code></pre>
<p>The logic behind this approach is straightforward:</p>
<ul>
<li><p>color harmony helps the outfit feel visually coherent</p>
</li>
<li><p>body-shape scoring helps the outfit feel flattering</p>
</li>
<li><p>undertone scoring helps the colors work better with the user’s profile</p>
</li>
</ul>
<p>I used a similar structure for discard recommendations and shopping suggestions, but with different factors and thresholds.</p>
<h3 id="heading-4-authentication-and-secure-multi-user-design">4. Authentication and Secure Multi-user Design</h3>
<p>Security was one of the most important parts of this build.</p>
<p>I implemented:</p>
<ul>
<li><p>short-lived access tokens</p>
</li>
<li><p>refresh tokens with JTI tracking</p>
</li>
<li><p>token rotation on refresh</p>
</li>
<li><p>session revocation (single session and all sessions)</p>
</li>
<li><p>email verification and password reset flows</p>
</li>
</ul>
<p>The snippet below is a simplified example of the refresh-token lifecycle used in the app. It shows the important control points rather than every helper function:</p>
<pre><code class="language-python">def refresh_access_token(refresh_token: str):
    payload = decode_jwt(refresh_token)
    jti = payload["jti"]

    token_record = db.get_refresh_token(jti)
    if not token_record or token_record.revoked:
        raise AuthError("Invalid refresh token")

    new_refresh, new_jti = issue_refresh_token(payload["sub"])
    token_record.revoked = True
    token_record.replaced_by_jti = new_jti

    new_access = issue_access_token(payload["sub"])
    return {"access_token": new_access, "refresh_token": new_refresh}
</code></pre>
<p>What this code is doing:</p>
<ul>
<li><p>It decodes the refresh token and looks up its JTI in the database.</p>
</li>
<li><p>It rejects reused or revoked sessions, which helps prevent replay attacks.</p>
</li>
<li><p>It rotates the refresh token instead of reusing it.</p>
</li>
<li><p>It issues a fresh access token so the session stays valid without forcing the user to log in again.</p>
</li>
</ul>
<p>This design made multi-device sessions safer and gave me server-side control over logout behavior.</p>
<h3 id="heading-5-background-jobs-for-long-running-operations">5. Background Jobs for Long-running Operations</h3>
<p>Image analysis can be expensive, especially when the app needs to classify clothing, analyze colors, and estimate body-shape-related signals. To keep the request path responsive, I added Celery + Redis support for background tasks.</p>
<p>That gave the app two modes:</p>
<ul>
<li><p>synchronous processing for simpler local development</p>
</li>
<li><p>queued processing for heavier or slower jobs</p>
</li>
</ul>
<p>That tradeoff mattered because it let me keep the developer experience simple without blocking the app during more expensive work.</p>
<h3 id="heading-6-data-model-and-feedback-capture">6. Data Model and Feedback Capture</h3>
<p>A recommendation system only improves if it captures the right signals.</p>
<p>So I added dedicated feedback tables for:</p>
<ul>
<li><p>outfit ratings (1-5 + optional comments)</p>
</li>
<li><p>recommendation helpful/unhelpful feedback</p>
</li>
<li><p>item usage actions (worn/kept/discarded)</p>
</li>
</ul>
<p>Here is the shape of one of those models:</p>
<pre><code class="language-python">class RecommendationFeedback(Base):
    __tablename__ = "recommendation_feedback"

    id = Column(Integer, primary_key=True)
    user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
    recommendation_type = Column(String(50), nullable=False)
    recommendation_id = Column(Integer, nullable=False)
    helpful = Column(Boolean, nullable=False)
    created_at = Column(DateTime, default=datetime.utcnow)
</code></pre>
<p>How to read this model:</p>
<ul>
<li><p><code>user_id</code> ties feedback to the person who gave it.</p>
</li>
<li><p><code>recommendation_type</code> tells me whether the feedback belongs to outfits, shopping, or discard suggestions.</p>
</li>
<li><p><code>recommendation_id</code> identifies the exact recommendation.</p>
</li>
<li><p><code>helpful</code> stores the user’s direct response.</p>
</li>
<li><p><code>created_at</code> makes it possible to analyze feedback trends over time.</p>
</li>
</ul>
<p>This part of the system gives the app a real learning foundation, even though the feedback-to-model-update loop is still a future improvement.</p>
<h2 id="heading-challenges-i-faced">Challenges I Faced</h2>
<p>This was the section that taught me the most.</p>
<h3 id="heading-1-image-heavy-endpoints-were-slower-than-i-wanted">1. Image-heavy endpoints were slower than I wanted</h3>
<p>The analyze and wardrobe upload flows were doing a lot of work at once: image validation, classification, color extraction, storage, and database writes.</p>
<p>At first, that made the request flow feel heavier than it should have.</p>
<p>What I changed:</p>
<ul>
<li><p>I bounded concurrent image jobs so the app wouldn't try to do too much at once.</p>
</li>
<li><p>I separated slower jobs into background processing where possible.</p>
</li>
<li><p>I used load-test results to confirm which endpoints were actually expensive.</p>
</li>
</ul>
<p>The practical effect was that heavy image requests stopped competing with each other so aggressively. Instead of letting many expensive tasks pile up inside the same request cycle, I limited the active work and pushed slower operations into the queue when needed.</p>
<p>Why this fixed it:</p>
<ul>
<li><p>Bounding concurrency prevented the system from overloading CPU-bound tasks.</p>
</li>
<li><p>Moving expensive work into async jobs kept the main request/response cycle more responsive.</p>
</li>
<li><p>Load testing gave me evidence instead of guesswork, so I could tune the system based on real performance behavior.</p>
</li>
</ul>
<p>In other words, I didn't just “optimize” the endpoint in theory. I changed the execution model so expensive analysis could not block every other request behind it.</p>
<h3 id="heading-2-jwt-sessions-needed-real-server-side-control">2. JWT sessions needed real server-side control</h3>
<p>A basic JWT setup is easy to get working, but it becomes less useful if you cannot revoke sessions or manage multiple devices cleanly.</p>
<p>What I changed:</p>
<ul>
<li><p>I stored refresh tokens in the database.</p>
</li>
<li><p>I tracked token JTI values.</p>
</li>
<li><p>I rotated refresh tokens when users refreshed their session.</p>
</li>
<li><p>I added endpoints for logging out a single session or all sessions.</p>
</li>
</ul>
<p>The important shift here was moving from “token exists, therefore session is valid” to “token exists, matches the database record, and has not been revoked or replaced.” That gave the server the authority to invalidate old sessions immediately.</p>
<p>Why this fixed it:</p>
<ul>
<li><p>Server-side token tracking made revocation possible.</p>
</li>
<li><p>Rotation reduced the chance of token reuse.</p>
</li>
<li><p>Session management became visible to the user, which made the app feel more trustworthy.</p>
</li>
</ul>
<p>This is what made logout-all and multi-device management work in a real way instead of just being cosmetic UI actions.</p>
<h3 id="heading-3-user-data-isolation-had-to-be-explicit">3. User data isolation had to be explicit</h3>
<p>Because this is a multi-user app, I had to be careful that one account could never accidentally see another account’s wardrobe data.</p>
<p>What I changed:</p>
<ul>
<li><p>I added ownership checks to user-scoped routes.</p>
</li>
<li><p>I kept all wardrobe and feedback queries filtered by <code>user_id</code>.</p>
</li>
<li><p>I used encrypted image storage instead of exposing raw paths.</p>
</li>
</ul>
<p>In practice, this meant every route had to ask the same question: “Does this user own the resource they are trying to access?” If the answer was no, the request stopped immediately.</p>
<p>Why this fixed it:</p>
<ul>
<li><p>Ownership checks made data access rules explicit.</p>
</li>
<li><p>User-filtered queries prevented accidental cross-account reads.</p>
</li>
<li><p>Encrypted storage improved privacy and reduced the risk of exposing image data directly.</p>
</li>
</ul>
<p>That combination is what kept wardrobe data, feedback history, and images separated correctly across accounts.</p>
<h3 id="heading-4-docker-made-the-project-easier-to-share-but-only-after-the-stack-was-organized">4. Docker made the project easier to share, but only after the stack was organized</h3>
<p>The app includes the frontend, backend, Redis, Celery worker, and Celery Beat, so the first challenge was making the setup feel reproducible instead of fragile.</p>
<p>What I changed:</p>
<ul>
<li><p>I defined the stack in Docker Compose.</p>
</li>
<li><p>I documented the required environment variables.</p>
</li>
<li><p>I kept the dev stack aligned with how the app runs in practice.</p>
</li>
</ul>
<p>This removed a lot of setup ambiguity. Instead of asking someone to manually figure out how the frontend, backend, Redis, and workers fit together, I made the stack describe itself.</p>
<p>Why this fixed it:</p>
<ul>
<li><p>Docker let contributors start the project with fewer manual steps.</p>
</li>
<li><p>Clear environment configuration reduced setup mistakes.</p>
</li>
<li><p>Matching the stack to the architecture made the app easier to understand and test.</p>
</li>
</ul>
<p>That was important because the app depends on several moving parts, and the simplest way to make the project approachable was to make startup behavior predictable.</p>
<h2 id="heading-what-i-learned">What I Learned</h2>
<p>This project taught me a few important lessons:</p>
<ul>
<li><p>Small features become much more valuable when they work together.</p>
</li>
<li><p>Feedback data is one of the strongest signals for improving recommendations.</p>
</li>
<li><p>Clean data modeling matters a lot when multiple users are involved.</p>
</li>
<li><p>Docker and clear setup instructions make a project much easier for other people to try.</p>
</li>
</ul>
<p>I also learned that a project does not need to be huge to be useful. A focused app that solves one problem well can still feel meaningful.</p>
<h2 id="heading-what-i-want-to-improve-next">What I Want to Improve Next</h2>
<p>My roadmap from here:</p>
<ol>
<li><p>Integrate feedback directly into ranking updates</p>
</li>
<li><p>Add visual analytics for recommendation quality trends</p>
</li>
<li><p>Improve mobile UX parity</p>
</li>
<li><p>Deploy with persistent cloud storage and production database defaults</p>
</li>
<li><p>Provide a public demo mode for easier evaluation</p>
</li>
</ol>
<h2 id="heading-future-improvements">Future Improvements</h2>
<p>There are still a few things I would like to add later:</p>
<ul>
<li><p>a more advanced recommendation engine</p>
</li>
<li><p>visual analytics for user feedback</p>
</li>
<li><p>better mobile support</p>
</li>
<li><p>live deployment with persistent cloud storage</p>
</li>
<li><p>a public demo mode for easier testing</p>
</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>This project began as a personal frustration and turned into a full web application with authentication, wardrobe storage, recommendation logic, and feedback infrastructure.</p>
<p>The most rewarding part was seeing how practical software decisions, not just flashy UI, can help people make everyday choices faster.</p>
<p>If you want to explore or run the project, <a href="https://github.com/Mokshitavp1/fashion_assistant">check out the repo</a>. You can try the flows and share feedback. I would especially love input on recommendation quality, UX clarity, and what features would make this genuinely useful in daily life.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build an Admin Dashboard Sidebar with shadcn/ui and Base UI ]]>
                </title>
                <description>
                    <![CDATA[ Admin dashboards are one of the most common real-world UI components you will build as a React developer. At the heart of nearly every dashboard is a sidebar, a persistent navigation panel that organi ]]>
                </description>
                <link>https://www.freecodecamp.org/news/build-an-admin-dashboard-sidebar-with-shadcn-ui-and-base-ui/</link>
                <guid isPermaLink="false">69de6a6491716f3cfb542305</guid>
                
                    <category>
                        <![CDATA[ shadcn ]]>
                    </category>
                
                    <category>
                        <![CDATA[ UI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ baseui ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Tailwind CSS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Vaibhav Gupta ]]>
                </dc:creator>
                <pubDate>Tue, 14 Apr 2026 16:25:08 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/3ce152b1-9a34-4c72-85f0-cabf7d4f3460.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Admin dashboards are one of the most common real-world UI components you will build as a React developer. At the heart of nearly every dashboard is a sidebar, a persistent navigation panel that organizes pages, tools, and features into a clean, scannable structure.</p>
<p>Building a sidebar from scratch involves much more than an <code>&lt;nav&gt;</code> element. You need collapsible submenus, active state tracking across parent and child items, accessible keyboard navigation, a scroll area for long nav lists, and a consistent design system that holds together across screen sizes.</p>
<p>In this tutorial, you'll learn how to build a fully functional, accessible admin dashboard sidebar using shadcn/ui, a collection of beautifully designed, accessible React components, and a pre-built community block from Shadcn Space, which extends shadcn/ui with ready-to-use dashboard UI patterns.</p>
<p>By the end of this tutorial, you'll have a working sidebar that includes:</p>
<ul>
<li><p>Grouped navigation sections with uppercase labels</p>
</li>
<li><p>Collapsible parent menu items with child links</p>
</li>
<li><p>Active state tracking across both parent and child items</p>
</li>
<li><p>A floating sidebar with an independent scroll area</p>
</li>
<li><p>A promotional card pinned inside the sidebar footer</p>
</li>
</ul>
<h2 id="heading-table-of-contents"><strong>Table of Contents</strong></h2>
<ul>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-what-you-will-build">What You Will Build</a></p>
</li>
<li><p><a href="#heading-why-shadcnui">Why shadcn/ui?</a></p>
</li>
<li><p><a href="#heading-what-is-shadcn-space">What is Shadcn Space?</a></p>
</li>
<li><p><a href="#heading-how-to-install-the-sidebar-block">How to Install the Sidebar Block</a></p>
</li>
<li><p><a href="#heading-how-to-understand-the-folder-structure">How to Understand the Folder Structure</a></p>
</li>
<li><p><a href="#heading-how-to-build-the-page-layout">How to Build the Page Layout</a></p>
</li>
<li><p><a href="#heading-how-to-build-the-appsidebar-component">How to Build the AppSidebar Component</a></p>
</li>
<li><p><a href="#heading-how-to-define-the-navigation-data">How to Define the Navigation Data</a></p>
</li>
<li><p><a href="#heading-how-to-build-the-navmain-component">How to Build the NavMain Component</a></p>
</li>
<li><p><a href="#heading-how-to-handle-active-states-and-collapsible-menus">How to Handle Active States and Collapsible Menus</a></p>
</li>
<li><p><a href="#heading-how-to-style-the-sidebar">How to Style the Sidebar</a></p>
</li>
<li><p><a href="#heading-live-preview">Live Preview</a></p>
</li>
<li><p><a href="#heading-summary">Summary</a></p>
</li>
</ul>
<h2 id="heading-prerequisites"><strong>Prerequisites</strong></h2>
<p>Before you start, make sure you have the following:</p>
<ul>
<li><p>Node.js 18+ installed on your machine</p>
</li>
<li><p>Basic knowledge of React and TypeScript</p>
</li>
<li><p>Familiarity with Tailwind CSS utility classes</p>
</li>
<li><p>A package manager installed (npm, pnpm, yarn, or bun)</p>
</li>
</ul>
<p>You don't need prior experience with shadcn/ui, but it helps to have read through <a href="https://ui.shadcn.com/docs">the official docs</a> at least once.</p>
<h2 id="heading-what-you-will-build"><strong>What You Will Build</strong></h2>
<p>In this article, you'll build a fully functional admin dashboard sidebar with the following features:</p>
<ol>
<li><p><strong>Floating sidebar shell</strong>: a card-style sidebar with rounded corners, a drop shadow, and a configurable width</p>
</li>
<li><p><strong>Grouped navigation</strong>: navigation items organized under section labels like Dashboards, Pages, Apps, and Form Elements</p>
</li>
<li><p><strong>Collapsible submenus</strong>: parent items like Blogs and Shadcn Forms that expand on click to reveal child links</p>
</li>
<li><p><strong>Active state tracking</strong>: visual highlighting of the selected parent and child item at all times</p>
</li>
<li><p><strong>Sidebar toggle</strong>: a trigger button in the page header that opens and closes the sidebar</p>
</li>
<li><p><strong>Promotional card</strong>: a "Get Premium" card at the bottom of the sidebar scroll area</p>
</li>
</ol>
<h2 id="heading-why-shadcnui"><strong>Why shadcn/ui?</strong></h2>
<p><a href="https://ui.shadcn.com/"><strong>shadcn/ui</strong></a> is a collection of beautifully designed, accessible React components built on top of Radix UI and styled with Tailwind CSS.</p>
<p>Instead of installing a traditional component library as a dependency, you copy components directly into your project using a CLI. This gives you full ownership of the code structure and styling. You can read every line, change anything, and the components never break because of a library update you didn't control.</p>
<p>Some key benefits of shadcn/ui include:</p>
<ul>
<li><p>Accessible by default, built on Radix and Base UI primitives</p>
</li>
<li><p>Fully styled with Tailwind CSS utility classes</p>
</li>
<li><p>Zero lock-in: the code lives in your project, not inside <code>node_modules</code></p>
</li>
<li><p>Works with Next.js, React, Astro, Vite, and other frameworks</p>
</li>
<li><p>A growing ecosystem of community-built blocks and registries</p>
</li>
</ul>
<p>The <code>Sidebar, Collapsible, ScrollArea, Card, and Button</code> Components you'll use in this tutorial all come from shadcn/ui.</p>
<h2 id="heading-what-is-shadcn-space"><strong>What is Shadcn Space?</strong></h2>
<p><strong>Shadcn Space</strong> is an open-source library of pre-built <a href="https://shadcnspace.com/blocks"><strong>UI blocks</strong></a> built on top of shadcn/ui. It provides ready-to-use dashboard layouts, sidebars, tables, cards, and other common admin UI patterns so you don't have to assemble them from individual primitives every time.</p>
<p>Each block in Shadcn Space is installable directly into your project using the shadcn CLI. Once installed, the code is yours: you can read it, extend it, and adapt it to your design system without any runtime dependency on Shadcn Space itself.</p>
<p>For this tutorial, you'll use the <code>sidebar-06</code> block (it’s free to use), which is a floating admin sidebar with grouped navigation, collapsible submenus, and an integrated scroll area.</p>
<p>Shadcn Space also provides a companion <a href="https://www.figma.com/community/file/1597967874273587400/shadcn-space-figma-ui-kit"><strong>Figma UI Kit</strong></a> that matches the design system used in the blocks, which is useful if you do design work alongside development.</p>
<p>You can explore the full block library and the getting-started documentation in the <a href="https://shadcnspace.com/docs/getting-started/blocks"><strong>official Shadcn Space docs</strong></a>.</p>
<h3 id="heading-how-to-set-up-the-project">How to Set Up the Project</h3>
<p>Start by creating a new Next.js project if you don't already have one:</p>
<pre><code class="language-javascript">npx shadcn@latest init --preset b0 --base base --template next
</code></pre>
<p>This command:</p>
<ul>
<li><p>Creates a Next.js project</p>
</li>
<li><p>Configures Tailwind CSS</p>
</li>
<li><p>Sets up Base UI as the component foundation</p>
</li>
<li><p>Uses Nova style preset</p>
</li>
<li><p>Configures Lucide icons</p>
</li>
<li><p>Uses Inter font</p>
</li>
<li><p>Applies neutral theme tokens</p>
</li>
</ul>
<p>Follow the prompts to configure your base color, CSS variables, and component output directory. This sets up the <code>components/ui</code> directory and the required Tailwind configuration that all shadcn/ui components depend on.</p>
<p>Once the initialization is complete, your <code>components.json</code> project will be created at the root of your project. This file tells the shadcn CLI where to place components, what path aliases you're using, and which styling configuration to follow.</p>
<p>Add this in <code>components.json</code>:</p>
<pre><code class="language-javascript">{
  "registries": {
    "@shadcn-space": {
      "url": "https://shadcnspace.com/r/{name}.json",
    }
  }
}
</code></pre>
<h2 id="heading-how-to-install-the-sidebar-block"><strong>How to Install the Sidebar Block</strong></h2>
<p>With shadcn/ui initialized, you can now pull in the <code>sidebar-06</code> block from Shadcn Space. While Shadcn Space provides components for both Radix UI and Base UI, this tutorial uses the Base UI version. Run one of the following commands depending on your package manager:</p>
<p><strong>npm:</strong></p>
<pre><code class="language-javascript">npx shadcn@latest add @shadcn-space/sidebar-06
</code></pre>
<p><strong>pnpm:</strong></p>
<pre><code class="language-javascript">pnpm dlx shadcn@latest add @shadcn-space/sidebar-06
</code></pre>
<p><strong>yarn:</strong></p>
<pre><code class="language-javascript">yarn dlx shadcn@latest add @shadcn-space/sidebar-06
</code></pre>
<p><strong>bun:</strong></p>
<pre><code class="language-javascript">bunx --bun shadcn@latest add @shadcn-space/sidebar-06
</code></pre>
<p>This command fetches the block from the Shadcn Space registry and scaffolds all the required component files into your project automatically. It also installs any shadcn/ui primitives the block depends on (such as <code>Sidebar, ScrollArea, Card, Button,</code> and <code>Collapsible</code>) if they aren't already present in your components/ui directory.</p>
<p>You can preview the live block and find the installation command on their <a href="https://shadcnspace.com/blocks/dashboard-ui/sidebars"><strong>shadcn sidebar</strong></a> page.</p>
<h2 id="heading-how-to-understand-the-folder-structure"><strong>How to Understand the Folder Structure</strong></h2>
<p>After installation, your project will contain the following new files:</p>
<pre><code class="language-javascript">app/
  sidebar-06/
    page.tsx                  ← Route entry point
assets/
  logo/
    logo.tsx                  ← Logo component
components/
  shadcn-space/
    blocks/
      sidebar-06/
        app-sidebar.tsx       ← Main sidebar shell
        nav-main.tsx          ← Navigation logic and rendering
</code></pre>
<p>Each file has a clearly defined responsibility:</p>
<ul>
<li><p><code>app/sidebar-06/page.tsx</code>: the route entry point that wires the sidebar into a page layout using SidebarProvider</p>
</li>
<li><p><code>assets/logo/logo.tsx</code>: the logo component rendered in the sidebar header</p>
</li>
<li><p><code>components/shadcn-space/blocks/sidebar-06/app-sidebar.tsx</code>: the main sidebar shell, including the header, scroll area, nav data, and promotional card</p>
</li>
<li><p><code>components/shadcn-space/blocks/sidebar-06/nav-main.tsx</code>: all navigation rendering logic, including section labels, leaf items, collapsible parents, and active state management</p>
</li>
</ul>
<p>You'll work through each of these files in detail in the sections below.</p>
<h2 id="heading-how-to-build-the-page-layout"><strong>How to Build the Page Layout</strong></h2>
<p>Open <code>app/sidebar-06/page.tsx</code>. This file is the entry point for your dashboard page. It uses <code>SidebarProvider</code> to establish sidebar context across the page, and <code>SidebarTrigger</code> to render a toggle button inside the header.</p>
<pre><code class="language-javascript">import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar";
import { AppSidebar } from "@/components/shadcn-space/blocks/sidebar-06/app-sidebar";

const Page = () =&gt; {
  return (
    &lt;SidebarProvider
      className="p-4 bg-muted"
      style={{ "--sidebar-width": "300px" } as React.CSSProperties}
    &gt;
      &lt;AppSidebar /&gt;

      {/* Main content area */}
      &lt;div className="flex flex-1 flex-col gap-4"&gt;
        &lt;header className="flex h-14 shrink-0 items-center gap-2 rounded-xl bg-background px-4 shadow-sm"&gt;
          &lt;SidebarTrigger className="cursor-pointer" /&gt;
        &lt;/header&gt;
        &lt;main className="flex-1 rounded-xl bg-background" /&gt;
      &lt;/div&gt;
    &lt;/SidebarProvider&gt;
  );
};

export default Page;
</code></pre>
<p>Let's break down the key parts of this layout:</p>
<p><strong>SidebarProvider</strong> wraps everything on the page. It manages the sidebar's open/closed state and passes it down to child components via React context. Any component that needs to read or change the sidebar state, including <code>SidebarTrigger</code> and <code>AppSidebar</code>, must be a descendant of <code>SidebarProvider</code>.</p>
<p><strong>The</strong> <code>--sidebar-width</code> <strong>CSS custom property</strong> controls the rendered width of the sidebar. It's set inline using a type assertion (<code>as React.CSSProperties</code>) because TypeScript doesn't know about this custom property by default. Setting it here rather than in a CSS file keeps the width configurable on a per-page basis.</p>
<p><code>SidebarTrigger</code> is a toggle button component that reads the sidebar open/closed state from the nearest <code>SidebarProvider</code> context and flips it on click. It renders in the header so users always have access to the toggle regardless of scroll position.</p>
<p><code>bg-muted</code> on <code>SidebarProvider</code> creates the light gray outer background that makes the floating sidebar card visually stand out from the page.</p>
<h2 id="heading-how-to-build-the-appsidebar-component"><strong>How to Build the AppSidebar Component</strong></h2>
<p>Open <code>components/shadcn-space/blocks/sidebar-06/app-sidebar.tsx</code>. This component is the main sidebar shell. It composes shadcn/ui's <code>Sidebar</code>, <code>SidebarHeader</code>, and <code>SidebarContent</code> layout primitives and wraps the scrollable navigation area in a <code>ScrollArea</code> component to handle overflow independently.</p>
<pre><code class="language-javascript">"use client";

import {
  Sidebar,
  SidebarContent,
  SidebarHeader,
  SidebarMenu,
  SidebarMenuItem,
} from "@/components/ui/sidebar";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Card, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import Logo from "@/assets/logo/logo";
import { NavItem, NavMain } from "@/components/shadcn-space/blocks/sidebar-06/nav-main";
import {
  AlignStartVertical,
  PieChart,
  CircleUserRound,
  ClipboardList,
  Notebook,
  NotepadText,
  Table,
  Languages,
  Ticket,
} from "lucide-react";
</code></pre>
<p>The <code>"use client"</code> directive at the top is required because this component uses React state (through <code>NavMain</code>) and event handlers, both of which require the component to run in the browser rather than being server-rendered by Next.js.</p>
<h2 id="heading-how-to-define-the-navigation-data"><strong>How to Define the Navigation Data</strong></h2>
<p>Inside <code>app-sidebar.tsx</code>the navigation structure is defined as a flat array of <code>NavItem</code> objects. Each item belongs to one of three categories:</p>
<ol>
<li><p><strong>A section label</strong> marked with <code>isSection: true</code> and a <code>label</code> string. Renders as an uppercase group heading.</p>
</li>
<li><p><strong>A leaf item</strong> has a <code>title, icon</code>, and <code>href</code>, but no <code>children</code>. Renders as a direct navigation link.</p>
</li>
<li><p><strong>A parent item</strong> has a <code>title, icon</code>, and a <code>children</code> array of sub-items. Renders as a collapsible trigger.</p>
</li>
</ol>
<pre><code class="language-javascript">
export const navData: NavItem[] = [
  // Dashboards Section
  { label: "Dashboards", isSection: true },
  { title: "Analytics", icon: PieChart, href: "#" },
  { title: "CRM Dashboard", icon: ClipboardList, href: "#" },

  // Pages Section
  { label: "Pages", isSection: true },
  { title: "Tables", icon: Table, href: "#" },
  { title: "Forms", icon: ClipboardList, href: "#" },
  { title: "User Profile", icon: CircleUserRound, href: "#" },

  // Apps Section
  { label: "Apps", isSection: true },
  { title: "Notes", icon: Notebook, href: "#" },
  { title: "Tickets", icon: Ticket, href: "#" },
  {
    title: "Blogs",
    icon: Languages,
    children: [
      { title: "Blog Post", href: "#" },
      { title: "Blog Detail", href: "#" },
      { title: "Blog Edit", href: "#" },
      { title: "Blog Create", href: "#" },
      { title: "Manage Blogs", href: "#" },
    ],
  },

  // Form Elements Section
  { label: "Form Elements", isSection: true },
  {
    title: "Shadcn Forms",
    icon: NotepadText,
    children: [
      { title: "Button", href: "#" },
      { title: "Input", href: "#" },
      { title: "Select", href: "#" },
      { title: "Checkbox", href: "#" },
      { title: "Radio", href: "#" },
    ],
  },
  {
    title: "Form layouts",
    icon: AlignStartVertical,
    children: [
      { title: "Forms Horizontal", href: "#" },
      { title: "Forms Vertical", href: "#" },
      { title: "Forms Validation", href: "#" },
      { title: "Forms Examples", href: "#" },
      { title: "Forms Wizard", href: "#" },
    ],
  },
];
</code></pre>
<p>This flat array approach is intentionally simple to maintain. You don't need a nested tree structure because the <code>NavMain</code> component handles the rendering logic for each item type by inspecting each item's shape. Adding a new section, item, or submenu is as straightforward as appending a new object to the array.</p>
<h2 id="heading-how-to-build-the-navmain-component"><strong>How to Build the NavMain Component</strong></h2>
<p>Open <code>components/shadcn-space/blocks/sidebar-06/nav-main.tsx</code>. This file contains all the navigation rendering logic. Start with the type definition and the top-level <code>NavMain</code> function:</p>
<pre><code class="language-javascript">"use client";

import * as React from "react";
import { ChevronRight, LucideIcon } from "lucide-react";
import { cn } from "@/lib/utils";
import {
  Collapsible,
  CollapsibleTrigger,
  CollapsibleContent,
} from "@/components/ui/collapsible";
import {
  SidebarGroup,
  SidebarGroupLabel,
  SidebarMenu,
  SidebarMenuButton,
  SidebarMenuItem,
  SidebarMenuSub,
  SidebarMenuSubItem,
  SidebarMenuSubButton,
} from "@/components/ui/sidebar";

export type NavItem = {
  label?: string;
  isSection?: boolean;
  title?: string;
  icon?: LucideIcon;
  href?: string;
  children?: NavItem[];
};

export function NavMain({ items }: { items: NavItem[] }) {
  const [activeParent, setActiveParent] = React.useState&lt;string | null&gt;(
    items.find((i) =&gt; !i.isSection)?.title || null
  );
  const [activeChild, setActiveChild] = React.useState&lt;string | null&gt;(null);

  return (
    &lt;&gt;
      {items.map((item, index) =&gt; (
        &lt;NavMainItem
          key={item.title || item.label || index}
          item={item}
          activeParent={activeParent}
          setActiveParent={setActiveParent}
          activeChild={activeChild}
          setActiveChild={setActiveChild}
        /&gt;
      ))}
    &lt;/&gt;
  );
}
</code></pre>
<p><code>activeParent</code> tracks which top-level nav item is currently selected. It initializes to the title of the first non-section item, so the sidebar always has a selection on first render, and you never show the sidebar with nothing highlighted. <code>activeChild</code> tracks which sub-item inside a collapsible menu is selected.</p>
<p>Both state values are passed down as props to each <code>NavMainItem</code>, so every item in the list can read the current selection and trigger updates to it.</p>
<h2 id="heading-how-to-handle-active-states-and-collapsible-menus"><strong>How to Handle Active States and Collapsible Menus</strong></h2>
<p>The <code>NavMainItem</code> function branches into one of three rendering paths based on the shape of the incoming item.</p>
<h3 id="heading-how-to-render-section-labels">How to Render Section Labels</h3>
<pre><code class="language-javascript">if (item.isSection &amp;&amp; item.label) {
  return (
    &lt;SidebarGroup className="p-0 pt-5 first:pt-0"&gt;
      &lt;SidebarGroupLabel className="p-0 text-xs font-medium uppercase text-sidebar-foreground"&gt;
        {item.label}
      &lt;/SidebarGroupLabel&gt;
    &lt;/SidebarGroup&gt;
  );
}
</code></pre>
<p>Section labels use <code>first:pt-0</code> to remove the top padding from the very first section, so the nav starts flush with the header.</p>
<h3 id="heading-how-to-render-collapsible-parent-items">How to Render Collapsible Parent Items</h3>
<pre><code class="language-javascript">if (hasChildren &amp;&amp; item.title) {
  return (
    &lt;SidebarGroup className="p-0"&gt;
      &lt;SidebarMenu&gt;
        &lt;Collapsible open={isOpen} onOpenChange={setIsOpen}&gt;
          &lt;SidebarMenuItem&gt;
            &lt;CollapsibleTrigger
              className="w-full"
              render={
                &lt;SidebarMenuButton
                  id={`nav-main-trigger-${item.title.toLowerCase().replace(/\s+/g, '-')}`}
                  tooltip={item.title}
                  isActive={isParentActive}
                  onClick={() =&gt; setActiveParent(item.title!)}
                  className={cn(
                    "rounded-md text-sm font-medium px-3 py-2 h-9 transition-colors cursor-pointer",
                    isParentActive ? "bg-primary! text-primary-foreground!" : ""
                  )}
                &gt;
                  {item.icon &amp;&amp; &lt;item.icon size={16} /&gt;}
                  &lt;span&gt;{item.title}&lt;/span&gt;
                  &lt;ChevronRight
                    className={cn(
                      "ml-auto transition-transform duration-200",
                      isOpen &amp;&amp; "rotate-90"
                    )}
                  /&gt;
                &lt;/SidebarMenuButton&gt;
              }
            /&gt;
            &lt;CollapsibleContent&gt;
              &lt;SidebarMenuSub className="me-0 pe-0"&gt;
                {item.children!.map((child, index) =&gt; (
                  &lt;NavMainSubItem
                    key={child.title || index}
                    item={child}
                    activeParent={activeParent}
                    setActiveParent={setActiveParent}
                    activeChild={activeChild}
                    setActiveChild={setActiveChild}
                    parentTitle={item.title}
                  /&gt;
                ))}
              &lt;/SidebarMenuSub&gt;
            &lt;/CollapsibleContent&gt;
          &lt;/SidebarMenuItem&gt;
        &lt;/Collapsible&gt;
      &lt;/SidebarMenu&gt;
    &lt;/SidebarGroup&gt;
  );
}
</code></pre>
<p>A <code>useEffect</code> inside <code>NavMainItem</code> syncs the local <code>isOpen</code> state with <code>activeParent</code> so that when a different parent is activated, the previously open collapsible stays open until the user explicitly closes it:</p>
<pre><code class="language-javascript">React.useEffect(() =&gt; {
  if (isParentActive) {
    setIsOpen(true);
  }
}, [isParentActive]);
</code></pre>
<p>The <code>ChevronRight</code> icon rotates 90 degrees when the submenu is open, using a Tailwind transition class:</p>
<pre><code class="language-javascript">&lt;ChevronRight
  className={cn(
    "ml-auto transition-transform duration-200",
    isOpen &amp;&amp; "rotate-90"
  )}
/&gt;
</code></pre>
<h3 id="heading-how-to-render-leaf-items">How to Render Leaf Items</h3>
<pre><code class="language-javascript">if (item.title) {
  return (
    &lt;SidebarGroup className="p-0"&gt;
      &lt;SidebarMenu&gt;
        &lt;SidebarMenuItem&gt;
          &lt;SidebarMenuButton
            id={`nav-main-button-${item.title.toLowerCase().replace(/\s+/g, '-')}`}
            tooltip={item.title}
            isActive={isParentActive}
            onClick={() =&gt; {
              setActiveParent(item.title!);
              setActiveChild(null);
            }}
            className={cn(
              "rounded-md text-sm font-medium px-3 py-2 h-9 transition-colors cursor-pointer",
              isParentActive ? "bg-primary! text-primary-foreground!" : ""
            )}
            render={&lt;a href={item.href} /&gt;}
          &gt;
            {item.icon &amp;&amp; &lt;item.icon /&gt;}
            {item.title}
          &lt;/SidebarMenuButton&gt;
        &lt;/SidebarMenuItem&gt;
      &lt;/SidebarMenu&gt;
    &lt;/SidebarGroup&gt;
  );
}
</code></pre>
<p>The <code>render</code> prop on <code>SidebarMenuButton</code> replaces the default button element with an <code>&lt;a&gt;</code> tag. This preserves correct anchor link semantics and accessibility while keeping the button's visual styling. When a leaf item is clicked, <code>activeChild</code> is reset to <code>null</code> since there is no child to track.</p>
<h3 id="heading-how-to-render-child-items-in-a-submenu">How to Render Child Items in a Submenu</h3>
<p>The <code>NavMainSubItem</code> function handles sub-items inside a collapsible. When a child is clicked, it sets both <code>activeChild</code> to itself and <code>activeParent</code> to its parent's title so the parent item remains visually highlighted:</p>
<pre><code class="language-javascript">if (item.title) {
  return (
    &lt;SidebarMenuSubItem className="w-full"&gt;
      &lt;SidebarMenuSubButton
        id={`nav-sub-button-${item.title.toLowerCase().replace(/\s+/g, '-')}`}
        className={cn(
          "w-full rounded-md transition-colors",
          activeChild === item.title ? "bg-muted! text-foreground!" : ""
        )}
        isActive={activeChild === item.title}
        onClick={() =&gt; {
          setActiveParent(parentTitle || "");
          setActiveChild(item.title!);
        }}
        render={&lt;a href={item.href}&gt;{item.title}&lt;/a&gt;}
      /&gt;
    &lt;/SidebarMenuSubItem&gt;
  );
}
</code></pre>
<p>The child uses a different active style (<code>bg-muted</code> with <code>text-foreground</code>) compared to the parent (<code>bg-primary</code> with <code>text-primary-foreground</code>). This visual distinction makes it easy to see both which section you are in and which specific page is currently active.</p>
<p>Sub-items also support nesting. If a child item itself has a <code>children</code> array, <code>NavMainSubItem</code> renders another <code>Collapsible</code> with a nested <code>SidebarMenuSub</code>, allowing you to build multi-level navigation trees without any changes to the data structure.</p>
<h2 id="heading-how-to-style-the-sidebar"><strong>How to Style the Sidebar</strong></h2>
<p>The full <code>AppSidebar</code> render function puts all of the pieces together:</p>
<pre><code class="language-javascript">export function AppSidebar() {
  return (
    &lt;Sidebar variant="floating" className="p-4 h-full [&amp;_[data-slot=sidebar-inner]]:h-full"&gt;
      &lt;div className="flex flex-col gap-6 overflow-hidden"&gt;

        {/* Header with Logo */}
        &lt;SidebarHeader className="px-4"&gt;
          &lt;SidebarMenu&gt;
            &lt;SidebarMenuItem&gt;
              &lt;a href="#" className="w-full h-full"&gt;
                &lt;Logo /&gt;
              &lt;/a&gt;
            &lt;/SidebarMenuItem&gt;
          &lt;/SidebarMenu&gt;
        &lt;/SidebarHeader&gt;

        {/* Scrollable Navigation Content */}
        &lt;SidebarContent className="overflow-hidden"&gt;
          &lt;ScrollArea className="h-[calc(100vh-100px)]"&gt;
            &lt;div className="px-4"&gt;
              &lt;NavMain items={navData} /&gt;
            &lt;/div&gt;

            {/* Promotional Card */}
            &lt;div className="pt-5 px-4"&gt;
              &lt;Card className="shadow-none ring-0 bg-secondary px-4 py-6"&gt;
                &lt;CardContent className="p-0 flex flex-col gap-3 items-center"&gt;
                  &lt;img
                    src="https://images.shadcnspace.com/assets/backgrounds/download-img.png"
                    alt="sidebar-img"
                    width={74}
                    height={74}
                    className="h-20 w-20"
                  /&gt;
                  &lt;div className="flex flex-col gap-4 items-center"&gt;
                    &lt;div&gt;
                      &lt;p className="text-base font-semibold text-card-foreground text-center"&gt;
                        Grab Pro Now
                      &lt;/p&gt;
                      &lt;p className="text-sm font-regular text-muted-foreground text-center"&gt;
                        Customize your admin
                      &lt;/p&gt;
                    &lt;/div&gt;
                    &lt;Button className="w-fit h-9 px-4 py-2 shadow-none cursor-pointer rounded-xl hover:bg-primary/80"&gt;
                      Get Premium
                    &lt;/Button&gt;
                  &lt;/div&gt;
                &lt;/CardContent&gt;
              &lt;/Card&gt;
            &lt;/div&gt;
          &lt;/ScrollArea&gt;
        &lt;/SidebarContent&gt;

      &lt;/div&gt;
    &lt;/Sidebar&gt;
  );
}
</code></pre>
<p>Let's walk through the key styling decisions:</p>
<p><code>variant="floating"</code> gives the sidebar a card-like appearance with rounded corners and a subtle drop shadow. It visually lifts the sidebar off the background rather than making it flush with the page edge like a standard sidebar would.</p>
<p><code>[&amp;_[data-slot=sidebar-inner]]:h-full</code> is an arbitrary Tailwind variant selector that targets shadcn/ui's internal sidebar slot element. Without this, the sidebar inner container doesn't fill the full available height, which breaks the layout. The <code>data-slot</code> attribute is how shadcn/ui identifies internal sub-elements of compound components.</p>
<p><code>h-[calc(100vh-100px)]</code> on <code>ScrollArea</code> makes the navigation list independently scrollable. The 100px offset accounts for the sidebar header and padding, so the scroll area doesn't overflow the viewport. The rest of the page layout remains static while the nav scrolls.</p>
<p>The <code>bg-secondary</code> card at the bottom of the scroll area is a common admin dashboard pattern, a soft prompt for an upgrade or onboarding action that lives passively in the sidebar without blocking navigation.</p>
<p>For more details on the Sidebar component's API, variants, and configuration options, refer to the official shadcn/ui sidebar docs.</p>
<h2 id="heading-live-preview"><strong>Live Preview</strong></h2>
<img src="https://cdn.hashnode.com/uploads/covers/68b53a3d851476bd2ce87f12/f1538441-fa73-4eb0-af91-04f5bf4fab08.png" alt="f1538441-fa73-4eb0-af91-04f5bf4fab08" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<h2 id="heading-summary"><strong>Summary</strong></h2>
<p>Congratulations! You have now built a complete, production-ready admin dashboard sidebar using shadcn/ui and a community block from Shadcn Space.</p>
<p>Here is a recap of everything you covered:</p>
<ul>
<li><p>Setting up a Next.js project with shadcn/ui initialized and a pre-built sidebar block installed from Shadcn Space</p>
</li>
<li><p>Using <code>SidebarProvider</code> and <code>SidebarTrigger</code> to manage the sidebar open/closed state across a page layout through React context</p>
</li>
<li><p>Defining navigation data as a flat array of typed <code>NavItem</code> objects covering section labels, leaf items, and collapsible parent items</p>
</li>
<li><p>Rendering all three item types from a single <code>navData</code> source in the <code>NavMain</code> and <code>NavMainItem</code> components</p>
</li>
<li><p>Tracking <code>activeParent</code> and <code>activeChild</code> state in a single location and passing them as props so every item can read and update the shared selection state</p>
</li>
<li><p>Using <code>Collapsible</code> with a <code>useEffect</code> sync to keep parent items open when they are active, and animate the chevron icon on expand and collapse</p>
</li>
<li><p>Applying the <code>floating</code> variant, an arbitrary Tailwind slot selector, and <code>ScrollArea</code> with a calculated height to produce a polished, production-appropriate sidebar layout</p>
</li>
</ul>
<p>This pattern scales well beyond what you built here. You can extend <code>NavItem</code> with additional fields like badge counts, permission flags, or external link indicators. You can swap in real <code>href</code> values and connect <code>activeParent</code> and <code>activeChild</code> to your router so the selection always reflects the current URL. You can also add more sections to <code>navData</code> without touching any rendering logic.</p>
<p>For a quick checkout, we have used the Shadcn Space free Shadcn dashboard block in this <a href="https://shadcnspace.com/blocks/dashboard-ui/dashboard-shell"><strong>dashboard shell</strong></a>.</p>
<p>If you want to explore more pre-built admin UI blocks, components, and templates built on top of shadcn/ui, you can browse the full library at <a href="https://shadcnspace.com/"><strong>Shadcn Space</strong></a>.</p>
<h3 id="heading-resources"><strong>Resources</strong></h3>
<ul>
<li><p><a href="https://shadcnspace.com/blocks"><strong>Shadcn UI Blocks</strong></a></p>
</li>
<li><p><a href="https://shadcnspace.com/components"><strong>Shadcn UI Components</strong></a></p>
</li>
<li><p><a href="https://shadcnspace.com/docs/getting-started/blocks"><strong>Shadcn Space Getting Started Docs</strong></a></p>
</li>
<li><p><a href="https://www.figma.com/community/file/1597967874273587400/shadcn-space-figma-ui-kit"><strong>Figma UI Kit Design System</strong></a></p>
</li>
<li><p><a href="https://ui.shadcn.com/docs/components/sidebar"><strong>shadcn/ui Sidebar Docs</strong></a></p>
</li>
<li><p><a href="https://base-ui.com/"><strong>Base UI</strong></a></p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build Responsive and Accessible UI Designs with React and Semantic HTML ]]>
                </title>
                <description>
                    <![CDATA[ Building modern React applications requires more than just functionality. It also demands responsive layouts and accessible user experiences. By combining semantic HTML, responsive design techniques,  ]]>
                </description>
                <link>https://www.freecodecamp.org/news/build-responsive-accessible-ui-with-react-and-semantic-html/</link>
                <guid isPermaLink="false">69d539975da14bc70e76871d</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Accessibility ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Responsive Web Design ]]>
                    </category>
                
                    <category>
                        <![CDATA[ semantichtml ]]>
                    </category>
                
                    <category>
                        <![CDATA[ aria ]]>
                    </category>
                
                    <category>
                        <![CDATA[ UI ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Gopinath Karunanithi ]]>
                </dc:creator>
                <pubDate>Tue, 07 Apr 2026 17:06:31 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/d2651d02-040d-4c4f-bbfe-ef92097edab4.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Building modern React applications requires more than just functionality. It also demands responsive layouts and accessible user experiences.</p>
<p>By combining semantic HTML, responsive design techniques, and accessibility best practices (like ARIA roles and keyboard navigation), developers can create interfaces that work across devices and for all users, including those with disabilities.</p>
<p>This article shows how to design scalable, inclusive React UIs using real-world patterns and code examples.</p>
<h2 id="heading-table-of-contents"><strong>Table of Contents</strong></h2>
<ul>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-overview">Overview</a></p>
</li>
<li><p><a href="#heading-why-accessibility-and-responsiveness-matter">Why Accessibility and Responsiveness Matter</a></p>
</li>
<li><p><a href="#heading-core-principles-of-accessible-and-responsive-design">Core Principles of Accessible and Responsive Design</a></p>
</li>
<li><p><a href="#heading-using-semantic-html-in-react">Using Semantic HTML in React</a></p>
</li>
<li><p><a href="#heading-structuring-a-page-with-semantic-elements">Structuring a Page with Semantic Elements</a></p>
</li>
<li><p><a href="#heading-building-responsive-layouts">Building Responsive Layouts</a></p>
</li>
<li><p><a href="#heading-accessibility-with-aria">Accessibility with ARIA</a></p>
</li>
<li><p><a href="#heading-keyboard-navigation">Keyboard Navigation</a></p>
</li>
<li><p><a href="#heading-focus-management">Focus Management</a></p>
</li>
<li><p><a href="#heading-forms-and-accessibility">Forms and Accessibility</a></p>
</li>
<li><p><a href="#heading-responsive-typography-and-images">Responsive Typography and Images</a></p>
</li>
<li><p><a href="#heading-building-a-fully-accessible-responsive-component-end-to-end-example">Building a Fully Accessible Responsive Component (End-to-End Example)</a></p>
</li>
<li><p><a href="#heading-testing-accessibility">Testing Accessibility</a></p>
</li>
<li><p><a href="#heading-best-practices">Best Practices</a></p>
</li>
<li><p><a href="#heading-when-not-to-overuse-accessibility-features">When NOT to Overuse Accessibility Features</a></p>
</li>
<li><p><a href="#heading-future-enhancements">Future Enhancements</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before following along, you should be familiar with:</p>
<ul>
<li><p>React fundamentals (components, hooks, JSX)</p>
</li>
<li><p>Basic HTML and CSS</p>
</li>
<li><p>JavaScript ES6 features</p>
</li>
<li><p>Basic understanding of accessibility concepts (helpful but not required)</p>
</li>
</ul>
<h2 id="heading-overview">Overview</h2>
<p>Modern web applications must serve a diverse audience across a wide range of devices, screen sizes, and accessibility needs. Users today expect seamless experiences whether they are browsing on a desktop, tablet, or mobile device – and they also expect interfaces that are usable regardless of physical or cognitive limitations.</p>
<p>Two essential principles help achieve this:</p>
<ul>
<li><p>Responsive design, which ensures layouts adapt to different screen sizes</p>
</li>
<li><p>Accessibility, which ensures applications are usable by people with disabilities</p>
</li>
</ul>
<p>In React applications, these principles are often implemented incorrectly or treated as afterthoughts. Developers may rely heavily on div-based layouts, ignore semantic HTML, or overlook accessibility features such as keyboard navigation and screen reader support.</p>
<p>This article will show you how to build responsive and accessible UI designs in React using semantic HTML. You'll learn how to:</p>
<ul>
<li><p>Structure components using semantic HTML elements</p>
</li>
<li><p>Build responsive layouts using modern CSS techniques</p>
</li>
<li><p>Improve accessibility with ARIA attributes and proper roles</p>
</li>
<li><p>Ensure keyboard navigation and screen reader compatibility</p>
</li>
<li><p>Apply best practices for scalable and inclusive UI design</p>
</li>
</ul>
<p>By the end of this guide, you'll be able to create React interfaces that are not only visually responsive but also accessible to all users.</p>
<h2 id="heading-why-accessibility-and-responsiveness-matter">Why Accessibility and Responsiveness Matter</h2>
<p>Responsive and accessible design isn't just about compliance. It directly impacts usability, performance, and reach.</p>
<p><strong>Accessibility benefits:</strong></p>
<ul>
<li><p>Supports users with visual, motor, or cognitive impairments</p>
</li>
<li><p>Improves SEO and content discoverability</p>
</li>
<li><p>Enhances usability for all users</p>
</li>
</ul>
<p><strong>Responsiveness benefits:</strong></p>
<ul>
<li><p>Ensures consistent UX across devices</p>
</li>
<li><p>Reduces bounce rates on mobile</p>
</li>
<li><p>Improves performance and scalability</p>
</li>
</ul>
<p>Ignoring these principles can result in broken layouts on smaller screens, poor screen reader compatibility, and limited reach and usability.</p>
<h2 id="heading-core-principles-of-accessible-and-responsive-design">Core Principles of Accessible and Responsive Design</h2>
<p>Before diving into the code, it’s important to understand the foundational principles.</p>
<h3 id="heading-1-semantic-html-first">1. Semantic HTML First</h3>
<p>Semantic HTML refers to using HTML elements that clearly describe their meaning and role in the interface, rather than relying on generic containers like <code>&lt;div&gt; or &lt;span&gt;.</code>These elements provide built-in accessibility, improve SEO, and make code more readable.</p>
<p>For example:</p>
<p><strong>Non-semantic:</strong></p>
<pre><code class="language-html">&lt;div onClick={handleClick}&gt;Submit&lt;/div&gt;
</code></pre>
<p><strong>Semantic:</strong></p>
<pre><code class="language-html">&lt;button type="button" onClick={handleClick}&gt;Submit&lt;/button&gt;
</code></pre>
<p>Another example:</p>
<p><strong>Non-semantic:</strong></p>
<pre><code class="language-html">&lt;div className="header"&gt;My App&lt;/div&gt;
</code></pre>
<p><strong>Semantic:</strong></p>
<pre><code class="language-html">&lt;header&gt;My App&lt;/header&gt;
</code></pre>
<p>Using semantic elements such as <code>&lt;header&gt;</code>, <code>&lt;nav&gt;</code>, <code>&lt;main&gt;</code>, <code>&lt;section&gt;</code>, <code>&lt;article&gt;</code>, and <code>&lt;button&gt;</code> helps browsers and assistive technologies (like screen readers) understand the structure and purpose of your UI without additional configuration.</p>
<p>Why this matters:</p>
<ul>
<li><p>Screen readers understand semantic elements automatically</p>
</li>
<li><p>It supports built-in accessibility (keyboard, focus, roles)</p>
</li>
<li><p>There's less need for ARIA attributes</p>
</li>
<li><p>It gives you better SEO and maintainability</p>
</li>
</ul>
<h3 id="heading-2-mobile-first-design">2. Mobile-First Design</h3>
<p>Mobile-first design means starting your UI design with the smallest screen sizes (typically mobile devices) and progressively enhancing the layout for larger screens such as tablets and desktops.</p>
<p>This approach makes sure that core content and functionality are prioritized, layouts remain simple and performant, and users on mobile devices get a fully usable experience.</p>
<p>In practice, mobile-first design involves:</p>
<ul>
<li><p>Using a single-column layout initially</p>
</li>
<li><p>Applying minimal styling and spacing</p>
</li>
<li><p>Avoiding complex UI patterns on small screens</p>
</li>
</ul>
<p>Then, you scale up using CSS media queries:</p>
<pre><code class="language-css">.container {
  display: flex;
  flex-direction: column;
}
@media (min-width: 768px) {
  .container {
    flex-direction: row;
  }
}
</code></pre>
<p>Here, the default layout is optimized for mobile, and enhancements are applied only when the screen size increases.</p>
<p><strong>Why this approach works:</strong></p>
<ul>
<li><p>Prioritizes essential content</p>
</li>
<li><p>Improves performance on mobile devices</p>
</li>
<li><p>Reduces layout bugs when scaling up</p>
</li>
<li><p>Aligns with how most users access web apps today</p>
</li>
</ul>
<h3 id="heading-3-progressive-enhancement">3. Progressive Enhancement</h3>
<p>Progressive enhancement is the practice of building a baseline user experience that works for all users (regardless of their device, browser capabilities, or network conditions) and then layering on advanced features for more capable environments.</p>
<p>This approach ensures that core functionality is always accessible, users on older devices or slow networks aren't blocked, and accessibility is preserved even when advanced features fail.</p>
<p>In practice, this means:</p>
<ul>
<li><p>Start with semantic HTML that delivers content and functionality</p>
</li>
<li><p>Add basic styling with CSS for layout and readability</p>
</li>
<li><p>Enhance interactivity using JavaScript (React) only where needed</p>
</li>
</ul>
<p>For example, a form should still be usable with plain HTML:</p>
<pre><code class="language-html">&lt;form&gt;
  &lt;label htmlFor="email"&gt;Email&lt;/label&gt;
  &lt;input id="email" type="email" /&gt;
  &lt;button type="submit"&gt;Submit&lt;/button&gt;
&lt;/form&gt;
</code></pre>
<p>Then, React can enhance it with validation, dynamic feedback, or animations.</p>
<p>By prioritizing functionality first and enhancements later, you ensure your application remains usable in a wide range of real-world scenarios.</p>
<h3 id="heading-4-keyboard-accessibility">4. Keyboard Accessibility</h3>
<p>Keyboard accessibility ensures that users can navigate and interact with your application using only a keyboard. This is critical for users with motor disabilities and also improves usability for power users.</p>
<p>Key aspects of keyboard accessibility include:</p>
<ul>
<li><p>Ensuring all interactive elements (buttons, links, inputs) are focusable</p>
</li>
<li><p>Maintaining a logical tab order across the page</p>
</li>
<li><p>Providing visible focus indicators (for example, outline styles)</p>
</li>
<li><p>Supporting keyboard events such as Enter and Space</p>
</li>
</ul>
<p><strong>Bad Example (Not Accessible)</strong></p>
<pre><code class="language-html">&lt;div onClick={handleClick}&gt;Submit&lt;/div&gt;
</code></pre>
<p>This element:</p>
<ul>
<li><p>Cannot be focused with a keyboard</p>
</li>
<li><p>Does not respond to Enter/Space</p>
</li>
<li><p>Is invisible to screen readers</p>
</li>
</ul>
<p><strong>Good Example</strong></p>
<pre><code class="language-html">&lt;button type="button" onClick={handleClick}&gt;Submit&lt;/button&gt;
</code></pre>
<p>This automatically supports:</p>
<ul>
<li><p>Keyboard interaction</p>
</li>
<li><p>Focus management</p>
</li>
<li><p>Screen reader announcements</p>
</li>
</ul>
<p><strong>Custom Component Example (if needed)</strong></p>
<pre><code class="language-html">&lt;div
  role="button"
  tabIndex={0}
  onClick={handleClick}
  onKeyDown={(e) =&gt; {
    if (e.key === 'Enter' || e.key === ' ') {
      e.preventDefault();
      handleClick();
    }
  }}
&gt;
  Submit
&lt;/div&gt;
</code></pre>
<p>But only use this when native elements aren't sufficient.</p>
<p>These principles form the foundation of accessible and responsive design:</p>
<ul>
<li><p>Use semantic HTML to communicate intent</p>
</li>
<li><p>Design for mobile first, then scale up</p>
</li>
<li><p>Enhance progressively for better compatibility</p>
</li>
<li><p>Ensure full keyboard accessibility</p>
</li>
</ul>
<p>Applying these early prevents major usability and accessibility issues later in development.</p>
<h2 id="heading-using-semantic-html-in-react">Using Semantic HTML in React</h2>
<p>As we briefly discussed above, semantic HTML plays a critical role in both accessibility (a11y) and code readability. Semantic elements clearly describe their purpose to both developers and browsers, which allows assistive technologies like screen readers to interpret and navigate the UI correctly.</p>
<p>For example, when you use a <code>&lt;button&gt;</code> element, browsers automatically provide keyboard support, focus behavior, and accessibility roles. In contrast, non-semantic elements like <code>&lt;div&gt;</code>require additional attributes and manual handling to achieve the same functionality.</p>
<p>From a readability perspective, semantic HTML makes your code easier to understand and maintain. Developers can quickly identify the structure and intent of a component without relying on class names or external documentation.</p>
<p><strong>Bad Example (Non-semantic)</strong></p>
<pre><code class="language-html">&lt;div onClick={handleClick}&gt;Submit&lt;/div&gt;
</code></pre>
<p>Why this is problematic:</p>
<ul>
<li><p>The <code>&lt;div&gt;</code>element has no inherent meaning or role</p>
</li>
<li><p>It is not focusable by default, so keyboard users can't access it</p>
</li>
<li><p>It does not respond to keyboard events like Enter or Space unless explicitly coded</p>
</li>
<li><p>Screen readers do not recognize it as an interactive element</p>
</li>
</ul>
<p>To make this accessible, you would need to add:</p>
<p><code>role="button"</code></p>
<p><code>tabIndex="0"</code></p>
<p><code>Keyboard event handlers</code></p>
<p><strong>Good Example (Semantic)</strong></p>
<pre><code class="language-html">&lt;button type="button" onClick={handleClick}&gt;Submit&lt;/button&gt;
</code></pre>
<p>Why this is better:</p>
<ul>
<li><p>The <code>&lt;button&gt;</code> element is inherently interactive</p>
</li>
<li><p>It is automatically focusable and keyboard accessible</p>
</li>
<li><p>It supports Enter and Space key activation by default</p>
</li>
<li><p>Screen readers correctly announce it as a button</p>
</li>
</ul>
<p>This reduces complexity while improving accessibility and usability.</p>
<h3 id="heading-why-all-this-matters">Why all this matters:</h3>
<p>There are many reasons to use semantic HTML.</p>
<p>First, semantic elements like <code>&lt;button&gt;, &lt;a&gt;,</code> and <code>&lt;form&gt;</code> come with default accessibility behaviors such as focus management and keyboard interaction</p>
<p>It also reduces complexity: you don’t need to manually implement roles, keyboard handlers, or tab navigation</p>
<p>They provide better screen reader support as well. Assistive technologies can correctly interpret the purpose of elements and announce them appropriately</p>
<p>Semantic HTML also improves maintainability and helps other developers quickly understand the intent of your code without reverse-engineering behavior from event handlers</p>
<p>Finally, you'll generally have fewer bugs in your code. Relying on native browser behavior reduces the risk of missing critical accessibility features</p>
<p>Here's another example:</p>
<p><strong>Non-semantic:</strong></p>
<pre><code class="language-html">&lt;div className="nav"&gt;
  &lt;div onClick={goHome}&gt;Home&lt;/div&gt;
&lt;/div&gt;
</code></pre>
<p><strong>Semantic:</strong></p>
<pre><code class="language-html">&lt;nav&gt;
  &lt;a href="/"&gt;Home&lt;/a&gt;
&lt;/nav&gt;
</code></pre>
<p>Here, <code>&lt;nav&gt;</code> clearly defines a navigation region, and <code>&lt;a&gt;</code> provides built-in link behavior, including keyboard navigation and proper screen reader announcements.</p>
<h2 id="heading-structuring-a-page-with-semantic-elements">Structuring a Page with Semantic Elements</h2>
<p>When building a React application, structuring your layout with semantic HTML elements helps define clear regions of your interface. Instead of relying on generic containers like <code>&lt;div&gt;</code>, semantic elements communicate the purpose of each section to both developers and assistive technologies.</p>
<p>In the example below, we're creating a basic page layout using commonly used semantic elements such as <code>&lt;header&gt;</code>, <code>&lt;nav&gt;</code>, <code>&lt;main&gt;</code>, <code>&lt;section&gt;</code>, and <code>&lt;footer&gt;</code>. Each of these elements represents a specific part of the UI and contributes to better accessibility and maintainability.</p>
<pre><code class="language-javascript">function Layout() {
  return (
    &lt;&gt;
      {/* Skip link for keyboard and screen reader users */}
      &lt;a href="#main-content" className="skip-link"&gt;
        Skip to main content
      &lt;/a&gt;

      &lt;header&gt;
        &lt;h1&gt;My App&lt;/h1&gt;
      &lt;/header&gt;

      &lt;nav&gt;
        &lt;ul&gt;
          &lt;li&gt;&lt;a href="/"&gt;Home&lt;/a&gt;&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/nav&gt;

      &lt;main id="main-content"&gt;
        &lt;section&gt;
          &lt;h2&gt;Dashboard&lt;/h2&gt;
        &lt;/section&gt;
      &lt;/main&gt;

      &lt;footer&gt;
        &lt;p&gt;© 2026&lt;/p&gt;
      &lt;/footer&gt;
    &lt;/&gt;
  );
}
</code></pre>
<p>Each element in this layout has a specific role:</p>
<ul>
<li><p>The skip link allows screen reader users to skip to the main content</p>
</li>
<li><p><code>&lt;header&gt;</code>: Represents introductory content or branding</p>
</li>
<li><p><code>&lt;nav&gt;</code>: Contains navigation links</p>
</li>
<li><p><code>&lt;main&gt;</code>: Holds the primary content of the page</p>
</li>
<li><p><code>&lt;section&gt;</code>: Groups related content within the page</p>
</li>
<li><p><code>&lt;footer&gt;</code>: Contains closing or supplementary information</p>
</li>
</ul>
<p>Using these elements correctly ensures your UI is both logically structured and accessible by default.</p>
<h3 id="heading-why-this-structure-is-important">Why this structure is important:</h3>
<p>Properly structuring a page like this brings with it many benefits.</p>
<p>For example, it gives you Improved screen reader navigation. This is because semantic elements allow screen readers to identify different regions of the page (for example, navigation, main content, footer). Users can quickly jump between these sections instead of reading the page linearly</p>
<p>It also gives you better document structure. Elements like <code>&lt;main&gt;</code> and <code>&lt;section&gt;</code> define a logical hierarchy, making content easier to parse for both browsers and assistive technologies</p>
<p>Search engines also use semantic structure to better understand page content and prioritize important sections, resulting in better SEO.</p>
<p>It also makes your code more readable, so other devs can immediately understand the layout and purpose of each section without relying on class names or comments</p>
<p>And it provides built-in accessibility landmarks using elements like <code>&lt;nav&gt;</code> and <code>&lt;main&gt;</code>, allowing assistive technologies to provide shortcuts for users.</p>
<h2 id="heading-building-responsive-layouts">Building Responsive Layouts</h2>
<p>Responsive layouts ensure that your UI adapts smoothly across different screen sizes, from mobile devices to large desktop displays. Instead of building separate layouts for each device, modern CSS techniques like Flexbox, Grid, and media queries allow you to create flexible, fluid designs.</p>
<p>In this section, we’ll look at how layout behavior changes based on screen size, starting with a mobile-first approach and progressively enhancing the layout for larger screens.</p>
<p><strong>Using CSS Flexbox:</strong></p>
<pre><code class="language-css">.container {
  display: flex;
  flex-direction: column;
}

@media (min-width: 768px) {
  .container {
    flex-direction: row;
  }
}
</code></pre>
<p>On smaller screens (mobile), elements are stacked vertically using <code>flex-direction: column</code>, making content easier to read and scroll.</p>
<p>On larger screens (768px and above), the layout switches to a horizontal row, utilizing available screen space more efficiently.</p>
<p><strong>Why this helps:</strong></p>
<ul>
<li><p>Ensures content is readable on small devices without horizontal scrolling</p>
</li>
<li><p>Improves layout efficiency on larger screens</p>
</li>
<li><p>Supports a mobile-first design strategy by defining the default layout for smaller screens first and enhancing it progressively</p>
</li>
</ul>
<p><strong>Using CSS Grid:</strong></p>
<pre><code class="language-css">.grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 16px;
}

@media (min-width: 768px) {
  .grid {
    grid-template-columns: repeat(3, 1fr);
  }
}
</code></pre>
<p>On mobile devices, content is displayed in a single-column layout (<code>1fr</code>), ensuring each item takes full width.</p>
<p>On larger screens, the layout shifts to three equal columns using <code>repeat(3, 1fr)</code>, creating a grid structure.</p>
<p><strong>Why this helps:</strong></p>
<ul>
<li><p>Provides a clean and consistent way to manage complex layouts</p>
</li>
<li><p>Makes it easy to scale from simple to multi-column designs</p>
</li>
<li><p>Improves visual balance and spacing across different screen sizes</p>
</li>
</ul>
<p><strong>React Example:</strong></p>
<pre><code class="language-javascript">function CardGrid() {
  return (
    &lt;div className="grid"&gt;
      &lt;div className="card"&gt;Item 1&lt;/div&gt;
      &lt;div className="card"&gt;Item 2&lt;/div&gt;
      &lt;div className="card"&gt;Item 3&lt;/div&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p>The React component uses the .grid class to apply responsive Grid behavior. Each card automatically adjusts its position based on screen size.</p>
<p><strong>Why this is effective:</strong></p>
<ul>
<li><p>Separates structure (React JSX) from layout (CSS)</p>
</li>
<li><p>Allows you to reuse the same component across different screen sizes without modification</p>
</li>
<li><p>Ensures consistent responsiveness across your application with minimal code</p>
</li>
</ul>
<p>By combining Flexbox for one-dimensional layouts and Grid for two-dimensional layouts, you can build highly adaptable interfaces that respond efficiently to different devices and screen sizes.</p>
<h2 id="heading-accessibility-with-aria">Accessibility with ARIA</h2>
<p>ARIA (Accessible Rich Internet Applications) is a set of attributes that enhance the accessibility of web content, especially when building custom UI components that cannot be fully implemented using native HTML elements.</p>
<p>ARIA works by providing additional semantic information to assistive technologies such as screen readers. It does this through:</p>
<ul>
<li><p>Roles, which define what an element is (for example, button, dialog, menu)</p>
</li>
<li><p>States and properties, which describe the current condition or behavior of an element (for example, expanded, hidden, live updates)</p>
</li>
</ul>
<p>For example, when you create a custom dropdown using <code>&lt;div&gt;</code> elements, browsers don't inherently understand its purpose. By applying ARIA roles and attributes, you can communicate that this structure behaves like a menu and ensure it is interpreted correctly.</p>
<p>Just make sure you use ARIA carefully. Incorrect or unnecessary usage can reduce accessibility. Here's a key rule to follow: use native HTML first. Only use ARIA when necessary.</p>
<p>ARIA is especially useful for:</p>
<ul>
<li><p>Custom UI components (modals, tabs, dropdowns)</p>
</li>
<li><p>Dynamic content updates</p>
</li>
<li><p>Complex interactions not covered by standard HTML</p>
</li>
</ul>
<p>Something to note before we get into the examples here: real-world accessibility is complex. For production apps, you should typically prefer well-tested libraries like react-aria, Radix UI, or Headless UI. These examples are primarily for educational purposes and aren't production-ready.</p>
<p><strong>Example: Accessible Modal</strong></p>
<pre><code class="language-javascript">function Modal({ isOpen, onClose }) {
  const dialogRef = React.useRef();

  React.useEffect(() =&gt; {
    if (isOpen) {
      dialogRef.current?.focus();
    }
  }, [isOpen]);

  if (!isOpen) return null;

  return (
    &lt;div
      role="dialog"
      aria-modal="true"
      aria-labelledby="modal-title"
      tabIndex={-1}
      ref={dialogRef}
      onKeyDown={(e) =&gt; {
        if (e.key === 'Escape') onClose();
      }}
    &gt;
      &lt;h2 id="modal-title"&gt;Modal Title&lt;/h2&gt;
      &lt;button type="button" onClick={onClose}&gt;Close&lt;/button&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p><strong>How this works:</strong></p>
<ul>
<li><p><code>role="dialog"</code> identifies the element as a modal dialog</p>
</li>
<li><p><code>aria-modal="true"</code> indicates that background content is inactive</p>
</li>
<li><p><code>aria-labelledby</code> connects the dialog to its visible title for screen readers</p>
</li>
<li><p><code>tabIndex={-1}</code> allows the dialog container to receive focus programmatically</p>
</li>
<li><p>Focus is moved to the dialog when it opens</p>
</li>
<li><p>Pressing Escape closes the modal, which is a standard accessibility expectation</p>
</li>
</ul>
<p>This ensures that users can understand, navigate, and exit the modal using both keyboard and assistive technologies.</p>
<h3 id="heading-key-aria-attributes">Key ARIA Attributes</h3>
<h4 id="heading-1-role">1. role</h4>
<p>Defines the type of element and its purpose. For example, <code>role="dialog"</code> tells assistive technologies that the element behaves like a modal dialog.</p>
<h4 id="heading-2-aria-label">2. aria-label</h4>
<p>Provides an accessible name for an element when visible text is not sufficient. Screen readers use this label to describe the element to users.</p>
<h4 id="heading-3-aria-hidden">3. aria-hidden</h4>
<p>Indicates whether an element should be ignored by assistive technologies. For example, <code>aria-hidden="true"</code> hides decorative elements from screen readers.</p>
<h4 id="heading-4-aria-live">4. aria-live</h4>
<p>Used for dynamic content updates. It tells screen readers to announce changes automatically without requiring user interaction (for example, form validation messages or notifications).</p>
<p><strong>Example: Accessible Dropdown (Custom Component)</strong></p>
<pre><code class="language-javascript">function Dropdown({ isOpen, toggle }) {
  return (
    &lt;div&gt;
      &lt;button
        type="button"
        aria-expanded={isOpen}
        aria-controls="dropdown-menu"
        onClick={toggle}
      &gt;
        Menu
      &lt;/button&gt;

      {isOpen &amp;&amp; (
        &lt;ul id="dropdown-menu"&gt;
          &lt;li&gt;
            &lt;button type="button" onClick={() =&gt; console.log('Item 1')}&gt;
              Item 1
            &lt;/button&gt;
          &lt;/li&gt;
          &lt;li&gt;
            &lt;button type="button" onClick={() =&gt; console.log('Item 2')}&gt;
              Item 2
            &lt;/button&gt;
          &lt;/li&gt;
        &lt;/ul&gt;
      )}
    &lt;/div&gt;
  );
}
</code></pre>
<p><strong>How this works:</strong></p>
<ul>
<li><p><code>aria-expanded</code> indicates whether the dropdown is open or closed</p>
</li>
<li><p><code>aria-controls</code> links the button to the dropdown content via its id</p>
</li>
<li><p>The <code>&lt;button&gt;</code> element acts as the trigger and is fully keyboard accessible</p>
</li>
<li><p>The <code>&lt;ul&gt;</code> and <code>&lt;li&gt;</code> elements provide a natural list structure</p>
</li>
<li><p>Using <code>&lt;a&gt;</code> elements ensures proper navigation behavior and accessibility</p>
</li>
</ul>
<p>Why this approach is correct:</p>
<ul>
<li><p>It follows standard web patterns instead of application-style menus</p>
</li>
<li><p>It avoids misusing ARIA roles like role="menu", which require complex keyboard handling</p>
</li>
<li><p>Screen readers can correctly interpret the structure without additional roles</p>
</li>
<li><p>It keeps the implementation simple, accessible, and maintainable</p>
</li>
</ul>
<p>If you need advanced menu behavior (like arrow key navigation), then ARIA menu roles may be appropriate –&nbsp;but only when fully implemented according to the ARIA Authoring Practices.</p>
<p>Note: Most dropdowns in web applications are not true "menus" in the ARIA sense. Avoid using role="menu" unless you are implementing full keyboard navigation (arrow keys, focus management, and so on).</p>
<h2 id="heading-keyboard-navigation">Keyboard Navigation</h2>
<p>Keyboard navigation ensures that users can fully interact with your application using only a keyboard, without relying on a mouse. This is essential for users with motor disabilities, but it also benefits power users and developers who prefer keyboard-based workflows.</p>
<p>In a well-designed interface, users should be able to:</p>
<ul>
<li><p>Navigate through interactive elements using the Tab key</p>
</li>
<li><p>Activate buttons and links using Enter or Space</p>
</li>
<li><p>Clearly see which element is currently focused</p>
</li>
</ul>
<p>In the example below, we’ll look at common mistakes in keyboard handling and why relying on native HTML elements is usually the better approach.</p>
<p><strong>Example:</strong></p>
<p>Avoid adding custom keyboard handlers to native elements like <code>&lt;button&gt;</code>, as they already support keyboard interaction by default.</p>
<p>For example, this is all you need:</p>
<pre><code class="language-html">&lt;button type="button" onClick={handleClick}&gt;Submit&lt;/button&gt;
</code></pre>
<p>This automatically supports:</p>
<ul>
<li><p>Enter and Space key activation</p>
</li>
<li><p>Focus management</p>
</li>
<li><p>Screen reader announcements</p>
</li>
</ul>
<p>Adding manual keyboard event handlers here is unnecessary and can introduce bugs or inconsistent behavior.</p>
<p><strong>What this example shows:</strong></p>
<p>Avoid manually handling keyboard events for native interactive elements like <code>&lt;button&gt;</code>. These elements already provide built-in keyboard support and accessibility features.</p>
<p>For example:</p>
<pre><code class="language-html">&lt;button type="button" onClick={handleClick}&gt;Submit&lt;/button&gt;
</code></pre>
<p>Why this works:</p>
<ul>
<li><p>Supports both Enter and Space key activation by default</p>
</li>
<li><p>Is focusable and participates in natural tab order</p>
</li>
<li><p>Provides built-in accessibility roles and screen reader announcements</p>
</li>
<li><p>Reduces the need for additional logic or ARIA attributes</p>
</li>
</ul>
<p>Adding custom keyboard handlers (like onKeyDown) to native elements is unnecessary and can introduce bugs or inconsistent behavior. Always prefer native HTML elements for interactivity whenever possible.</p>
<h3 id="heading-avoiding-common-keyboard-traps">Avoiding Common Keyboard Traps</h3>
<p>One of the most common keyboard accessibility issues is “trapping users inside interactive components”, such as modals or custom dropdowns. This happens when focus is moved into a component but can't escape using Tab, Shift+Tab, or other keyboard controls. Users relying on keyboards may become stuck, unable to navigate to other parts of the page.</p>
<p>In the example below, you'll see a simple modal that tries to set focus, but doesn’t manage Tab behavior properly.</p>
<pre><code class="language-javascript">function Modal({ isOpen }) {
  const ref = React.useRef();

  React.useEffect(() =&gt; {
    if (isOpen) ref.current?.focus();
  }, [isOpen]);

  return (
    &lt;div role="dialog"&gt;
      &lt;button type="button" ref={ref}&gt;Close&lt;/button&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p>What this code shows:</p>
<ul>
<li><p>When the modal opens, focus is moved to the Close button using <code>ref.current.focus()</code></p>
</li>
<li><p>The modal uses <code>role="dialog"</code> to communicate its purpose</p>
</li>
</ul>
<p>There are some issues with this code that you should be aware of. First, tabbing inside the modal may allow focus to move outside the modal if additional focusable elements exist.</p>
<p>Users may also become trapped if no mechanism returns focus to the triggering element when the modal closes.</p>
<p>There's also no handling of Shift+Tab or cycling focus is present.</p>
<p>This demonstrates a <strong>partial focus management</strong>, but it’s not fully accessible yet.</p>
<p>To improve focus management, you can trap focus within the modal by ensuring that Tab and Shift+Tab cycle only through elements inside the modal.</p>
<p>You can also return focus to the trigger: when the modal closes, return focus to the element that opened it.</p>
<p><strong>Example improvement (conceptual):</strong></p>
<pre><code class="language-javascript">function Modal({ isOpen, onClose, triggerRef }) {
  const modalRef = React.useRef();

  React.useEffect(() =&gt; {
    if (isOpen) {
      modalref.current?.focus();
      // Add focus trap logic here
    } else {
      triggerref.current?.focus();
    }
  }, [isOpen]);

  return (
    &lt;div role="dialog" ref={modalRef} tabIndex={-1}&gt;
      &lt;button type="button" onClick={onClose}&gt;Close&lt;/button&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p>Remember that this modal is not fully accessible without focus trapping. In production, use a library like <code>focus-trap-react</code>, <code>react-aria</code>, or Radix UI.</p>
<p><strong>Key points:</strong></p>
<ul>
<li><p><code>tabIndex={-1}</code> allows the div to receive programmatic focus</p>
</li>
<li><p>Focus trap ensures users cannot tab out unintentionally</p>
</li>
<li><p>Returning focus preserves context, so users can continue where they left off</p>
</li>
</ul>
<p><strong>Best practices:</strong></p>
<ul>
<li><p>Always move focus into modals</p>
</li>
<li><p>Return focus to the trigger element when closed</p>
</li>
<li><p>Ensure Tab cycles correctly</p>
</li>
</ul>
<p>As a general rule, always prefer native HTML elements for interactivity. Only implement custom keyboard handling when building advanced components that cannot be achieved with standard elements.</p>
<h2 id="heading-focus-management">Focus Management</h2>
<p>Focus management is the practice of controlling where keyboard focus goes when users interact with components such as modals, forms, or interactive widgets. Proper focus management ensures that:</p>
<ul>
<li><p>Users relying on keyboards or assistive technologies can navigate seamlessly</p>
</li>
<li><p>Focus does not get lost or trapped in unexpected places</p>
</li>
<li><p>Users maintain context when content updates dynamically</p>
</li>
</ul>
<p>The example below shows a common approach that only partially handles focus:</p>
<p><strong>Bad Example:</strong></p>
<pre><code class="language-javascript">// Bad Example: Automatically focusing input without context
const ref = React.useRef();
React.useEffect(() =&gt; {
  ref.current?.focus();
}, []);
&lt;input ref={ref} placeholder="Name" /&gt;
</code></pre>
<p>In the above code, the input receives focus as soon as the component mounts, but there’s no handling for returning focus when the user navigates away.</p>
<p>If this input is inside a modal or dynamic content, users may get lost or trapped. There aren't any focus indicators or context for assistive technologies.</p>
<p>This is a minimal solution that can cause confusion in real applications.</p>
<p><strong>Improved Example:</strong></p>
<pre><code class="language-javascript">// Improved Example: Managing focus in a modal context
function Modal({ isOpen, onClose, triggerRef }) {  
const dialogRef = React.useRef();

  React.useEffect(() =&gt; {
    if (isOpen) {
      dialogRef.current?.focus();
    } else if (triggerRef?.current) {
      triggerref.current?.focus();
    }
  }, [isOpen]);

  React.useEffect(() =&gt; {
    function handleKeyDown(e) {
      if (e.key === 'Escape') {
        onClose();
      }
    }

    if (isOpen) {
      document.addEventListener('keydown', handleKeyDown);
    }

    return () =&gt; {
      document.removeEventListener('keydown', handleKeyDown);
    };
  }, [isOpen, onClose]);

  if (!isOpen) return null;

  return (
    &lt;div
      role="dialog"
      aria-modal="true"
      aria-labelledby="modal-title"
      tabIndex={-1}
      ref={dialogRef}
    &gt;
      &lt;h2 id="modal-title"&gt;Modal Title&lt;/h2&gt;
      &lt;button type="button" onClick={onClose}&gt;Close&lt;/button&gt;
      &lt;input type="text" placeholder="Name" /&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p><strong>Explanation:</strong></p>
<ul>
<li><p><code>tabIndex={-1}</code> enables the dialog container to receive focus</p>
</li>
<li><p>Focus is moved to the modal when it opens, ensuring keyboard users start in the correct context</p>
</li>
<li><p>Focus is returned to the trigger element when the modal closes, preserving user flow</p>
</li>
<li><p><code>aria-labelledby</code> provides an accessible name for the dialog</p>
</li>
<li><p>Escape key handling allows users to close the modal without a mouse</p>
</li>
</ul>
<p>Note: For full accessibility, you should also implement focus trapping so users cannot tab outside the modal while it is open.</p>
<p>Tip: In production applications, use libraries like react-aria, focus-trap-react, or Radix UI to handle focus trapping and accessibility edge cases reliably.</p>
<p>Also, keep in mind here that the document-level keydown listener is global, which affects the entire page and can conflict with other components.</p>
<pre><code class="language-javascript">document.addEventListener('keydown', handleKeyDown);
</code></pre>
<p>A safer alternative is to scope it to the modal:</p>
<pre><code class="language-javascript">&lt;div
  onKeyDown={(e) =&gt; {
    if (e.key === 'Escape') onClose();
  }}
&gt;
</code></pre>
<p>For simple cases, attach <code>onKeyDown</code> to the dialog instead of the document.</p>
<h4 id="heading-best-practice">Best Practice:</h4>
<p>For complex components, use libraries like <code>focus-trap-react</code> or <code>react-aria</code> to manage focus reliably, especially for modals, dropdowns, and popovers.</p>
<h2 id="heading-forms-and-accessibility">Forms and Accessibility</h2>
<p>Forms are critical points of interaction in web applications, and proper accessibility ensures that all users – including those using screen readers or other assistive technologies – can understand and interact with them effectively.</p>
<p>Proper labeling means that every input field, checkbox, radio button, or select element has an associated label that clearly describes its purpose. This allows screen readers to announce the input meaningfully and helps keyboard-only users understand what information is expected.</p>
<p>In addition to labeling, form accessibility includes:</p>
<ul>
<li><p>Providing clear error messages when input is invalid</p>
</li>
<li><p>Ensuring error messages are announced to assistive technologies</p>
</li>
<li><p>Maintaining logical focus order so users can navigate inputs easily</p>
</li>
</ul>
<p><strong>Bad Example:</strong></p>
<pre><code class="language-html">&lt;input type="text" placeholder="Name" /&gt;
</code></pre>
<p>Why this isn't good:</p>
<ul>
<li><p>This input relies only on a placeholder for context</p>
</li>
<li><p>Screen readers may not announce the purpose of the field clearly</p>
</li>
<li><p>Once a user starts typing, the placeholder disappears, leaving no guidance</p>
</li>
<li><p>Keyboard-only users may not have enough context to know what to enter</p>
</li>
</ul>
<p><strong>Good Example:</strong></p>
<pre><code class="language-html">&lt;label htmlFor="name"&gt;Name&lt;/label&gt;
&lt;input id="name" type="text" /&gt;
</code></pre>
<p>Why this is better:</p>
<ul>
<li><p>The <code>&lt;label&gt;</code> is explicitly associated with the input via <code>htmlFor / id</code></p>
</li>
<li><p>Screen readers announce "Name" before the input, providing clear context</p>
</li>
<li><p>Users navigating with Tab understand the field’s purpose</p>
</li>
<li><p>The label persists even when the user types, unlike a placeholder</p>
</li>
</ul>
<p><strong>Error Handling:</strong></p>
<pre><code class="language-html">&lt;label htmlFor="name"&gt;Name&lt;/label&gt;
&lt;input
  id="name"
  type="text"
  aria-describedby="name-error"
  aria-invalid="true"
/&gt;

&lt;p id="name-error" role="alert"&gt;
  Name is required
&lt;/p&gt;
</code></pre>
<p><strong>Explanation</strong></p>
<ul>
<li><p><code>aria-describedby</code> links the input to the error message using the element’s id</p>
</li>
<li><p>Screen readers announce the error message when the input is focused</p>
</li>
<li><p><code>aria-invalid="true"</code> indicates that the field currently contains an error</p>
</li>
<li><p><code>role="alert"</code> ensures the error message is announced immediately when it appears</p>
</li>
</ul>
<p>This creates a clear relationship between the input and its validation message, improving usability for screen reader users.</p>
<p>Tip: Only apply aria-invalid and error messages when validation fails. Avoid marking fields as invalid before user interaction.</p>
<h2 id="heading-responsive-typography-and-images">Responsive Typography and Images</h2>
<p>Responsive typography and images ensure that your content remains readable and visually appealing across a wide range of devices, from small smartphones to large desktop monitors.</p>
<p>This is important, because text should scale naturally so it remains legible on all screens, and images should adjust to container sizes to avoid layout issues or overflow. Both contribute to a better user experience and accessibility</p>
<p>In this section, we’ll cover practical ways to implement responsive typography and images in React and CSS.</p>
<pre><code class="language-css">h1 {
  font-size: clamp(1.5rem, 2vw, 3rem);
}
</code></pre>
<p>In this code:</p>
<ul>
<li><p>The <code>clamp()</code> function allows text to scale fluidly:</p>
</li>
<li><p>The first value (1.5rem) is the “minimum font size”</p>
</li>
<li><p>The second value (2vw) is the “preferred size based on viewport width”</p>
</li>
<li><p>The third value (3rem) is the “maximum font size”</p>
</li>
<li><p>This ensures headings are “readable on small screens” without becoming too large on desktops</p>
</li>
</ul>
<p>Alternative methods include using <code>media queries</code> to adjust font sizes at different breakpoints</p>
<p><strong>Responsive Images:</strong></p>
<pre><code class="language-html">&lt;img src="image.jpg" alt="Description" loading="lazy" /&gt;
</code></pre>
<p>In this code, responsive images adapt to different screen sizes and resolutions to prevent layout issues or slow loading times. Key techniques include:</p>
<h3 id="heading-1-fluid-images-using-css">1. Fluid images using CSS:</h3>
<pre><code class="language-css">img {
     max-width: 100%;
     height: auto;
   }
</code></pre>
<p>This makes sure that images never overflow their container and maintains aspect ratio automatically.</p>
<h3 id="heading-2-using-srcset-for-multiple-resolutions">2. Using <code>srcset</code> for multiple resolutions:</h3>
<pre><code class="language-html">&lt;img src="image-small.jpg"
     srcset="image-small.jpg 480w,
             image-medium.jpg 1024w,
             image-large.jpg 1920w"
     sizes="(max-width: 600px) 480px,
            (max-width: 1200px) 1024px,
            1920px"
     alt="Description"&gt;
</code></pre>
<p>This provides different image files depending on screen size or resolution and reduces loading times and improves performance on smaller devices.</p>
<h3 id="heading-3-always-include-descriptive-alt-text">3. Always include descriptive alt text</h3>
<p>This is critical for screen readers and accessibility. It also helps users understand the image if it cannot be loaded.</p>
<p>Tip: Combine responsive typography, images, and flexible layout containers (like CSS Grid or Flexbox) to create interfaces that scale gracefully across all devices and maintain accessibility.</p>
<h3 id="heading-4-ensure-sufficient-color-contrast">4. Ensure Sufficient Color Contrast</h3>
<p>Low contrast text can make content unreadable for many users.</p>
<pre><code class="language-css">.bad-text {
  color: #aaa;
}

.good-text {
  color: #222;
}
</code></pre>
<p>Use tools like WebAIM Contrast Checker and Chrome DevTools Accessibility panel to check your color contrasts. Also note that WCAG AA requires 4.5:1 contrast ratio for normal text.</p>
<h2 id="heading-building-a-fully-accessible-responsive-component-end-to-end-example">Building a Fully Accessible Responsive Component (End-to-End Example)</h2>
<p>To understand how responsiveness and accessibility work together in practice, let’s build a reusable accessible card component that adapts to screen size and supports keyboard and screen reader users.</p>
<h3 id="heading-step-1-component-structure-semantic-html">Step 1: Component Structure (Semantic HTML)</h3>
<pre><code class="language-javascript">function ProductCard({ title, description, onAction }) {
  return (
    &lt;article className="card"&gt;
      &lt;h3&gt;{title}&lt;/h3&gt;
      &lt;p&gt;{description}&lt;/p&gt;
      &lt;button type="button" onClick={onAction}&gt;
        View Details
      &lt;/button&gt;
    &lt;/article&gt;
  );
}
</code></pre>
<p><strong>Why This Works</strong></p>
<ul>
<li><p><code>&lt;article&gt;</code> provides semantic meaning for standalone content</p>
</li>
<li><p><code>&lt;h3&gt;</code> establishes a proper heading hierarchy</p>
</li>
<li><p><code>&lt;button&gt;</code> ensures built-in keyboard and accessibility support</p>
</li>
</ul>
<h3 id="heading-step-2-responsive-styling">Step 2: Responsive Styling</h3>
<pre><code class="language-css">.card {
  padding: 16px;
  border: 1px solid #ddd;
  border-radius: 8px;
}

@media (min-width: 768px) {
  .card {
    padding: 24px;
  }
}
</code></pre>
<p>This ensures comfortable spacing on mobile and improved readability on larger screens.</p>
<h3 id="heading-step-3-accessibility-enhancements">Step 3: Accessibility Enhancements</h3>
<pre><code class="language-html">&lt;button type="button" onClick={onAction}&gt;
  View Details
&lt;/button&gt;
</code></pre>
<p>The visible button text provides a clear and accessible label, so no additional ARIA attributes are needed.</p>
<h3 id="heading-step-4-keyboard-focus-styling">Step 4: Keyboard Focus Styling</h3>
<pre><code class="language-css">button:focus {
  outline: 2px solid blue;
  outline-offset: 2px;
}
</code></pre>
<p>Focus indicators are essential for keyboard users.</p>
<h3 id="heading-step-5-using-the-component">Step 5: Using the Component</h3>
<pre><code class="language-javascript">function App() {
  return (
    &lt;div className="grid"&gt;
      &lt;ProductCard
        title="Product 1"
        description="Accessible and responsive"
        onAction={() =&gt; alert('Clicked')}
      /&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p><strong>Key Takeaways</strong></p>
<p>This simple component demonstrates:</p>
<ul>
<li><p>Semantic HTML structure</p>
</li>
<li><p>Responsive design</p>
</li>
<li><p>Built-in accessibility via native elements</p>
</li>
<li><p>Minimal ARIA usage</p>
</li>
</ul>
<p>In real-world applications, this pattern scales into entire design systems.</p>
<h2 id="heading-testing-accessibility">Testing Accessibility</h2>
<p>Accessibility should be validated continuously, not just at the end of development. There are various automated tools you can use to help you with this process:</p>
<ul>
<li><p>Lighthouse (built into Chrome DevTools)</p>
</li>
<li><p>axe DevTools for detailed audits</p>
</li>
<li><p>ESLint plugins for accessibility rules</p>
</li>
</ul>
<h3 id="heading-manual-testing">Manual Testing</h3>
<p>But automated tools cannot catch everything. Manual testing is essential to make sure users can navigate using only the keyboard and use a screen reader (NVDA or VoiceOver. You should also test zoom levels (up to 200%) and check the color contrast manually.</p>
<p><strong>Example: ESLint Accessibility Plugin</strong></p>
<pre><code class="language-shell">npm install eslint-plugin-jsx-a11y --save-dev
</code></pre>
<p>This helps catch accessibility issues during development.</p>
<h2 id="heading-best-practices">Best Practices</h2>
<ul>
<li><p>Use semantic HTML first</p>
</li>
<li><p>Avoid unnecessary ARIA</p>
</li>
<li><p>Test keyboard navigation</p>
</li>
<li><p>Design mobile-first</p>
</li>
<li><p>Ensure color contrast</p>
</li>
<li><p>Use consistent spacing</p>
</li>
</ul>
<h2 id="heading-when-not-to-overuse-accessibility-features">When NOT to Overuse Accessibility Features</h2>
<ul>
<li><p>Avoid adding ARIA when native HTML works</p>
</li>
<li><p>Do not override browser defaults unnecessarily</p>
</li>
<li><p>Avoid complex custom components without accessibility support</p>
</li>
</ul>
<h2 id="heading-future-enhancements">Future Enhancements</h2>
<ul>
<li><p>Design systems with accessibility built-in</p>
</li>
<li><p>Automated accessibility testing in CI/CD</p>
</li>
<li><p>Advanced focus management libraries</p>
</li>
<li><p>Accessibility-first component libraries</p>
</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Building responsive and accessible React applications is not a one-time effort—it is a continuous design and engineering practice. Instead of treating accessibility as a checklist, developers should integrate it into the core of their component design process.</p>
<p>If you are starting out, focus on using semantic HTML and mobile-first layouts. These two practices alone solve a large percentage of accessibility and responsiveness issues. As your application grows, introduce ARIA enhancements, keyboard navigation, and automated accessibility testing.</p>
<p>The key is to build interfaces that work for everyone by default. When responsiveness and accessibility are treated as first-class concerns, your React applications become more usable, scalable, and future-proof.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build an Interactive University Ranking System Using React and Data Viz Tools ]]>
                </title>
                <description>
                    <![CDATA[ Hi! I'm Daria, and I'm a software engineering student with a keen interest in data visualization. I've been actively exploring various visualization tools through small pet projects, and I'd like to s ]]>
                </description>
                <link>https://www.freecodecamp.org/news/build-an-interactive-ranking-system-using-react-and-data-viz-tools/</link>
                <guid isPermaLink="false">69c44d7d10e664c5daefbf5c</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ #data visualisation ]]>
                    </category>
                
                    <category>
                        <![CDATA[ charts ]]>
                    </category>
                
                    <category>
                        <![CDATA[ pivottable ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Daria Filozop ]]>
                </dc:creator>
                <pubDate>Wed, 25 Mar 2026 21:02:53 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/5f31dd6f-e199-4cf3-a76a-daf985a4e7d7.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Hi! I'm Daria, and I'm a software engineering student with a keen interest in data visualization. I've been actively exploring various visualization tools through small pet projects, and I'd like to share my latest demo with you.</p>
<p>In this tutorial, we'll build a project that displays the historical rankings of universities around the world. It's interesting to check out and analyze which institutions consistently maintain their top-tier positions and which ones experience significant movement up or down the rankings over the years.</p>
<p>While building the dashboard, we'll load and structure the dataset, display all important information in a pivot table, and then create charts to visualize the top 10 universities and their ranking trends over time.</p>
<p>Even though we'll walk through this specific example here, you can apply this same approach to many other datasets to build data viz dashboards.</p>
<h2 id="heading-what-well-cover">What We’ll Cover:</h2>
<ol>
<li><p><a href="#heading-tech-stack">Tech Stack</a></p>
</li>
<li><p><a href="#heading-flexmonster-pivot-table-setup">Flexmonster Pivot Table Setup</a></p>
</li>
<li><p><a href="#heading-loading-and-displaying-the-data">Loading and Displaying the Data</a></p>
</li>
<li><p><a href="#heading-creating-charts">Creating Charts</a></p>
</li>
<li><p><a href="#heading-adding-year-filtering-buttons">Adding Year Filtering Buttons</a></p>
</li>
<li><p><a href="#heading-styling">Styling</a></p>
</li>
</ol>
<h2 id="heading-tech-stack">Tech Stack</h2>
<p>Let’s talk about the tools we’ll be using so you know what you’ll need to have before following along.</p>
<p>First, we’ll use <a href="https://react.dev/"><strong>React</strong></a>, which is a popular JavaScript library for building interactive web interfaces. It helps create reusable components and manage data efficiently. According to <a href="https://survey.stackoverflow.co/2025/technology"><strong>the 2025 Stack Overflow Developer Survey</strong></a>, React is the second most popular web framework.</p>
<p>We’ll also use <a href="https://www.flexmonster.com/"><strong>Flexmonster Pivot Table</strong></a>, a web component for displaying data in a table format. In general, pivot tables are widely used for data visualization because they allow you to quickly group, aggregate, filter, and explore large datasets from different perspectives.</p>
<p>With Flexmonster, you can easily create reports and customize your information. It also integrates smoothly with almost all popular modern frameworks (like React, which we’re using here!).</p>
<p>Next, we have <a href="https://echarts.apache.org/en/index.html"><strong>ECharts</strong></a>, which complements the detailed data provided by the pivot table. It’s a powerful, open-source charting library that’ll give us the visual insights we need, offering over 20 chart types to effectively visualize historical university ranking trends.</p>
<p>Finally, we’ll use the <a href="https://www.kaggle.com/datasets/mylesoneill/world-university-rankings"><strong>World University Rankings Dataset</strong></a>. It contains data from three global rankings (<a href="https://www.timeshighereducation.com/world-university-rankings"><strong>THE</strong></a>, <a href="https://www.shanghairanking.com/"><strong>ARWU</strong></a>, and <a href="https://cwur.org/"><strong>CWUR</strong></a>), providing information about well-known universities from 2012 to 2015 for detailed analytical research. The dataset size is 186.38 kB.</p>
<p>As a small disclaimer, this tutorial uses React, so some familiarity with it will help you follow along. But actually, you can use any other framework that's convenient for you. Flexmonster Pivot Table offers <a href="https://www.flexmonster.com/doc/available-tutorials-integration/">many integrations with popular frameworks</a>, including Angular, Vue, Svelte, and more.</p>
<p>When we’re done with the project, we’ll get an interactive dashboard like this:</p>
<img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXe49D_OM3dE-Q8bLmhsYyejXE0CF0PZTZeXhm_3vJfCDOO8JmQMCbLk8x_d_FBAWJjcsKFd0Mm3uTntW2zcp6NYGMXUVnyfjpwWI0izXInLvUhcHgtoSPoCt4EFtHpVm5WOzP-khwnxPryrgO1FjEUqWXSEWtI?key=44ff_UPj0hN24gZs8A8Dew" alt="Interactive dashboard for visualizing university ratings" width="600" height="400" loading="lazy">

<p>So now that you’re familiar with the tools, let’s get started!</p>
<h2 id="heading-flexmonster-pivot-table-setup">Flexmonster Pivot Table Setup</h2>
<p>To get started, you’ll need to integrate Flexmonster into your React project. I’ll walk you through how it works with React, but you can use other frameworks, too. You can find complete instructions in <a href="https://www.flexmonster.com/doc/available-tutorials-integration/"><strong>the Flexmonster docs</strong></a>.</p>
<p>First, create a React application using Vite:</p>
<pre><code class="language-shell">npm create vite@latest flexmonster-project -- --template react
</code></pre>
<p>Also, don’t forget to install npm dependencies:</p>
<pre><code class="language-shell">cd flexmonster-project
npm install
</code></pre>
<p>Next, we’ll install the Flexmonster wrapper for React. To do this, use this command:</p>
<pre><code class="language-shell">npm install -g flexmonster-cli
flexmonster add react-flexmonster
</code></pre>
<p>Then add Flexmonster styles and the component to your App.jsx file:</p>
<pre><code class="language-javascript">import FlexmonsterReact from "react-flexmonster";
import "flexmonster/flexmonster.css";
</code></pre>
<h2 id="heading-loading-and-displaying-the-data">Loading and Displaying the Data</h2>
<p>Now it’s time to create a report object. This is a configuration that defines how the pivot table should load and display data. It describes how the fields should be interpreted, and how the table should organize and aggregate the information.</p>
<p>The first part of this is <code>dataSource</code>. It defines where the data comes from and how it should be read by the pivot table (format of the dataset, location of the file, and the structure of the fields that will be used).</p>
<p>In our case, we'll load a CSV file that contains the university rankings dataset and define the fields that will appear in the pivot table:</p>
<pre><code class="language-javascript">&nbsp;&nbsp;const report = {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dataSource: {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;type: "csv",
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;filename: "/data/world-university-rankings.csv",
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mapping: {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;world_rank: { type: "number", caption: "World Rank" },
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;institution: { type: "string" },
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;country: { type: "string" },
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;score: { type: "number", caption: "Score" },
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;year: { type: "number", caption: "Year" },
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;},
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;},
…
}
</code></pre>
<p>The second part is the slice section. It defines which subset of the dataset will be displayed and how it should be organized. There, you'll render a table by setting measures, rows, and columns. You can also set the <code>flatOrder</code> property, where you define the order of the fields in the flat form.</p>
<p>You can find more detailed information about the <a href="https://www.flexmonster.com/api/slice-object/"><strong>Slice Object here</strong></a>. There are lots of interesting functional possibilities!</p>
<pre><code class="language-javascript">const report = {
…
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;slice: {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;rows: [{ uniqueName: "institution" }],
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;columns: [{ uniqueName: "[Measures]" }],
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;measures: [
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{ uniqueName: "world_rank", aggregation: "min" },
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;uniqueName: "year",
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;aggregation: "none",
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;filter: { members: [`year.[${selectedYear}]`] },
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;},
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;],
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;flatOrder: ["institution", "world_rank"],
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;},
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;options: {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;grid: { type: "flat", showGrandTotals: "off" },
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;},
&nbsp;&nbsp;&nbsp;&nbsp;};
</code></pre>
<p>You’ll also need to include Flexmonster inside the React component. In this example, we'll include the <code>FlexmonsterReact</code> tag in JSX and pass the report object (which we defined earlier) as a property. You can do that with this code snippet:</p>
<pre><code class="language-javascript">&lt;FlexmonsterReact
&nbsp;&nbsp;&nbsp;&nbsp;ref={pivotRef}
&nbsp;&nbsp;&nbsp;&nbsp;toolbar={true}
&nbsp;&nbsp;&nbsp;&nbsp;report={report}
&nbsp;&nbsp;&nbsp;&nbsp;width="100%"
&nbsp;&nbsp;&nbsp;&nbsp;height="100%"
&nbsp;&nbsp;&nbsp;&nbsp;reportcomplete={createChart}
/&gt;
</code></pre>
<p>As a result, we've got pivot table with all the info about the universities:</p>
<img src="https://cdn.hashnode.com/uploads/covers/690da61ea51d4259bd4a849b/ab96b9fc-c322-482c-b95a-33d17887a94e.png" alt="Pivot table with university data and rankings" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<h2 id="heading-creating-charts">Creating Charts</h2>
<p>For some users, charts are easier to understand than tables, so let’s also create some now. I decided to display the top 10 universities in the world using bar charts. Bar charts are commonly used to compare values between categories, and they're quite useful for highlighting rankings or top performers.</p>
<p>We'll use ECharts here, but you can easily integrate Flexmonster with <a href="https://www.flexmonster.com/doc/available-tutorials-charts/"><strong>the most convenient chart library for you</strong></a>.</p>
<p>As a first step, make sure to install ECharts in your project:</p>
<pre><code class="language-shell">npm install echarts
</code></pre>
<p>To prepare our data for display in the charts, we’ll create the <code>prepareData()</code> function. This function picks out the universities' names and their ranks, removes any invalid data, sorts it by rank, and keeps only the top 10. It returns two arrays: one with the names (for chart labels) and another with the rankings (for chart values):</p>
<pre><code class="language-javascript">const prepareData = (rawData) =&gt; {
&nbsp;&nbsp;&nbsp;&nbsp;const rows = rawData.data
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.map((r) =&gt; ({ name: r.r0, rank: r.v0 }))
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.filter((r) =&gt; r.name &amp;&amp; !isNaN(r.rank))
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.sort((a, b) =&gt; a.rank - b.rank)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.slice(0, 10);
&nbsp;&nbsp;&nbsp;&nbsp;return {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;labels: rows.map((r) =&gt; r.name).reverse(),
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;values: rows.map((r) =&gt; r.rank).reverse(),
&nbsp;&nbsp;&nbsp;&nbsp;};
};
</code></pre>
<p>Next, we'll set up chart options. You’ll need to decide in what form your dataset will be displayed: in this example, we'll choose a bar chart. We'll also set the title (here "Top 10 Universities by World Rank"), x-axis (shows the rank numbers) and y-axis (shows universities names), and tooltip, which shows info when you hover over a bar.</p>
<p>Also, don't forget to initialize the chart and apply these options. You can do all this with this code snippet:</p>
<pre><code class="language-javascript">const drawChart = (rawData) =&gt; {
&nbsp;&nbsp;&nbsp;&nbsp;const { labels, values } = prepareData(rawData);
&nbsp;&nbsp;&nbsp;&nbsp;const options = {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;title: {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;text: "Top 10 Universities by World Rank",
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;left: "center",
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;textStyle: { fontSize: 20, fontWeight: "bold" },
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;},
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;tooltip: { trigger: "axis", axisPointer: { type: "shadow" } },
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;xAxis: { type: "value", name: "Rank" },
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;yAxis: { type: "category", data: labels },
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;series: [{ type: "bar", data: values, barMaxWidth: 30 }],
&nbsp;&nbsp;&nbsp;&nbsp;};
&nbsp;&nbsp;&nbsp;&nbsp;chartInstance = echarts.init(chartRef.current);
&nbsp;&nbsp;&nbsp;&nbsp;chartInstance.setOption(options);
};
</code></pre>
<p>Also, an important part of chart configuration is the <code>updateCharts()</code> function, which redraws the chart when needed:</p>
<pre><code class="language-javascript">const updateChart = (rawData) =&gt; {
&nbsp;&nbsp;&nbsp;&nbsp;if (chartInstance) chartInstance.dispose();
&nbsp;&nbsp;&nbsp;&nbsp;drawChart(rawData);
};
</code></pre>
<p>So now we can see the charts we've created! This might look a bit basic and really hard to read, but don’t worry – we’ll make it look nicer and easier to understand in a later section.</p>
<img src="https://cdn.hashnode.com/uploads/covers/690da61ea51d4259bd4a849b/65cf5e06-f0dd-4d45-b1eb-e78797b44d2b.png" alt="Bar charts showing the top 10 universities by world rank" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<h2 id="heading-adding-year-filtering-buttons">Adding Year Filtering Buttons</h2>
<p>You might have noticed that the dataset contains ratings for different years (from 2012 to 2015). It would be great if we could use the whole dataset, not just information for one year.</p>
<p>To manage this, we'll create filtering buttons for each year to provide more straightforward navigation.</p>
<p>First, we’ll add a div element which contains buttons for each year:</p>
<pre><code class="language-javascript">&lt;div className="years-container"&gt;
&nbsp;&nbsp;&nbsp;&nbsp;{[2012, 2013, 2014, 2015].map((year) =&gt; (
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;button
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;key={year}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;onClick={() =&gt; handleYearChange(year)}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;className={`year-btn ${selectedYear === year ? "active" : ""}`}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&gt;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{year}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;/button&gt;
&nbsp;&nbsp;&nbsp;&nbsp;))}
&lt;/div&gt;
</code></pre>
<p>In the above code snippet, you can see that each button calls <code>handleYearChange(year)</code> when being clicked. Let’s now examine what this handler does:</p>
<pre><code class="language-javascript">const handleYearChange = (year) =&gt; {
&nbsp;&nbsp;&nbsp;&nbsp;setSelectedYear(year);

&nbsp;&nbsp;&nbsp;&nbsp;const pivot = pivotRef.current?.flexmonster;
&nbsp;&nbsp;&nbsp;&nbsp;if (pivot) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;const newReport = {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;...report,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;slice: {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;...report.slice,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;measures: [
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{ uniqueName: "world_rank", aggregation: "min" },
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;uniqueName: "year",
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;aggregation: "none",
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;filter: { members: [`year.[${year}]`] },
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;},
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;],
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;},
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;};
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pivot.setReport(newReport);
&nbsp;&nbsp;&nbsp;&nbsp;}
};&nbsp;
</code></pre>
<p>This function modifies the pivot table report to filter by that year, and refreshes the table. This way, clicking a button instantly shows only the data for the chosen year.</p>
<p>And now we've got buttons which display years from our dataset:</p>
<img src="https://cdn.hashnode.com/uploads/covers/690da61ea51d4259bd4a849b/184608a5-6207-44d8-b5fe-80fd355201c0.png" alt="Buttons to filter data by year" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<h2 id="heading-styling">Styling</h2>
<p>Finally, here’s my favorite part of every project: customization! I love experimenting with different styles and choosing the most appropriate one.</p>
<p>For this dashboard, I chose light violet and white colors to make the interface clean and easy to read. I personally associate the university vibe with these colors, so I think they match our dashboard perfectly. So let's go step by step through it.</p>
<p>The main container defines the overall layout and background. It centers the content on the page, adds some spacing, and sets the base font and colors.</p>
<pre><code class="language-javascript">.app-container {
    min-height: 100vh;
    width: 100vw;
    padding: 32px;
    display: flex;
    flex-direction: column;
    align-items: center;

    background: #ebe6f7;
    font-family: "Inter", system-ui, -apple-system, sans-serif;
    color: #1b2a4e;
}
</code></pre>
<p>Next, we'll style the year filter buttons. They have rounded corners, a soft shadow, and a small hover effect so the interface feels interactive. The active button gets a violet background so it’s easy to see which year is selected.</p>
<pre><code class="language-javascript">.year-btn {
    padding: 10px 20px;
    background: #ffffff;
    border: 1px solid #cdd2e0;
    border-radius: 8px;
    cursor: pointer;
}

.year-btn:hover {
    background: #e1dffa;
}

.year-btn.active {
    background: #6c5ce7;
    color: white;
}
</code></pre>
<p>Also, the pivot table and the chart are located inside simple containers with rounded corners and slight shadows. This visually separates the components and keeps the layout logically structured.</p>
<pre><code class="language-javascript">.pivot-container,
.chart-container {
    width: 90%;
    background: #ffffff;
    border-radius: 12px;
    padding: 14px;
    box-shadow: 0 6px 20px rgba(15, 35, 95, 0.12);
}

.pivot-container {
    height: 56vh;
    margin-bottom: 24px;
}

.chart-container {
    height: 40vh;
}
</code></pre>
<p>And now, here's the result of our work!</p>
<img src="https://cdn.hashnode.com/uploads/covers/690da61ea51d4259bd4a849b/ed35dbdd-9c5d-4f7e-b6e1-4f0546a0d660.png" alt="Universities ranking pivot table with interactive year filters" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<img src="https://cdn.hashnode.com/uploads/covers/690da61ea51d4259bd4a849b/1a0b0f1c-9c26-40e2-8c28-bd45517ea145.png" alt="Charts showing the top 10 universities by world rank" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<h2 id="heading-wrapping-up">Wrapping Up</h2>
<p>In this tutorial, we've build an interactive dashboard for visualizing the world's top university rankings over the years. You learned how to load and show data in pivot table in a convenient and compact way, make bar charts to show comparative data, and add buttons to filter info by year.</p>
<p>You can now use these skills to visualize other datasets and play with different charts and customization options.</p>
<p>If you want to look closer at my code and get detailed styling code, you can check out my GitHub: <a href="https://github.com/filozopdasha/universities-dashboard"><strong>https://github.com/filozopdasha/universities-dashboard</strong></a></p>
<p>I would be delighted to hear your thoughts about this small project. I’m curious to see what you build!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Use OpenStreetMap as a Free Alternative to Google Maps ]]>
                </title>
                <description>
                    <![CDATA[ Google Maps has been the default choice for developers building location-based applications for years. But for many teams, especially those operating at scale, pricing has become a real concern. Googl ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-use-openstreetmap-free-alternative-to-google-maps/</link>
                <guid isPermaLink="false">69c41cdf10e664c5dacd6389</guid>
                
                    <category>
                        <![CDATA[ #LocationServices  ]]>
                    </category>
                
                    <category>
                        <![CDATA[ maps ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Aiyedogbon Abraham ]]>
                </dc:creator>
                <pubDate>Wed, 25 Mar 2026 17:35:27 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/0ab3655a-4212-451d-93e1-5c707ed1b07e.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Google Maps has been the default choice for developers building location-based applications for years. But for many teams, especially those operating at scale, pricing has become a real concern.</p>
<p>Google Maps provides a $200 monthly credit, but beyond that, usage is billed per request. For applications like logistics, ride-hailing, or fleet tracking – where thousands of requests are made daily – costs can grow quickly depending on which APIs you use.</p>
<p>OpenStreetMap (OSM) offers a different approach. Instead of charging for access to map APIs, it provides free, open geographic data that you can build on.</p>
<p>In this guide, you'll learn what OpenStreetMap is, how it differs from Google Maps, and how to integrate it into a React application using Leaflet.</p>
<h2 id="heading-what-well-cover">What We'll Cover:</h2>
<ol>
<li><p><a href="#heading-what-is-openstreetmap">What is OpenStreetMap?</a></p>
</li>
<li><p><a href="#heading-why-choose-openstreetmap-over-google-maps">Why Choose OpenStreetMap Over Google Maps?</a></p>
</li>
<li><p><a href="#heading-understanding-the-open-street-map-ecosystem">Understanding the OpenStreetMap Ecosystem</a></p>
<ul>
<li><p><a href="#heading-data-layer-openstreetmap">Data Layer (OpenStreetMap)</a></p>
</li>
<li><p><a href="#heading-rendering-layer-leaflet-maplibre">Rendering Layer (Leaflet, MapLibre)</a></p>
</li>
<li><p><a href="#heading-services-layer">Services Layer</a></p>
</li>
<li><p><a href="#heading-how-everything-works-together">How Everything Works Together</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-how-to-integrate-openstreetmap-in-react-with-leaflet">How to Integrate OpenStreetMap in React with Leaflet</a></p>
</li>
<li><p><a href="#heading-how-to-add-geocoding-with-nominatim">How to Add Geocoding with Nominatim</a></p>
</li>
<li><p><a href="#heading-advanced-features">Advanced Features</a></p>
</li>
<li><p><a href="#heading-when-to-choose-openstreetmap-vs-google-maps">When to Choose OpenStreetMap vs Google Maps</a></p>
</li>
<li><p><a href="#heading-wrapping-up">Wrapping Up</a></p>
</li>
</ol>
<h2 id="heading-what-is-openstreetmap">What is OpenStreetMap?</h2>
<p>OpenStreetMap is a free, open, and community-driven map of the world. Anyone can contribute to it, and anyone can use it.</p>
<p>Unlike Google Maps, which gives access through controlled APIs, OpenStreetMap gives you access to the underlying geographic data itself.</p>
<p>This data is structured in three main ways:</p>
<ol>
<li><p><strong>Nodes</strong>: single points (for example, a bus stop or a tree)</p>
</li>
<li><p><strong>Ways</strong>: lines or shapes made up of nodes (like roads or buildings)</p>
</li>
<li><p><strong>Relations</strong>: groups of nodes and ways that define more complex things (like routes or boundaries)</p>
</li>
</ol>
<p>Each of these elements includes tags (key-value pairs), such as:</p>
<pre><code class="language-plaintext">highway=residential
name=Allen Avenue
</code></pre>
<p>So instead of just displaying a map, OpenStreetMap lets you work with structured geographic data.</p>
<h3 id="heading-the-open-database-license-odbl">The Open Database License (ODbL)</h3>
<p>OpenStreetMap data is licensed under the ODbL. This means:</p>
<ul>
<li><p>You can use it for commercial or personal projects</p>
</li>
<li><p>You must give proper attribution</p>
</li>
</ul>
<p>This makes it especially useful for developers who want clarity around data ownership.</p>
<h2 id="heading-why-choose-openstreetmap-over-google-maps">Why Choose OpenStreetMap Over Google Maps?</h2>
<h3 id="heading-cost">Cost</h3>
<p>OpenStreetMap data is free to use. But it's important to be precise here: <strong>OpenStreetMap removes licensing costs, but not infrastructure costs.</strong></p>
<p>You may still need to pay for:</p>
<ul>
<li><p>Tile hosting</p>
</li>
<li><p>Geocoding services</p>
</li>
<li><p>Routing engines</p>
</li>
</ul>
<h3 id="heading-control">Control</h3>
<p>With Google Maps, you can't modify the data, and you rely entirely on Google's APIs</p>
<p>But with OpenStreetMap, you can download and store the data, modify it, and build custom solutions on top of it.</p>
<h3 id="heading-customization">Customization</h3>
<p>OpenStreetMap gives you more flexibility:</p>
<ul>
<li><p>You control how maps are rendered</p>
</li>
<li><p>You can choose or build your own map styles</p>
</li>
<li><p>You can create domain-specific maps</p>
</li>
</ul>
<h3 id="heading-adoption">Adoption</h3>
<p>OpenStreetMap is widely used. Companies like Meta and Microsoft contribute to it, and many platforms rely on it directly or indirectly.</p>
<p>This shows that the ecosystem is mature and reliable.</p>
<h2 id="heading-understanding-the-openstreetmap-ecosystem">Understanding the OpenStreetMap Ecosystem</h2>
<p>A common mistake is to think that OpenStreetMap works like a single API. It doesn't.</p>
<p>Instead, it works as a set of layers, where each layer handles a different responsibility.</p>
<h3 id="heading-data-layer-openstreetmap">Data Layer (OpenStreetMap)</h3>
<p>This is the foundation. It contains all the raw geographic data:</p>
<ul>
<li><p>Roads</p>
</li>
<li><p>Buildings</p>
</li>
<li><p>Landmarks</p>
</li>
<li><p>Boundaries</p>
</li>
</ul>
<p>This is what you are ultimately working with.</p>
<h3 id="heading-rendering-layer-leaflet-maplibre">Rendering Layer (Leaflet, MapLibre)</h3>
<p>Raw data isn't visual. It needs to be turned into something users can see.</p>
<p>There are two main approaches:</p>
<ol>
<li><p><strong>Raster tiles</strong> (used by Leaflet): pre-rendered images</p>
</li>
<li><p><strong>Vector tiles</strong> (used by MapLibre): raw geometry styled in the browser</p>
</li>
</ol>
<p>Leaflet uses raster tiles by default, which makes it simple and fast to start with.</p>
<h3 id="heading-services-layer">Services Layer</h3>
<p>This is what makes your map interactive. <strong>Geocoding</strong> converts addresses into coordinates, while <strong>reverse geocoding</strong> converts coordinates into addresses.</p>
<p><strong>Routing</strong> calculates directions between points, and <strong>tile servers</strong> provide the actual map visuals.</p>
<h3 id="heading-how-everything-works-together">How Everything Works Together</h3>
<p>When a user searches for a place:</p>
<ol>
<li><p>The user enters a location</p>
</li>
<li><p>A geocoding service converts it into coordinates</p>
</li>
<li><p>The map updates its position</p>
</li>
<li><p>A tile server provides the visual map</p>
</li>
</ol>
<p>Each part is separate, but they work together to create the full experience.</p>
<h2 id="heading-how-to-integrate-openstreetmap-in-react-with-leaflet">How to Integrate OpenStreetMap in React with Leaflet</h2>
<p>Let's build a simple map.</p>
<h3 id="heading-step-1-create-a-react-app">Step 1: Create a React App</h3>
<pre><code class="language-bash">npm create vite@latest osm-app -- --template react
cd osm-app
npm install
</code></pre>
<h3 id="heading-step-2-install-dependencies">Step 2: Install Dependencies</h3>
<pre><code class="language-bash">npm install leaflet react-leaflet
npm install --save-dev @types/leaflet
</code></pre>
<h3 id="heading-step-3-import-leaflet-css">Step 3: Import Leaflet CSS</h3>
<pre><code class="language-javascript">import 'leaflet/dist/leaflet.css';
</code></pre>
<p>This is required for the map to display correctly.</p>
<h3 id="heading-step-4-create-a-map-component">Step 4: Create a Map Component</h3>
<pre><code class="language-javascript">import { MapContainer, TileLayer, Marker, Popup } from 'react-leaflet';

function Map() {
  const position = [51.505, -0.09]; // latitude, longitude

  return (
    &lt;MapContainer
      center={position}
      zoom={13}
      style={{ height: '100vh' }}
    &gt;
      &lt;TileLayer
        attribution='&amp;copy; &lt;a href="https://www.openstreetmap.org/copyright"&gt;OpenStreetMap&lt;/a&gt; contributors'
        url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
      /&gt;
      &lt;Marker position={position}&gt;
        &lt;Popup&gt;Hello from OpenStreetMap&lt;/Popup&gt;
      &lt;/Marker&gt;
    &lt;/MapContainer&gt;
  );
}

export default Map;
</code></pre>
<p>Let's break down the important parts here:</p>
<p><code>MapContainer</code> initializes the map.</p>
<ul>
<li><p><code>center</code> is where the map starts</p>
</li>
<li><p><code>zoom</code> is how close the view is</p>
</li>
<li><p><code>style</code> must include height, or the map won't show</p>
</li>
</ul>
<p><code>TileLayer</code> defines where the map visuals come from.</p>
<ul>
<li><p><code>{z}</code> is the zoom level</p>
</li>
<li><p><code>{x}</code>, <code>{y}</code> are the tile coordinates</p>
</li>
<li><p><code>{s}</code> is the subdomain</p>
</li>
</ul>
<p>Each tile is a small image (usually 256×256 pixels), and Leaflet combines them to form the full map.</p>
<p><code>Marker</code> adds a point on the map at a specific coordinate.</p>
<p><code>Popup</code> displays information when the marker is clicked.</p>
<h4 id="heading-important-note">Important note:</h4>
<p>The default OpenStreetMap tile server:</p>
<pre><code class="language-plaintext">https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png
</code></pre>
<p>is meant for learning, demos, and low-traffic apps. For production, you should use a dedicated provider or your own tile server.</p>
<h2 id="heading-how-to-add-geocoding-with-nominatim">How to Add Geocoding with Nominatim</h2>
<p>Nominatim is OpenStreetMap's geocoding service. It allows you to convert addresses into coordinates and coordinates into readable locations.</p>
<h3 id="heading-custom-hook-for-geocoding">Custom Hook for Geocoding</h3>
<pre><code class="language-javascript">import { useState } from 'react';

export function useGeocoding() {
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  const searchAddress = async (query) =&gt; {
    setLoading(true);
    setError(null);

    try {
      const response = await fetch(
        `https://nominatim.openstreetmap.org/search?q=${encodeURIComponent(query)}&amp;format=json&amp;limit=5`,
        {
          headers: {
            'User-Agent': 'YourAppName/1.0'
          }
        }
      );

      if (!response.ok) {
        throw new Error('Request failed');
      }

      const data = await response.json();
      setLoading(false);
      return data;
    } catch (err) {
      setError(err.message);
      setLoading(false);
      return [];
    }
  };

  return { searchAddress, loading, error };
}
</code></pre>
<p>In this code:</p>
<ul>
<li><p><code>useState</code> manages loading and error states</p>
</li>
<li><p><code>encodeURIComponent</code> ensures safe URLs</p>
</li>
<li><p><code>User-Agent</code> is required by Nominatim</p>
</li>
<li><p><code>response.json()</code> converts response into usable data</p>
</li>
</ul>
<p>Nominatim returns coordinates as strings, so you have to convert them before using them.</p>
<h3 id="heading-important-usage-rules">Important Usage Rules</h3>
<p>The public Nominatim service:</p>
<ul>
<li><p>Allows about 1 request per second</p>
</li>
<li><p>Requires proper identification</p>
</li>
<li><p>May block excessive usage</p>
</li>
</ul>
<p>You should debounce user input, cache results, and avoid repeated requests.</p>
<h3 id="heading-creating-a-search-component">Creating a Search Component</h3>
<p>The search component lets users type an address or place name and get matching locations via Nominatim. It includes a text input and a submit button.</p>
<p>When the form is submitted, it calls our <code>searchAddress</code> function (from the <code>useGeocoding</code> hook), which fetches up to 5 address results. These results are displayed below the input as clickable items.</p>
<p>When the user clicks a result, the component parses the returned latitude and longitude into numbers and passes them (along with a display name) up to the parent component via the <code>onLocationSelect</code> callback. This will allow the parent (for example, the map) to update its center based on the chosen location.</p>
<pre><code class="language-javascript">function SearchBox({ onLocationSelect }) {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const { searchAddress, loading } = useGeocoding();

  const handleSearch = async (e) =&gt; {
    e.preventDefault();
    if (!query.trim()) return;

    const data = await searchAddress(query);
    setResults(data);
  };

  const selectLocation = (result) =&gt; {
    onLocationSelect({
      lat: parseFloat(result.lat),
      lon: parseFloat(result.lon),
      name: result.display_name
    });
  };

  return (
    &lt;div&gt;
      &lt;form onSubmit={handleSearch}&gt;
        &lt;input
          value={query}
          onChange={(e) =&gt; setQuery(e.target.value)}
          placeholder="Search location"
        /&gt;
        &lt;button type="submit"&gt;
          {loading ? 'Searching...' : 'Search'}
        &lt;/button&gt;
      &lt;/form&gt;

      &lt;div&gt;
        {results.map((result) =&gt; (
          &lt;div key={result.place_id} onClick={() =&gt; selectLocation(result)}&gt;
            {result.display_name}
          &lt;/div&gt;
        ))}
      &lt;/div&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p>Key concepts here:</p>
<ul>
<li><p><code>useState</code> stores the current input (<code>query</code>) and the array of search <code>results</code>.</p>
</li>
<li><p><code>e.preventDefault()</code> stops the form submission from reloading the page.</p>
</li>
<li><p>Calling <code>searchAddress(query)</code> fetches geocoding results from Nominatim.</p>
</li>
<li><p><code>parseFloat()</code> converts the returned <code>lat</code>/<code>lon</code> strings into JavaScript numbers before using them.</p>
</li>
<li><p><code>onLocationSelect</code> is a callback prop that sends the selected coordinates and name back to the parent component (for example to update the map).</p>
</li>
</ul>
<h2 id="heading-advanced-features">Advanced Features</h2>
<p>We can further extend the map app by adding more advanced functionality. For example:</p>
<h3 id="heading-routing-osrm-graphhopper">Routing (OSRM, GraphHopper)</h3>
<p>You can integrate turn-by-turn routing on your map. A common solution is to use a library like <a href="https://www.liedman.net/leaflet-routing-machine/">Leaflet Routing Machine</a>, which supports OSRM out of the box and has plugins for GraphHopper. This adds a route UI control where users enter start and end points, and the library fetches a route from one of these engines to draw on the map.</p>
<h3 id="heading-custom-tile-providers-carto-maptiler-and-so-on"><strong>Custom Tile Providers (Carto, MapTiler, and so on)</strong></h3>
<p>Instead of the standard <a href="http://tile.openstreetmap.org"><code>tile.openstreetmap.org</code></a>, you can use hosted tile services that offer OSM-based maps. For example, Carto and MapTiler both provide tile APIs (often with custom style options and higher usage limits).</p>
<p>Carto, MapTiler, and similar services are listed among the providers that allow free usage of OSM tiles. By using a custom tile provider, you gain flexibility in map design and avoid hitting the public server’s limits.</p>
<h3 id="heading-vector-maps-maplibre-gl-js">Vector Maps (MapLibre GL JS)</h3>
<p>You can switch from raster tiles to vector tiles for even richer interactivity. Vector tiles send raw map data (geometries and attributes) to the client, which are then rendered in the browser. This allows dynamic styling and advanced features: for instance, you can change the map’s theme on the fly (for example, switch to a “dark mode” style at night) or highlight certain features like bike lanes more prominently.</p>
<p>Libraries like MapLibre GL JS (the open-source successor to Mapbox GL) can display OSM vector tiles with highly customizable styles and smooth zooming/rotation. This makes your map more responsive and adaptable to different use cases.</p>
<h2 id="heading-when-to-choose-openstreetmap-vs-google-maps">When to Choose OpenStreetMap vs Google Maps</h2>
<h3 id="heading-choose-openstreetmap-when">Choose OpenStreetMap when:</h3>
<ul>
<li><p>You need flexibility</p>
</li>
<li><p>You want to reduce costs at scale</p>
</li>
<li><p>You want control over data</p>
</li>
</ul>
<h3 id="heading-choose-google-maps-when">Choose Google Maps when:</h3>
<ul>
<li><p>You want an all-in-one solution</p>
</li>
<li><p>You need features like Street View</p>
</li>
<li><p>You want minimal setup</p>
</li>
</ul>
<h2 id="heading-wrapping-up">Wrapping Up</h2>
<p>OpenStreetMap offers a powerful alternative to Google Maps for developers who need cost control, data ownership, and customization. While it requires understanding different components, the flexibility it provides is worth the learning curve.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Full-Stack CRUD App with React, AWS Lambda, DynamoDB, and Cognito Auth ]]>
                </title>
                <description>
                    <![CDATA[ Building a web application that works only on your local machine is one thing. Building one that is secure, connected to a real database, and accessible to anyone on the internet is another challenge  ]]>
                </description>
                <link>https://www.freecodecamp.org/news/full-stack-aws-react-lambda-dynamodb-tutorial/</link>
                <guid isPermaLink="false">69b96f7ec22d3eeb8ac3bf81</guid>
                
                    <category>
                        <![CDATA[ serverless ]]>
                    </category>
                
                    <category>
                        <![CDATA[ AWS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ full stack ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Cloud Computing ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Benedicta Onyebuchi ]]>
                </dc:creator>
                <pubDate>Tue, 17 Mar 2026 15:13:02 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/1a996eff-72f5-4f4d-b8da-cf4d646c3224.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Building a web application that works only on your local machine is one thing. Building one that is secure, connected to a real database, and accessible to anyone on the internet is another challenge entirely. And it requires a different set of tools.</p>
<p>Most production web applications share a common set of needs: they store and retrieve data, they expose that data through an API, they require users to authenticate before accessing sensitive operations, and they need to be deployed somewhere reliable and fast.</p>
<p>Meeting all of those needs used to require managing servers, configuring databases, handling authentication infrastructure, and provisioning hosting environments – often as separate, manual processes.</p>
<p>AWS changes that model significantly. With the combination of services you'll use in this tutorial (Lambda, DynamoDB, API Gateway, Cognito, and CloudFront), you can build and deploy a fully functional, secured, globally distributed application without managing a single server.</p>
<p>Each service handles one specific responsibility:</p>
<ul>
<li><p>DynamoDB stores your data</p>
</li>
<li><p>Lambda runs your business logic on demand</p>
</li>
<li><p>API Gateway exposes your functions as a REST API</p>
</li>
<li><p>Cognito manages user authentication</p>
</li>
<li><p>CloudFront delivers your frontend worldwide over HTTPS.</p>
</li>
</ul>
<p>The AWS CDK (Cloud Development Kit) ties all of this together by letting you define every one of those services as TypeScript code. Instead of clicking through the AWS Console to configure each resource manually, you describe your entire infrastructure in a single file and deploy it with one command.</p>
<p>By the end of this tutorial, you will have a fully deployed vendor management dashboard. Users can sign up, log in, and then create, read, and delete vendors, with all data securely stored in AWS DynamoDB and all routes protected by Amazon Cognito authentication.</p>
<h2 id="heading-what-youll-build">What You'll Build</h2>
<p>In this handbook, you'll build a two-panel web app where authenticated users can:</p>
<ul>
<li><p>Add a new vendor (name, category, contact email)</p>
</li>
<li><p>View all saved vendors in real time</p>
</li>
<li><p>Delete a vendor from the list</p>
</li>
<li><p>Sign in and sign out securely</p>
</li>
</ul>
<p>The frontend is built with Next.js. The backend runs entirely on AWS: DynamoDB stores the data, Lambda functions handle the logic, API Gateway exposes a REST API, Cognito manages authentication, and CloudFront serves the app globally over HTTPS.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-who-this-is-for">Who This Is For</a></p>
</li>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-architecture-overview">Architecture Overview</a></p>
</li>
<li><p><a href="#heading-part-1-set-up-your-aws-account-and-tools">Part 1: Set Up Your AWS Account and Tools</a></p>
</li>
<li><p><a href="#heading-part-2-set-up-the-project-structure">Part 2: Set Up the Project Structure</a></p>
</li>
<li><p><a href="#heading-part-3-define-the-database-dynamodb">Part 3: Define the Database (DynamoDB)</a></p>
</li>
<li><p><a href="#heading-part-4-write-the-lambda-functions">Part 4: Write the Lambda Functions</a></p>
</li>
<li><p><a href="#heading-part-5-build-the-api-with-api-gateway">Part 5: Build the API with API Gateway</a></p>
</li>
<li><p><a href="#heading-part-6-deploy-the-backend-to-aws">Part 6: Deploy the Backend to AWS</a></p>
</li>
<li><p><a href="#heading-part-7-build-the-react-frontend">Part 7: Build the React Frontend</a></p>
</li>
<li><p><a href="#heading-part-8-add-authentication-with-amazon-cognito">Part 8: Add Authentication with Amazon Cognito</a></p>
</li>
<li><p><a href="#heading-part-9-deploy-the-frontend-with-s3-and-cloudfront">Part 9: Deploy the Frontend with S3 and CloudFront</a></p>
</li>
<li><p><a href="#heading-what-you-built">What You Built</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-who-this-is-for">Who This Is For</h2>
<p>This tutorial is for developers who know basic JavaScript and React but have never used AWS. You don't need any prior backend, cloud, or DevOps experience. I'll explain every AWS concept before we use it.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before starting, make sure you have the following installed and available:</p>
<ul>
<li><p><strong>Node.js 18 or higher</strong>: <a href="https://nodejs.org">Download here</a></p>
</li>
<li><p><strong>npm</strong>: Included with Node.js</p>
</li>
<li><p><strong>A code editor</strong>: I recommend VS Code</p>
</li>
<li><p><strong>A terminal</strong>: Any terminal on macOS, Linux, or Windows (WSL recommended on Windows)</p>
</li>
<li><p><strong>An AWS account</strong>: You will create one in Part 1. A credit card is required, but the Free Tier covers everything in this tutorial.</p>
</li>
<li><p><strong>Basic familiarity with React and TypeScript</strong>: You should understand components, <code>useState</code>, and <code>useEffect</code>.</p>
</li>
</ul>
<h2 id="heading-architecture-overview">Architecture Overview</h2>
<p>Before writing any code, here's a plain-English description of how the pieces fit together.</p>
<p>When a user clicks "Add Vendor" in the React app:</p>
<ol>
<li><p>The frontend reads the user's JWT auth token from the browser session</p>
</li>
<li><p>It sends a <code>POST</code> request to API Gateway, including the token in the request header</p>
</li>
<li><p>API Gateway checks the token against Cognito. If the token is invalid or missing, it rejects the request with a 401 error immediately</p>
</li>
<li><p>If the token is valid, API Gateway passes the request to the createVendor Lambda function</p>
</li>
<li><p>The Lambda function writes the new vendor to DynamoDB</p>
</li>
<li><p>DynamoDB confirms the write, and the Lambda returns a success response</p>
</li>
<li><p>The frontend re-fetches the vendor list and updates the UI</p>
</li>
</ol>
<p>The same flow applies to reading and deleting vendors, with different Lambda functions and HTTP methods.</p>
<img src="https://cdn.hashnode.com/uploads/covers/62d53ab5bc2c7a1dc672b04f/70486bdc-f272-45db-be30-f10752916546.png" alt="Architecture diagram of the Vendors Tracker Application" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p><strong>How the app is deployed:</strong> Your React app is exported as a static site, uploaded to an S3 bucket, and served globally through CloudFront. Your backend infrastructure (Lambda functions, API Gateway, DynamoDB, Cognito) is defined in TypeScript using AWS CDK and deployed with a single command.</p>
<h2 id="heading-part-1-set-up-your-aws-account-and-tools">Part 1: Set Up Your AWS Account and Tools</h2>
<p>Before writing any application code, you need three things in place: an AWS account, the right tools on your machine, and credentials that let those tools communicate with AWS on your behalf.</p>
<h3 id="heading-11-create-your-aws-account">1.1 Create Your AWS Account</h3>
<p>If you don't have an AWS account:</p>
<ol>
<li><p>Go to <a href="https://aws.amazon.com">https://aws.amazon.com</a></p>
</li>
<li><p>Click <strong>Create an AWS Account</strong></p>
</li>
<li><p>Follow the sign-up prompts and add a payment method</p>
</li>
<li><p>Once registered, log in to the AWS Management Console</p>
</li>
</ol>
<p>AWS has a Free Tier that covers all the services used in this tutorial. You won't be charged for normal use while following along.</p>
<h3 id="heading-12-install-the-aws-cli-and-cdk">1.2 Install the AWS CLI and CDK</h3>
<p>The <strong>AWS CLI</strong> is a command-line tool that lets you interact with AWS from your terminal: checking resources, configuring credentials, and more.</p>
<p>The <strong>AWS CDK (Cloud Development Kit)</strong> is the tool you will use to define your entire backend (database, Lambda functions, API) using TypeScript code. Instead of clicking through the AWS Console to create each resource, you describe what you want in a TypeScript file and CDK builds it for you.</p>
<p>Install both:</p>
<pre><code class="language-shell"># Install AWS CLI (macOS)
curl "https://awscli.amazonaws.com/AWSCLIV2.pkg" -o "AWSCLIV2.pkg"
sudo installer -pkg AWSCLIV2.pkg -target /

# For Linux, see: https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2-linux.html
# For Windows, see: https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2-windows.html

# Install AWS CDK globally
npm install -g aws-cdk
</code></pre>
<p>Verify both are installed:</p>
<pre><code class="language-shell">aws --version
cdk --version
</code></pre>
<p>Both commands should print a version number. If they do, you are ready to move on.</p>
<h3 id="heading-13-configure-your-aws-credentials-iam">1.3 Configure Your AWS Credentials (IAM)</h3>
<p>This step is critical. Your terminal needs a set of credentials – like a username and password – to act on your behalf inside AWS.</p>
<p>Think of your root account (the one you signed up with) as the master key to your entire AWS account. You should never use it for day-to-day development. Instead, you will create a separate IAM user with its own set of keys. If those keys are ever exposed, you can delete them without compromising your root account.</p>
<h4 id="heading-phase-1-create-an-iam-user">Phase 1: Create an IAM User</h4>
<ol>
<li><p>Log in to the AWS Console and search for IAM in the top search bar</p>
</li>
<li><p>In the left sidebar, click Users, then click Create user</p>
</li>
<li><p>Name the user <code>cdk-dev</code>. Leave "Provide user access to the AWS Management Console" unchecked – you only need terminal access, not console access</p>
</li>
<li><p>On the permissions screen, choose Attach policies directly</p>
</li>
</ol>
<img src="https://cdn.hashnode.com/uploads/covers/62d53ab5bc2c7a1dc672b04f/d4699108-c1aa-4dd3-957c-b84292c719a2.png" alt="IAM Console showing the “Attach policies directly” screen with AdministratorAccess checked" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<ol>
<li>Search for <code>AdministratorAccess</code> and check the box next to it</li>
</ol>
<p>Note on permissions: In a production job you would use a more restricted policy. For this tutorial, Administrator access is needed because CDK creates many different types of AWS resources.</p>
<p>6. Click through to the end and click Create user</p>
<h4 id="heading-phase-2-generate-access-keys">Phase 2: Generate Access Keys</h4>
<ol>
<li><p>Click on your newly created <code>cdk-dev</code> user from the Users list</p>
</li>
<li><p>Go to the Security credentials tab</p>
</li>
<li><p>Scroll down to Access keys and click Create access key</p>
</li>
<li><p>Select Command Line Interface (CLI), check the acknowledgment box, and click Next</p>
</li>
<li><p>Click Create access key</p>
</li>
</ol>
<p><strong>Important</strong>: Copy both the Access Key ID and the Secret Access Key right now. You will never be able to see the Secret Access Key again after closing this screen. Save both values in a password manager or secure note.</p>
<img src="https://cdn.hashnode.com/uploads/covers/62d53ab5bc2c7a1dc672b04f/d85bb4eb-0ecf-4d92-be92-d75af5a534c6.png" alt="IAM Console showing the Create access key screen with the Access Key ID and Secret Access Key" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<h4 id="heading-phase-3-connect-your-terminal-to-aws">Phase 3: Connect Your Terminal to AWS</h4>
<p>Run the following command in your terminal:</p>
<pre><code class="language-shell">aws configure
</code></pre>
<p>You will be prompted for four values:</p>
<pre><code class="language-shell">AWS Access Key ID:     [paste your Access Key ID]
AWS Secret Access Key: [paste your Secret Access Key]
Default region name:   us-east-1
Default output format: json
</code></pre>
<p>Use <code>us-east-1</code> as your region for this tutorial. After this step, every CDK and AWS CLI command you run will use these credentials automatically.</p>
<h2 id="heading-part-2-set-up-the-project-structure">Part 2: Set Up the Project Structure</h2>
<p>You will use a <strong>monorepo</strong> layout – one top-level folder with two sub-projects inside: <code>frontend</code> for your React app and <code>backend</code> for your AWS infrastructure code. They are deployed independently but live side by side.</p>
<h3 id="heading-21-create-the-workspace">2.1 Create the Workspace</h3>
<pre><code class="language-shell">mkdir vendor-tracker &amp;&amp; cd vendor-tracker
mkdir backend frontend
</code></pre>
<h3 id="heading-22-initialize-the-frontend-nextjs">2.2 Initialize the Frontend (Next.js)</h3>
<p>Navigate into the <code>frontend</code> folder and run:</p>
<pre><code class="language-shell">cd frontend
npx create-next-app@latest .
</code></pre>
<p>When prompted, choose the following options:</p>
<ul>
<li><p><strong>TypeScript</strong> --&gt; Yes</p>
</li>
<li><p><strong>ESLint</strong> --&gt; Yes</p>
</li>
<li><p><strong>Tailwind CSS</strong> --&gt; Yes</p>
</li>
<li><p><strong>src/ directory</strong> --&gt;No</p>
</li>
<li><p><strong>App Router</strong> --&gt; Yes</p>
</li>
<li><p><strong>Import alias</strong> --&gt; No</p>
</li>
</ul>
<h3 id="heading-23-initialize-the-backend-cdk">2.3 Initialize the Backend (CDK)</h3>
<p>Navigate into the <code>backend</code> folder and run:</p>
<pre><code class="language-shell">cd ../backend
cdk init app --language typescript
</code></pre>
<p>This generates a boilerplate CDK project. The most important file it creates is <code>backend/lib/backend-stack.ts</code>. This is where you will define all of your AWS infrastructure as TypeScript code.</p>
<p>Also install <code>esbuild</code>, which CDK uses to bundle your Lambda functions:</p>
<pre><code class="language-shell">npm install --save-dev esbuild
</code></pre>
<h3 id="heading-24-understanding-cdk-before-you-write-any-code">2.4 Understanding CDK Before You Write Any Code</h3>
<p>CDK is likely different from most tools you have used. Here is how it works:</p>
<p>Normally, you would create AWS resources by clicking through the AWS Console: create a table here, configure a Lambda function there. CDK lets you do all of that using TypeScript code instead.</p>
<p>When you run <code>cdk deploy</code>, CDK reads your TypeScript file, converts it into an AWS CloudFormation template (an internal AWS format for describing infrastructure), and submits it to AWS. AWS then creates all the resources you described.</p>
<p>A few terms you will see throughout this tutorial:</p>
<ul>
<li><p><strong>Stack</strong>: The collection of all AWS resources you define together. Your <code>BackendStack</code> class is your stack.</p>
</li>
<li><p><strong>Construct</strong>: Each individual AWS resource you create inside a stack (a table, a Lambda function, an API) is called a construct.</p>
</li>
<li><p><strong>Deploy</strong>: Running <code>cdk deploy</code> sends your TypeScript definition to AWS and creates or updates the real resources.</p>
</li>
</ul>
<p>The main file you'll work in is <code>backend/lib/backend-stack.ts</code>. Think of it as the blueprint for your entire backend.</p>
<p>Your final project structure will look like this:</p>
<pre><code class="language-plaintext">vendor-tracker/
├── backend/
│   ├── lambda/
│   │   ├── createVendor.ts
│   │   ├── getVendors.ts
│   │   └── deleteVendor.ts
│   ├── lib/
│   │   └── backend-stack.ts
│   └── package.json
└── frontend/
    ├── app/
    │   ├── layout.tsx
    │   ├── page.tsx
    │   └── providers.tsx
    ├── lib/
    │   └── api.ts
    ├── types/
    │   └── vendor.ts
    └── .env.local
</code></pre>
<h2 id="heading-part-3-define-the-database-dynamodb">Part 3: Define the Database (DynamoDB)</h2>
<p>DynamoDB is AWS's NoSQL database. Think of it as a fast, scalable key-value store in the cloud. Every item in a DynamoDB table must have a unique ID called the <strong>partition key</strong>. For your vendor table, that key will be <code>vendorId</code>.</p>
<p>Open <code>backend/lib/backend-stack.ts</code>. Replace the entire file contents with the following:</p>
<pre><code class="language-typescript">import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';

export class BackendStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // 1. DynamoDB Table
    const vendorTable = new dynamodb.Table(this, 'VendorTable', {
      partitionKey: {
        name: 'vendorId',
        type: dynamodb.AttributeType.STRING,
      },
      billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
      removalPolicy: cdk.RemovalPolicy.DESTROY, // For development only
    });
  }
}
</code></pre>
<p><strong>What each line does:</strong></p>
<ul>
<li><p><code>partitionKey</code> tells DynamoDB that <code>vendorId</code> is the unique identifier for every record. No two vendors can share the same <code>vendorId</code>.</p>
</li>
<li><p><code>PAY_PER_REQUEST</code> means you only pay when data is actually read or written. There is no charge when the table is idle, which makes it cost-effective for learning.</p>
</li>
<li><p><code>RemovalPolicy.DESTROY</code> means the table will be deleted when you run <code>cdk destroy</code>. For production apps you would not use this.</p>
</li>
</ul>
<h2 id="heading-part-4-write-the-lambda-functions">Part 4: Write the Lambda Functions</h2>
<p>A Lambda function is your server, but unlike a traditional server, it only runs when it's called. AWS spins it up on demand, runs your code, and shuts it down. You're only charged for the time your code is actually running.</p>
<p>You'll write three Lambda functions:</p>
<ul>
<li><p><code>createVendor.ts</code>: Adds a new vendor to DynamoDB</p>
</li>
<li><p><code>getVendors.ts</code>: Returns all vendors from DynamoDB</p>
</li>
<li><p><code>deleteVendor.ts</code>: Removes a vendor from DynamoDB by ID</p>
</li>
</ul>
<p>Create a new folder inside <code>backend</code>:</p>
<pre><code class="language-shell">mkdir backend/lambda
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/62d53ab5bc2c7a1dc672b04f/6330a84b-77c3-4001-9783-5fedc89ae1c0.png" alt="6330a84b-77c3-4001-9783-5fedc89ae1c0" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<h3 id="heading-a-note-on-the-aws-sdk">A Note on the AWS SDK</h3>
<p>All three Lambda functions use <strong>AWS SDK v3</strong> (<code>@aws-sdk/client-dynamodb</code> and <code>@aws-sdk/lib-dynamodb</code>). This is the current standard. An older version of the SDK (<code>aws-sdk</code>) exists but is deprecated and not bundled in the Node.js 18 Lambda runtime, which is what you'll use. Stick to v3 throughout.</p>
<h3 id="heading-41-create-vendor-lambda">4.1 Create Vendor Lambda</h3>
<p>Create <code>backend/lambda/createVendor.ts</code>:</p>
<pre><code class="language-typescript">import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient, PutCommand } from "@aws-sdk/lib-dynamodb";
import { randomUUID } from "crypto";

const client = new DynamoDBClient({});
const docClient = DynamoDBDocumentClient.from(client);

export const handler = async (event: any) =&gt; {
  try {
    const body = JSON.parse(event.body);

    const item = {
      vendorId: randomUUID(), // Generates a collision-safe unique ID
      name: body.name,
      category: body.category,
      contactEmail: body.contactEmail,
      createdAt: new Date().toISOString(),
    };

    await docClient.send(
      new PutCommand({
        TableName: process.env.TABLE_NAME!,
        Item: item,
      })
    );

    return {
      statusCode: 201,
      headers: {
        "Access-Control-Allow-Origin": "*",
        "Access-Control-Allow-Headers": "Content-Type,Authorization",
        "Access-Control-Allow-Methods": "OPTIONS,POST,GET,DELETE",
      },
      body: JSON.stringify({ message: "Vendor created", vendorId: item.vendorId }),
    };
  } catch (error) {
    console.error("Error creating vendor:", error);
    return {
      statusCode: 500,
      headers: { "Access-Control-Allow-Origin": "*" },
      body: JSON.stringify({ error: "Failed to create vendor" }),
    };
  }
};
</code></pre>
<p><strong>What each part does:</strong></p>
<ul>
<li><p><code>randomUUID()</code> generates a universally unique ID using Node's built-in <code>crypto</code> module. No extra package is needed. This is more reliable than <code>Date.now()</code>, which can produce duplicate IDs if two requests arrive within the same millisecond.</p>
</li>
<li><p><code>process.env.TABLE_NAME</code> reads the DynamoDB table name from an environment variable. You'll set this value in the CDK stack. This avoids hardcoding the table name inside your Lambda code.</p>
</li>
<li><p>The <code>headers</code> block is required for CORS (Cross-Origin Resource Sharing). Without <code>Access-Control-Allow-Origin</code>, your browser will block responses from a different domain than your frontend. Without <code>Access-Control-Allow-Headers</code>, the <code>Authorization</code> header you add later for Cognito will be rejected during the browser's preflight check.</p>
</li>
</ul>
<h3 id="heading-42-get-vendors-lambda">4.2 Get Vendors Lambda</h3>
<p>Create <code>backend/lambda/getVendors.ts</code>:</p>
<pre><code class="language-typescript">import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient, ScanCommand } from "@aws-sdk/lib-dynamodb";

const client = new DynamoDBClient({});
const docClient = DynamoDBDocumentClient.from(client);

export const handler = async () =&gt; {
  try {
    const response = await docClient.send(
      new ScanCommand({
        TableName: process.env.TABLE_NAME!,
      })
    );

    return {
      statusCode: 200,
      headers: {
        "Access-Control-Allow-Origin": "*",
        "Access-Control-Allow-Headers": "Content-Type,Authorization",
        "Content-Type": "application/json",
      },
      body: JSON.stringify(response.Items ?? []),
    };
  } catch (error) {
    console.error("Error fetching vendors:", error);
    return {
      statusCode: 500,
      headers: { "Access-Control-Allow-Origin": "*" },
      body: JSON.stringify({ error: "Failed to fetch vendors" }),
    };
  }
};
</code></pre>
<p><strong>What each part does:</strong></p>
<ul>
<li><p><code>ScanCommand</code> reads every item in the table and returns them as an array. For a learning project this is fine. In a production app with millions of rows, you would use a more targeted <code>QueryCommand</code> to avoid reading the entire table on every request.</p>
</li>
<li><p><code>response.Items ?? []</code> returns an empty array if the table is empty, preventing the frontend from crashing when there are no vendors yet.</p>
</li>
</ul>
<h3 id="heading-43-delete-vendor-lambda">4.3 Delete Vendor Lambda</h3>
<p>Create <code>backend/lambda/deleteVendor.ts</code>:</p>
<pre><code class="language-typescript">import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient, DeleteCommand } from "@aws-sdk/lib-dynamodb";

const client = new DynamoDBClient({});
const docClient = DynamoDBDocumentClient.from(client);

export const handler = async (event: any) =&gt; {
  try {
    const body = JSON.parse(event.body);
    const { vendorId } = body;

    if (!vendorId) {
      return {
        statusCode: 400,
        headers: { "Access-Control-Allow-Origin": "*" },
        body: JSON.stringify({ error: "vendorId is required" }),
      };
    }

    await docClient.send(
      new DeleteCommand({
        TableName: process.env.TABLE_NAME!,
        Key: { vendorId },
      })
    );

    return {
      statusCode: 200,
      headers: {
        "Access-Control-Allow-Origin": "*",
        "Access-Control-Allow-Headers": "Content-Type,Authorization",
        "Access-Control-Allow-Methods": "OPTIONS,POST,GET,DELETE",
      },
      body: JSON.stringify({ message: "Vendor deleted" }),
    };
  } catch (error) {
    console.error("Error deleting vendor:", error);
    return {
      statusCode: 500,
      headers: { "Access-Control-Allow-Origin": "*" },
      body: JSON.stringify({ error: "Failed to delete vendor" }),
    };
  }
};
</code></pre>
<p><strong>What each part does:</strong></p>
<ul>
<li><p><code>DeleteCommand</code> removes the item whose <code>vendorId</code> matches the key you provide. DynamoDB doesn't return an error if the item doesn't exist. It simply does nothing.</p>
</li>
<li><p>The <code>400</code> guard at the top returns a clear error if the caller forgets to send a <code>vendorId</code>, rather than letting DynamoDB throw a confusing internal error.</p>
</li>
</ul>
<h2 id="heading-part-5-build-the-api-with-api-gateway">Part 5: Build the API with API Gateway</h2>
<p>API Gateway is what gives your Lambda functions a public URL. Without it, there's no way for your browser to trigger a Lambda function. Think of it as the front door of your backend: it receives HTTP requests, checks whether the caller is authorized, routes the request to the correct Lambda, and returns the Lambda's response to the caller.</p>
<p>Now you'll wire everything together in <code>backend/lib/backend-stack.ts</code>.</p>
<h3 id="heading-51-add-lambda-functions-and-api-gateway-to-the-stack">5.1 Add Lambda Functions and API Gateway to the Stack</h3>
<p>Replace the entire contents of <code>backend/lib/backend-stack.ts</code> with this complete, assembled file:</p>
<pre><code class="language-typescript">import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';

export class BackendStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // 1. DynamoDB Table 
    const vendorTable = new dynamodb.Table(this, 'VendorTable', {
      partitionKey: {
        name: 'vendorId',
        type: dynamodb.AttributeType.STRING,
      },
      billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
      removalPolicy: cdk.RemovalPolicy.DESTROY,
    });

    // 2. Lambda Functions
    const lambdaEnv = { TABLE_NAME: vendorTable.tableName };

    const createVendorLambda = new NodejsFunction(this, 'CreateVendorHandler', {
      entry: 'lambda/createVendor.ts',
      handler: 'handler',
      environment: lambdaEnv,
    });

    const getVendorsLambda = new NodejsFunction(this, 'GetVendorsHandler', {
      entry: 'lambda/getVendors.ts',
      handler: 'handler',
      environment: lambdaEnv,
    });

    const deleteVendorLambda = new NodejsFunction(this, 'DeleteVendorHandler', {
      entry: 'lambda/deleteVendor.ts',
      handler: 'handler',
      environment: lambdaEnv,
    });

    // 3. Permissions (Least Privilege)
    vendorTable.grantWriteData(createVendorLambda);
    vendorTable.grantReadData(getVendorsLambda);
    vendorTable.grantWriteData(deleteVendorLambda);

    // 4. API Gateway
    const api = new apigateway.RestApi(this, 'VendorApi', {
      restApiName: 'Vendor Service',
      defaultCorsPreflightOptions: {
        allowOrigins: apigateway.Cors.ALL_ORIGINS,
        allowMethods: apigateway.Cors.ALL_METHODS,
        allowHeaders: ['Content-Type', 'Authorization'],
      },
    });

    const vendors = api.root.addResource('vendors');
    vendors.addMethod('POST', new apigateway.LambdaIntegration(createVendorLambda));
    vendors.addMethod('GET', new apigateway.LambdaIntegration(getVendorsLambda));
    vendors.addMethod('DELETE', new apigateway.LambdaIntegration(deleteVendorLambda));

    // 5. Outputs
    new cdk.CfnOutput(this, 'ApiEndpoint', {
      value: api.url,
    });
  }
}
</code></pre>
<p><strong>What each section does:</strong></p>
<p><code>NodejsFunction</code> is a special CDK construct that automatically bundles your Lambda code and all its dependencies into a single file using <code>esbuild</code> before uploading it to AWS. This is why you installed <code>esbuild</code> in Part 2.</p>
<p>Always use <code>NodejsFunction</code> instead of the basic <code>lambda.Function</code> construct. The basic version requires you to manually manage bundling, which causes "Module not found" errors at runtime.</p>
<p><strong>Permissions (Least Privilege):</strong> In AWS, no resource can communicate with any other resource by default. A Lambda function has no access to DynamoDB, S3, or anything else unless you explicitly grant it.</p>
<p>This is called the <strong>Least Privilege</strong> principle: each piece of your system gets exactly the permissions it needs, and nothing more. <code>grantWriteData</code> lets a Lambda write and delete items. <code>grantReadData</code> lets a Lambda read items. Using separate grants for each function means the <code>getVendors</code> Lambda can never accidentally delete data.</p>
<p><code>CfnOutput</code> prints a value to your terminal after <code>cdk deploy</code> completes. You'll use the <code>ApiEndpoint</code> URL to configure your frontend.</p>
<h2 id="heading-part-6-deploy-the-backend-to-aws">Part 6: Deploy the Backend to AWS</h2>
<p>Your infrastructure is fully defined in code. Now you'll deploy it to AWS and get a live API URL.</p>
<h3 id="heading-61-bootstrap-your-aws-environment">6.1 Bootstrap Your AWS Environment</h3>
<p>Before your first CDK deployment, AWS needs a small landing zone in your account – an S3 bucket where CDK can upload your Lambda bundles and other assets. This setup step is called <strong>bootstrapping</strong> and only needs to be done once per AWS account per region.</p>
<p>From inside your <code>backend</code> folder, run:</p>
<pre><code class="language-shell">cdk bootstrap
</code></pre>
<p><strong>Important</strong>: Bootstrapping is region-specific. If you ever switch to a different AWS region, you will need to run <code>cdk bootstrap</code> again in that region.</p>
<h3 id="heading-62-deploy">6.2 Deploy</h3>
<p>Run:</p>
<pre><code class="language-shell">cdk deploy
</code></pre>
<p>CDK will display a summary of everything it is about to create and ask for your confirmation. Type <code>y</code> and press Enter.</p>
<p>When the deployment finishes, you'll see an <strong>Outputs</strong> section in your terminal:</p>
<pre><code class="language-plaintext">Outputs:
BackendStack.ApiEndpoint = https://abcdef123.execute-api.us-east-1.amazonaws.com/prod/
</code></pre>
<p>Copy that URL. You'll need it when building the frontend.</p>
<h3 id="heading-63-troubleshooting-how-to-read-aws-error-logs">6.3 Troubleshooting: How to Read AWS Error Logs</h3>
<p>Real deployments rarely go perfectly the first time. If something goes wrong after deploying, here is how to find the actual error message.</p>
<h4 id="heading-error-502-bad-gateway">Error: 502 Bad Gateway</h4>
<p>A <code>502</code> means API Gateway received your request but your Lambda crashed before it could respond. The most common cause is a missing environment variable – for example, if <code>TABLE_NAME</code> was not passed correctly and the Lambda cannot find the table.</p>
<p>To find the actual error message, use <strong>CloudWatch Logs</strong>:</p>
<ol>
<li><p>Log in to the AWS Console and search for CloudWatch</p>
</li>
<li><p>In the left sidebar, click Logs --&gt; Log groups</p>
</li>
</ol>
<img src="https://cdn.hashnode.com/uploads/covers/62d53ab5bc2c7a1dc672b04f/abfb78fc-574b-4a75-a12b-12fb09f041b3.png" alt="CloudWatch left sidebar with log groups, and the search field showing /aws/lambda/" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<ol>
<li><p>Find the group named <code>/aws/lambda/BackendStack-CreateVendorHandler...</code></p>
</li>
<li><p>Click the most recent Log stream</p>
</li>
<li><p>Read the error message. It will tell you exactly what went wrong</p>
</li>
</ol>
<p>Two common messages and their fixes:</p>
<ul>
<li><p><code>Runtime.ImportModuleError</code> : Your Lambda cannot find a module. Make sure you're using <code>NodejsFunction</code> (not <code>lambda.Function</code>) in your CDK stack. <code>NodejsFunction</code> automatically bundles dependencies; <code>lambda.Function</code> does not.</p>
</li>
<li><p><code>AccessDeniedException</code>: Your Lambda tried to access DynamoDB but doesn't have permission. Check that you have the correct <code>grantWriteData</code> or <code>grantReadData</code> call in your stack for that Lambda.</p>
</li>
</ul>
<h2 id="heading-part-7-build-the-react-frontend">Part 7: Build the React Frontend</h2>
<p>Your backend is live. Now you'll build the React UI that talks to it.</p>
<h3 id="heading-71-define-the-vendor-type">7.1 Define the Vendor Type</h3>
<p>Before writing any API or component code, define what a "vendor" looks like in TypeScript. This gives you type safety throughout your frontend code.</p>
<p>Create <code>frontend/types/vendor.ts</code>:</p>
<pre><code class="language-typescript">export interface Vendor {
  vendorId?: string; // Optional when creating — the Lambda generates it
  name: string;
  category: string;
  contactEmail: string;
  createdAt?: string;
}
</code></pre>
<p>The <code>vendorId?</code> is marked optional with <code>?</code> because when you are <em>creating</em> a new vendor, you don't have an ID yet. The <code>createVendor</code> Lambda generates one. When you <em>read</em> vendors back from the API, <code>vendorId</code> will always be present.</p>
<h3 id="heading-72-create-the-api-service-layer">7.2 Create the API Service Layer</h3>
<p>Rather than writing <code>fetch</code> calls directly inside your React components, you'll centralize all your API logic in one file. This pattern is called a <strong>service layer</strong>. It keeps your components clean and makes it easy to update API calls in one place.</p>
<p>First, create a <code>.env.local</code> file inside your <code>frontend</code> folder to store your API URL:</p>
<pre><code class="language-bash"># frontend/.env.local
NEXT_PUBLIC_API_URL=https://abcdef123.execute-api.us-east-1.amazonaws.com/prod
</code></pre>
<p>Replace the URL with the <code>ApiEndpoint</code> value from your <code>cdk deploy</code> output. The <code>NEXT_PUBLIC_</code> prefix is required by Next.js to make an environment variable accessible in the browser.</p>
<p>You might be wondering: <strong>why not hardcode the URL</strong>? If you paste your API URL directly into your code and push it to GitHub, it becomes publicly visible. While an API URL alone does not expose your data (Cognito will protect that), it's good practice to keep URLs and secrets out of source control. Always use .env.local and add it to your .gitignore.</p>
<p>Make sure <code>.env.local</code> is in your <code>.gitignore</code>:</p>
<pre><code class="language-shell">echo ".env.local" &gt;&gt; frontend/.gitignore
</code></pre>
<p>Now create <code>frontend/lib/api.ts</code>:</p>
<pre><code class="language-typescript">import { Vendor } from '@/types/vendor';

const BASE_URL = process.env.NEXT_PUBLIC_API_URL!;

export const getVendors = async (): Promise&lt;Vendor[]&gt; =&gt; {
  const response = await fetch(`${BASE_URL}/vendors`);
  if (!response.ok) throw new Error('Failed to fetch vendors');
  return response.json();
};

export const createVendor = async (vendor: Omit&lt;Vendor, 'vendorId' | 'createdAt'&gt;): Promise&lt;void&gt; =&gt; {
  const response = await fetch(`${BASE_URL}/vendors`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(vendor),
  });
  if (!response.ok) throw new Error('Failed to create vendor');
};

export const deleteVendor = async (vendorId: string): Promise&lt;void&gt; =&gt; {
  const response = await fetch(`${BASE_URL}/vendors`, {
    method: 'DELETE',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ vendorId }),
  });
  if (!response.ok) throw new Error('Failed to delete vendor');
};
</code></pre>
<p><strong>What each part does:</strong></p>
<ul>
<li><p><code>Omit&lt;Vendor, 'vendorId' | 'createdAt'&gt;</code> means the <code>createVendor</code> function accepts a vendor without an ID or timestamp (those are generated server-side).</p>
</li>
<li><p><code>if (!response.ok) throw new Error(...)</code> ensures that any HTTP error (4xx or 5xx) surfaces as a JavaScript error in your component, where you can show the user a meaningful message instead of silently failing.</p>
</li>
</ul>
<p>You'll update these functions later in Part 8 to include the Cognito auth token.</p>
<h3 id="heading-73-build-the-main-page">7.3 Build the Main Page</h3>
<p>Now create the main page component. It includes a form for adding vendors and a live list that displays all current vendors.</p>
<p>Replace the contents of <code>frontend/app/page.tsx</code> with:</p>
<pre><code class="language-typescript">'use client';

import { useState, useEffect } from 'react';
import { createVendor, getVendors, deleteVendor } from '@/lib/api';
import { Vendor } from '@/types/vendor';

export default function Home() {
  const [vendors, setVendors] = useState&lt;Vendor[]&gt;([]);
  const [form, setForm] = useState({ name: '', category: '', contactEmail: '' });
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState('');

  const loadVendors = async () =&gt; {
    try {
      const data = await getVendors();
      setVendors(data);
    } catch {
      setError('Failed to load vendors.');
    }
  };

  // Load vendors once when the page first renders
  useEffect(() =&gt; {
    loadVendors();
  }, []);
  // The empty [] means this runs only once. Without it, the effect would
  // run after every render, causing an infinite loop of fetch requests.

  const handleSubmit = async (e: React.FormEvent) =&gt; {
    e.preventDefault(); // Prevent the browser from reloading the page on submit
    setLoading(true);
    setError('');
    try {
      await createVendor(form);
      setForm({ name: '', category: '', contactEmail: '' }); // Reset the form
      await loadVendors(); // Refresh the list from DynamoDB
    } catch {
      setError('Failed to add vendor. Please try again.');
    } finally {
      setLoading(false);
    }
  };

  const handleDelete = async (vendorId: string) =&gt; {
    try {
      await deleteVendor(vendorId);
      await loadVendors(); // Refresh after deleting
    } catch {
      setError('Failed to delete vendor.');
    }
  };

  return (
    &lt;main className="p-10 max-w-5xl mx-auto"&gt;
      &lt;h1 className="text-3xl font-bold mb-2 text-gray-900"&gt;Vendor Tracker&lt;/h1&gt;
      &lt;p className="text-gray-500 mb-8"&gt;Manage your vendors, stored in AWS DynamoDB.&lt;/p&gt;

      {error &amp;&amp; (
        &lt;div className="mb-4 p-3 bg-red-100 text-red-700 rounded"&gt;{error}&lt;/div&gt;
      )}

      &lt;div className="grid grid-cols-1 md:grid-cols-2 gap-10"&gt;

        {/* ── Add Vendor Form ── */}
        &lt;section&gt;
          &lt;h2 className="text-xl font-semibold mb-4 text-gray-800"&gt;Add New Vendor&lt;/h2&gt;
          &lt;form onSubmit={handleSubmit} className="space-y-4"&gt;
            &lt;input
              className="w-full p-2 border rounded text-black focus:outline-none focus:ring-2 focus:ring-orange-400"
              placeholder="Vendor Name"
              value={form.name}
              onChange={e =&gt; setForm({ ...form, name: e.target.value })}
              required
            /&gt;
            &lt;input
              className="w-full p-2 border rounded text-black focus:outline-none focus:ring-2 focus:ring-orange-400"
              placeholder="Category (e.g. SaaS, Hardware)"
              value={form.category}
              onChange={e =&gt; setForm({ ...form, category: e.target.value })}
              required
            /&gt;
            &lt;input
              className="w-full p-2 border rounded text-black focus:outline-none focus:ring-2 focus:ring-orange-400"
              placeholder="Contact Email"
              type="email"
              value={form.contactEmail}
              onChange={e =&gt; setForm({ ...form, contactEmail: e.target.value })}
              required
            /&gt;
            &lt;button
              type="submit"
              disabled={loading}
              className="w-full bg-orange-500 text-white p-2 rounded hover:bg-orange-600 disabled:bg-gray-400 transition-colors"
            &gt;
              {loading ? 'Saving...' : 'Add Vendor'}
            &lt;/button&gt;
          &lt;/form&gt;
        &lt;/section&gt;

        {/* ── Vendor List ── */}
        &lt;section&gt;
          &lt;h2 className="text-xl font-semibold mb-4 text-gray-800"&gt;
            Current Vendors ({vendors.length})
          &lt;/h2&gt;
          &lt;div className="space-y-3"&gt;
            {vendors.length === 0 ? (
              &lt;p className="text-gray-400 italic"&gt;No vendors yet. Add one using the form.&lt;/p&gt;
            ) : (
              vendors.map(v =&gt; (
                &lt;div
                  key={v.vendorId}
                  className="p-4 border rounded shadow-sm bg-white flex justify-between items-start"
                &gt;
                  &lt;div&gt;
                    &lt;p className="font-semibold text-gray-900"&gt;{v.name}&lt;/p&gt;
                    &lt;p className="text-sm text-gray-500"&gt;{v.category} · {v.contactEmail}&lt;/p&gt;
                  &lt;/div&gt;
                  &lt;button
                    onClick={() =&gt; v.vendorId &amp;&amp; handleDelete(v.vendorId)}
                    className="ml-4 text-sm text-red-500 hover:text-red-700 hover:underline"
                  &gt;
                    Delete
                  &lt;/button&gt;
                &lt;/div&gt;
              ))
            )}
          &lt;/div&gt;
        &lt;/section&gt;

      &lt;/div&gt;
    &lt;/main&gt;
  );
}
</code></pre>
<p><strong>Key points in this component:</strong></p>
<ul>
<li><p><code>'use client'</code> at the top is a Next.js directive. It tells Next.js that this component uses browser APIs (<code>useState</code>, <code>useEffect</code>, event handlers) and must run in the browser, not be pre-rendered on the server.</p>
</li>
<li><p><code>e.preventDefault()</code> inside <code>handleSubmit</code> stops the browser's default form submission behavior, which would cause a full page reload and wipe your React state.</p>
</li>
<li><p>After every <code>createVendor</code> or <code>deleteVendor</code> call, <code>loadVendors()</code> is called again. This re-fetches the latest data from DynamoDB so the UI always matches what is actually stored in the database.</p>
</li>
</ul>
<h3 id="heading-74-test-the-app-locally">7.4 Test the App Locally</h3>
<p>Start your Next.js development server:</p>
<pre><code class="language-shell">cd frontend
npm run dev
</code></pre>
<p>Open <code>http://localhost:3000</code> in your browser. You should see the two-panel layout. Try adding a vendor and confirm it appears in the list.</p>
<img src="https://cdn.hashnode.com/uploads/covers/62d53ab5bc2c7a1dc672b04f/281f971a-27b8-49b3-9079-e12601525d80.png" alt="The running Vendor Tracker app at localhost:3000 showing the two-panel layout with the Add Vendor form on the left and an empty vendor list on the right" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<img src="https://cdn.hashnode.com/uploads/covers/62d53ab5bc2c7a1dc672b04f/88b5dd74-5847-4310-bec3-b1a2b129fbaa.png" alt="The Vendor Tracker app after a vendor has been added, showing the vendor card in the list" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<h4 id="heading-verifying-the-connection-to-aws">Verifying the connection to AWS:</h4>
<p>Open Chrome DevTools (F12) and click the Network tab. When you add a vendor, you should see:</p>
<ul>
<li><p>A <code>POST</code> request to your AWS API URL returning a <strong>201</strong> status code</p>
</li>
<li><p>A <code>GET</code> request returning <strong>200</strong> with the updated vendor list</p>
</li>
</ul>
<p>You can also verify the data was saved by opening the AWS Console, navigating to <strong>DynamoDB --&gt; Tables --&gt; VendorTable --&gt; Explore table items</strong>. Your vendor should appear there.</p>
<h2 id="heading-part-8-add-authentication-with-amazon-cognito">Part 8: Add Authentication with Amazon Cognito</h2>
<p>Right now your API is completely open. Anyone who finds your API URL can add or delete vendors. You'll fix that with <strong>Amazon Cognito</strong>.</p>
<p>Cognito is AWS's authentication service. It manages a User Pool – a database of registered users with usernames and passwords. When a user logs in, Cognito issues a JWT (JSON Web Token): a cryptographically signed string that proves who the user is. Your API Gateway will check for this token on every request. No valid token means no access.</p>
<p><strong>What is a JWT?</strong> A JSON Web Token is a string that looks like <code>eyJhbGci...</code>. It contains encoded information about the user and is signed by Cognito using a secret key.</p>
<p>API Gateway can verify the signature without contacting Cognito on every request, which makes token checking fast. Think of it as a tamper-proof badge: anyone can read the name on it, but only Cognito's signature makes it valid.</p>
<h3 id="heading-81-add-cognito-to-the-cdk-stack">8.1 Add Cognito to the CDK Stack</h3>
<p>Open <code>backend/lib/backend-stack.ts</code> and update it to include Cognito. Here is the complete updated file:</p>
<pre><code class="language-typescript">import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
import * as cognito from 'aws-cdk-lib/aws-cognito';
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';

export class BackendStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // ─── 1. DynamoDB Table ────────────────────────────────────────────────────
    const vendorTable = new dynamodb.Table(this, 'VendorTable', {
      partitionKey: { name: 'vendorId', type: dynamodb.AttributeType.STRING },
      billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
      removalPolicy: cdk.RemovalPolicy.DESTROY,
    });

    // ─── 2. Lambda Functions ──────────────────────────────────────────────────
    const lambdaEnv = { TABLE_NAME: vendorTable.tableName };

    const createVendorLambda = new NodejsFunction(this, 'CreateVendorHandler', {
      entry: 'lambda/createVendor.ts',
      handler: 'handler',
      environment: lambdaEnv,
    });

    const getVendorsLambda = new NodejsFunction(this, 'GetVendorsHandler', {
      entry: 'lambda/getVendors.ts',
      handler: 'handler',
      environment: lambdaEnv,
    });

    const deleteVendorLambda = new NodejsFunction(this, 'DeleteVendorHandler', {
      entry: 'lambda/deleteVendor.ts',
      handler: 'handler',
      environment: lambdaEnv,
    });

    // ─── 3. Permissions ───────────────────────────────────────────────────────
    vendorTable.grantWriteData(createVendorLambda);
    vendorTable.grantReadData(getVendorsLambda);
    vendorTable.grantWriteData(deleteVendorLambda);

    // ─── 4. Cognito User Pool ─────────────────────────────────────────────────
    const userPool = new cognito.UserPool(this, 'VendorUserPool', {
      selfSignUpEnabled: true,
      signInAliases: { email: true },
      autoVerify: { email: true },
      userVerification: {
        emailStyle: cognito.VerificationEmailStyle.CODE,
      },
    });

    // Required to host Cognito's internal auth endpoints
    userPool.addDomain('VendorUserPoolDomain', {
      cognitoDomain: {
        domainPrefix: `vendor-tracker-${this.account}`,
      },
    });

    const userPoolClient = userPool.addClient('VendorAppClient');

    // ─── 5. API Gateway + Authorizer ──────────────────────────────────────────
    const api = new apigateway.RestApi(this, 'VendorApi', {
      restApiName: 'Vendor Service',
      defaultCorsPreflightOptions: {
        allowOrigins: apigateway.Cors.ALL_ORIGINS,
        allowMethods: apigateway.Cors.ALL_METHODS,
        allowHeaders: ['Content-Type', 'Authorization'],
      },
    });

    const authorizer = new apigateway.CognitoUserPoolsAuthorizer(
      this,
      'VendorAuthorizer',
      { cognitoUserPools: [userPool] }
    );

    const authOptions = {
      authorizer,
      authorizationType: apigateway.AuthorizationType.COGNITO,
    };

    const vendors = api.root.addResource('vendors');
    vendors.addMethod('GET', new apigateway.LambdaIntegration(getVendorsLambda), authOptions);
    vendors.addMethod('POST', new apigateway.LambdaIntegration(createVendorLambda), authOptions);
    vendors.addMethod('DELETE', new apigateway.LambdaIntegration(deleteVendorLambda), authOptions);

    // ─── 6. Outputs ───────────────────────────────────────────────────────────
    new cdk.CfnOutput(this, 'ApiEndpoint', { value: api.url });
    new cdk.CfnOutput(this, 'UserPoolId', { value: userPool.userPoolId });
    new cdk.CfnOutput(this, 'UserPoolClientId', { value: userPoolClient.userPoolClientId });
  }
}
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/62d53ab5bc2c7a1dc672b04f/c5e91abf-e6af-429f-bf5b-b14d18233f6c.png" alt="The newly created User Pool (VendorUserPool...) in the User Pools list, with the User Pool ID visible" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p><strong>What changed:</strong></p>
<ul>
<li><p><code>CognitoUserPoolsAuthorizer</code> tells API Gateway to check every request for a valid Cognito JWT before passing it to any Lambda. If the token is missing or invalid, API Gateway rejects the request with a <code>401 Unauthorized</code> response without ever touching your Lambda.</p>
</li>
<li><p><code>authOptions</code> is applied to all three API methods: GET, POST, and DELETE. All routes are now protected.</p>
</li>
<li><p><code>autoVerify: { email: true }</code> tells Cognito to mark the email attribute as verified after a user confirms via the verification code email. It doesn't skip the verification email, as users still receive a code. If you want to skip verification during development, you can manually confirm users in the Cognito console (covered in section 8.5).</p>
</li>
<li><p>Two new <code>CfnOutput</code> values (<code>UserPoolId</code> and <code>UserPoolClientId</code>) will appear in your terminal after the next deployment. Your frontend needs them to connect to Cognito.</p>
</li>
</ul>
<p>Deploy the updated stack:</p>
<pre><code class="language-shell">cd backend
cdk deploy
</code></pre>
<p>After deployment, your terminal output will include three values:</p>
<pre><code class="language-plaintext">Outputs:
BackendStack.ApiEndpoint     = https://abc123.execute-api.us-east-1.amazonaws.com/prod/
BackendStack.UserPoolId      = us-east-1_xxxxxxxx
BackendStack.UserPoolClientId = xxxxxxxxxxxxxxxxxxxx
</code></pre>
<p>Save all three values. You'll use them in the next step.</p>
<h3 id="heading-82-install-and-configure-aws-amplify">8.2 Install and Configure AWS Amplify</h3>
<p><strong>AWS Amplify</strong> is a frontend library that handles all the complex authentication logic for you: it manages the login UI, stores tokens in the browser, refreshes expired tokens automatically, and exposes a simple API to read the current user's session.</p>
<p>Install the Amplify libraries inside your <code>frontend</code> folder:</p>
<pre><code class="language-shell">cd frontend
npm install aws-amplify @aws-amplify/ui-react
</code></pre>
<p>Create <code>frontend/app/providers.tsx</code>. This file initializes Amplify with your Cognito configuration. It runs once when the app loads:</p>
<pre><code class="language-typescript">'use client';

import { Amplify } from 'aws-amplify';

Amplify.configure(
  {
    Auth: {
      Cognito: {
        userPoolId: process.env.NEXT_PUBLIC_USER_POOL_ID!,
        userPoolClientId: process.env.NEXT_PUBLIC_USER_POOL_CLIENT_ID!,
      },
    },
  },
  { ssr: true }
);

export function Providers({ children }: { children: React.ReactNode }) {
  return &lt;&gt;{children}&lt;/&gt;;
}
</code></pre>
<p>Add the Cognito IDs to your <code>frontend/.env.local</code> file:</p>
<pre><code class="language-shell">NEXT_PUBLIC_API_URL=https://abc123.execute-api.us-east-1.amazonaws.com/prod
NEXT_PUBLIC_USER_POOL_ID=us-east-1_xxxxxxxx
NEXT_PUBLIC_USER_POOL_CLIENT_ID=xxxxxxxxxxxxxxxxxxxx
</code></pre>
<p>Replace the values with the outputs from your <code>cdk deploy</code>.</p>
<h3 id="heading-83-wire-providers-into-the-app-layout">8.3 Wire Providers into the App Layout</h3>
<p><strong>This step is critical.</strong> Amplify must be initialized before any component tries to use authentication. If you skip this step, <code>fetchAuthSession()</code> will throw an "Amplify not configured" error and nothing will work.</p>
<p>Open <code>frontend/app/layout.tsx</code> and update it to wrap the app in the <code>Providers</code> component:</p>
<pre><code class="language-typescript">import type { Metadata } from 'next';
import './globals.css';
import { Providers } from './providers';

export const metadata: Metadata = {
  title: 'Vendor Tracker',
  description: 'Manage your vendors with AWS',
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    &lt;html lang="en"&gt;
      &lt;body&gt;
        &lt;Providers&gt;{children}&lt;/Providers&gt;
      &lt;/body&gt;
    &lt;/html&gt;
  );
}
</code></pre>
<p>By wrapping <code>{children}</code> in <code>&lt;Providers&gt;</code>, you ensure that Amplify is configured once at the root of the app, before any child page or component renders.</p>
<h3 id="heading-84-protect-the-ui-with-withauthenticator">8.4 Protect the UI with withAuthenticator</h3>
<p>Now wrap your <code>Home</code> component so that unauthenticated users see a login screen instead of the dashboard.</p>
<p>Replace the contents of <code>frontend/app/page.tsx</code> with this updated version:</p>
<pre><code class="language-typescript">'use client';

import { useState, useEffect } from 'react';
import { withAuthenticator } from '@aws-amplify/ui-react';
import '@aws-amplify/ui-react/styles.css';
import { getVendors, createVendor, deleteVendor } from '@/lib/api';
import { Vendor } from '@/types/vendor';

// withAuthenticator injects `signOut` and `user` as props automatically
function Home({ signOut, user }: { signOut?: () =&gt; void; user?: any }) {
  const [vendors, setVendors] = useState&lt;Vendor[]&gt;([]);
  const [form, setForm] = useState({ name: '', category: '', contactEmail: '' });
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState('');

  const loadVendors = async () =&gt; {
    try {
      const data = await getVendors();
      setVendors(data);
    } catch {
      setError('Failed to load vendors.');
    }
  };

  useEffect(() =&gt; {
    loadVendors();
  }, []);

  const handleSubmit = async (e: React.FormEvent) =&gt; {
    e.preventDefault();
    setLoading(true);
    setError('');
    try {
      await createVendor(form);
      setForm({ name: '', category: '', contactEmail: '' });
      await loadVendors();
    } catch {
      setError('Failed to add vendor.');
    } finally {
      setLoading(false);
    }
  };

  const handleDelete = async (vendorId: string) =&gt; {
    try {
      await deleteVendor(vendorId);
      await loadVendors();
    } catch {
      setError('Failed to delete vendor.');
    }
  };

  return (
    &lt;main className="p-10 max-w-5xl mx-auto"&gt;
      {/* ── Header ── */}
      &lt;header className="flex justify-between items-center mb-8 p-4 bg-gray-100 rounded"&gt;
        &lt;div&gt;
          &lt;h1 className="text-xl font-bold text-gray-900"&gt;Vendor Tracker&lt;/h1&gt;
          &lt;p className="text-sm text-gray-500"&gt;Signed in as: {user?.signInDetails?.loginId}&lt;/p&gt;
        &lt;/div&gt;
        &lt;button
          onClick={signOut}
          className="bg-red-500 text-white px-4 py-2 rounded hover:bg-red-600 transition-colors"
        &gt;
          Sign Out
        &lt;/button&gt;
      &lt;/header&gt;

      {error &amp;&amp; (
        &lt;div className="mb-4 p-3 bg-red-100 text-red-700 rounded"&gt;{error}&lt;/div&gt;
      )}

      &lt;div className="grid grid-cols-1 md:grid-cols-2 gap-10"&gt;

        {/* ── Add Vendor Form ── */}
        &lt;section&gt;
          &lt;h2 className="text-xl font-semibold mb-4 text-gray-800"&gt;Add New Vendor&lt;/h2&gt;
          &lt;form onSubmit={handleSubmit} className="space-y-4"&gt;
            &lt;input
              className="w-full p-2 border rounded text-black"
              placeholder="Vendor Name"
              value={form.name}
              onChange={e =&gt; setForm({ ...form, name: e.target.value })}
              required
            /&gt;
            &lt;input
              className="w-full p-2 border rounded text-black"
              placeholder="Category (e.g. SaaS, Hardware)"
              value={form.category}
              onChange={e =&gt; setForm({ ...form, category: e.target.value })}
              required
            /&gt;
            &lt;input
              className="w-full p-2 border rounded text-black"
              placeholder="Contact Email"
              type="email"
              value={form.contactEmail}
              onChange={e =&gt; setForm({ ...form, contactEmail: e.target.value })}
              required
            /&gt;
            &lt;button
              type="submit"
              disabled={loading}
              className="w-full bg-orange-500 text-white p-2 rounded hover:bg-orange-600 disabled:bg-gray-400"
            &gt;
              {loading ? 'Saving...' : 'Add Vendor'}
            &lt;/button&gt;
          &lt;/form&gt;
        &lt;/section&gt;

        {/* ── Vendor List ── */}
        &lt;section&gt;
          &lt;h2 className="text-xl font-semibold mb-4 text-gray-800"&gt;
            Current Vendors ({vendors.length})
          &lt;/h2&gt;
          &lt;div className="space-y-3"&gt;
            {vendors.length === 0 ? (
              &lt;p className="text-gray-400 italic"&gt;No vendors yet.&lt;/p&gt;
            ) : (
              vendors.map(v =&gt; (
                &lt;div
                  key={v.vendorId}
                  className="p-4 border rounded shadow-sm bg-white flex justify-between items-start"
                &gt;
                  &lt;div&gt;
                    &lt;p className="font-semibold text-gray-900"&gt;{v.name}&lt;/p&gt;
                    &lt;p className="text-sm text-gray-500"&gt;{v.category} · {v.contactEmail}&lt;/p&gt;
                  &lt;/div&gt;
                  &lt;button
                    onClick={() =&gt; v.vendorId &amp;&amp; handleDelete(v.vendorId)}
                    className="ml-4 text-sm text-red-500 hover:text-red-700 hover:underline"
                  &gt;
                    Delete
                  &lt;/button&gt;
                &lt;/div&gt;
              ))
            )}
          &lt;/div&gt;
        &lt;/section&gt;

      &lt;/div&gt;
    &lt;/main&gt;
  );
}

// Wrapping Home with withAuthenticator means any user who is not logged in
// will see Amplify's built-in login/signup screen instead of this component.
export default withAuthenticator(Home);
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/62d53ab5bc2c7a1dc672b04f/e65a88dc-ea75-4daa-b7cf-eac3406c8060.png" alt="Amplify-generated login screen" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<h3 id="heading-85-pass-the-auth-token-to-api-calls">8.5 Pass the Auth Token to API Calls</h3>
<p>Now that API Gateway requires a JWT on every request, your <code>fetch</code> calls need to include the token in the <code>Authorization</code> header. Without it, every request will return a <code>401 Unauthorized</code> error.</p>
<p>Update <code>frontend/lib/api.ts</code> with a token helper and updated fetch calls:</p>
<pre><code class="language-typescript">import { fetchAuthSession } from 'aws-amplify/auth';
import { Vendor } from '@/types/vendor';

const BASE_URL = process.env.NEXT_PUBLIC_API_URL!;

// Retrieves the current user's JWT token from the active Amplify session
const getAuthToken = async (): Promise&lt;string&gt; =&gt; {
  const session = await fetchAuthSession();
  const token = session.tokens?.idToken?.toString();
  if (!token) throw new Error('No active session. Please sign in.');
  return token;
};

export const getVendors = async (): Promise&lt;Vendor[]&gt; =&gt; {
  const token = await getAuthToken();
  const response = await fetch(`${BASE_URL}/vendors`, {
    headers: { Authorization: token },
  });
  if (!response.ok) throw new Error('Failed to fetch vendors');
  return response.json();
};

export const createVendor = async (
  vendor: Omit&lt;Vendor, 'vendorId' | 'createdAt'&gt;
): Promise&lt;void&gt; =&gt; {
  const token = await getAuthToken();
  const response = await fetch(`${BASE_URL}/vendors`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: token,
    },
    body: JSON.stringify(vendor),
  });
  if (!response.ok) throw new Error('Failed to create vendor');
};

export const deleteVendor = async (vendorId: string): Promise&lt;void&gt; =&gt; {
  const token = await getAuthToken();
  const response = await fetch(`${BASE_URL}/vendors`, {
    method: 'DELETE',
    headers: {
      'Content-Type': 'application/json',
      Authorization: token,
    },
    body: JSON.stringify({ vendorId }),
  });
  if (!response.ok) throw new Error('Failed to delete vendor');
};
</code></pre>
<p><strong>What</strong> <code>getAuthToken</code> <strong>does:</strong></p>
<p><code>fetchAuthSession()</code> reads the currently logged-in user's session from the browser. Amplify stores the session in memory and <code>localStorage</code> after the user signs in.</p>
<p><code>session.tokens?.idToken</code> is the JWT string that API Gateway's Cognito Authorizer is looking for. Passing it as the <code>Authorization</code> header tells API Gateway: "This request is from an authenticated user."</p>
<h3 id="heading-86-troubleshooting-cognito">8.6 Troubleshooting Cognito</h3>
<h4 id="heading-unconfirmed-user-error-after-sign-up">"Unconfirmed" user error after sign-up</h4>
<p>When a new user signs up through the Amplify UI, Cognito marks the account as <em>Unconfirmed</em> until the user verifies their email address. A verification code is sent to the user's email. After entering the code, the account becomes confirmed and the user can log in.</p>
<p>If you are testing locally and want to skip the email step, you can manually confirm any account in the AWS Console:</p>
<ol>
<li><p>Open the AWS Console and navigate to Cognito</p>
</li>
<li><p>Click on your User Pool (<code>VendorUserPool...</code>)</p>
</li>
<li><p>Click the Users tab</p>
</li>
<li><p>Click on the user's email address</p>
</li>
<li><p>Open the Actions dropdown and click Confirm account</p>
</li>
</ol>
<img src="https://cdn.hashnode.com/uploads/covers/62d53ab5bc2c7a1dc672b04f/158fb773-9cb1-4c14-9fd7-49e4369ba7e3.png" alt=" Cognito Users list showing a user with &quot;Unconfirmed&quot; status" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<img src="https://cdn.hashnode.com/uploads/covers/62d53ab5bc2c7a1dc672b04f/5637ac80-ee0c-4fdf-93cf-d4b7d71f6a65.png" alt="Cognito Users list showing a user with &quot;Unconfirmed&quot; status" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<h4 id="heading-401-unauthorized-errors-after-deployment">401 Unauthorized errors after deployment</h4>
<p>If you are getting 401 errors, check two things:</p>
<ol>
<li><p>Open Chrome DevTools --&gt; Network tab, click the failing request, and look at the <strong>Request Headers</strong>. You should see an <code>Authorization</code> header with a long string of characters. If it is missing, <code>getAuthToken</code> is failing. Check that Amplify is configured correctly in <code>providers.tsx</code> and wired in via <code>layout.tsx</code>.</p>
</li>
<li><p>In your CDK stack, confirm that <code>authorizationType: apigateway.AuthorizationType.COGNITO</code> is present on every protected method definition. If it is missing, API Gateway may not be checking tokens even though the authorizer is defined.</p>
</li>
</ol>
<h2 id="heading-part-9-deploy-the-frontend-with-s3-and-cloudfront">Part 9: Deploy the Frontend with S3 and CloudFront</h2>
<p>Your app works locally. Now you'll deploy it to a real HTTPS URL that anyone in the world can visit.</p>
<p><strong>The strategy:</strong> Next.js will export your React app as a set of static HTML, CSS, and JavaScript files. Those files will be uploaded to an <strong>S3 bucket</strong> (AWS's file storage service). <strong>CloudFront</strong> sits in front of the bucket as a Content Delivery Network (CDN), distributing your files to servers around the world and serving them over HTTPS.</p>
<h3 id="heading-91-configure-nextjs-for-static-export">9.1 Configure Next.js for Static Export</h3>
<p>Open <code>frontend/next.config.js</code> (or <code>next.config.mjs</code>) and add the <code>output: 'export'</code> setting:</p>
<pre><code class="language-javascript">/** @type {import('next').NextConfig} */
const nextConfig = {
  output: 'export', // Generates a static /out folder instead of a Node.js server
};

export default nextConfig;
</code></pre>
<p><strong>Note on 'use client' and static export</strong>: When output: 'export' is set, Next.js builds every page at compile time. Any component that uses browser-only APIs – like withAuthenticator from Amplify – must have 'use client' at the top of the file. This tells Next.js to skip server-side rendering for that component and run it only in the browser.</p>
<p>You already have 'use client' in page.tsx. If you ever see a build error mentioning window is not defined or similar, check that the relevant component has 'use client' at the top.</p>
<p>Build the frontend:</p>
<pre><code class="language-shell">cd frontend
npm run build
</code></pre>
<p>This generates an <code>/out</code> folder containing your complete website as static files. Verify the folder was created:</p>
<pre><code class="language-shell">ls out
# You should see: index.html, _next/, etc.
</code></pre>
<h3 id="heading-92-add-s3-and-cloudfront-to-the-cdk-stack">9.2 Add S3 and CloudFront to the CDK Stack</h3>
<p>Open <code>backend/lib/backend-stack.ts</code> and add the hosting infrastructure. Here's the complete final version of the file:</p>
<pre><code class="language-typescript">import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
import * as cognito from 'aws-cdk-lib/aws-cognito';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as cloudfront from 'aws-cdk-lib/aws-cloudfront';
import * as origins from 'aws-cdk-lib/aws-cloudfront-origins';
import * as s3deploy from 'aws-cdk-lib/aws-s3-deployment';
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';

export class BackendStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // 1. DynamoDB Table 
    const vendorTable = new dynamodb.Table(this, 'VendorTable', {
      partitionKey: { name: 'vendorId', type: dynamodb.AttributeType.STRING },
      billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
      removalPolicy: cdk.RemovalPolicy.DESTROY,
    });

    // 2. Lambda Functions
    const lambdaEnv = { TABLE_NAME: vendorTable.tableName };

    const createVendorLambda = new NodejsFunction(this, 'CreateVendorHandler', {
      entry: 'lambda/createVendor.ts',
      handler: 'handler',
      environment: lambdaEnv,
    });

    const getVendorsLambda = new NodejsFunction(this, 'GetVendorsHandler', {
      entry: 'lambda/getVendors.ts',
      handler: 'handler',
      environment: lambdaEnv,
    });

    const deleteVendorLambda = new NodejsFunction(this, 'DeleteVendorHandler', {
      entry: 'lambda/deleteVendor.ts',
      handler: 'handler',
      environment: lambdaEnv,
    });

    // 3. Permissions
    vendorTable.grantWriteData(createVendorLambda);
    vendorTable.grantReadData(getVendorsLambda);
    vendorTable.grantWriteData(deleteVendorLambda);

    // 4. Cognito User Pool
    const userPool = new cognito.UserPool(this, 'VendorUserPool', {
      selfSignUpEnabled: true,
      signInAliases: { email: true },
      autoVerify: { email: true },
      userVerification: {
        emailStyle: cognito.VerificationEmailStyle.CODE,
      },
    });

    userPool.addDomain('VendorUserPoolDomain', {
      cognitoDomain: { domainPrefix: `vendor-tracker-${this.account}` },
    });

    const userPoolClient = userPool.addClient('VendorAppClient');

    // 5. API Gateway + Authorizer
    const api = new apigateway.RestApi(this, 'VendorApi', {
      restApiName: 'Vendor Service',
      defaultCorsPreflightOptions: {
        allowOrigins: apigateway.Cors.ALL_ORIGINS,
        allowMethods: apigateway.Cors.ALL_METHODS,
        allowHeaders: ['Content-Type', 'Authorization'],
      },
    });

    const authorizer = new apigateway.CognitoUserPoolsAuthorizer(
      this,
      'VendorAuthorizer',
      { cognitoUserPools: [userPool] }
    );

    const authOptions = {
      authorizer,
      authorizationType: apigateway.AuthorizationType.COGNITO,
    };

    const vendors = api.root.addResource('vendors');
    vendors.addMethod('GET', new apigateway.LambdaIntegration(getVendorsLambda), authOptions);
    vendors.addMethod('POST', new apigateway.LambdaIntegration(createVendorLambda), authOptions);
    vendors.addMethod('DELETE', new apigateway.LambdaIntegration(deleteVendorLambda), authOptions);

    // 6. S3 Bucket (Frontend Files) 
    const siteBucket = new s3.Bucket(this, 'VendorSiteBucket', {
      blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
      removalPolicy: cdk.RemovalPolicy.DESTROY,
      autoDeleteObjects: true,
    });

    // 7. CloudFront Distribution (HTTPS + CDN)
    const distribution = new cloudfront.Distribution(this, 'SiteDistribution', {
      defaultBehavior: {
        origin: new origins.S3Origin(siteBucket),
        viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
      },
      defaultRootObject: 'index.html',
      errorResponses: [
        {
          // Redirect all 404s back to index.html so React can handle routing
          httpStatus: 404,
          responseHttpStatus: 200,
          responsePagePath: '/index.html',
        },
      ],
    });

    // 8. Deploy Frontend Files to S3 
    new s3deploy.BucketDeployment(this, 'DeployWebsite', {
      sources: [s3deploy.Source.asset('../frontend/out')],
      destinationBucket: siteBucket,
      distribution,
      distributionPaths: ['/*'], // Clears CloudFront cache on every deploy
    });

    // 9. Outputs ───────────────────────────────────────────────────────────
    new cdk.CfnOutput(this, 'ApiEndpoint', { value: api.url });
    new cdk.CfnOutput(this, 'UserPoolId', { value: userPool.userPoolId });
    new cdk.CfnOutput(this, 'UserPoolClientId', { value: userPoolClient.userPoolClientId });
    new cdk.CfnOutput(this, 'CloudFrontURL', {
      value: `https://${distribution.distributionDomainName}`,
    });
  }
}
</code></pre>
<p><strong>What the hosting infrastructure does:</strong></p>
<ul>
<li><p>The <strong>S3 bucket</strong> stores your static HTML, CSS, and JavaScript files. It is private – users cannot access it directly.</p>
</li>
<li><p><strong>CloudFront</strong> is the CDN that sits in front of S3. It gives you an HTTPS URL and caches your files at edge locations worldwide, so the app loads fast no matter where users are located. <code>REDIRECT_TO_HTTPS</code> automatically upgrades any HTTP request to HTTPS.</p>
</li>
<li><p>The <strong>error response</strong> for 404 returns <code>index.html</code> instead of an error page. This is necessary for single-page apps: if a user navigates directly to a route like <code>/vendors/123</code>, CloudFront cannot find a file at that path, but sending back <code>index.html</code> lets the React app handle the routing correctly.</p>
</li>
<li><p><code>distributionPaths: ['/*']</code> tells CloudFront to invalidate its entire cache after every deployment. This ensures users always see the latest version of your app immediately.</p>
</li>
<li><p><code>BucketDeployment</code> is a CDK construct that automatically uploads the contents of your <code>frontend/out</code> folder to the S3 bucket every time you run <code>cdk deploy</code>.</p>
</li>
</ul>
<h3 id="heading-93-run-the-final-deployment">9.3 Run the Final Deployment</h3>
<p>First, build the frontend with the latest environment variables:</p>
<pre><code class="language-shell">cd frontend
npm run build
</code></pre>
<p>Then deploy everything from the backend folder:</p>
<pre><code class="language-shell">cd ../backend
cdk deploy
</code></pre>
<p>After deployment finishes, copy the <code>CloudFrontURL</code> from the terminal output:</p>
<pre><code class="language-plaintext">Outputs:
BackendStack.CloudFrontURL = https://d1234abcd.cloudfront.net
</code></pre>
<p>Open that URL in your browser. Your app is now live on the internet, served over HTTPS, globally distributed.</p>
<img src="https://cdn.hashnode.com/uploads/covers/62d53ab5bc2c7a1dc672b04f/f8e14979-a667-4afc-bdd4-9afe4abd9593.png" alt="f8e14979-a667-4afc-bdd4-9afe4abd9593" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<h2 id="heading-what-you-built">What You Built</h2>
<p>You now have a fully deployed, production-style full-stack application. Here is a summary of every piece you built and what it does:</p>
<table>
<thead>
<tr>
<th>Layer</th>
<th>Service</th>
<th>What it does</th>
</tr>
</thead>
<tbody><tr>
<td>Frontend</td>
<td>Next.js + CloudFront</td>
<td>React UI served globally over HTTPS</td>
</tr>
<tr>
<td>Auth</td>
<td>Amazon Cognito + Amplify</td>
<td>User sign-up, login, and JWT token management</td>
</tr>
<tr>
<td>API</td>
<td>API Gateway</td>
<td>Routes HTTP requests, validates auth tokens</td>
</tr>
<tr>
<td>Logic</td>
<td>AWS Lambda (×3)</td>
<td>Creates, reads, and deletes vendors on demand</td>
</tr>
<tr>
<td>Database</td>
<td>DynamoDB</td>
<td>Stores vendor records with no idle cost</td>
</tr>
<tr>
<td>Storage</td>
<td>S3</td>
<td>Holds your built frontend files</td>
</tr>
<tr>
<td>Infrastructure</td>
<td>AWS CDK</td>
<td>Defines and deploys all of the above as code</td>
</tr>
</tbody></table>
<h2 id="heading-conclusion">Conclusion</h2>
<p>You have built and deployed the foundational pattern of almost every cloud application: a secured API backed by a database, deployed with infrastructure as code. Here is everything you accomplished:</p>
<p>You set up a professional AWS development environment with scoped IAM credentials. You defined your entire backend infrastructure as TypeScript code using AWS CDK, which means your database, API, Lambda functions, and authentication system are all version-controlled, repeatable, and deployable with a single command.</p>
<p>You wrote three Lambda functions that handle create, read, and delete operations, each with proper error handling and the correct AWS SDK v3 patterns. You connected them to a REST API through API Gateway and protected every route with Amazon Cognito authentication, so only registered, verified users can interact with your data.</p>
<p>On the frontend, you built a Next.js application with a service layer that cleanly separates API logic from UI components, manages JWTs automatically through AWS Amplify, and gives users a complete sign-up and sign-in flow without you writing a single line of authentication UI code.</p>
<p>Finally, you deployed the entire system: your backend to AWS Lambda and DynamoDB, and your frontend as a static site served globally through CloudFront over HTTPS.</p>
<p>The full source code for this tutorial is available on <a href="https://github.com/BenedictaUche/vendor-tracker">GitHub</a>. Clone it, modify it, and use it as a reference for your own projects.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ The Modern React Data Fetching Handbook: Suspense, use(), and ErrorBoundary Explained ]]>
                </title>
                <description>
                    <![CDATA[ Most React developers don’t break the data fetching process all at once. It usually degrades gradually, slowly. Traditionally, you may have used a useEffect here, a loading flag there, and an error state along with it to tackle data fetching. Moving ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/the-modern-react-data-fetching-handbook-suspense-use-and-errorboundary-explained/</link>
                <guid isPermaLink="false">698def824dcd2f8caf2d36ef</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ data ]]>
                    </category>
                
                    <category>
                        <![CDATA[ handbook ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Tapas Adhikary ]]>
                </dc:creator>
                <pubDate>Thu, 12 Feb 2026 15:19:30 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1770847474747/df826db8-4d18-45f5-a4aa-bfafa9c3aa80.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Most React developers don’t break the data fetching process all at once. It usually degrades gradually, slowly.</p>
<p>Traditionally, you may have used a <code>useEffect</code> here, a loading flag there, and an error state along with it to tackle data fetching. Moving forward, another fetch depended on the first one, then a second useEffect, and another loading and error state.</p>
<p>This likely continued until you started feeling like you were writing code that you yourself could’t even maintain in the future.</p>
<p>Requests that should run in parallel started running sequentially. Components re-rendered unnecessarily just to satisfy another data fetch request-response. Loading spinners appeared when nothing meaningfully changed. Error states got scattered inside the component.</p>
<p>Well, none of these things are React’s problem. These are core design problems you should be aware of while coding your React Apps.</p>
<p>In this handbook, we’ll walk through one React Pattern that fixes data fetching at the architecture level without ignoring real data dependencies, and without introducing any new magic. If data fetching in React has ever felt harder than it should be, this pattern will make even more sense to you.</p>
<p>You’ll learn how to use React’s <code>Suspense</code> with the recently introduced <code>use()</code> API to handle data fetching smoothly. In case of errors, you’ll learn how an <code>Error Boundary</code> can help handle them gracefully.</p>
<p>Give this one a read, and code along to get a better grip on this pattern’s mental model.</p>
<p>This handbook is also available as a video tutorial as part of the <a target="_blank" href="https://www.youtube.com/playlist?list=PLIJrr73KDmRyQVT__uFZvaVfWPdfyMFHC">15 Days of React Design Patterns</a> <a target="_blank" href="https://www.youtube.com/playlist?list=PLIJrr73KDmRyQVT__uFZvaVfWPdfyMFHC">initiative</a>. You can check it out if you’d like:</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/tY8rhkLdr2A" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<p> </p>
<p>We’ll use a lot of source code to demonstrate the problems with the traditional data-fetching approach and how the Suspense Pattern can improve things. I would suggest that you try the code as you read. But if you want to take a look at the source code ahead of time, you can find it on the <a target="_blank" href="https://github.com/tapascript/15-days-of-react-design-patterns/tree/main/day-16">tapaScript GitHub</a>.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><p><a class="post-section-overview" href="#heading-the-traditional-way-of-data-fetching-in-react">The Traditional Way of Data Fetching in React</a></p>
<ul>
<li><a class="post-section-overview" href="#heading-the-problem-with-the-traditional-way">The Problem with the Traditional Way</a></li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-lets-build-a-dashboard-with-the-traditional-data-fetching-approach">Let’s Build a Dashboard with the Traditional Data Fetching Approach</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-suspense">What is Suspense?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-the-use-api-in-react">What is the <code>use()</code> API in React?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-use-suspense-and-the-use-api-for-data-fetching">How to Use Suspense and the <code>use()</code> API for Data Fetching</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-lets-build-the-dashboard-with-suspense-and-the-use-api">Let’s Build the Dashboard with Suspense and the <code>use()</code> API</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-project-setup">Project Setup</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-api-services">API Services</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-create-a-centralised-user-resource">Create a Centralised User Resource</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-create-individual-components">Create Individual Components</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-create-the-fallback-ui">Create the Fallback UI</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-create-the-dashboard-component-with-suspense">Create the Dashboard Component with Suspense</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-run-the-dashboard-app">Run the Dashboard App</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-handle-error-scenarios-with-error-boundaries">How to Handle Error Scenarios with Error Boundaries?</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-error-boundary">Error Boundary</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-suspense-and-error-boundary">Suspense and Error Boundary</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-learn-from-the-15-days-of-react-design-patterns">Learn from the 15 Days of React Design Patterns</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-before-we-end">Before We End…</a></p>
</li>
</ol>
<h2 id="heading-the-traditional-way-of-data-fetching-in-react">The Traditional Way of Data Fetching in React</h2>
<p>To understand why data fetching can become painful in React, we first need to understand how React works under the hood.</p>
<p>React works in phases. It doesn’t do everything at once. At a high level, every update in React goes through three distinct phases:</p>
<ul>
<li><p><strong>Render phase</strong> – React figures out <em>what</em> the UI should look like</p>
</li>
<li><p><strong>Commit phase</strong> – React applies those changes to the DOM</p>
</li>
<li><p><strong>Effect phase</strong> – React synchronises with the outside world</p>
</li>
</ul>
<p>This separation is intentional. It’s what allows React to be predictable, interruptible, and efficient.</p>
<p>Now, let’s see where data fetching with <code>useEffect</code> fits into this picture:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770089281941/9a0dbc5d-6b45-4813-9e62-8bc4cbad82ea.png" alt="The useEffect Phases" class="image--center mx-auto" width="1642" height="781" loading="lazy"></p>
<p>Where does <code>useEffect</code> actually run? It doesn’t run during rendering. It runs after React has already committed the UI to the DOM.</p>
<p>That means the flow looks like this:</p>
<ol>
<li><p>React renders the component (without data)</p>
</li>
<li><p>React commits the UI</p>
</li>
<li><p>useEffect runs</p>
</li>
<li><p>Data fetching starts</p>
</li>
<li><p>State updates when the data arrives</p>
</li>
<li><p>React renders again</p>
</li>
</ol>
<pre><code class="lang-javascript">useEffect(<span class="hljs-function">() =&gt;</span> {
  fetchData().then(setData);
}, []);
</code></pre>
<p>Hence, the fetch only starts after the UI has already rendered.</p>
<h3 id="heading-the-problem-with-the-traditional-way">The Problem with the Traditional Way</h3>
<p>Consider a very common scenario: you fetch a user, and then fetch related data using the user ID.</p>
<p>The traditional React data fetching solution would look like this:</p>
<pre><code class="lang-javascript">useEffect(<span class="hljs-function">() =&gt;</span> {
  fetchUser().then(setUser);
}, []);

useEffect(<span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">if</span> (!user) <span class="hljs-keyword">return</span>;
  fetchOrders(user.id).then(setOrders);
}, [user]);
</code></pre>
<p>What actually happens is:</p>
<ul>
<li><p>The component renders</p>
</li>
<li><p>React commits the UI</p>
</li>
<li><p>The first effect runs and fetches the user</p>
</li>
<li><p>React re-renders</p>
</li>
<li><p>The second effect runs and fetches orders</p>
</li>
</ul>
<p>Even if the network is fast, the requests are forced to start one after another, because each fetch is triggered by a render that only happens after the previous fetch completes.</p>
<p>This paradigm of data fetching is called <code>Fetch-On-Render</code>. The fetching logic is no longer controlled by data dependencies – it’s controlled by render timing. But that’s not all. There are other problems with this approach: you create and maintain unnecessary states.</p>
<p>Now, let’s see both these problems in action by building something practical.</p>
<h2 id="heading-lets-build-a-dashboard-with-the-traditional-data-fetching-approach">Let’s Build a Dashboard with the Traditional Data Fetching Approach</h2>
<p>Let’s build a simple dashboard with the traditional data fetching approach using the <code>useEffect</code> hook at the center. The dashboard will have four primary sections:</p>
<ul>
<li><p>A Static heading.</p>
</li>
<li><p>A <code>Profile</code> section welcoming the user with their name.</p>
</li>
<li><p>An <code>Order</code> section listing the items ordered by the user.</p>
</li>
<li><p>An <code>Analytics</code> section showing a few metrics for the same user.</p>
</li>
</ul>
<p>You can visualise it like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770184122812/d8cd8d82-9f38-4ab6-b029-e2e68dbbd66a.png" alt="Dashboard" class="image--center mx-auto" width="806" height="919" loading="lazy"></p>
<p>The profile, order, and analytics sections should show the dynamic data of a user and their order and analytics. Hence, we’ll simulate three API calls to get the user details, order details, and the analytics data.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// API to fetch User</span>
<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">fetchUser</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve</span>) =&gt;</span> {
    <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> {
      resolve({ <span class="hljs-attr">id</span>: <span class="hljs-number">1</span>, <span class="hljs-attr">name</span>: <span class="hljs-string">"Tapas"</span> });
    }, <span class="hljs-number">1500</span>);
  });
}

<span class="hljs-comment">// API to fetch the Orders of a User</span>
<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">fetchOrders</span>(<span class="hljs-params">userId</span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =&gt;</span> {
    <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> {
      resolve([
          <span class="hljs-string">`Order A for user <span class="hljs-subst">${userId}</span>`</span>,
          <span class="hljs-string">`Order B for user <span class="hljs-subst">${userId}</span>`</span>
      ]);
    }, <span class="hljs-number">1500</span>);
  });
}

<span class="hljs-comment">// API to fetch the Analytics of a User</span>
<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">fetchAnalytics</span>(<span class="hljs-params">userId</span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve</span>) =&gt;</span> {
    <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> {
      resolve({
        <span class="hljs-attr">revenue</span>: <span class="hljs-string">"$12,000"</span>,
        <span class="hljs-attr">growth</span>: <span class="hljs-string">"18%"</span>,
        userId
      });
    }, <span class="hljs-number">1500</span>);
  });
}
</code></pre>
<p>As you can see in this code:</p>
<ul>
<li><p>Each of the API functions returns a promise.</p>
</li>
<li><p>There is an intentional delay of 1.5 seconds using setTimeout to simulate the feel of a network call. The promise resolves after the delay passes.</p>
</li>
<li><p>Once the promise gets resolved, we get the data.</p>
</li>
</ul>
<p>Now, let’s create the Dashboard component:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { fetchAnalytics, fetchOrders, fetchUser } <span class="hljs-keyword">from</span> <span class="hljs-string">"../api"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Dashboard</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">const</span> [user, setUser] = useState(<span class="hljs-literal">null</span>);
    <span class="hljs-keyword">const</span> [orders, setOrders] = useState(<span class="hljs-literal">null</span>);
    <span class="hljs-keyword">const</span> [analytics, setAnalytics] = useState(<span class="hljs-literal">null</span>);

    <span class="hljs-comment">// Step 1: Fetch user</span>
    useEffect(<span class="hljs-function">() =&gt;</span> {
        fetchUser().then(setUser);
    }, []);

    <span class="hljs-comment">// Step 2: Fetch orders (depends on user)</span>
    useEffect(<span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">if</span> (!user) <span class="hljs-keyword">return</span>;
        fetchOrders(user.id).then(setOrders);
    }, [user]);

    <span class="hljs-comment">// Step 3: Fetch analytics (depends on user)</span>
    useEffect(<span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">if</span> (!user) <span class="hljs-keyword">return</span>;
        fetchAnalytics(user.id).then(setAnalytics);
    }, [user]);

    <span class="hljs-comment">// Logic to ensure that user, orders, and analytics data </span>
    <span class="hljs-comment">// loaded before we render them on JSX</span>
    <span class="hljs-keyword">if</span> (!user || !orders || !analytics) {
        <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-xl m-3"</span>&gt;</span>Loading dashboard...<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span></span>;
    }

    <span class="hljs-keyword">return</span> (
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"m-2"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">header</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-5xl mb-12"</span>&gt;</span>📊 Dashboard<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">header</span>&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">h2</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-3xl"</span>&gt;</span>Welcome, {user.name}<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">h2</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-3xl mt-3"</span>&gt;</span>Orders<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>
                {orders.map((o) =&gt; (
                    <span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-xl"</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{o}</span>&gt;</span>
                        {o}
                    <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
                ))}
            <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">h2</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-3xl mt-3"</span>&gt;</span>Analytics<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-xl"</span>&gt;</span>Revenue: {analytics.revenue}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-xl"</span>&gt;</span>Growth: {analytics.growth}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
    );
}
</code></pre>
<p>Let’s break it down:</p>
<ul>
<li><p>The first thing you’ll notice is that we have three states for holding the data of users, orders, and analytics.</p>
</li>
<li><p>Then we have three <code>useEffect</code>s to manage the fetching of data and updating the states.</p>
</li>
<li><p>Then we show the data values in the JSX.</p>
</li>
<li><p>We’re using a <code>Fetch-On-Render</code> methodology.</p>
</li>
</ul>
<p>However, in between, there’s an explicit logic to check if the user data, order data, or analytics data has been loaded. If the data are not loaded, then we don’t even process the JSX – rather, we show a loading message.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Logic to ensure that user, orders, and analytics data </span>
<span class="hljs-comment">// loaded before we render them on JSX</span>
<span class="hljs-keyword">if</span> (!user || !orders || !analytics) {
  <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-xl m-3"</span>&gt;</span>Loading dashboard...<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span></span>;
}
</code></pre>
<p>This is good as a measure so that the UI doesn’t crash at runtime. But this is not a declarative approach. Since React is declarative, it would make more sense if we could handle this scenario in a declarative way as well.</p>
<p>In <code>Declarative programming</code>, you as a programmer don’t specify how to to solve certain problems. You declare what you want to achieve, and the programming language/framework takes care of the “how” part for you. When you specify the “how” part, it becomes <code>imperative</code>, not declarative.</p>
<p>React is declarative because you don’t specify how to update the browser DOM to render the UI changes. You declare them using JSX, and React takes care of it under the hood.</p>
<p>As an alternative to the explicit imperative logic like the above, you could also handle it using loading states. You could have loading states for profile, orders, and analytics. The loading states could decide when to show the data conditionally. But this approach needs additional state management and conditional rendering of JSX.</p>
<p>Along with these issues, think of handling errors! Again, you would need states for error handling and the conditional logic to show and hide the error messages. That’s too much to manage.</p>
<p>So, with the <code>useEffect</code> strategy, data fetching in React is not that effective. We need a better pattern to handle data along with loading states and errors.</p>
<p>But before we move on, I want to clarify that <code>useEffect</code> isn’t bad. It has a purpose, but sometimes we don’t use it as intended. If you’re someone who wants to learn the effective usages of this hook and how to debug it properly, you can <a target="_blank" href="https://www.youtube.com/watch?v=z2VnpJ6st-4">check out this session</a>.</p>
<h2 id="heading-what-is-suspense">What is Suspense?</h2>
<p>At its core, React <code>Suspense</code> is not a loading feature. It’s a rendering coordination mechanism. Suspense allows a component to tell React, “I’m not ready to be rendered, yet”. When that happens, React pauses rendering for that part of the tree and shows a fallback UI until the required data becomes available.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770193224537/fb10d206-11f0-4cdf-b984-bfcf07b19ae8.png" alt="Suspense Mechanism" class="image--center mx-auto" width="1296" height="1043" loading="lazy"></p>
<p>This is fundamentally different from how data fetching works with useEffect.</p>
<p>With the traditional <code>Fetch-On-Render</code> approach, React must first render a component before it’s allowed to start fetching data. Effects run after the commit phase, which means data fetching is always a reaction to rendering, never a prerequisite for it. As applications grow, this creates render-fetch-re-render loops, hidden waterfalls, and loading logic spread across components.</p>
<p>Suspense flips that model.</p>
<p>Instead of rendering first and fetching later, Suspense enables <code>Render-as-you-Fetch</code>. Data fetching can begin before React attempts to commit the UI, and rendering simply waits until the data is ready. The UI doesn’t guess when to show loading states. React coordinates it declaratively through Suspense boundaries.</p>
<p>With Suspense, you need to wrap the component that handles the asynchronous call.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770195686150/1160a43a-6be0-488a-aa19-315ac9f33985.png" alt="Suspense as Wrapper" class="image--center mx-auto" width="1137" height="472" loading="lazy"></p>
<p>Suspense can pause the rendering while the wrapped component is dealing with the promise. Suspense can show a fallback UI (it could be a loader, UI skeleton, and so on) until the promise is resolved (or rejected). Once the promise is resolved, Suspense replaces the fallback UI with the actual wrapped component baked with the data. No hard-coded logic, no extra state management is needed.</p>
<h2 id="heading-what-is-the-use-api-in-react">What is the <code>use()</code> API in React?</h2>
<p><code>use()</code> is an API introduced in React 19 that accepts a promise and returns its resolved value. If the promise hasn’t resolved yet, React doesn’t continue rendering. It suspends. If the promise fails, React throws an error. Both cases are handled declaratively by Suspense and Error Boundaries.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { use } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">fetchUser</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> fetch(<span class="hljs-string">"/api/user"</span>).then(<span class="hljs-function"><span class="hljs-params">res</span> =&gt;</span> res.json());
}

<span class="hljs-keyword">const</span> userPromise = fetchUser();

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Profile</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> user = use(userPromise);
  <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>Welcome, {user.name}<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span></span>;
}
</code></pre>
<p><strong>What’s important here:</strong></p>
<ul>
<li><p><code>use()</code> is called during render</p>
</li>
<li><p>If the promise is unresolved, rendering pauses</p>
</li>
<li><p>No <code>useEffect</code>, no loading state</p>
</li>
</ul>
<p><code>use()</code> is very powerful. It can read promises that depend on other promises.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> userPromise = fetch(<span class="hljs-string">"/api/user"</span>).then(<span class="hljs-function"><span class="hljs-params">r</span> =&gt;</span> r.json());

<span class="hljs-keyword">const</span> ordersPromise = userPromise.then(<span class="hljs-function"><span class="hljs-params">user</span> =&gt;</span>
  fetch(<span class="hljs-string">`/api/orders?userId=<span class="hljs-subst">${user.id}</span>`</span>).then(<span class="hljs-function"><span class="hljs-params">r</span> =&gt;</span> r.json())
);

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Orders</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> orders = use(ordersPromise);
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>
      {orders.map(o =&gt; <span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{o.id}</span>&gt;</span>{o.title}<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>)}
    <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span></span>
  );
}
</code></pre>
<p>Here:</p>
<ul>
<li><p>Dependencies are expressed in data, not effects</p>
</li>
<li><p>Rendering is coordinated automatically (declaratively)</p>
</li>
</ul>
<p>The primary mental model is: this render is not allowed to complete without this data. It suspends.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> data = use(promise);
</code></pre>
<p>We need to use Suspense to handle this gap (when the promise hasn’t resolved yet) using a fallback UI, and then to continue rendering once the promise is resolved.</p>
<h2 id="heading-how-to-use-suspense-and-the-use-api-for-data-fetching">How to Use Suspense and the <code>use()</code> API for Data Fetching</h2>
<p>The <code>use()</code> API is what finally makes Suspense practical for data fetching. Before <code>use()</code>, Suspense could pause rendering, but React didn’t have a clean way to consume asynchronous data during render without hacks. Most examples relied on custom abstractions or libraries to bridge that gap. <code>use()</code> changed that by allowing React components to read async values directly during rendering.</p>
<p>When a component reads data using <code>use(promise)</code>, React treats that promise as a render dependency. If the promise hasn’t resolved yet, React pauses rendering at the nearest Suspense boundary. When it resolves, React retries rendering automatically, without manual state updates, effects, or conditional logic.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { Suspense, use } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">const</span> userPromise = fetch(<span class="hljs-string">"/api/user"</span>).then(<span class="hljs-function"><span class="hljs-params">res</span> =&gt;</span> res.json());

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Profile</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> user = use(userPromise);
  <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>Welcome, {user.name}<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span></span>;
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Suspense</span> <span class="hljs-attr">fallback</span>=<span class="hljs-string">{</span>&lt;<span class="hljs-attr">p</span>&gt;</span>Loading profile...<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>}&gt;
      <span class="hljs-tag">&lt;<span class="hljs-name">Profile</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Suspense</span>&gt;</span></span>
  );
}
</code></pre>
<p><strong>What happens here:</strong></p>
<ul>
<li><p>Profile tries to read userPromise</p>
</li>
<li><p>If the promise is unresolved, React pauses rendering</p>
</li>
<li><p>React renders the nearest Suspense fallback</p>
</li>
<li><p>When the promise resolves, React retries rendering automatically</p>
</li>
</ul>
<p>Here, there are no effects, no loading flags, and no manual re-rendering.</p>
<p>Now, let’s see all these in action together by rebuilding the same Dashboard app.</p>
<h2 id="heading-lets-build-the-dashboard-with-suspense-and-the-use-api">Let’s Build the Dashboard with Suspense and the <code>use()</code> API</h2>
<p>Now that we have a better understanding of Suspense and <code>use()</code>, let’s rewrite the same Dashboard application with it.</p>
<h3 id="heading-project-setup">Project Setup</h3>
<p>First, you’ll need to create a React project scaffolding using <a target="_blank" href="https://vite.dev/guide/#scaffolding-your-first-vite-project">Vite</a>. You can use the following command to create a Vite-based React project with modern toolings:</p>
<pre><code class="lang-bash">npx degit atapas/code-in-react-19<span class="hljs-comment">#main suspense-patterns</span>
</code></pre>
<p>This will create a React 19 project with TailwindCSS configured.</p>
<p>Now use the <code>npm install</code> command to install the dependencies. This will create the node_modules folder for you. At this point, the directory structure should look like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770281531136/c34dcf94-7142-4033-9445-53b08d010a5e.png" alt="React 19 Project Directory Structure" class="image--center mx-auto" width="384" height="752" loading="lazy"></p>
<h3 id="heading-api-services">API Services</h3>
<p>Now under <code>src/</code>, create a new folder called <code>api/</code>. Then create an <code>index.js</code> file under <code>src/app/</code> with the following code snippet:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">fetchUser</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve</span>) =&gt;</span> {
    <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> {
      resolve({ <span class="hljs-attr">id</span>: <span class="hljs-number">1</span>, <span class="hljs-attr">name</span>: <span class="hljs-string">"Tapas"</span> });
    }, <span class="hljs-number">1500</span>);
  });
}

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">fetchOrders</span>(<span class="hljs-params">userId</span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =&gt;</span> {
    <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> {
      resolve([
          <span class="hljs-string">`Order A for user <span class="hljs-subst">${userId}</span>`</span>,
          <span class="hljs-string">`Order B for user <span class="hljs-subst">${userId}</span>`</span>
      ]);
    }, <span class="hljs-number">1500</span>);
  });
}

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">fetchAnalytics</span>(<span class="hljs-params">userId</span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve</span>) =&gt;</span> {
    <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> {
      resolve({
        <span class="hljs-attr">revenue</span>: <span class="hljs-string">"$12,000"</span>,
        <span class="hljs-attr">growth</span>: <span class="hljs-string">"18%"</span>,
        userId
      });
    }, <span class="hljs-number">1500</span>);
  });
}
</code></pre>
<p>These are the same APIs we used before when constructing the dashboard with <code>useEffect</code>.</p>
<ul>
<li><p><code>fetchUser</code>: For fetching the user’s profile</p>
</li>
<li><p><code>fetchOrders</code>: For fetching the orders made by a user</p>
</li>
<li><p><code>fetchAnalytics</code>: For fetching the analytics data of a user</p>
</li>
</ul>
<h3 id="heading-create-a-centralised-user-resource">Create a Centralised User Resource</h3>
<p>Now, let’s create a centralised JavaScript utility file where we can create each of the promises by calling their respective fetch methods. It’s a good practice to handle all the fetch APIs and their promises from a single place, rather than keeping them scattered. The same utility can export the promises so that we can consume them in the components.</p>
<p>Create a <code>resources/</code> folder under <code>src/</code>. Create a file <code>userResource.js</code> file under <code>src/resources/</code> with the following code:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { fetchAnalytics, fetchOrders, fetchUser } <span class="hljs-keyword">from</span> <span class="hljs-string">"../api"</span>;

<span class="hljs-keyword">let</span> userPromise;
<span class="hljs-keyword">let</span> ordersPromise;
<span class="hljs-keyword">let</span> analyticsPromise;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">createUserResources</span>(<span class="hljs-params"></span>) </span>{
  userPromise = fetchUser();

  ordersPromise = userPromise.then(<span class="hljs-function"><span class="hljs-params">user</span> =&gt;</span>
    fetchOrders(user.id)
  );

  analyticsPromise = userPromise.then(<span class="hljs-function"><span class="hljs-params">user</span> =&gt;</span>
    fetchAnalytics(user.id)
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getUserResources</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> {
    userPromise,
    ordersPromise,
    analyticsPromise
  };
}
</code></pre>
<p>Here, we export two functions:</p>
<ol>
<li><p>The <code>createUserResources()</code> creates all the promises and keeps them ready.</p>
</li>
<li><p>The <code>getUserResources()</code> returns all the promises we can consume later.</p>
</li>
</ol>
<p>Now, the question is, when will we create these promises? That is, where we will call the <code>createUserResources()</code> function? We should create these promises when the application starts up, and the <code>main.jsx</code> file would be the perfect place for that.</p>
<p>Open the main.jsx file, import <code>{createUserResources}</code>, and invoke it immediately.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> ReactDOM <span class="hljs-keyword">from</span> <span class="hljs-string">"react-dom/client"</span>;
<span class="hljs-keyword">import</span> App <span class="hljs-keyword">from</span> <span class="hljs-string">"./App.jsx"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"./index.css"</span>;

<span class="hljs-keyword">import</span> { createUserResources } <span class="hljs-keyword">from</span> <span class="hljs-string">"./resources/userResource.js"</span>;

createUserResources();

ReactDOM.createRoot(<span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"root"</span>)).render(
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">React.StrictMode</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">App</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">React.StrictMode</span>&gt;</span></span>,
);
</code></pre>
<p>Great! Our data fetching APIs and the promises are ready. Let’s create the components where we’ll be using these promises.</p>
<h3 id="heading-create-individual-components">Create Individual Components</h3>
<p>We’ll create three components to compose the dashboard: Profile, Orders, and Analytics.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770297241100/91d9c21b-afcb-40ee-a73d-34a2be0f8cad.png" alt="Component Hierarchy" class="image--center mx-auto" width="1291" height="582" loading="lazy"></p>
<p>Let’s start with the Profile component. Create a folder <code>components/</code> under the <code>src/</code>. Now, create a <code>Profile.jsx</code> with the following code:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { use } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { getUserResources } <span class="hljs-keyword">from</span> <span class="hljs-string">"../resources/userResource"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Profile</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">const</span> { userPromise } = getUserResources();
    <span class="hljs-keyword">const</span> user = use(userPromise);
    <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">h2</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-3xl"</span>&gt;</span>Welcome, {user.name}<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span></span>;
}
</code></pre>
<p>Let’s break it down:</p>
<ul>
<li><p>We imported the <code>use()</code> from React, as we’ll be dealing with the use promise here to handle it and get the user name to render.</p>
</li>
<li><p>Next, we need the user promise. We have the <code>getUserResources()</code> function to get that, so we imported it.</p>
</li>
<li><p>Then, inside the Profile component, we destructured <code>userPromise</code> from the <code>getUserResources()</code> function.</p>
</li>
<li><p>After that, we passed the promise to the <code>use()</code>. We have learned that the <code>use()</code> API accepts a promise and returns the result when it is resolved. Until then, the passed-in promise itself will be returned.</p>
</li>
<li><p>Finally, we used the resolved <code>user</code> to extract the name property and render it.</p>
</li>
</ul>
<p>Simple, right? Let’s quickly create the Orders and Analytics components.</p>
<p>The Orders component:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { use } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { getUserResources } <span class="hljs-keyword">from</span> <span class="hljs-string">"../resources/userResource"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Orders</span>(<span class="hljs-params"></span>) </span>{
   <span class="hljs-keyword">const</span> { ordersPromise } = getUserResources();
  <span class="hljs-keyword">const</span> orders = use(ordersPromise);

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h2</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-3xl mt-2"</span>&gt;</span>Orders<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>
        {orders.map((o) =&gt; (
          <span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-xl"</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{o}</span>&gt;</span>{o}<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
        ))}
      <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
    <span class="hljs-tag">&lt;/&gt;</span></span>
  );
}
</code></pre>
<p>It has the same flow as the Profile component.</p>
<p>Now, let’s do the Analytics component:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { use } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { getUserResources } <span class="hljs-keyword">from</span> <span class="hljs-string">"../resources/userResource"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Analytics</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">const</span> { analyticsPromise } = getUserResources();
    <span class="hljs-keyword">const</span> analytics = use(analyticsPromise);

    <span class="hljs-keyword">return</span> (
        <span class="xml"><span class="hljs-tag">&lt;&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">h2</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-3xl mt-2"</span>&gt;</span>Analytics<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-xl"</span>&gt;</span>Revenue: {analytics.revenue}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-xl"</span>&gt;</span>Growth: {analytics.growth}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
        <span class="hljs-tag">&lt;/&gt;</span></span>
    );
}
</code></pre>
<p>All three components are ready. Before we move further, let’s reflect once more on what we learned about <code>Suspense</code>.</p>
<p>Suspense wraps a component that deals with promises (async operations). Until the promise gets resolved, the Suspense holds the rendering and can show a fallback UI in the meantime. Once the promise gets resolved and we have the value, the fallback UI gets swapped with the actual component Suspense wrapped.</p>
<p>So, we have the ideal case now: wrapping the <code>&lt;Profile /&gt;</code>, <code>&lt;Orders/&gt;</code>, and <code>&lt;Analytics/&gt;</code> with the <code>&lt;Suspense&gt;…&lt;/Suspense&gt;</code> boundary to handle the promises and resolved data for each of the components.</p>
<p>Let’s do that, but aren’t we missing something? Yeah we are: the fallback UI. Let’s create it.</p>
<h3 id="heading-create-the-fallback-ui">Create the Fallback UI</h3>
<p>Now we’ll create three different fallback UI components. Create a file <code>Skeletons.jsx</code> under the <code>src/components/</code> with the following code:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> ProfileSkeleton = <span class="hljs-function">() =&gt;</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-3xl m-2"</span>&gt;</span>Loading user...<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span></span>;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> OrdersSkeleton = <span class="hljs-function">() =&gt;</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-3xl m-2"</span>&gt;</span>Loading orders...<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span></span>;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> AnalyticsSkeleton = <span class="hljs-function">() =&gt;</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-3xl m-2"</span>&gt;</span>Loading analytics...<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span></span>;
</code></pre>
<p>We now have a fallback skeleton UI for each of our components. These are very simple components that just render loading messages.</p>
<h3 id="heading-create-the-dashboard-component-with-suspense">Create the Dashboard Component with Suspense</h3>
<p>Now we have everything to make our Dashboard work. Create a <code>suspense/</code> folder under <code>src/</code>. Then create a <code>Dashboard.jsx</code> file under <code>src/suspense/</code> with the following code:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { Suspense } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">import</span> Analytics <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/Analytics"</span>;
<span class="hljs-keyword">import</span> Orders <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/Orders"</span>;
<span class="hljs-keyword">import</span> Profile <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/Profile"</span>;

<span class="hljs-keyword">import</span> {
    AnalyticsSkeleton,
    OrdersSkeleton,
    ProfileSkeleton,
} <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/Skeletons"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Dashboard</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">return</span> (
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"m-2"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">header</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-5xl mb-12"</span>&gt;</span>📊 Dashboard<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">header</span>&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">Suspense</span> <span class="hljs-attr">fallback</span>=<span class="hljs-string">{</span>&lt;<span class="hljs-attr">ProfileSkeleton</span> /&gt;</span>}&gt;
               <span class="hljs-tag">&lt;<span class="hljs-name">Profile</span> /&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">Suspense</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">Suspense</span> <span class="hljs-attr">fallback</span>=<span class="hljs-string">{</span>&lt;<span class="hljs-attr">OrdersSkeleton</span> /&gt;</span>}&gt;
               <span class="hljs-tag">&lt;<span class="hljs-name">Orders</span> /&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">Suspense</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">Suspense</span> <span class="hljs-attr">fallback</span>=<span class="hljs-string">{</span>&lt;<span class="hljs-attr">AnalyticsSkeleton</span> /&gt;</span>}&gt;
               <span class="hljs-tag">&lt;<span class="hljs-name">Analytics</span> /&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">Suspense</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
    );
}
</code></pre>
<p>First, let me explain the code:</p>
<ul>
<li><p>We imported Suspense from React, all the components, and all the fallback UI components.</p>
</li>
<li><p>Then we rendered a static header and three suspense boundaries for each of the components. We wrapped Profile, Orders, and Analytics with Suspense, respectively. To handle the pending promise state, we have passed the individual skeleton component as the fallback to the suspense.</p>
</li>
</ul>
<p>How clean is this? If you scroll up and recheck our old implementation of the dashboard using useEffect and compare it with the one we created with suspense, the positive differences are clear.</p>
<ul>
<li><p>It’s declarative.</p>
</li>
<li><p>There’s less code, with the chance of fewer bugs</p>
</li>
<li><p>There’s no effect management and synchronisations</p>
</li>
<li><p>There’s no conditional JSX</p>
</li>
</ul>
<p>It’s a huge win 🏆.</p>
<h3 id="heading-run-the-dashboard-app">Run the Dashboard App</h3>
<p>To run the dashboard app, import the dashboard component in the <code>App.jsx</code> file and use it like this:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> Dashboard <span class="hljs-keyword">from</span> <span class="hljs-string">"./suspense/Dashboard"</span>;
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">return</span> (
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex items-center justify-center gap-12"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">Dashboard</span> /&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
    );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<p>Next, open the terminal. Run the app using the <code>npm run dev</code> command. You get the same dashboard back, but it’s much improved:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770184122812/d8cd8d82-9f38-4ab6-b029-e2e68dbbd66a.png" alt="New dashboard" class="image--center mx-auto" width="806" height="919" loading="lazy"></p>
<ul>
<li><p>It loads the data of each of the sections independently.</p>
</li>
<li><p>Each of the sections shows the data loading indicator when the promise is pending.</p>
</li>
<li><p>It doesn’t block the entire UI.</p>
</li>
</ul>
<p>Suspense and <code>use()</code> together are very powerful. Now you have learned that powerful pattern end-to-end.</p>
<h2 id="heading-how-to-handle-error-scenarios-with-error-boundaries">How to Handle Error Scenarios with Error Boundaries</h2>
<p>This data fetching handbook wouldn’t be complete without talking about error scenarios and how to handle them. So far, we’ve spoken about only the happy path. But what if any of the promises are rejected? How do we handle that?</p>
<p>To understand this in depth, let’s reject one of the promises – say the Order promise. Open the <code>index.js</code> file under the <code>src/api/</code> folder and replace the <code>fetchOrder()</code> function with this updated code:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">fetchOrders</span>(<span class="hljs-params">userId</span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =&gt;</span> {
    <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> {
      <span class="hljs-comment">// Simulate failure</span>
      <span class="hljs-keyword">if</span> (<span class="hljs-built_in">Math</span>.random() &lt; <span class="hljs-number">0.5</span>) {
        reject(<span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"Failed to fetch orders"</span>));
      } <span class="hljs-keyword">else</span> {
        resolve([
          <span class="hljs-string">`Order A for user <span class="hljs-subst">${userId}</span>`</span>,
          <span class="hljs-string">`Order B for user <span class="hljs-subst">${userId}</span>`</span>
        ]);
      }
    }, <span class="hljs-number">1500</span>);
  });
}
</code></pre>
<p>Here, the changes are:</p>
<ul>
<li><p>We have simulated a failure by rejecting a promise.</p>
</li>
<li><p>The promise gets rejected randomly and throws an error with an error message.</p>
</li>
</ul>
<p>At this point, if you refresh the UI a few times, you’ll randomly get a blank broken UI with the error message logged into the browser console. This isn’t ideal. It kills the UX of the app.</p>
<p>A better way of handling would be to show the error message on the UI and provide a way to retry and check if the user can recover from the error.</p>
<p>This is where <code>Error Boundary</code> comes in.</p>
<h3 id="heading-error-boundary">Error Boundary</h3>
<p>Error Boundaries in React exist for a simple reason: Errors are inevitable, and we must handle them gracefully. There could be:</p>
<ul>
<li><p>Network requests fail</p>
</li>
<li><p>Data is malformed</p>
</li>
<li><p>The assumptions break</p>
</li>
</ul>
<p>Without boundaries, a single tiny rendering error can crash the entire React tree. Error Boundaries provide React with a structured way to handle failures.</p>
<p>Technically, an Error Boundary is a component that catches errors thrown during rendering. When an error occurs, React stops rendering the subtree and renders a fallback UI instead.</p>
<p>Let’s now create an Error Boundary. Create a file called <code>ErrorBoundary.jsx</code> under <code>src/components</code> with the following code:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { Component } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { createUserResources } <span class="hljs-keyword">from</span> <span class="hljs-string">"../resources/userResource"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ErrorBoundary</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Component</span> </span>{
  state = { <span class="hljs-attr">error</span>: <span class="hljs-literal">null</span> };

  <span class="hljs-keyword">static</span> getDerivedStateFromError(error) {
    <span class="hljs-keyword">return</span> { error };
  }

  handleRetry = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-built_in">this</span>.setState({ <span class="hljs-attr">error</span>: <span class="hljs-literal">null</span> });
    createUserResources();
  };

  render() {
    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.state.error) {
      <span class="hljs-keyword">return</span> (
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"border border-red-700 rounded p-1"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-xl"</span>&gt;</span>{this.state.error.message}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">button</span> 
                <span class="hljs-attr">className</span>=<span class="hljs-string">"bg-orange-400 rounded-xl p-1 text-black cursor-pointer"</span> 
                <span class="hljs-attr">onClick</span>=<span class="hljs-string">{this.handleRetry}</span>&gt;</span>
            Retry
          <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
      );
    }

    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.props.children;
  }
}
</code></pre>
<p>Now, let’s understand what’s going on in the code above:</p>
<ul>
<li><p>This is a class component that inherits from <code>React.Component</code>. That’s because Error Boundaries must use class lifecycle methods, which are not available in function components.</p>
</li>
<li><p>The component keeps track of whether an error has occurred. <code>state = { error: null }</code> means everything is rendering normally. When an error happens, this state will store the error object.</p>
</li>
<li><p>The <code>static getDerivedStateFromError()</code> is a special lifecycle method. React automatically calls it when a child component throws an error during render.</p>
</li>
<li><p>The <code>handleRetry()</code> method resets the error state back to null. It calls the <code>createUserResources()</code> to reinitialise the async resources.</p>
</li>
<li><p>In the <code>render()</code> method, if an error exists, render a fallback UI, show the error message, and provide an ability to retry the error using a retry button. If no error exists, render the <code>children</code> normally. The Error Boundary becomes invisible when everything works without an error. The fallback UI also can be an external component that we can pass as a prop to the Error Boundary.</p>
</li>
</ul>
<p>If you’re interested in diving deep into the <code>Error Boundary</code> pattern and want to learn various use cases of it, <a target="_blank" href="https://www.youtube.com/watch?v=0qxF4jb-eUg">here is a dedicated video</a> you can check out.</p>
<h3 id="heading-suspense-and-error-boundary">Suspense and Error Boundary</h3>
<p>Next, we’ll now use the Error Boundary to wrap each of the Suspense boundaries so that if an error originated from any of those, it can be managed. Open the <code>Dashboard.jsx</code> file and wrap each of the Suspense boundaries with the <code>ErrorBoundary</code> component as shown below:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { Suspense } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> Analytics <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/Analytics"</span>;
<span class="hljs-keyword">import</span> ErrorBoundary <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/ErrorBoundary"</span>;
<span class="hljs-keyword">import</span> Orders <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/Orders"</span>;
<span class="hljs-keyword">import</span> Profile <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/Profile"</span>;
<span class="hljs-keyword">import</span> {
    AnalyticsSkeleton,
    OrdersSkeleton,
    ProfileSkeleton,
} <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/Skeletons"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Dashboard</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">return</span> (
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"m-2"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">header</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-5xl mb-12"</span>&gt;</span>📊 Dashboard<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">header</span>&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">ErrorBoundary</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Suspense</span> <span class="hljs-attr">fallback</span>=<span class="hljs-string">{</span>&lt;<span class="hljs-attr">ProfileSkeleton</span> /&gt;</span>}&gt;
                    <span class="hljs-tag">&lt;<span class="hljs-name">Profile</span> /&gt;</span>
                <span class="hljs-tag">&lt;/<span class="hljs-name">Suspense</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">ErrorBoundary</span>&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">ErrorBoundary</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Suspense</span> <span class="hljs-attr">fallback</span>=<span class="hljs-string">{</span>&lt;<span class="hljs-attr">OrdersSkeleton</span> /&gt;</span>}&gt;
                    <span class="hljs-tag">&lt;<span class="hljs-name">Orders</span> /&gt;</span>
                <span class="hljs-tag">&lt;/<span class="hljs-name">Suspense</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">ErrorBoundary</span>&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">ErrorBoundary</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Suspense</span> <span class="hljs-attr">fallback</span>=<span class="hljs-string">{</span>&lt;<span class="hljs-attr">AnalyticsSkeleton</span> /&gt;</span>}&gt;
                    <span class="hljs-tag">&lt;<span class="hljs-name">Analytics</span> /&gt;</span>
                <span class="hljs-tag">&lt;/<span class="hljs-name">Suspense</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">ErrorBoundary</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
    );
}
</code></pre>
<p>That’s it. Now, access the dashboard on the browser. Whenever the order promise rejects, we’ll get the fallback error UI from the error boundary. Note, the remaining UI isn’t broken and rendered successfully. The partial failure of the UI is also recoverable, as we have provided a retry button to attempt to revive that portion. It provides a great UX.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770301723655/36fbba52-805b-4a23-8d18-9516a413160e.png" alt="Error Boundary with Suspense" class="image--center mx-auto" width="644" height="689" loading="lazy"></p>
<p>This is how the Suspense boundary, the <code>use()</code> API, and the Error Boundary work together to help you write scalable React code that can be maintained very easily in the future. I hope you found it helpful. All the source code used in this handbook is in the <a target="_blank" href="https://github.com/tapascript/15-days-of-react-design-patterns/tree/main/day-16">tapaScript GitHub Repository</a>.</p>
<h2 id="heading-learn-from-the-15-days-of-react-design-patterns">Learn from the 15 Days of React Design Patterns</h2>
<p>I have some great news for you: after my 40 days of JavaScript initiative, I have now completed a brand new initiative called <a target="_blank" href="https://www.youtube.com/playlist?list=PLIJrr73KDmRyQVT__uFZvaVfWPdfyMFHC">15 Days of React Design Patterns</a> (with Bonus Episodes).</p>
<p>If you enjoyed learning from this handbook, I’m sure you’ll love this series, featuring the 15+ most important React design patterns. Check it out, subscribe, and get it for free:</p>
<p><a target="_blank" href="https://www.youtube.com/playlist?list=PLIJrr73KDmRyQVT__uFZvaVfWPdfyMFHC"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770087890778/c89e0666-898d-448c-9d2c-443788fd4413.png" alt="15 days of React Design Patterns" class="image--center mx-auto" width="1562" height="894" loading="lazy"></a></p>
<h2 id="heading-before-we-end">Before We End…</h2>
<p>That’s all! I hope you found this insightful.</p>
<p><a target="_blank" href="https://github.com/tapascript/15-days-of-react-design-patterns/tree/main/day-03/compound-components-patterns">Let’s connect:</a></p>
<ul>
<li><p>Subscribe to my <a target="_blank" href="https://www.youtube.com/tapasadhikary?sub_confirmation=1">YouTube Channel</a>.</p>
</li>
<li><p>Check out my courses, <a target="_blank" href="https://www.youtube.com/playlist?list=PLIJrr73KDmRw2Fwwjt6cPC_tk5vcSICCu">40 Days of JavaScript</a>, <a target="_blank" href="https://www.youtube.com/playlist?list=PLIJrr73KDmRyQVT__uFZvaVfWPdfyMFHC">15 Days of React Design Patterns</a>, and <a target="_blank" href="https://www.youtube.com/playlist?list=PLIJrr73KDmRwT8Msc4H3_CP5Tf8MqqqVZ">Thinking in Debugging</a>.</p>
</li>
<li><p>Follow on <a target="_blank" href="https://www.linkedin.com/in/tapasadhikary/">LinkedIn</a> if you don't want to miss the daily dose of up-skilling tips.</p>
</li>
<li><p>Join my <a target="_blank" href="https://discord.gg/zHHXx4vc2H">Discord Server</a>, and let’s learn together.</p>
</li>
<li><p>Follow my work on <a target="_blank" href="https://github.com/tapascript">GitHub</a>.</p>
</li>
</ul>
<p>See you soon with my next article. Until then, please take care of yourself and keep learning.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Debug React State Updates Like a Pro (Without Polluting Production) ]]>
                </title>
                <description>
                    <![CDATA[ When you’re debugging a large React codebase, you might start to feel like a detective. Especially when you are looking for unexpected state changes, components that re-render when they like, or Context values that disappear into thin air without any... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-debug-react-state-updates-like-a-pro-without-polluting-production/</link>
                <guid isPermaLink="false">698115a622cd39b64c5fac49</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ debugging ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Frontend Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React state management ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Kelechi Apugo ]]>
                </dc:creator>
                <pubDate>Mon, 02 Feb 2026 21:22:46 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1770067225475/d0910306-5756-465a-8b6f-adf839fe004a.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>When you’re debugging a large React codebase, you might start to feel like a detective. Especially when you are looking for unexpected state changes, components that re-render when they like, or Context values that disappear into thin air without any prior warning sign.</p>
<p>And the main difficulty isn’t necessarily what went wrong – it’s pinpointing where it went wrong.</p>
<p>React offers powerful ways to change state, but it doesn’t specify who or what caused those changes. In large apps with many layers of components, hooks, and contexts, this lack of insight can turn simple bugs into frustrating, time-consuming puzzles.</p>
<p>This is where more innovative debugging methods become crucial. Before now, the go-to solution was to sprinkle <code>console.log</code> calls at key points or to fall back to DevTools.</p>
<p>But these days, you can write a small but powerful utility function that can catch the criminal involved in the crimes against your codebase. This utility function can log changes, display meaningful stack traces, and work smoothly with <code>useState</code>, <code>useReducer</code>, Context providers, and custom hooks. And all of the above can occur while remaining invisible in production.</p>
<p>This article guides you through how to use this helper function to improve clarity, minimise guesswork, and debug efficiently without affecting performance or code cleanliness in your live environment.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-the-problem">The Problem</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-why-this-problem-exists">Why This Problem Exists</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-my-solution-createdebugsetter">My Solution: <code>createDebugSetter</code></a></p>
<ul>
<li><a class="post-section-overview" href="#heading-practical-examples-of-createdebugsetter">Practical Examples of <code>createDebugSetter</code></a></li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-best-practices-for-using-createdebugsetter">Best Practices for using <code>createDebugSetter</code></a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-things-to-avoid">Things to Avoid</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-convert-createdebugsetter-to-a-hook">Bonus: How to Convert <code>createDebugSetter</code> to a Hook</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-the-problem">The Problem</h2>
<p>React’s state system is powerful, but it hides too much information when something goes wrong – for example, when an unexpected update happens or a component re-renders endlessly. React doesn’t tell you <em>what</em> triggered the update, <em>what</em> changed, or <em>why</em> it happened. This lack of visibility creates several challenges.</p>
<p>The first is that you can’t easily see which component, function, or effect initiated a state update. In large applications, where the same state may be modified from multiple places, this quickly turns debugging into guesswork. Without clear traces, developers often sprinkle <code>console.log</code> throughout their code to find the source of a single update.</p>
<p>Secondly, React lacks a built-in method for directly comparing previous and current values. This complicates diagnosing whether a bug stems from an incorrect calculation, a faulty API response, or erroneous business logic. The challenge increases with nested objects, arrays, or shared context.</p>
<p>Thirdly, Context updates can trigger re-renders across the entire tree, even for components wrapped in memoisation. But React doesn’t explain <em>why</em> a particular provider changed, leaving teams to wonder what triggered the cascade.</p>
<p>Finally, infinite loops caused by effects, unstable dependencies, or repeated setState calls provide no clues in the console. You only see symptoms like “loading…” repeating endlessly, with no indication of the source.</p>
<p>All of this makes debugging complex React apps frustrating, slow, and often misleading without additional tools or structured techniques.</p>
<h2 id="heading-why-this-problem-exists">Why This Problem Exists</h2>
<p>React intentionally conceals its internal update process to keep the framework fast and predictable.</p>
<p>Because of this:</p>
<ul>
<li><p><code>setState()</code> doesn’t report where it was called from</p>
</li>
<li><p>Context re-renders can originate from anywhere.</p>
</li>
<li><p>State overrides can happen silently.</p>
</li>
<li><p>Debugging often relies on manually adding console logs.</p>
</li>
</ul>
<p>In large applications, this lack of visibility makes it nearly impossible to trace unexpected state changes.</p>
<h2 id="heading-my-solution-createdebugsetter">My Solution: <code>createDebugSetter</code></h2>
<p>A small helper function, <code>createDebugSetter</code>, wraps your state setter and logs:</p>
<ul>
<li><p>The label of the state</p>
</li>
<li><p>The new value</p>
</li>
<li><p>A complete stack trace showing exactly where the update originated</p>
</li>
</ul>
<p>And best of all, it automatically disables itself in production using <code>NODE_ENV</code> so there’s no impact on your live app.</p>
<h3 id="heading-createdebugsetter">createDebugSetter</h3>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">createDebugSetter</span>(<span class="hljs-params">
  label: <span class="hljs-built_in">string</span>,
  setter:
    | React.Dispatch&lt;React.SetStateAction&lt;unknown&gt;&gt;
    | React.Dispatch&lt;unknown&gt;
</span>): <span class="hljs-title">React</span>.<span class="hljs-title">Dispatch</span>&lt;<span class="hljs-title">React</span>.<span class="hljs-title">SetStateAction</span>&lt;<span class="hljs-title">unknown</span>&gt;&gt; | <span class="hljs-title">React</span>.<span class="hljs-title">Dispatch</span>&lt;<span class="hljs-title">unknown</span>&gt; </span>{
  <span class="hljs-comment">// In production, return the original setter unchanged</span>
  <span class="hljs-keyword">if</span> (<span class="hljs-keyword">import</span>.meta.env.PROD <span class="hljs-comment">/* vite-react */</span>) {
    <span class="hljs-keyword">return</span> setter;
  }

  <span class="hljs-comment">// Create a wrapper that logs before calling the original setter</span>
  <span class="hljs-keyword">return</span> <span class="hljs-function">(<span class="hljs-params">value: React.SetStateAction&lt;unknown&gt; | unknown</span>) =&gt;</span> {
    <span class="hljs-comment">// Log the state change</span>
    <span class="hljs-built_in">console</span>.groupCollapsed(
      <span class="hljs-string">`%c🔄 [<span class="hljs-subst">${label}</span>] State Update`</span>,
      <span class="hljs-string">"color: #adad01; font-weight: bold;"</span>
    );
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"🆕 New value:"</span>, value);
    <span class="hljs-built_in">console</span>.trace(<span class="hljs-string">"📍 Update triggered from:"</span>);
    <span class="hljs-built_in">console</span>.groupEnd();

    <span class="hljs-comment">// Call the original setter</span>
    setter(value);
  };
}
</code></pre>
<p>The function above is a <strong>debugging wrapper for React state setters</strong> that logs during development.</p>
<p>It takes two parameters: a label for identification and the <code>setState</code> function you want to debug. In development mode, every state update triggers a collapsible console log that shows the new value and the stack trace of the update's origin. In production, the hook skips entirely and returns the original setter unchanged, ensuring zero runtime overhead in deployed applications.</p>
<p>How it does it:</p>
<pre><code class="lang-typescript"> <span class="hljs-comment">// In production, return the original setter unchanged</span>
  <span class="hljs-keyword">if</span> (<span class="hljs-keyword">import</span>.meta.env.PROD <span class="hljs-comment">/* vite-react */</span>) {
    <span class="hljs-keyword">return</span> setter;
  }
</code></pre>
<p>In production, the <code>createDebugSetter</code> function returns the React <code>setState</code> as-is. This is because we don’t want to log anything when our code is running in a production environment. Here, we’re using the <code>import.meta.env.PROD</code> from React-vite. It returns a <code>Boolean</code> value that tells us if it’s in the production environment or not.</p>
<p>If it’s not in production, we return the modified setter below.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Create a wrapper that logs before calling the original setter</span>
  <span class="hljs-keyword">return</span> <span class="hljs-function">(<span class="hljs-params">value: React.SetStateAction&lt;unknown&gt; | unknown</span>) =&gt;</span> {
    <span class="hljs-comment">// Log the state change</span>
    <span class="hljs-built_in">console</span>.groupCollapsed(
      <span class="hljs-string">`%c🔄 [<span class="hljs-subst">${label}</span>] State Update`</span>,
      <span class="hljs-string">"color: #adad01; font-weight: bold;"</span>
    );
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"🆕 New value:"</span>, value);
    <span class="hljs-built_in">console</span>.trace(<span class="hljs-string">"📍 Update triggered from:"</span>);
    <span class="hljs-built_in">console</span>.groupEnd();

    <span class="hljs-comment">// Call the original setter</span>
    setter(value);
  };
</code></pre>
<p>This new setter function first logs a collapsible console group with the label and emoji. Then it shows the new value being set. After that, it displays a stack trace showing where the update was triggered. Lastly, it calls the original setter to actually update the state.</p>
<h3 id="heading-practical-examples-of-createdebugsetter">Practical Examples of createDebugSetter</h3>
<p>Let’s now see how <code>createDebugSetter</code> can be used in several places within a codebase.</p>
<h4 id="heading-context-providers">Context Providers</h4>
<p>You can use <code>createDebugSetter</code> within a Context provider to log state changes when <code>setState</code> is called. This can help log and trace state changes whenever <code>setState</code> is called in a Context Provider anywhere in the application.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { createContext, useContext, useState, <span class="hljs-keyword">type</span> ReactNode } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { createDebugSetter } <span class="hljs-keyword">from</span> <span class="hljs-string">"../utils/createDebugSetter"</span>;

<span class="hljs-keyword">interface</span> User {
  name: <span class="hljs-built_in">string</span>;
  email: <span class="hljs-built_in">string</span>;
  role: <span class="hljs-built_in">string</span>;
}

<span class="hljs-keyword">interface</span> UserContextType {
  user: User | <span class="hljs-literal">null</span>;
  setUser: React.Dispatch&lt;React.SetStateAction&lt;User | <span class="hljs-literal">null</span>&gt;&gt;;
  login: <span class="hljs-function">(<span class="hljs-params">name: <span class="hljs-built_in">string</span>, email: <span class="hljs-built_in">string</span></span>) =&gt;</span> <span class="hljs-built_in">void</span>;
  logout: <span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">void</span>;
}

<span class="hljs-keyword">const</span> UserContext = createContext&lt;UserContextType | <span class="hljs-literal">undefined</span>&gt;(<span class="hljs-literal">undefined</span>);

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">UserProvider</span>(<span class="hljs-params">{ children }: { children: ReactNode }</span>) </span>{
  <span class="hljs-keyword">const</span> [user, setUserOriginal] = useState&lt;User | <span class="hljs-literal">null</span>&gt;(<span class="hljs-literal">null</span>);

  <span class="hljs-comment">// Wrap setter with debug functionality</span>
  <span class="hljs-keyword">const</span> setUser = createDebugSetter(
    <span class="hljs-string">"UserContext"</span>,
    setUserOriginal
  ) <span class="hljs-keyword">as</span> React.Dispatch&lt;React.SetStateAction&lt;User | <span class="hljs-literal">null</span>&gt;&gt;;

  <span class="hljs-keyword">const</span> login = <span class="hljs-function">(<span class="hljs-params">name: <span class="hljs-built_in">string</span>, email: <span class="hljs-built_in">string</span></span>) =&gt;</span> {
    setUser({
      name,
      email,
      role: <span class="hljs-string">"user"</span>,
    });
  };

  <span class="hljs-keyword">const</span> logout = <span class="hljs-function">() =&gt;</span> {
    setUser(<span class="hljs-literal">null</span>);
  };

  <span class="hljs-keyword">return</span> (
    &lt;UserContext.Provider value={{ user, setUser, login, logout }}&gt;
      {children}
    &lt;/UserContext.Provider&gt;
  );
}
</code></pre>
<p>In the above code sample, we create a modified <code>setUserOriginal</code> called <code>setUser</code> that uses <code>createDebugSetter</code> under the hood. We then expose it to the context value instead of <code>setUserOriginal</code> .</p>
<p>Whenever <code>setUser</code> is called, it triggers <code>createDebugSetter</code> which does its job of checking the environment the code is running in, and returns a modified setter that will call <code>setUserOriginal</code> after the logging process, or will return <code>setUserOriginal</code> as-is.</p>
<p>This is useful because Context updates can trigger many re-renders. This reveals exactly who changed the shared state.</p>
<h4 id="heading-usestate">useState</h4>
<p>As you saw in the Context provider example above, we can use the same technique in regular components that use React state setters (just as in Context providers). We log and trace the value. It also shows where it was triggered from within the component or application.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { useDebugSetter } <span class="hljs-keyword">from</span> <span class="hljs-string">"../hooks/useDebugSetter"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">UseStateExample</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [count, setCountOriginal] = useState(<span class="hljs-number">0</span>);
  <span class="hljs-keyword">const</span> [name, setNameOriginal] = useState(<span class="hljs-string">"React"</span>);

  <span class="hljs-comment">// Wrap setters with debug functionality</span>
  <span class="hljs-keyword">const</span> setCount = useDebugSetter(<span class="hljs-string">"Counter"</span>, setCountOriginal);
  <span class="hljs-keyword">const</span> setName = useDebugSetter(<span class="hljs-string">"Name"</span>, setNameOriginal);

  <span class="hljs-keyword">const</span> handleIncrement = <span class="hljs-function">() =&gt;</span> {
    setCount(count + <span class="hljs-number">1</span>);
  };

  <span class="hljs-keyword">const</span> handleDecrement = <span class="hljs-function">() =&gt;</span> {
    setCount(count - <span class="hljs-number">1</span>);
  };

  <span class="hljs-keyword">const</span> handleNameChange = <span class="hljs-function">() =&gt;</span> {
    setName(name === <span class="hljs-string">"React"</span> ? <span class="hljs-string">"Vite"</span> : <span class="hljs-string">"React"</span>);
  };

  <span class="hljs-keyword">return</span> (
    &lt;div
      style={{
        padding: <span class="hljs-string">"20px"</span>,
        border: <span class="hljs-string">"1px solid #ccc"</span>,
        borderRadius: <span class="hljs-string">"8px"</span>,
        margin: <span class="hljs-string">"10px"</span>,
      }}
    &gt;
      &lt;h2&gt;useState Example&lt;/h2&gt;
      &lt;p&gt;Open the <span class="hljs-built_in">console</span> to see debug logs when state changes.&lt;/p&gt;

      &lt;div style={{ marginTop: <span class="hljs-string">"15px"</span> }}&gt;
        &lt;p&gt;
          Count: &lt;strong&gt;{count}&lt;/strong&gt;
        &lt;/p&gt;
        &lt;button onClick={handleIncrement} style={{ marginRight: <span class="hljs-string">"10px"</span> }}&gt;
          Increment
        &lt;/button&gt;
        &lt;button onClick={handleDecrement}&gt;Decrement&lt;/button&gt;
      &lt;/div&gt;

      &lt;div style={{ marginTop: <span class="hljs-string">"15px"</span> }}&gt;
        &lt;p&gt;
          Name: &lt;strong&gt;{name}&lt;/strong&gt;
        &lt;/p&gt;
        &lt;button onClick={handleNameChange}&gt;Toggle Name&lt;/button&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p>This works exactly like the Context providers example. The only differences are that the component uses the <code>setCount</code> and <code>setName</code> functions out of the box in buttons and related components. Also, unlike the Context provider, this component has local state that can be passed to its child components if needed.</p>
<p>This is ideal for monitoring unforeseen local state changes or loops triggered by effects.</p>
<h4 id="heading-usereducer">useReducer</h4>
<p>React reducers are used to calculate complex logic before updating the state. This can introduce unwanted side effects during the complex phase. <code>createDebugSetter</code> can help in debugging, as shown below:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useReducer } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { createDebugSetter } <span class="hljs-keyword">from</span> <span class="hljs-string">"../utils/createDebugSetter"</span>;

<span class="hljs-keyword">interface</span> CounterState {
  count: <span class="hljs-built_in">number</span>;
  step: <span class="hljs-built_in">number</span>;
}

<span class="hljs-keyword">type</span> CounterAction =
  | { <span class="hljs-keyword">type</span>: <span class="hljs-string">"increment"</span> }
  | { <span class="hljs-keyword">type</span>: <span class="hljs-string">"decrement"</span> }
  | { <span class="hljs-keyword">type</span>: <span class="hljs-string">"reset"</span> }
  | { <span class="hljs-keyword">type</span>: <span class="hljs-string">"setStep"</span>; step: <span class="hljs-built_in">number</span> };

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">counterReducer</span>(<span class="hljs-params">
  state: CounterState,
  action: CounterAction
</span>): <span class="hljs-title">CounterState</span> </span>{
  <span class="hljs-keyword">switch</span> (action.type) {
    <span class="hljs-keyword">case</span> <span class="hljs-string">"increment"</span>:
      <span class="hljs-keyword">return</span> { ...state, count: state.count + state.step };
    <span class="hljs-keyword">case</span> <span class="hljs-string">"decrement"</span>:
      <span class="hljs-keyword">return</span> { ...state, count: state.count - state.step };
    <span class="hljs-keyword">case</span> <span class="hljs-string">"reset"</span>:
      <span class="hljs-keyword">return</span> { ...state, count: <span class="hljs-number">0</span> };
    <span class="hljs-keyword">case</span> <span class="hljs-string">"setStep"</span>:
      <span class="hljs-keyword">return</span> { ...state, step: action.step };
    <span class="hljs-keyword">default</span>:
      <span class="hljs-keyword">return</span> state;
  }
}

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">UseReducerExample</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [state, dispatchOriginal] = useReducer(counterReducer, {
    count: <span class="hljs-number">0</span>,
    step: <span class="hljs-number">1</span>,
  });

  <span class="hljs-comment">// Wrap dispatch with debug functionality</span>
  <span class="hljs-keyword">const</span> dispatch = createDebugSetter(<span class="hljs-string">"CounterReducer"</span>, dispatchOriginal);

  <span class="hljs-keyword">return</span> (
    &lt;div
      style={{
        padding: <span class="hljs-string">"20px"</span>,
        border: <span class="hljs-string">"1px solid #ccc"</span>,
        borderRadius: <span class="hljs-string">"8px"</span>,
        margin: <span class="hljs-string">"10px"</span>,
      }}
    &gt;
      &lt;h2&gt;useReducer Example&lt;/h2&gt;
      &lt;p&gt;Open the <span class="hljs-built_in">console</span> to see debug logs <span class="hljs-keyword">for</span> reducer actions.&lt;/p&gt;

      &lt;div style={{ marginTop: <span class="hljs-string">"15px"</span> }}&gt;
        &lt;p&gt;
          Count: &lt;strong&gt;{state.count}&lt;/strong&gt;
        &lt;/p&gt;
        &lt;p&gt;
          Step: &lt;strong&gt;{state.step}&lt;/strong&gt;
        &lt;/p&gt;

        &lt;div style={{ marginTop: <span class="hljs-string">"10px"</span> }}&gt;
          &lt;button
            onClick={<span class="hljs-function">() =&gt;</span> dispatch({ <span class="hljs-keyword">type</span>: <span class="hljs-string">"increment"</span> })}
            style={{ marginRight: <span class="hljs-string">"10px"</span> }}
          &gt;
            Increment (+{state.step})
          &lt;/button&gt;
          &lt;button
            onClick={<span class="hljs-function">() =&gt;</span> dispatch({ <span class="hljs-keyword">type</span>: <span class="hljs-string">"decrement"</span> })}
            style={{ marginRight: <span class="hljs-string">"10px"</span> }}
          &gt;
            Decrement (-{state.step})
          &lt;/button&gt;
          &lt;button
            onClick={<span class="hljs-function">() =&gt;</span> dispatch({ <span class="hljs-keyword">type</span>: <span class="hljs-string">"reset"</span> })}
            style={{ marginRight: <span class="hljs-string">"10px"</span> }}
          &gt;
            Reset
          &lt;/button&gt;
          &lt;button
            onClick={<span class="hljs-function">() =&gt;</span>
              dispatch({ <span class="hljs-keyword">type</span>: <span class="hljs-string">"setStep"</span>, step: state.step === <span class="hljs-number">1</span> ? <span class="hljs-number">5</span> : <span class="hljs-number">1</span> })
            }
          &gt;
            Toggle Step ({state.step === <span class="hljs-number">1</span> ? <span class="hljs-string">"1→5"</span> : <span class="hljs-string">"5→1"</span>})
          &lt;/button&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p><code>dispatchOriginal</code>, which is the main dispatch function, is replaced with a custom function called <code>dispatch</code> that uses <code>createDebugSetter</code>. When the custom <code>dispatch</code> function is called, it does the job of <code>createDebugSetter</code> and by extension, the job of <code>dispatchOriginal</code> .</p>
<p>This is perfect for logging reducer actions and understanding complex state transitions.</p>
<h4 id="heading-custom-hooks">Custom Hooks</h4>
<p>Custom hooks are not left out of the equation, as they can use <code>setState</code> in some cases. They’re also capable of running complex logic that could backfire when updating <code>state</code>.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useState, useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { useDebugSetter } <span class="hljs-keyword">from</span> <span class="hljs-string">"../hooks/useDebugSetter"</span>;

<span class="hljs-comment">// Custom hook that manages a timer</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">useTimer</span>(<span class="hljs-params">initialSeconds: <span class="hljs-built_in">number</span> = 0</span>) </span>{
  <span class="hljs-keyword">const</span> [seconds, setSecondsOriginal] = useState(initialSeconds);
  <span class="hljs-keyword">const</span> [isRunning, setIsRunningOriginal] = useState(<span class="hljs-literal">false</span>);

  <span class="hljs-comment">// Wrap setters with debug functionality</span>
  <span class="hljs-keyword">const</span> setSeconds = useDebugSetter(<span class="hljs-string">"Timer.seconds"</span>, setSecondsOriginal);
  <span class="hljs-keyword">const</span> setIsRunning = useDebugSetter(<span class="hljs-string">"Timer.isRunning"</span>, setIsRunningOriginal);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">if</span> (!isRunning) <span class="hljs-keyword">return</span>;

    <span class="hljs-keyword">const</span> interval = <span class="hljs-built_in">setInterval</span>(<span class="hljs-function">() =&gt;</span> {
      setSeconds(<span class="hljs-function">(<span class="hljs-params">prev</span>) =&gt;</span> prev + <span class="hljs-number">1</span>);
    }, <span class="hljs-number">1000</span>);

    <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">clearInterval</span>(interval);
  }, [isRunning, setSeconds]);

  <span class="hljs-keyword">const</span> start = <span class="hljs-function">() =&gt;</span> setIsRunning(<span class="hljs-literal">true</span>);
  <span class="hljs-keyword">const</span> stop = <span class="hljs-function">() =&gt;</span> setIsRunning(<span class="hljs-literal">false</span>);
  <span class="hljs-keyword">const</span> reset = <span class="hljs-function">() =&gt;</span> {
    setSeconds(<span class="hljs-number">0</span>);
    setIsRunning(<span class="hljs-literal">false</span>);
  };

  <span class="hljs-keyword">return</span> {
    seconds,
    isRunning,
    start,
    stop,
    reset,
  };
}

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">CustomHookExample</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> timer = useTimer(<span class="hljs-number">0</span>);

  <span class="hljs-keyword">const</span> formatTime = <span class="hljs-function">(<span class="hljs-params">seconds: <span class="hljs-built_in">number</span></span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> mins = <span class="hljs-built_in">Math</span>.floor(seconds / <span class="hljs-number">60</span>);
    <span class="hljs-keyword">const</span> secs = seconds % <span class="hljs-number">60</span>;
    <span class="hljs-keyword">return</span> <span class="hljs-string">`<span class="hljs-subst">${mins.toString().padStart(<span class="hljs-number">2</span>, <span class="hljs-string">"0"</span>)}</span>:<span class="hljs-subst">${secs
      .toString()
      .padStart(<span class="hljs-number">2</span>, <span class="hljs-string">"0"</span>)}</span>`</span>;
  };

  <span class="hljs-keyword">return</span> (
    &lt;div
      style={{
        padding: <span class="hljs-string">"20px"</span>,
        border: <span class="hljs-string">"1px solid #ccc"</span>,
        borderRadius: <span class="hljs-string">"8px"</span>,
        margin: <span class="hljs-string">"10px"</span>,
      }}
    &gt;
      &lt;h2&gt;Custom Hook Example&lt;/h2&gt;
      &lt;p&gt;Open the <span class="hljs-built_in">console</span> to see debug logs <span class="hljs-keyword">for</span> internal hook state changes.&lt;/p&gt;

      &lt;div style={{ marginTop: <span class="hljs-string">"15px"</span> }}&gt;
        &lt;p style={{ fontSize: <span class="hljs-string">"24px"</span>, fontWeight: <span class="hljs-string">"bold"</span> }}&gt;
          {formatTime(timer.seconds)}
        &lt;/p&gt;
        &lt;p&gt;
          Status: &lt;strong&gt;{timer.isRunning ? <span class="hljs-string">"Running"</span> : <span class="hljs-string">"Stopped"</span>}&lt;/strong&gt;
        &lt;/p&gt;

        &lt;div style={{ marginTop: <span class="hljs-string">"15px"</span> }}&gt;
          &lt;button
            onClick={timer.start}
            disabled={timer.isRunning}
            style={{ marginRight: <span class="hljs-string">"10px"</span> }}
          &gt;
            Start
          &lt;/button&gt;
          &lt;button
            onClick={timer.stop}
            disabled={!timer.isRunning}
            style={{ marginRight: <span class="hljs-string">"10px"</span> }}
          &gt;
            Stop
          &lt;/button&gt;
          &lt;button onClick={timer.reset}&gt;Reset&lt;/button&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p>As shown in previous examples, <code>setSecondsOriginal</code> and <code>setIsRunningOriginal</code> are replaced with <code>setSeconds</code> and <code>setIsRunning</code>. The latter uses the <code>createDebugSetter</code> helper function. This enables console log statements to be printed every second for better visualisation.</p>
<p>Custom hooks often hide multiple internal updates, making it hard to see exactly where each begins.</p>
<h2 id="heading-best-practices-for-using-createdebugsetter">Best Practices for Using <code>createDebugSetter</code></h2>
<p>When using helper functions like <code>createDebugSetter</code>, it’s best to keep in mind why you’re actually using them. For our purpose here, we’re using it to debug a React application. So I’ll share some tips that will help with this debugging process.</p>
<h3 id="heading-use-clear-labels">Use Clear Labels</h3>
<p>Using labels that can say where <code>createDebugSetter</code> was triggered from is a step in the right direction. Detailed labels will help you better understand where and why the issue may be occurring. Also, keep in mind that the <code>createDebugSetter</code> utility function could be used in several places in your application, and improper labelling could make debugging difficult.  </p>
<p>Using the name of the component or area that calls it as the label for <code>createDebugSetter</code> can also be a good pointer for clear labelling, as shown below.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Bad</span>
createDebugSetter(<span class="hljs-string">"aaa"</span>, setUser)
createDebugSetter(<span class="hljs-string">"1"</span>, setUser)

<span class="hljs-comment">// Good </span>
createDebugSetter(<span class="hljs-string">"UserContextProvider"</span>, setUser)
createDebugSetter(<span class="hljs-string">"From UserContextProvider"</span>, setUser)
<span class="hljs-comment">// Too long but can still work</span>
createDebugSetter(<span class="hljs-string">"From UserContextProvider in user-context.tsx file"</span>, setUser)
</code></pre>
<h3 id="heading-use-createdebugsetter-only-in-dev-mode">Use <code>createDebugSetter</code> Only in Dev Mode</h3>
<p>Using <code>createDebugSetter</code> only in a development environment can prevent many headaches. It’s not a good practice to mistakenly expose or log sensitive data in production. Also, logging in production can cause cluttering.</p>
<h3 id="heading-use-createdebugsetter-with-react-devtools">Use <code>createDebugSetter</code> with React DevTools</h3>
<p><strong>The</strong> <code>createDebugSetter</code> may not be enough for some complex bugs. You can use <code>createDebugSetter</code> and React DevTools for a more powerful/thorough debugging session<strong>.</strong> Although <code>createDebugSetter</code> cannot be directly integrated with React DevTools, it shows who triggered the update, whereas React DevTools displays what was re-rendered.</p>
<h3 id="heading-place-createdebugsetter-in-utils">Place <code>createDebugSetter</code> in <code>utils</code></h3>
<p><code>createDebugSetter</code> is a utility function, as I have mentioned above. This means you should place it in a <code>utils</code> folder so any team member can access and use it when needed across your React application.</p>
<h2 id="heading-things-to-avoid">Things to Avoid</h2>
<ol>
<li><p>Avoid using debug setters in production builds. While they are safe, unnecessary logs can slow down debugging tools. Also, sensitive credentials could be logged mistakenly. There are professional tools you can use, such as Sentry, that let you trace errors and debug your app effortlessly.</p>
</li>
<li><p>Don’t conditionally wrap setter functions within components. Perform wrapping outside renders to prevent the creation of new setter identities.</p>
</li>
<li><p>Don’t rely on it to replace proper state architecture. This tool helps identify issues, but doesn’t fix poor state design.</p>
</li>
<li><p>Don’t depend solely on console logs. Use it as part of a broader debugging workflow, not as the only strategy.</p>
</li>
</ol>
<h2 id="heading-bonus-how-to-convert-createdebugsetter-to-a-hook">Bonus: How to Convert <code>createDebugSetter</code> to a Hook</h2>
<h3 id="heading-converting-to-a-hook">Converting to a Hook</h3>
<p>The plain <code>createDebugSetter</code> function works, but it creates a new wrapper function on every render when used inside React components. By converting it into a custom hook with <code>useCallback</code>, we can ensure that the wrapper function maintains a stable reference across re-renders, preventing unnecessary performance overhead and making it safe to use in dependency arrays.</p>
<p>Here’s the hook version:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useCallback } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">useDebugSetter</span>&lt;<span class="hljs-title">T</span>&gt;(<span class="hljs-params">
  label: <span class="hljs-built_in">string</span>,
  setState: React.Dispatch&lt;React.SetStateAction&lt;T&gt;&gt;
</span>): <span class="hljs-title">React</span>.<span class="hljs-title">Dispatch</span>&lt;<span class="hljs-title">React</span>.<span class="hljs-title">SetStateAction</span>&lt;<span class="hljs-title">T</span>&gt;&gt; </span>{
  <span class="hljs-keyword">const</span> debugSetter = useCallback(
    <span class="hljs-function">(<span class="hljs-params">newValue: React.SetStateAction&lt;T&gt;</span>) =&gt;</span> {
      <span class="hljs-comment">// Only log in development</span>
      <span class="hljs-keyword">if</span> (!<span class="hljs-keyword">import</span>.meta.env.PROD) {
        <span class="hljs-built_in">console</span>.groupCollapsed(
          <span class="hljs-string">`%c🔄 State Update: <span class="hljs-subst">${label}</span>`</span>,
          <span class="hljs-string">"color: #2fa; font-weight: bold;"</span>
        );
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"🆕 New value:"</span>, newValue);
        <span class="hljs-built_in">console</span>.trace(<span class="hljs-string">"📍 Update triggered from:"</span>);
        <span class="hljs-built_in">console</span>.groupEnd();
      }

      setState(newValue);
    },
    [label, setState]
  );

  <span class="hljs-comment">// In production, return the original setter (no wrapping overhead)</span>
  <span class="hljs-comment">// In development, return the debug wrapper</span>
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">import</span>.meta.env.PROD ? setState : debugSetter;
}
</code></pre>
<h3 id="heading-how-the-hook-version-works">How the Hook Version Works</h3>
<p>The core difference between the <code>useDebugSetter</code> hook and <code>createDebugSetter</code> is that the function is wrapped in a <code>useCallback</code> that logs debug information before calling the original setter. Apart from this, all other components of the functions remain the same.</p>
<h3 id="heading-why-the-hook-version-is-better">Why the Hook Version is Better</h3>
<p>The hook version is superior for component usage because it leverages <code>useCallback</code> memoisation of the debug wrapper. This means the function reference stays the same across renders, avoiding potential re-render cascades when the setter is passed to child components or used in <code>useEffect</code> dependencies.</p>
<p>The plain function, by contrast, generates a brand new wrapper on every render, which can break React's optimisation strategies and cause subtle bugs. In production, both versions simply return the original setter, so there's no performance difference there – but in development, the hook prevents unnecessary work.</p>
<h3 id="heading-when-to-use-the-hook">When to Use the Hook</h3>
<p>Use <code>useDebugSetter</code> whenever you're inside a React component and need to debug state updates. This covers the vast majority of cases: wrapping <code>useState</code> setters, passing debug setters to child components, or including them in effect dependencies.</p>
<p>Only reach for the plain <code>createDebugSetter</code> function when you're working outside React components entirely, such as in utility modules, global stores, or configuration files where hooks can't be used. For day-to-day component debugging, the hook is the right choice.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Debugging React state doesn’t have to be guesswork. With a simple helper, you can instantly see what changed, who changed it, where the change originated, and how your app reached that state – all without touching your production environment.</p>
<p>This small utility function can save hours spent searching through your codebase, making you faster, more precise, and more confident in your React application’s behaviour.</p>
<p>Once you adopt this approach, you’ll never debug state the old way again. 🚀</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Use the tailwind-sidebar NPM Package in Your React and Next.js Apps ]]>
                </title>
                <description>
                    <![CDATA[ These days, developers are increasingly preferring utility-first CSS frameworks like Tailwind CSS to help them build fast, scalable, and highly customizable user interfaces. In this article, you’ll learn what the tailwind-sidebar NPM package is, how ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-use-tailwind-sidebar-npm-package-in-react-nextjs/</link>
                <guid isPermaLink="false">6967d8278e420016a8b8a729</guid>
                
                    <category>
                        <![CDATA[ Tailwind sidebar ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Tailwind CSS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ npm packages ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Next.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Frontend Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Hitesh Chauhan ]]>
                </dc:creator>
                <pubDate>Wed, 14 Jan 2026 17:53:43 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1768413200090/f31cbba6-9b9e-4719-bc07-13fe98049d52.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>These days, developers are increasingly preferring utility-first CSS frameworks like Tailwind CSS to help them build fast, scalable, and highly customizable user interfaces.</p>
<p>In this article, you’ll learn what the <code>tailwind-sidebar</code> NPM package is, how it works internally, and how to install and configure it in a real project. We’ll walk through setting up a responsive sidebar using Tailwind CSS, explore its key features with practical examples, and see how you can customize and control the sidebar behavior to fit different layouts and screen sizes.</p>
<p>If you’re building a React or Next.js application and want a lightweight yet powerful sidebar solution, <a target="_blank" href="https://www.npmjs.com/package/tailwind-sidebar"><strong>tailwind-sidebar</strong></a> is an excellent choice.</p>
<h2 id="heading-table-of-contents"><strong>Table of Contents</strong></h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-introduction-to-the-tailwind-sidebar-npm-package">Introduction to the tailwind-sidebar NPM Package</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-why-choose-tailwind-sidebar">Why Choose Tailwind Sidebar?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-get-started-with-tailwind-sidebar">How to Get Started with Tailwind Sidebar</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-features-of-tailwind-sidebar">Features of Tailwind Sidebar:</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-wrapping-up">Wrapping Up</a></p>
</li>
</ul>
<h2 id="heading-prerequisites"><strong>Prerequisites</strong></h2>
<ul>
<li><p>Basic knowledge of JavaScript (ES6+)</p>
</li>
<li><p>Familiarity with React fundamentals (components, props, JSX)</p>
</li>
<li><p>Basic understanding of Next.js project structure and routing</p>
</li>
<li><p>Experience using npm or yarn for package installation</p>
</li>
<li><p>Working knowledge of Tailwind CSS</p>
</li>
</ul>
<h2 id="heading-introduction-to-the-tailwind-sidebar-npm-package">Introduction to the tailwind-sidebar NPM Package</h2>
<p>The tailwind-sidebar NPM package is a modern, developer-friendly sidebar component that’s built entirely with Tailwind CSS. It’s designed to help developers create responsive, customizable, and accessible sidebars without the overhead of heavy UI frameworks.</p>
<h3 id="heading-understanding-tailwind-sidebar">Understanding Tailwind Sidebar</h3>
<p><code>tailwind-sidebar</code> is a lightweight utility package designed to simplify building responsive sidebars using Tailwind CSS. Instead of manually handling sidebar state, transitions, and responsive behavior, the package provides a small JavaScript layer that works alongside Tailwind’s utility classes.</p>
<p>At its core, the package toggles Tailwind classes to control whether the sidebar is open or closed. This makes it framework-agnostic - it works with plain HTML, as well as frameworks like React, Vue, or Next.js, as long as Tailwind CSS is available.</p>
<p>Because it relies on Tailwind utilities rather than custom CSS, the sidebar stays easy to customize, extend, and maintain.</p>
<h3 id="heading-what-is-tailwind-css">What is Tailwind CSS?</h3>
<p>Tailwind CSS is a utility-first CSS framework that lets you build modern, responsive user interfaces directly in your markup. Instead of predefined components, Tailwind provides low-level utility classes that give you full design control without leaving your HTML.</p>
<p>Tailwind Sidebar is built on top of Tailwind CSS, offering a clean, flexible, and highly customizable sidebar solution for modern web applications.</p>
<h2 id="heading-why-choose-tailwind-sidebar">Why Choose Tailwind Sidebar?</h2>
<h3 id="heading-optimized-performance">Optimized Performance</h3>
<p>Tailwind Sidebar relies on utility classes instead of heavy JavaScript logic. This means that it delivers fast load times and smooth interactions, and is ideal for performance-critical applications.</p>
<h3 id="heading-developer-friendly">Developer-Friendly</h3>
<p>It doesn’t have any complex configuration or component APIs. If you know Tailwind CSS, you already know how to customize the sidebar.</p>
<h3 id="heading-easy-maintenance">Easy Maintenance</h3>
<p>With a simple and predictable structure, updates and custom changes are straightforward, making it suitable for both small projects and large-scale applications.</p>
<h3 id="heading-growing-tailwind-community">Growing Tailwind Community</h3>
<p>Tailwind CSS has a massive and active community. This means better tooling, regular updates, and a wealth of learning resources to support your development workflow.</p>
<h2 id="heading-how-to-get-started-with-tailwind-sidebar">How to Get Started with Tailwind Sidebar</h2>
<p>This section will walk you through installing and setting up <code>tailwind-sidebar</code> in a React and Next.js application. You’ll learn how to add a sidebar, create menus, add a logo, and customize navigation.</p>
<h3 id="heading-step-1-install-tailwind-sidebar">Step 1: Install tailwind-sidebar</h3>
<p>To begin, you’ll need to install <a target="_blank" href="https://www.npmjs.com/package/tailwind-sidebar">tailwind Sidebar</a> into your React and Next.js project. You can do this using either npm or yarn.</p>
<h4 id="heading-using-npm">Using npm:</h4>
<pre><code class="lang-javascript">npm i tailwind-sidebar
</code></pre>
<h4 id="heading-using-yarn">Using yarn:</h4>
<pre><code class="lang-javascript">yarn add tailwind-sidebar
</code></pre>
<p>This will add <code>tailwind-sidebar</code> and its dependencies to your project.</p>
<h3 id="heading-step-2-import-the-tailwind-sidebar-component">Step 2: Import the Tailwind Sidebar Component</h3>
<p>Once the package is installed, you can import the necessary components from tailwind-sidebar into your project. These components will allow you to customize the sidebar with menus, submenus, and even a logo.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> {

  AMSidebar,

  AMMenuItem,

  AMMenu,

  AMSubmenu,

  AMLogo,

} <span class="hljs-keyword">from</span> <span class="hljs-string">"tailwind-sidebar"</span>;
</code></pre>
<h4 id="heading-adding-styles-to-tailwind-sidebar">Adding Styles to Tailwind Sidebar</h4>
<p>To use the default styles of tailwind-sidebar, you need to import its CSS file at the top of your project:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> <span class="hljs-string">"tailwind-sidebar/styles.css"</span>;
</code></pre>
<h3 id="heading-step-3-routing-setup-for-react-and-nextjs">Step 3: Routing Setup for React and Next.Js</h3>
<p>To enable navigation inside <code>tailwind-sidebar</code> components like <code>AMMenuItem</code> or <code>AMLogo</code>, you need to pass a link component from either react-router or next/link using the component prop inside the corresponding component, like what’s shown in the below example:</p>
<p>If you're using <strong>React</strong>:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { Link } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-router-dom"</span>;

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AMSidebar</span> <span class="hljs-attr">width</span>=<span class="hljs-string">{</span>"<span class="hljs-attr">270px</span>"}&gt;</span>

     <span class="hljs-tag">&lt;<span class="hljs-name">AMLogo</span> <span class="hljs-attr">img</span>=<span class="hljs-string">"https://adminmart.com/wp-content/uploads/2024/03/logo-admin-mart-news.png"</span>
        <span class="hljs-attr">component</span>=<span class="hljs-string">{Link}</span>
        <span class="hljs-attr">href</span>=<span class="hljs-string">"/"</span>
      &gt;</span>
        Adminmart

      <span class="hljs-tag">&lt;/<span class="hljs-name">AMLogo</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">AMMenu</span> <span class="hljs-attr">subHeading</span>=<span class="hljs-string">"HOME"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span>
         <span class="hljs-attr">icon</span>=<span class="hljs-string">{</span>&lt;<span class="hljs-attr">Home</span> /&gt;</span>}
          link="/"  // Passing link to component for routing
          badge={true}
          badgeType="default"
          badgeColor={"bg-secondary"}
          isSelected={true}
        &gt;
          {/* text for your link */}
          Link Text
        <span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">AMMenu</span>&gt;</span>
   <span class="hljs-tag">&lt;/<span class="hljs-name">AMSidebar</span>&gt;</span></span>
</code></pre>
<p>And if you're using <strong>Next.js</strong>:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span>  Link  <span class="hljs-keyword">from</span> <span class="hljs-string">"next/link"</span>;<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AMSidebar</span> <span class="hljs-attr">width</span>=<span class="hljs-string">{</span>"<span class="hljs-attr">270px</span>"}&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">AMLogo</span> <span class="hljs-attr">img</span>=<span class="hljs-string">"https://adminmart.com/wp-content/uploads/2024/03/logo-admin-mart-news.png"</span>
        <span class="hljs-attr">component</span>=<span class="hljs-string">{Link}</span> // <span class="hljs-attr">Passing</span> <span class="hljs-attr">link</span> <span class="hljs-attr">to</span> <span class="hljs-attr">component</span> <span class="hljs-attr">for</span> <span class="hljs-attr">routing</span>
        <span class="hljs-attr">href</span>=<span class="hljs-string">"/"</span>
      &gt;</span>
        Adminmart
      <span class="hljs-tag">&lt;/<span class="hljs-name">AMLogo</span>&gt;</span>
        AdminMart
      <span class="hljs-tag">&lt;/<span class="hljs-name">Logo</span>&gt;</span></span>
      <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AMMenu</span> <span class="hljs-attr">subHeading</span>=<span class="hljs-string">"HOME"</span>&gt;</span>
         <span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span>
          <span class="hljs-attr">icon</span>=<span class="hljs-string">{</span>&lt;<span class="hljs-attr">Home</span> /&gt;</span>}
          link="/tes"
          component={Link}
          isSelected={true}
        &gt;
           Link Text {/* text for your link */}
        <span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">AMMenu</span>&gt;</span></span>
    &lt;/AMSidebar&gt;
</code></pre>
<h3 id="heading-step-4-initializing-the-sidebar">Step 4: Initializing the Sidebar</h3>
<p>Now we’ll set up the <code>AMSidebar</code> component in your application. You can set the width of the sidebar using the <code>width</code> prop. Here’s a simple example:</p>
<pre><code class="lang-javascript">&lt;AMSidebar width={<span class="hljs-string">"270px"</span>}&gt; <span class="hljs-comment">// pass the width you want your sidebar to have</span>

{<span class="hljs-comment">/* Sidebar Content Goes Here */</span>}

&lt;/AMSidebar&gt;
</code></pre>
<p>This initializes the sidebar with a width of <code>270px</code>. You can adjust this width based on your design requirements.</p>
<h3 id="heading-step-5-adding-a-logo-to-the-sidebar">Step 5: Adding a Logo to the Sidebar</h3>
<p>You can add a logo inside the sidebar by using the <code>AMLogo</code> component. To do so, you can provide an <code>img</code> prop to link to a CDN logo image. You can also make the logo clickable by passing a navigation link using the <code>component</code> and <code>href</code> props. Here’s how you can include a logo:</p>
<pre><code class="lang-javascript">&lt;AMSidebar width={<span class="hljs-string">"270px"</span>}&gt;

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AMLogo</span> <span class="hljs-attr">img</span>=<span class="hljs-string">"https://adminmart.com/wp-content/uploads/2024/03/logo-admin-mart-news.png"</span>
<span class="hljs-attr">component</span>=<span class="hljs-string">{Link}</span>
<span class="hljs-attr">href</span>=<span class="hljs-string">"/"</span> 
&gt;</span>        
Adminmart
<span class="hljs-tag">&lt;/<span class="hljs-name">AMLogo</span>&gt;</span></span>

&lt;/AMSidebar&gt;
</code></pre>
<p>In this example, we’ve added a logo from a CDN using the <code>img</code> prop, used the <code>component</code> prop to pass the <code>Link</code>, and set the navigation path to <code>(/)</code> homepage using the <code>href</code> prop and set the text “AdminMart” as the name of the application.</p>
<h3 id="heading-step-6-creating-a-menu-inside-the-sidebar">Step 6: Creating a Menu Inside the Sidebar</h3>
<p>Now let’s create a menu inside the sidebar using the <code>AMMenu</code> component. You can specify a submenu heading using the <code>subHeading</code> prop. Inside the <code>AMMenu</code>, you can add <code>AMMenuItem</code> components for each item.</p>
<p>You can also provide a <code>link</code> prop along with the <code>component</code> prop to the <code>AMMenuItem</code> to turn the item into a clickable link.</p>
<p>Here’s how you can structure the menu:</p>
<pre><code class="lang-javascript">&lt;AMSidebar
 width={<span class="hljs-string">"270px"</span>}&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AMMenu</span> <span class="hljs-attr">subHeading</span>=<span class="hljs-string">"HOME"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span>  <span class="hljs-attr">component</span>=<span class="hljs-string">{Link}</span>  <span class="hljs-attr">link</span>=<span class="hljs-string">"/"</span>  <span class="hljs-attr">badge</span>=<span class="hljs-string">”true”</span>  <span class="hljs-attr">isSelected</span>=<span class="hljs-string">{true}</span> &gt;</span>
      Modern
    <span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span>&gt;</span>eCommerce<span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span>&gt;</span>Analytical<span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">AMMenu</span>&gt;</span></span>
&lt;/AMSidebar&gt;
</code></pre>
<p>In this example:</p>
<ul>
<li><p>We’ve added a <code>AMMenu</code> with the heading “HOME”.</p>
</li>
<li><p>The first <code>AMMenuItem</code> has a <code>link</code> prop, so clicking it will navigate to the homepage <code>(/)</code>.</p>
</li>
<li><p>The second and third <code>AMMenuItem</code> components are simple text items without links.</p>
</li>
</ul>
<p>You can use the <code>badge="true"</code> prop to indicate a badge or notification on the <strong>AMMenuItem</strong>. The <code>isSelected={true}</code> prop marks this menu item as currently selected or active (though you can customize this feature according to your needs).</p>
<h3 id="heading-step-7-adding-submenus-optional">Step 7: Adding Submenus (Optional)</h3>
<p>To add submenus inside the main menu, use the <code>AMSubmenu</code> component. The <code>AMSubmenu</code> can be nested inside the <code>AMMenu</code> component and contains its own set of <code>AMMenuItem</code>. Use the <code>title</code> prop to set the submenu heading</p>
<p>Here’s an example of adding a submenu:</p>
<pre><code class="lang-javascript">&lt;AMSidebar  width={<span class="hljs-string">"270px"</span>}&gt;
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AMMenu</span> <span class="hljs-attr">subHeading</span>=<span class="hljs-string">"SERVICES"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span>  <span class="hljs-attr">component</span>=<span class="hljs-string">{Link}</span>   <span class="hljs-attr">link</span>=<span class="hljs-string">"/web-development"</span>&gt;</span>Web Development<span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span> <span class="hljs-attr">component</span>=<span class="hljs-string">{Link}</span>  <span class="hljs-attr">link</span>=<span class="hljs-string">"/seo-services"</span>&gt;</span>SEO Services<span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">AMSubmenu</span> <span class="hljs-attr">title</span>=<span class="hljs-string">"Marketing"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span> <span class="hljs-attr">component</span>=<span class="hljs-string">{Link}</span>  <span class="hljs-attr">link</span>=<span class="hljs-string">"/digital-marketing"</span>&gt;</span>Digital Marketing<span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span>
           <span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span> <span class="hljs-attr">component</span>=<span class="hljs-string">{Link}</span>  <span class="hljs-attr">link</span>=<span class="hljs-string">"/content-marketing"</span>&gt;</span>Content Marketing<span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">AMSubmenu</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">AMMenu</span>&gt;</span></span>
&lt;/AMSidebar&gt;
</code></pre>
<p>In this example:</p>
<ul>
<li><p>A submenu under the "Marketing" heading is added inside the "SERVICES" menu.</p>
</li>
<li><p>The submenu contains two <code>AMMenuItem</code> with links to different service pages.</p>
</li>
</ul>
<h2 id="heading-features-of-tailwind-sidebar">Features of Tailwind Sidebar:</h2>
<h3 id="heading-utility-first-amp-lightweight">Utility-First &amp; Lightweight</h3>
<p>Tailwind Sidebar is built entirely using Tailwind CSS utility classes. This means there’s no heavy JavaScript logic or extra styling framework, keeping your bundle size small and performance fast.</p>
<p><strong>Code Example:</strong></p>
<pre><code class="lang-javascript">&lt;AMSidebar width=<span class="hljs-string">"260px"</span> className=<span class="hljs-string">"bg-gray-900 text-white"</span>&gt;
     <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AMMenu</span>&gt;</span>
           <span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span>&gt;</span>Dashboard<span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">AMMenu</span>&gt;</span></span>
 &lt;/AMSidebar&gt;
</code></pre>
<p>What’s going on here:</p>
<ul>
<li><p><code>bg-gray-900 text-white</code>: Applies a dark background and white text <em>directly</em> via Tailwind classes (no separate CSS file).</p>
</li>
<li><p><code>width="260px"</code>: The component prop sets the sidebar width. Here, it shows how Tailwind utilities combine with props to control layout.</p>
</li>
</ul>
<p>Because all spacing and colors are from Tailwind classes, you don’t need additional custom styles.</p>
<h3 id="heading-fully-responsive-design">Fully Responsive Design</h3>
<p>The sidebar adapts seamlessly to different screen sizes. Whether you’re building for desktop, tablet, or mobile, Tailwind Sidebar ensures a smooth and consistent navigation experience.</p>
<p><strong>Example usage:</strong></p>
<pre><code class="lang-javascript">&lt;AMSidebar width=<span class="hljs-string">"260px"</span> className=<span class="hljs-string">"hidden md:block"</span>&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AMMenu</span>&gt;</span>
           <span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span>&gt;</span>Home<span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">AMMenu</span>&gt;</span></span>
&lt;/AMSidebar&gt;
</code></pre>
<p>What’s going on here:</p>
<ul>
<li><code>hidden md:block</code>: Uses Tailwind <em>responsive utility classes</em> to hide the component (<code>hidden</code>) on mobile screens and show it starting at the <code>md</code> breakpoint (<code>md:block</code>).</li>
</ul>
<p>This pattern is Tailwind’s common way of controlling visibility across breakpoints without media queries.</p>
<h3 id="heading-highly-customizable">Highly Customizable</h3>
<p>Tailwind Sidebar allows full customization of colors, hover states, spacing, and typography directly through Tailwind classes. You can customize layout and animations – all without touching custom CSS files.</p>
<p><strong>Example usage:</strong></p>
<pre><code class="lang-javascript"> &lt;AMMenuItem className=<span class="hljs-string">"hover:bg-blue-600 rounded-lg px-4 py-2”&gt;
      Analytics
  &lt;/AMMenuItem&gt;</span>
</code></pre>
<p>What’s going on here:</p>
<ul>
<li><p><code>hover:bg-blue-600</code>: When you hover the menu item, the background changes to blue, purely via Tailwind.</p>
</li>
<li><p><code>rounded-lg</code>: Adds rounded corners.</p>
</li>
<li><p><code>px-4 py-2</code>: Controls horizontal (<code>px</code>) and vertical (<code>py</code>) padding to adjust spacing.</p>
</li>
</ul>
<p>Together, these utilities show how Tailwind gives control of design details directly at the HTML/JSX level.</p>
<h3 id="heading-integration-with-react-amp-nextjs">Integration with React &amp; Next.Js:</h3>
<p>Tailwind Sidebar seamlessly integrates with both React &amp; Next.js, offering a familiar and efficient development experience.</p>
<p>The sidebar works natively with both <strong>React Router</strong> and <strong>Next.js routing</strong> by passing the <code>Link</code> component.</p>
<pre><code class="lang-javascript">React Example
{
  <span class="hljs-comment">/* if you are using react then import link from  */</span>
}

<span class="hljs-keyword">import</span> { Link } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-router-dom"</span>;
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span> <span class="hljs-attr">component</span>=<span class="hljs-string">{Link}</span> <span class="hljs-attr">link</span>=<span class="hljs-string">"/dashboard"</span>&gt;</span>
            Dashboard 
<span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span></span>

Next.js Example
{
  <span class="hljs-comment">/* if you are using nextjs then import link from  */</span>
}

<span class="hljs-keyword">import</span> Link <span class="hljs-keyword">from</span> <span class="hljs-string">"next/link"</span>;

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span> <span class="hljs-attr">component</span>=<span class="hljs-string">{Link}</span> <span class="hljs-attr">link</span>=<span class="hljs-string">"/dashboard"</span>&gt;</span>
         Dashboard
<span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span></span>
</code></pre>
<p>What’s going on here:</p>
<ul>
<li><code>component={Link} link="/dashboard"</code>: Shows how you pass your framework’s routing component into <code>AMMenuItem</code>, turning it into a real navigational link.</li>
</ul>
<p>This means Tailwind Sidebar adapts to both React Router and Next.js routing without boilerplate.</p>
<h3 id="heading-menu-amp-submenu-support">Menu &amp; Submenu Support</h3>
<p>Organize your navigation with:</p>
<ul>
<li><p>Main menu items</p>
</li>
<li><p>Nested submenus</p>
</li>
</ul>
<p>This makes it easy to manage complex navigation structures while keeping the UI clean and intuitive.</p>
<p><strong>Example usage:</strong></p>
<pre><code class="lang-javascript">&lt;AMMenu subHeading=<span class="hljs-string">"MANAGEMENT"</span>&gt;
             <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span>&gt;</span>Users<span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span></span>
                <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AMSubmenu</span> <span class="hljs-attr">title</span>=<span class="hljs-string">"Reports"</span>&gt;</span>
                   <span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span>&gt;</span>Sales<span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span>
                      <span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span>&gt;</span>Revenue<span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span>
                    <span class="hljs-tag">&lt;/<span class="hljs-name">AMSubmenu</span>&gt;</span></span>
&lt;/AMMenu&gt;
</code></pre>
<p>What’s going on here:</p>
<ul>
<li><p><code>subHeading="SERVICES"</code>: Adds a labeled grouping for menu items.</p>
</li>
<li><p>The nested <code>&lt;AMSubmenu&gt;</code> demonstrates how nested navigation is rendered and structured in JSX.</p>
</li>
</ul>
<p>This example clearly shows hierarchical menus without additional CSS – structure and Tailwind classes handle layout.</p>
<h3 id="heading-icon-support">Icon Support</h3>
<p>The sidebar comes with built-in support for icons, allowing developers to enhance the visual appeal and usability of their application. Developers can use any icon library and provide the icon component.</p>
<p><strong>Example using lucide-react:</strong></p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { Home } <span class="hljs-keyword">from</span> <span class="hljs-string">"lucide-react"</span>;

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span> <span class="hljs-attr">icon</span>=<span class="hljs-string">{</span>&lt;<span class="hljs-attr">Home</span> <span class="hljs-attr">size</span>=<span class="hljs-string">{18}</span> /&gt;</span>}&gt;
     Home
<span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span></span>
</code></pre>
<p>What’s going on here:</p>
<ul>
<li><p><code>icon={&lt;Home size={18} /&gt;}</code>: Here, an icon component is passed as a prop.</p>
</li>
<li><p>You control the size directly via the icon library (Lucide here) and Tailwind handles spacing/placement next to text.</p>
</li>
</ul>
<p>This illustrates how icons and text combine in the sidebar component.</p>
<h3 id="heading-smooth-transitions">Smooth Transitions</h3>
<p>Tailwind Sidebar provides built-in animation support through the animation prop. When enabled, menu items and submenus animate smoothly, improving the overall user experience without requiring custom CSS or JavaScript.</p>
<p><strong>Example Usage:</strong></p>
<pre><code class="lang-javascript">&lt;AMSidebar width=<span class="hljs-string">"270px"</span> animation={<span class="hljs-literal">true</span>}&gt;
           <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AMMenu</span> <span class="hljs-attr">subHeading</span>=<span class="hljs-string">"SETTINGS"</span>&gt;</span>
                  <span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span>&gt;</span>Profile<span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span>
                  <span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span>&gt;</span>Security<span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span>
             <span class="hljs-tag">&lt;/<span class="hljs-name">AMMenu</span>&gt;</span></span>
&lt;/AMSidebar&gt;
</code></pre>
<p>What’s going on here:</p>
<ul>
<li><code>animation={true}</code>: Enables built-in animation support.</li>
</ul>
<p>The example shows how adding this prop triggers smooth transitions defined by the component itself (still relying on Tailwind utilities internally). You don’t have to write CSS keyframes or transition utilities manually.</p>
<h2 id="heading-wrapping-up">Wrapping Up</h2>
<p>You have now successfully integrated a fully functional sidebar in your React and Next.js application using <code>tailwind-sidebar</code>. You can further customize the sidebar by:</p>
<ul>
<li><p>Modifying the width and design.</p>
</li>
<li><p>Adding more submenus, menu items, or icons.</p>
</li>
<li><p>Using links to navigate between pages.</p>
</li>
</ul>
<p>This setup provides a flexible, responsive, and easy-to-use sidebar, which is perfect for most web applications.</p>
<p>If you want to see how this kind of sidebar fits into a real dashboard layout, you can explore an open-source Tailwind CSS admin template at <a target="_blank" href="https://tailwind-admin.com/">https://tailwind-admin.com/</a>.</p>
<h3 id="heading-try-it-out">Try It Out:</h3>
<p>You can view the working demo of the tailwind-sidebar here: <a target="_blank" href="https://tailwind-admin-react-free.netlify.app/"><strong>View Demo</strong></a><strong>.</strong></p>
<p><strong>Note:</strong> in this tutorial, we utilized <code>lucide-react</code> to construct this sidebar. Feel free to choose an alternative library or use different icons based on your specific requirements.</p>
<h3 id="heading-other-sidebar-npm-packages">Other Sidebar NPM Packages</h3>
<p>You can also try <a target="_blank" href="https://www.npmjs.com/package/nextjs-tailwind-sidebar"><strong>Next.js Tailwind Sidebar</strong></a> – a simple and responsive sidebar built for Next.js, and <a target="_blank" href="https://www.npmjs.com/package/react-tailwind-sidebar"><strong>React Tailwind Sidebar</strong></a> – a lightweight Tailwind-based sidebar for React applications.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Optimize React ]]>
                </title>
                <description>
                    <![CDATA[ React makes it easy to build UIs, but building fast React apps is a different skill altogether. We just posted a hands-on, real-world React Performance Optimization course on the freeCodeCamp.org YouTube channel. You’ll learn how React actually re-re... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-optimize-react/</link>
                <guid isPermaLink="false">695fce0e01d33dbb4b94d874</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ youtube ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Beau Carnes ]]>
                </dc:creator>
                <pubDate>Thu, 08 Jan 2026 15:32:30 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1767886210356/5acb2070-3390-4982-8ca2-452043857d8e.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>React makes it easy to build UIs, but building fast React apps is a different skill altogether.</p>
<p>We just posted a hands-on, real-world React Performance Optimization course on the freeCodeCamp.org YouTube channel. You’ll learn how React actually re-renders, why your app slows down, and which performance patterns truly matter in production. Tapas created this course.</p>
<p>This video is about knowing what to optimize, when to optimize, and how to do it right.</p>
<p>Here are the sections in this course:</p>
<ul>
<li><p>Performance Patterns</p>
</li>
<li><p>What’s in Part 1?</p>
</li>
<li><p>Re-Rendering in React</p>
</li>
<li><p>Memoization</p>
</li>
<li><p>The memo()</p>
</li>
<li><p>The useCallback()</p>
</li>
<li><p>the useMemo()</p>
</li>
<li><p>The Derived State</p>
</li>
<li><p>Debouncing</p>
</li>
<li><p>Throttling</p>
</li>
<li><p>Tasks from Part 1</p>
</li>
<li><p>Advanced Patterns</p>
</li>
<li><p>What’s in Part 2?</p>
</li>
<li><p>React Compiler</p>
</li>
<li><p>Lazy Loading &amp; Suspense</p>
</li>
<li><p>Component Isolation</p>
</li>
<li><p>Context Optimizations</p>
</li>
<li><p>Virtualization</p>
</li>
<li><p>Concurrency and useTransition()</p>
</li>
<li><p>Deferred Value</p>
</li>
<li><p>List and Keys</p>
</li>
<li><p>Tools</p>
</li>
<li><p>Tasks from Part 2</p>
</li>
<li><p>One Word of Wisdom</p>
</li>
</ul>
<p>Watch the full course on <a target="_blank" href="https://www.youtube.com/watch?v=keTcXT145CI">the freeCodeCamp.org YouTube channel</a> (2-hour watch).</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/keTcXT145CI" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Real-Time Systems for Web Developers: From Theory to a Live Go + React App ]]>
                </title>
                <description>
                    <![CDATA[ Many developers think that “real-time” is about Websockets, Live data, or instant refreshes on web application dashboards. And although these concepts are closely related to what real-time means, the systems engineering definition is a bit different.... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/real-time-systems-for-web-developers-from-theory-to-a-live-go-react-app/</link>
                <guid isPermaLink="false">695e96b3ee29df356daaf7c0</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Go Language ]]>
                    </category>
                
                    <category>
                        <![CDATA[ websockets ]]>
                    </category>
                
                    <category>
                        <![CDATA[ realtime apps ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Emmanuel Etukudo ]]>
                </dc:creator>
                <pubDate>Wed, 07 Jan 2026 17:24:03 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1767748231282/074fcd8b-8808-4a3d-8c9c-17d5a7b388e6.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Many developers think that “real-time” is about Websockets, Live data, or instant refreshes on web application dashboards.</p>
<p>And although these concepts are closely related to what real-time means, the systems engineering definition is a bit different. A real-time system is not defined by how fast it is, but how predictable it is. </p>
<p>In this tutorial, you’ll learn about what real-time systems are, why most web applications are not real-time, and how to build a <strong>soft real-time system</strong> with tools you’re likely already familiar with: Go, React, and TypeScript.</p>
<p>At the end of this tutorial, we’ll build a live application that:</p>
<ul>
<li><p>processes time-sensitive events</p>
</li>
<li><p>enforces deadlines</p>
</li>
<li><p>drops work when it’s too late </p>
</li>
<li><p>and visualises latency and missed deadlines in real-time</p>
</li>
</ul>
<p>  This article will help shape your mind the next time you’re building a real-time system.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-a-real-time-system-really-means">What a Real-Time System Really Means</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-types-of-real-time-systems">Types of Real-Time Systems</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-why-most-web-apps-are-not-real-time">Why Most Web Apps Are Not Real-Time</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-what-we-are-going-to-build">What We Are Going to Build</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-system-architecture">System Architecture</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-why-go-is-a-good-fit-for-our-use-case">Why Go is a Good Fit for Our Use Case</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-generating-events-with-go">Generating Events with Go</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-deadline-aware-processing">Deadline-aware Processing</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-applying-back-pressure">Applying Back-Pressure</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-streaming-events-to-the-browser">Streaming Events to the Browser</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-consuming-a-websocket-event-react-typescript">Consuming a WebSocket event (React + TypeScript)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-making-react-real-time-friendly">Making React Real-Time Friendly</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-creating-the-statsbar-component">Creating the StatsBar Component</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-creating-the-events-table">Creating the Events Table</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-putting-it-all-together">Putting it All Together</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-final-thoughts">Final Thoughts</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>This tutorial assumes that you have basic knowledge of <code>Go</code>, <code>React</code>, and <code>WebSockets</code>, particularly working with go-routines and consuming <code>WebSockets</code> events in <code>React</code>. If you don’t, I strongly recommend reviewing introductory tutorials before continuing so you can get the most out of this guide.  </p>
<p>Helpful references include:</p>
<ul>
<li><p><strong>Go Concurrency and Goroutines:</strong> The official Go blog covers concurrency patterns, including goroutines and channels, which are <a target="_blank" href="https://go.dev/blog/concurrency-patterns">fundamental for writing concurrent Go code</a>.</p>
</li>
<li><p><strong>WebSockets with Go:</strong> A thorough WebSocket tutorial using the popular gorilla/websocket package that shows how to set up a WebSocket server in Go and manage connections and messages. <a target="_blank" href="https://tutorialedge.net/golang/go-websocket-tutorial">Here’s a Go WebSocket tutorial (with gorilla/websocket)</a>.</p>
</li>
<li><p><strong>WebSockets in React:</strong> A complete guide to WebSockets in React, explaining how to open and manage a WebSocket connection and handle incoming messages in components. <a target="_blank" href="https://ably.com/blog/websockets-react-tutorial">Here’s a complete guide to WebSockets with React</a>.</p>
</li>
</ul>
<p>Technologies we’ll work with include:</p>
<ul>
<li><p><code>Go</code>, for building our backend system and enforcing real-time guarantees</p>
</li>
<li><p><code>React</code>, for building a responsive frontend <code>UI</code> that displays streamed events</p>
</li>
<li><p><code>Websocket</code>, for low-latency delivery of data from the backend to the client</p>
</li>
</ul>
<h2 id="heading-what-a-real-time-system-really-means">What a Real-Time System Really Means</h2>
<p>In a traditional web application, correctness is measured by whether the system produced the right result. In a real-time system, correctness is measured by whether the system produced the right result <strong>before the deadline</strong>. If the result from the test is “No”, then the system has failed – even though the result is correct.</p>
<h3 id="heading-types-of-real-time-systems">Types of Real-Time Systems</h3>
<p>There are a few different types of real-time systems that you should be aware of, each with varying levels of strictness:</p>
<table><tbody><tr><td><p><strong>Realtime System</strong></p></td><td><p><strong>Niche applicable&nbsp;</strong></p></td></tr><tr><td><p><strong>Hard Real-time: </strong>Missing a deadline is catastrophic here.</p></td><td><p>Flight Control &amp; Pacemakers</p></td></tr><tr><td><p><strong>Soft Real-time:</strong> Missing deadline degrades quality but does not crash the system.&nbsp;</p></td><td><p>Video Streaming &amp; Trading Dashboards</p></td></tr><tr><td><p><strong>Firm Real-time</strong>: Late results are useless and should be discarded.</p></td><td><p>Car Auction Web/Mobile Apps</p></td></tr></tbody></table>

<p>The majority of web-based real-time systems fall under the soft real-time category, and that’s exactly what we’ll build here.</p>
<h3 id="heading-why-most-web-apps-are-not-real-time">Why Most Web Apps Are Not Real-Time</h3>
<p>There are many reasons a system may not be real-time, and typically, even those marketed as real-time applications lack this guarantee.</p>
<p>Here’s why:</p>
<ol>
<li><p>Websockets guarantee delivery, not timeliness</p>
</li>
<li><p>Massage queues are optimized for durability and throughput </p>
</li>
<li><p>Infinite buffering hides deadlines </p>
</li>
<li><p>User Interfaces (UIs) render when they can, not when they should  </p>
</li>
</ol>
<p>In other words, data will arrive eventually, but nothing enforces when it must be processed. That’s exactly the gap we’ll address in this tutorial.</p>
<h2 id="heading-what-we-are-going-to-build">What We Are Going to Build</h2>
<p>In this tutorial, we’ll build a Deadline-Aware Live Event Monitor. You can think of it as a simplified real-time system for sensor data, trading events, alerts, or live telemetry.</p>
<p>Our app will have these features and constraints:</p>
<ul>
<li><p>Events are generated at a fixed rate</p>
</li>
<li><p>Each event has a deadline </p>
</li>
<li><p>The backend processes events only if they can be completed on time</p>
</li>
<li><p>Late events are marked or dropped </p>
</li>
<li><p>The frontend visualizes:</p>
<ul>
<li><p>processing latency </p>
</li>
<li><p>missed deadlines</p>
</li>
<li><p>and system health </p>
</li>
</ul>
</li>
</ul>
<p>This will give us the required metrics to measure the real-time behavior of the system instead of guessing.</p>
<h2 id="heading-system-architecture">System Architecture</h2>
<p>The high-level system architecture looks like this:</p>
<pre><code class="lang-plaintext">
+-------------+     +------------------+     +----------------+
| Event       | --&gt; | Deadline-Aware   | --&gt; | WebSocket      |
| Generator   |     | Go Processor     |     | Server         |
+-------------+     +------------------+     +----------------+
                                                     |
                                                     v
                                           +----------------+
                                           | React Dashboard|
                                           +----------------+
</code></pre>
<p>Let’s break down the responsibilities:</p>
<p><strong>Backend:</strong></p>
<ul>
<li><p>Generates time-sensitive events </p>
</li>
<li><p>Enforces deadline </p>
</li>
<li><p>Applies back pressure</p>
</li>
<li><p>Streams result to client (frontend)</p>
</li>
</ul>
<p><strong>Front End:</strong></p>
<ul>
<li><p>Consumes real-time events</p>
</li>
<li><p>Renders live metrics </p>
</li>
<li><p>Remains responsive under load </p>
</li>
</ul>
<h4 id="heading-time-is-part-of-your-data-model">Time is part of your Data Model</h4>
<p>In a real-time system, time is explicit, not implicit. This means that each event processed includes:</p>
<ul>
<li><p>when it was created </p>
</li>
<li><p>how long is it allowed to live, and</p>
</li>
<li><p>when it was processed </p>
</li>
</ul>
<p>Conceptually, a typical data model for an event looks like this:</p>
<pre><code class="lang-typescript">{
  id: <span class="hljs-built_in">string</span>
  createdAt: <span class="hljs-built_in">number</span>
  deadlineMs: <span class="hljs-built_in">number</span>
  processedAt?: <span class="hljs-built_in">number</span>
  status: <span class="hljs-string">"on-time"</span> | <span class="hljs-string">"late"</span> | <span class="hljs-string">"dropped"</span>
}
</code></pre>
<p>This is the mindset shift we hope to establish: in a real-time system, time is an essential component for your system that guarantees accuracy.</p>
<h2 id="heading-why-go-is-a-good-fit-for-our-use-case">Why Go is a Good Fit for Our Use Case</h2>
<p>Go is not a hard real-time language, but it’s excellent for soft real-time workloads. This is because of its:</p>
<ul>
<li><p>Cheap goroutines</p>
</li>
<li><p>Structured concurrency with channels</p>
</li>
<li><p>Deadline propagation via <code>context.Context</code></p>
</li>
<li><p>Simple runtime behavior</p>
</li>
</ul>
<p>Most importantly, Go makes it easy to <strong>fail fast</strong>, which is essential for real-time systems.</p>
<h3 id="heading-generating-events-with-go">Generating Events with Go</h3>
<p>We’ll begin the development of our backend system by first defining the Event struct and creating a fixed-rate event generator function:</p>
<pre><code class="lang-go"><span class="hljs-keyword">type</span> Event <span class="hljs-keyword">struct</span> {
        ID         <span class="hljs-keyword">string</span>
        CreatedAt time.Time
        DeadlineMs  time.Duration
}
</code></pre>
<p>Here we’ve created an <code>Event</code> struct with the following properties:</p>
<ul>
<li><p><code>ID</code> a unique identifier that helps in the management of each event processed by the system</p>
</li>
<li><p><code>CreatedAt</code> to track the time the event was created</p>
</li>
<li><p><code>Deadline</code> to help evaluate if the event met the assigned deadline or if it failed</p>
</li>
</ul>
<p>Next, we’ll create the event generator <code>startGenerator</code> function:</p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">startGenerator</span><span class="hljs-params">(out <span class="hljs-keyword">chan</span>&lt;- Event)</span></span> {
        ticker := time.NewTicker(<span class="hljs-number">50</span> * time.Millisecond)
        <span class="hljs-keyword">defer</span> ticker.Stop()

        <span class="hljs-keyword">for</span> <span class="hljs-keyword">range</span> ticker.C {
                event := Event{
                        ID:         uuid.New().String(),
                        CreatedAt:  time.Now(),
                        DeadlineMs: <span class="hljs-number">100</span>,
                }


                <span class="hljs-keyword">select</span> {
                <span class="hljs-keyword">case</span> out &lt;- event:
                <span class="hljs-keyword">default</span>:
                  <span class="hljs-comment">// Drop event when load peaks on the goroutine</span>
                }
        }
}
</code></pre>
<p>Here, the event generator function accepts a <code>Go</code> channel as a parameter and uses a <code>time.Ticker</code> channel that fires every <strong>50 milliseconds</strong>. On each tick, it creates a new <code>Event</code> with a unique<code>ID</code>, a creation timestamp, and a <strong>deadline of 100 milliseconds</strong> (<code>DeadlineMs: 100</code>).</p>
<p>The generator then attempts to send the event into the output channel using a non-blocking send. If the channel is ready, the event is delivered immediately. If the channel is not ready (for example, because downstream consumers are slow or overloaded), the <code>default</code> case is executed, and the event is dropped.</p>
<p>Why do we have to drop the event here? Well, because hiding overload drops real-time guarantees. In short, <strong>dropping events is a deliberate backpressure strategy</strong>: it prevents overload from cascading through the system and protects latency bounds, which is often more important than completeness in real-time streaming systems.</p>
<h3 id="heading-deadline-aware-processing">Deadline-aware Processing</h3>
<p>Next, we’ll create the <code>processEvent</code> function to handle the processing of the event. In Go, you can enforce deadlines by using <code>context.WithTimeout</code> like this:</p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">processEvent</span><span class="hljs-params">(event Event)</span> <span class="hljs-title">string</span></span> {
        ctx, cancel := context.WithTimeout(
                context.Background(),
                event.Deadline,
        )

        <span class="hljs-keyword">defer</span> cancel()
        workDone := <span class="hljs-built_in">make</span>(<span class="hljs-keyword">chan</span> <span class="hljs-keyword">struct</span>{})

        <span class="hljs-keyword">go</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">()</span></span> {
                time.Sleep(<span class="hljs-number">50</span> * time.Millisecond)
                <span class="hljs-built_in">close</span>(workDone)

        }()

        <span class="hljs-keyword">select</span> {
        <span class="hljs-keyword">case</span> &lt;-workDone:
                <span class="hljs-keyword">return</span> <span class="hljs-string">"on-time"</span>
        <span class="hljs-keyword">case</span> &lt;-ctx.Done():
                <span class="hljs-keyword">return</span> <span class="hljs-string">"late"</span>
        }
}
</code></pre>
<p>Here we’ve intentionally ensured that work finishes before the deadline or it fails immediately.</p>
<p>In the <code>processEvent</code> function, each event is processed under a hard deadline enforced by a context with a timeout. The timeout duration is derived directly from the event’s deadline, meaning the event is only considered valid within the specified time window.</p>
<p>The actual work is executed in a separate goroutine, which simulates processing by sleeping for <strong>50 milliseconds</strong> and then signaling completion by closing the <code>workDone</code> channel. We’ve intentionally structured this so that work either completes within the deadline or is treated as a failure immediately.</p>
<h3 id="heading-applying-back-pressure"><strong>Applying Back-Pressure</strong></h3>
<p>In real-time systems, queues do not solve overload – they merely postpone it. When incoming events arrive faster than they can be processed, a queue continues to grow, increasing the time each event spends waiting.</p>
<p>Buffers can also hide failure. By absorbing excess load, they create the illusion that the system is healthy, even as processing delays grow beyond acceptable limits. This hidden degradation is dangerous because the system continues operating in a compromised state, producing results that are technically correct but operationally useless due to lateness.</p>
<p>As queues and buffers grow, latency increases silently. There is often no explicit error or signal that deadlines are being missed – the system simply becomes slower over time. In real-time systems, this silent latency growth is especially harmful because it violates the assumption that results are delivered within a known and bounded time window.</p>
<p>For these reasons, I strongly recommend that you use bounded channels. When the system becomes overwhelmed, bounded channels enforce back-pressure by refusing additional work. Instead of blocking indefinitely or growing unbounded queues, the system drops events when it cannot keep up.</p>
<p>This behavior makes failures visible. Dropped events are an explicit signal that the system is operating beyond its capacity. Rather than degrading unpredictably, the system degrades in a controlled and observable way. In this context, dropping events is a feature, not a bug, because it preserves latency guarantees for the events that do get processed and allows operators to detect, reason about, and respond to overload conditions immediately.</p>
<h3 id="heading-streaming-events-to-the-browser">Streaming Events to the Browser</h3>
<p>Next, we’ll build the Websocket broadcast system to push processed events to the frontend using the <a target="_blank" href="http://github.com/gorilla/websocket">Gorilla</a> <code>Go</code> websocket package (but feel free to use any package of your choice).</p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> (
        <span class="hljs-string">"encoding/json"</span>
        <span class="hljs-string">"net/http"</span>
        <span class="hljs-string">"github.com/gorilla/websocket"</span>
)

<span class="hljs-keyword">var</span> upgrader = websocket.Upgrader{
        CheckOrigin: <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(r *http.Request)</span> <span class="hljs-title">bool</span></span> {
                <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>
        },
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">wsHandler</span><span class="hljs-params">(out &lt;-<span class="hljs-keyword">chan</span> Event)</span> <span class="hljs-title">http</span>.<span class="hljs-title">HandlerFunc</span></span> {
        <span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(w http.ResponseWriter, r *http.Request)</span></span> {
                conn, _ := upgrader.Upgrade(w, r, <span class="hljs-literal">nil</span>)
                <span class="hljs-keyword">defer</span> conn.Close()
                <span class="hljs-keyword">for</span> event := <span class="hljs-keyword">range</span> out {
                        data, _ := json.Marshal(event)
                        conn.WriteMessage(websocket.TextMessage, data)
                }
        }
}
</code></pre>
<p>Here, we simply upgrade an incoming <code>HTTP</code> request to a <code>WebSocket</code> connection and continuously reads events from our <code>Event</code> channel we created earlier, serializing each event to <code>JSON</code> and broadcasting it to the connected client. It acts purely as a transport layer, pushing already-processed events to clients with low latency.  </p>
<p>It’s important to note that <code>WebSockets</code> do not make a system real-time. <code>WebSockets</code> merely provide low-latency delivery from the backend to the client. The real-time guarantees are established earlier in the backend pipeline through deliberate design choices: fixed-rate event generation, explicit per-event deadlines, bounded queues, non-blocking sends, deadline-aware processing using contexts, and fail-fast behavior when deadlines are exceeded.</p>
<p>By the time an event is sent over a <code>WebSocket</code>, it has already either met its real-time constraints or been discarded. The <code>WebSocket</code> layer simply transports the result – it doesn’t enforce or create real-time behavior.</p>
<h3 id="heading-consuming-a-websocket-event-react-typescript">Consuming a WebSocket Event (React + TypeScript)</h3>
<p>Up until now, we’ve been building the backend of our real-time event generator and broadcast system. In the next sections, we’ll build the frontend of the system using <code>React</code> and <code>TypeScript</code>.</p>
<p>We’ll start by initializing a <code>WebSocket</code> client to consume incoming events from the backend using the traditional WebSocket browser interface.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> socket = <span class="hljs-keyword">new</span> WebSocket(<span class="hljs-string">"ws://localhost:8080/ws"</span>);
socket.onmessage = <span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> data = <span class="hljs-built_in">JSON</span>.parse(event.data);
  buffer.push(data);
};
</code></pre>
<p>Here we simply initialize a new <code>WebSocket</code>, passing along the WebSocket URL from the backend. You have to reference the same URL. Instead of rendering every message immediately, it’s recommended that you always batch updates.</p>
<h3 id="heading-making-react-real-time-friendly">Making React Real-Time Friendly</h3>
<p>Next, let’s create a <code>React</code> <code>useRealTimeEvents</code> hook to handle streaming and processing of event broadcasts from the backend. Rendering on every message causes render storms, UI lag, and misleading dashboards. Instead, we render on animation frames.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useEffect, useRef, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { RealTimeEvent } <span class="hljs-keyword">from</span> <span class="hljs-string">'types/types'</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">useRealTimeEvents</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [events, setEvents] = useState&lt;RealTimeEvent[]&gt;([]);
  <span class="hljs-keyword">const</span> buffer = useRef&lt;RealTimeEvent[]&gt;([]);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> ws = <span class="hljs-keyword">new</span> WebSocket(<span class="hljs-string">'ws://localhost:8080/ws'</span>);
    ws.onmessage = <span class="hljs-function">(<span class="hljs-params">msg</span>) =&gt;</span> {
      buffer.current.push(<span class="hljs-built_in">JSON</span>.parse(msg.data));
    };

    <span class="hljs-keyword">let</span> raf: <span class="hljs-built_in">number</span>;

    <span class="hljs-keyword">const</span> flush = <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-keyword">if</span> (buffer.current.length &gt; <span class="hljs-number">0</span>) {
        <span class="hljs-keyword">const</span> pendingEvents = buffer.current.slice(<span class="hljs-number">0</span>);

        setEvents(<span class="hljs-function">(<span class="hljs-params">prev</span>) =&gt;</span> {
          <span class="hljs-keyword">const</span> next = [...prev, ...pendingEvents];
          <span class="hljs-keyword">return</span> next.slice(<span class="hljs-number">-50</span>);
        });
      }
      raf = requestAnimationFrame(flush);
    };

    raf = requestAnimationFrame(flush);

    <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> {
      ws.close();
      cancelAnimationFrame(raf);
    };
  }, []);
  <span class="hljs-keyword">return</span> events;
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> useRealTimeEvents;
</code></pre>
<p>The UI is part of the real-time system. It’s important to note that the system broadcasts messages to the frontend in milliseconds. This behavior already crosses the web browser refresh rate threshold.</p>
<p>In certain situations, you might even guess that the use of <code>setTime</code> should come in handy here. And that’s a good alternative – but there’s a better solution: using <code>requestAnimationFrame()</code>.</p>
<p>The <code>requestAnimationFrame()</code> takes in a callback function <code>flush</code> which is regulated by the animation frame, ensuring that we don’t cross the refresh rate threshold before the next repaint. You can learn more about it <code>requestAnimationFrame()</code> <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/DedicatedWorkerGlobalScope/requestAnimationFrame">here</a>.</p>
<h3 id="heading-creating-the-statsbar-component">Creating the StatsBar Component</h3>
<p>Next, let’s create a little <code>statusBar</code> component to show events that arrived within the deadline and those that came in late.</p>
<p>Create a new component <code>StatsBar</code> and add the code below:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { <span class="hljs-keyword">type</span> FC } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { RealTimeEvent } <span class="hljs-keyword">from</span> <span class="hljs-string">'types/types'</span>;

<span class="hljs-keyword">const</span> StatsBar: FC&lt;{ events: RealTimeEvent[] }&gt; = <span class="hljs-function">(<span class="hljs-params">{ events }</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> late = events.filter(<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> e.status === <span class="hljs-string">'late'</span>).length;
  <span class="hljs-keyword">return</span> (
    &lt;div className=<span class="hljs-string">"flex flex-row gap-2 bg-gray-500 w-full py-2.5 px-2"</span>&gt;
      &lt;strong&gt;Events: {events.length} | &lt;/strong&gt;
      &lt;strong&gt;Late: {late}&lt;/strong&gt;
    &lt;/div&gt;
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> StatsBar;
</code></pre>
<p>Here we are creating a minimal stats component to show the total number of events and those that arrived late by looping through the incoming event list whose statuses are late.</p>
<h3 id="heading-creating-the-events-table">Creating the Events Table</h3>
<p>Next, we’ll create a <code>EventTable</code> component to display the events.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { <span class="hljs-keyword">type</span> FC } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { RealTimeEvent } <span class="hljs-keyword">from</span> <span class="hljs-string">'types/types'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> EventsTable: FC&lt;{ events: RealTimeEvent[] }&gt; = <span class="hljs-function">(<span class="hljs-params">{ events }</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> formatDate = <span class="hljs-function">(<span class="hljs-params">date: <span class="hljs-built_in">string</span></span>) =&gt;</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(date).toLocaleString();
  <span class="hljs-keyword">return</span> (
    &lt;div className=<span class="hljs-string">"w-full"</span>&gt;
      &lt;div className=<span class="hljs-string">"relative overflow-x-auto bg-neutral-primary-soft shadow-xs rounded-base border border-default"</span>&gt;
        &lt;table className=<span class="hljs-string">"w-full text-sm text-left rtl:text-right text-body"</span>&gt;
          &lt;thead className=<span class="hljs-string">"text-sm text-body bg-neutral-secondary-soft border-b rounded-base border-default"</span>&gt;
            &lt;tr&gt;
              &lt;th scope=<span class="hljs-string">"col"</span> className=<span class="hljs-string">"px-6 py-3 font-medium"</span>&gt;
                ID
              &lt;/th&gt;
              &lt;th scope=<span class="hljs-string">"col"</span> className=<span class="hljs-string">"px-6 py-3 font-medium"</span>&gt;
                Satus
              &lt;/th&gt;
              &lt;th scope=<span class="hljs-string">"col"</span> className=<span class="hljs-string">"px-6 py-3 font-medium"</span>&gt;
                Created At
              &lt;/th&gt;
              &lt;th scope=<span class="hljs-string">"col"</span> className=<span class="hljs-string">"px-6 py-3 font-medium"</span>&gt;
                Processed At
              &lt;/th&gt;
              &lt;th scope=<span class="hljs-string">"col"</span> className=<span class="hljs-string">"px-6 py-3 font-medium"</span>&gt;
                Deadline
              &lt;/th&gt;
            &lt;/tr&gt;
          &lt;/thead&gt;
          &lt;tbody&gt;
            {events.map(<span class="hljs-function">(<span class="hljs-params">e, i</span>) =&gt;</span> (
              &lt;tr
                key={e.id + i}
                className=<span class="hljs-string">"bg-neutral-primary border-b border-default"</span>
              &gt;
                &lt;td className=<span class="hljs-string">"px-6 py-4"</span>&gt;{e.id.slice(<span class="hljs-number">0</span>, <span class="hljs-number">6</span>)}&lt;/td&gt;
                &lt;td className=<span class="hljs-string">"px-6 py-4"</span>&gt;{e.status}&lt;/td&gt;
                &lt;td className=<span class="hljs-string">"px-6 py-4"</span>&gt;{formatDate(e.createdAt)}&lt;/td&gt;
                &lt;td className=<span class="hljs-string">"px-6 py-4"</span>&gt;{formatDate(e.processedAt)}&lt;/td&gt;
                &lt;td className=<span class="hljs-string">"px-6 py-4"</span>&gt;{e.deadlineMs}&lt;/td&gt;
              &lt;/tr&gt;
            ))}
          &lt;/tbody&gt;
        &lt;/table&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
};
</code></pre>
<p>Here we loop through all incoming events and display the event’s <code>id</code>, <code>status</code>, and <code>deadline</code>. These metrics will help us gain insight into the performance of our real-time events broadcast system.</p>
<h3 id="heading-putting-it-all-together">Putting it All Together</h3>
<p>At this point, we have implemented a complete, end-to-end real-time application that connects a deadline-aware <code>Go</code> backend to a lightweight <code>React</code> frontend. On the backend, events are generated at a fixed rate, processed under explicit deadlines, and dropped when the system is under load to preserve real-time guarantees. Only events that meet these guarantees are forwarded to connected clients via <code>WebSocket</code> broadcast.</p>
<p>On the frontend, we’ve built the <code>useRealtimeEvents</code> hook to establish a persistent <code>WebSocket</code> connection and continuously stream events from the backend as they arrive. The <code>StatsBar</code> component provides immediate visibility into the system’s behavior by summarizing key characteristics of the event stream, while the <code>EventTable</code> component renders individual events in the order they are received. Together, these components clearly mirror the behavior of the system under normal conditions in real-time.</p>
<p>With both the frontend and backend components in place, the application now functions as a real-time monitor. The backend enforces timelines and correctness, and the frontend simply reflects the outcome of those decisions in real-time. There is no buffering or replay logic on the client side – what’s displayed on the <code>UI</code> is exactly what the system was able to process within the specified deadline.</p>
<p>Finally, replace your <code>Welcome</code> component with the code below to display the <code>StatusBar</code> and <code>EventsTable</code> component.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useRealtimeEvents } <span class="hljs-keyword">from</span> <span class="hljs-string">"./hooks/useRealtimeEvents"</span>;
<span class="hljs-keyword">import</span> { StatsBar } <span class="hljs-keyword">from</span> <span class="hljs-string">"./components/StatsBar"</span>;
<span class="hljs-keyword">import</span> { EventTable } <span class="hljs-keyword">from</span> <span class="hljs-string">"./components/EventTable"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> events = useRealtimeEvents();

  <span class="hljs-keyword">return</span> (
    &lt;div&gt;
      &lt;h1&gt;Real-Time Event Monitor&lt;/h1&gt;
      &lt;StatsBar events={events} /&gt;
      &lt;EventTable events={events} /&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p>The React frontend is scaffolded using <a target="_blank" href="https://react.dev/learn/creating-a-react-app">create-react-app</a>, but the same approach can be used for other frameworks, such as <code>Next.js</code> or <code>Vite</code>. The complete source code, including the frontend and backend, is available in the repository <a target="_blank" href="https://github.com/emmanueletukudo/realtime-go-react">here</a>. You can reach out to me on the <a target="_blank" href="https://x.com/eetukudo_">X platform</a> if you need my assistance.</p>
<h2 id="heading-final-thoughts">Final Thoughts</h2>
<p>If you’ve followed this tutorial up to this point, congratulations! You’ve learnt the most critical part of building resilient deadline-aware real-time systems.</p>
<p>Remember, real-time systems are not about being fast, but about how <strong>predictable they are.</strong> You don’t need a Real-Time Operating System (RTOS), a PhD, or specialized hardware to start learning real-time design.</p>
<p>All you need to excel is to respect time, bound your resources, and accept that sometimes, dropping data is the correct behavior. If you understand that, you’re already thinking like a real-time systems engineer.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Use the Optimistic UI Pattern with the useOptimistic() Hook in React ]]>
                </title>
                <description>
                    <![CDATA[ Have you ever clicked a Like icon on a social media app and noticed the count jumps instantly? The colour of the icon changes at the same time, even before the server finishes the action. Now imagine you hit the same Like button, but it takes its swe... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-use-the-optimistic-ui-pattern-with-the-useoptimistic-hook-in-react/</link>
                <guid isPermaLink="false">693c5d28a2bfa1537f407a65</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ design patterns ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Tapas Adhikary ]]>
                </dc:creator>
                <pubDate>Fri, 12 Dec 2025 18:21:28 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1765561440350/c3546e6c-8b23-476a-86d4-b63fd2cb9f6c.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Have you ever clicked a <code>Like</code> icon on a social media app and noticed the count jumps instantly? The colour of the icon changes at the same time, even before the server finishes the action.</p>
<p>Now imagine you hit the same Like button, but it takes its sweet time in making the server call, performing the DB updates, and getting you the response back to update the state of the Like button.</p>
<p>Which experience would you like the most? You are most likely to select the first scenario. We all love “instant feedback”. The magic of instant feedback is powered by a pattern called the <code>Optimistic UI Pattern</code>.</p>
<p>In this article, we will uncover:</p>
<ul>
<li><p>What does Optimistic UI really mean?</p>
</li>
<li><p>Why does it massively improve the user experience?</p>
</li>
<li><p>How does React 19’s new useOptimistic() hook make it easier than ever?</p>
</li>
<li><p>How to implement a real-world scenario using the Optimistic Pattern</p>
</li>
<li><p>A bunch of use cases where you will be able to use this pattern.</p>
</li>
</ul>
<p>By the end, you will be proactively thinking of using this design pattern to improve the UX of your project.</p>
<p>This article is also available as a video tutorial as part of the <a target="_blank" href="https://www.youtube.com/playlist?list=PLIJrr73KDmRyQVT__uFZvaVfWPdfyMFHC">15 Days of React Design Patterns</a> <a target="_blank" href="https://www.youtube.com/playlist?list=PLIJrr73KDmRyQVT__uFZvaVfWPdfyMFHC">initiative</a>. Please check it out.</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/x03yX-yNxas" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<p> </p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><p><a class="post-section-overview" href="#heading-what-is-optimistic-ui">What is Optimistic UI</a>?</p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-does-it-work-under-the-hood">How Does it Work Under the Hood?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-build-an-optimistic-like-button">How to Build an Optimistic Like Button</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-pitfalls-and-anti-patterns">The Pitfalls and Anti-Patterns</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-15-days-of-react-design-patterns">15 Days of React Design Patterns</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-before-we-end">Before We End...</a></p>
</li>
</ol>
<h2 id="heading-what-is-optimistic-ui">What is Optimistic UI?</h2>
<p><code>Optimistic UI</code> (also known as optimistic updates) is a pattern that helps you update the UI immediately, assuming the server operation will succeed, and if it later fails, you roll back the UI to the correct state.</p>
<p>Instead of waiting for the round-trip of the client request, database write, server response, and then the UI render, the UI just updates instantly. This dramatically increases what’s called the <code>perceived speed</code>. The user of the application perceives the UI update as instant – but the actual operation may take place in the background.</p>
<h3 id="heading-without-an-optimistic-update">Without an Optimistic Update:</h3>
<p>If you’re not using the optimistic pattern, it’s just a traditional client-server mechanism, where:</p>
<ul>
<li><p>At the client side, a user interacts with a UI element.</p>
</li>
<li><p>An <a target="_blank" href="https://www.youtube.com/watch?v=WQdCffdPPKI">async call</a> (request) is made to the server.</p>
</li>
<li><p>The server processes the request and may make DB updates.</p>
</li>
<li><p>On a successful case, the server sends back the response to the client.</p>
</li>
<li><p>The client updates the relevant UI.</p>
</li>
<li><p>In an error case, the server sends back the error response to the client.</p>
</li>
<li><p>The client informs the user about the error.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765334108586/aabd3f16-b175-4b1d-ae33-94f33e1b894a.png" alt="Without an Optimistic Update" class="image--center mx-auto" width="1240" height="704" loading="lazy"></p>
<p>In this case, the user has to wait for the success/failure of the request to perceive any change after their interaction. This wait is neither uniform nor optimal. It may vary based on the network speed, network latency, and deployment strategies of the application.</p>
<h3 id="heading-with-an-optimistic-update">With an Optimistic Update:</h3>
<p>When you’re using an optimistic update, here’s how things go:</p>
<ul>
<li><p>At the client side, a user interacts with a UI element.</p>
</li>
<li><p>The UI gets updated instantly, and the user perceives the feedback immediately.</p>
</li>
<li><p>In parallel, in the background, the client initiates the server call.</p>
</li>
<li><p>The server processes the request and may make DB updates.</p>
</li>
<li><p>On a successful case, the server doesn’t do anything else, as the UI has been updated already, assuming this call will succeed.</p>
</li>
<li><p>In an error case, the server sends back the error response to the client.</p>
</li>
<li><p>The client rolls back the eager, optimistic UI update it made.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765334174203/e8bef9ba-28b6-45e0-8f22-0fc1468e3219.png" alt="With an Optimistic Update" class="image--center mx-auto" width="1189" height="892" loading="lazy"></p>
<p>In this case, the user doesn’t wait for the server round-trip to complete before the UI is updated. It’s much faster, assuming that, in most cases, the parallel server call will succeed.</p>
<p>With this comparison, we can now understand why Optimistic Updates matter in modern UI.</p>
<ul>
<li><p>It improves perceived speed</p>
</li>
<li><p>It keeps users engaged</p>
</li>
<li><p>It eliminates the awkward feelings like “Did my click work?”</p>
</li>
</ul>
<p>And so on. Optimistic updates are critical for real-time feeling features like chat messages, likes, comments, cart updates, poll votes, collaborative editing, and more. Even AI-driven apps that take time to respond benefit from optimistic placeholders like “Thinking…”, “Sending…” and so on.</p>
<h2 id="heading-how-does-it-work-under-the-hood">How Does it Work Under the Hood?</h2>
<p>Under the hood, there are actually two states:</p>
<ol>
<li><p>The Actual State: This is the actual source of truth. This data should be in sync with the server.</p>
</li>
<li><p>The Optimistic State: This is temporary and instantly shown to the user.</p>
</li>
</ol>
<p>When the server request succeeds, do nothing. Your optimistic state is now correct. If the server request fails, perform a rollback, and return UI the actual state.</p>
<p>React 19 introduced a built-in hook to help with this pattern called <code>useOptimistic()</code> . In the next section, we will take a deep dive into it with code and working internals.</p>
<h3 id="heading-the-useoptimistic-hook-in-react-19">The <code>useOptimistic()</code> Hook in React 19</h3>
<p><code>useOptimistic()</code> is a React hook introduced in React 19 to help with optimistic updates. The syntax and usage of the hook go like this:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> [optimisticState, addOptimistic] = useOptimistic(state, updateFn);
</code></pre>
<p>When an async action is underway, the <code>useOptimistic()</code> hook allows you to show different states.</p>
<p>It accepts:</p>
<ol>
<li><p><strong>currentState</strong>: your real source of truth (useState, Redux, server state, and so on)</p>
</li>
<li><p><strong>updateFn</strong>: a pure function that says how to compute the optimistic value</p>
</li>
</ol>
<p>It returns:</p>
<ol>
<li><p><strong>optimisticState</strong>: the temporary UI state</p>
</li>
<li><p><strong>addOptimisticUpdate(input)</strong>: function you call to apply immediate updates</p>
</li>
</ol>
<p>Take a look at the picture below. It shows the relationship between the current state and the optimistic state clearly:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765434835916/249e71eb-bba6-4b98-951a-feb397dc36e2.png" alt="Anatomy" class="image--center mx-auto" width="1744" height="781" loading="lazy"></p>
<p>Here’s what’s going on there:</p>
<ol>
<li><p>We pass the current state and an updater function to the <code>useOptimistic</code> hook.</p>
</li>
<li><p>The updater function takes the current state and a user input to compute and return the next optimistic state.</p>
</li>
<li><p>The input to the updater function is supplied using the <code>addOptimistic(input)</code> function.</p>
</li>
<li><p>Finally, the optimistic state value is used in the component.</p>
</li>
</ol>
<p>Let’s now build something exciting using this hook to understand its internals better.</p>
<h2 id="heading-how-to-build-an-optimistic-like-button">How to Build an Optimistic Like Button</h2>
<p>We will be building the Like button functionality optimistically. The flow will be like this:</p>
<ul>
<li><p>The user clicks on the Like button.</p>
</li>
<li><p>We update the Like button’s state immediately and optimistically.</p>
</li>
<li><p>In parallel, we send the server call to persist the value into the DB (we will simulate it)</p>
</li>
<li><p>Then we handle any error scenarios.</p>
</li>
</ul>
<p>First, let’s simulate a network call to the server using JavaScript’s Promise object and the <code>setTimeout()</code> web API:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// simulate a network call to the Server</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sendLikeToServer</span>(<span class="hljs-params">postId</span>) </span>{
    <span class="hljs-keyword">await</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">r</span>) =&gt;</span> <span class="hljs-built_in">setTimeout</span>(r, <span class="hljs-number">700</span>));

    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">Math</span>.random() &lt; <span class="hljs-number">0.2</span>) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"Network failed"</span>);
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Sent a like for the post id <span class="hljs-subst">${postId}</span>`</span>);
    <span class="hljs-keyword">return</span> { <span class="hljs-attr">success</span>: <span class="hljs-literal">true</span> };
}
</code></pre>
<p>The <code>sendLikeToServer</code> function takes a post ID as a parameter and simulates a fake network call using a Promise and a delay of 700 ms. It pretends to submit a request to the server to persist a post’s likes value.</p>
<p>To make it a bit more realistic, we have created a random error. The function will throw an error randomly so that we can understand the rollback scenario as well.</p>
<p>Next, we will create the real source of truth, the actual state for the Like count:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> [likes, setLikes] = useState(initialLikes);
</code></pre>
<p>Then, create the optimistic state value with the <code>useOptimistic()</code> hook:</p>
<pre><code class="lang-javascript"> <span class="hljs-keyword">const</span> [optimisticLikes, addOptimisticLike] = useOptimistic(
        likes, <span class="hljs-function">(<span class="hljs-params">currentLikes, delta</span>) =&gt;</span> currentLikes + delta);
</code></pre>
<p>Let’s understand this declaration well:</p>
<ul>
<li><p>We have passed the actual state value (likes) and the updater function to the <code>useOptimistic()</code> hook.</p>
</li>
<li><p>Take a look at the updater function, <code>(currentLikes, delta) =&gt; currentLikes + delta</code>. It’s an arrow function that gets the current like value and a delta. It returns the sum of the current like value and the delta. The return value logic is your own business logic. For incrementing the like count, it makes sense to increase the current like value by a delta value (of 1).</p>
</li>
<li><p>Now, the question is, how do we get this delta value? Who passes it? That’s where the return values of <code>useOptimistic()</code> come in handy. The <code>addOptimisticLike</code> is a function through which we can pass that delta value. How? Let’s take a look.</p>
</li>
</ul>
<p>When someone clicks on the Like button, we need to handle the click event and increase the like count value. So here is a <code>handleLike()</code> function which does that:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> handleLike = <span class="hljs-keyword">async</span> () =&gt; {
        addOptimisticLike(<span class="hljs-number">1</span>);
        <span class="hljs-keyword">try</span> {
            <span class="hljs-keyword">await</span> sendLikeToServer(postId);
            setLikes(<span class="hljs-function">(<span class="hljs-params">prev</span>) =&gt;</span> prev + <span class="hljs-number">1</span>);
        } <span class="hljs-keyword">catch</span> (err) {
            <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Like failed:"</span>, err);
            setLikes(<span class="hljs-function">(<span class="hljs-params">s</span>) =&gt;</span> s); 
        }
};
</code></pre>
<p>A lot is happening here:</p>
<ul>
<li><p>We call the <code>addOptimisticLike()</code> function with a delta value of 1. This call will ensure that the updater function <code>(currentLikes, delta) =&gt; currentLikes + delta</code> of the <code>useOptimistic()</code> will be called. The return value will be set to the optimistic state, that is, <code>optimisticLikes</code>.</p>
</li>
<li><p>This optimistic state value we use in the JSX. So we can see the increased like count immediately.</p>
</li>
<li><p>Then we make the fake server call, and also update the actual state, provided the server call was successful.</p>
</li>
<li><p>In case of an error, the control goes into the catch-block, where we roll back the likes value to the previous one. This will also sync the optimistic state’s value with a rollback.</p>
</li>
</ul>
<p>Here is the complete code of the <code>LikeButton</code> component:</p>
<pre><code class="lang-javascript">
<span class="hljs-keyword">import</span> { startTransition, useOptimistic, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-comment">// simulate a network call to the Server</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sendLikeToServer</span>(<span class="hljs-params">postId</span>) </span>{
    <span class="hljs-keyword">await</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">r</span>) =&gt;</span> <span class="hljs-built_in">setTimeout</span>(r, <span class="hljs-number">700</span>));

    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">Math</span>.random() &lt; <span class="hljs-number">0.2</span>) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"Network failed"</span>);
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Sent a like for the post id <span class="hljs-subst">${postId}</span>`</span>);
    <span class="hljs-keyword">return</span> { <span class="hljs-attr">success</span>: <span class="hljs-literal">true</span> };
}

<span class="hljs-comment">// The Like Button Component</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">LikeButton</span>(<span class="hljs-params">{ postId, initialLikes = <span class="hljs-number">0</span> }</span>) </span>{
    <span class="hljs-comment">// the "real" source of truth for likes (committed)</span>
    <span class="hljs-keyword">const</span> [likes, setLikes] = useState(initialLikes);
    <span class="hljs-comment">// optimistic state and updater function</span>
    <span class="hljs-keyword">const</span> [optimisticLikes, addOptimisticLike] = useOptimistic(
        likes,
        <span class="hljs-function">(<span class="hljs-params">currentLikes, delta</span>) =&gt;</span> currentLikes + delta
    );

    <span class="hljs-keyword">const</span> handleLike = <span class="hljs-keyword">async</span> () =&gt; {
        <span class="hljs-comment">// 1) Apply optimistic change *immediately*</span>
        addOptimisticLike(<span class="hljs-number">1</span>);

        <span class="hljs-comment">// 2) Start server call in low priority to avoid blocking UI</span>

        <span class="hljs-keyword">try</span> {
            <span class="hljs-keyword">await</span> sendLikeToServer(postId);
            <span class="hljs-comment">// On success, commit the real state update:</span>
            <span class="hljs-comment">// IMPORTANT: update the real state so optimistic snapshot eventually matches</span>
            setLikes(<span class="hljs-function">(<span class="hljs-params">prev</span>) =&gt;</span> prev + <span class="hljs-number">1</span>);
        } <span class="hljs-keyword">catch</span> (err) {
            <span class="hljs-comment">// On error, rollback the real state (or trigger a refetch)</span>
            <span class="hljs-comment">// Because we never incremented likes (real), just leave likes unchanged</span>
            <span class="hljs-comment">// But we should show an error to user:</span>
            <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Like failed:"</span>, err);
            <span class="hljs-comment">// Optionally: show toast or set an error state</span>
            <span class="hljs-comment">// And — to force the optimistic view to refresh and reflect real state,</span>
            <span class="hljs-comment">// call setLikes to current value</span>
            setLikes(<span class="hljs-function">(<span class="hljs-params">s</span>) =&gt;</span> s); <span class="hljs-comment">// no-op but will cause optimistic to reflect the</span>
                                <span class="hljs-comment">// committed value Or you can trigger a re-fetch of the </span>
                                <span class="hljs-comment">// post state</span>
        }
    };

    <span class="hljs-keyword">return</span> (
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{handleLike}</span>&gt;</span>❤️ {optimisticLikes}<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =&gt;</span> startTransition(async () =&gt; handleLike())}&gt;
                ❤️ {optimisticLikes}
            <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
    );
}
</code></pre>
<p>Have you noticed that we have wrapped the <code>handleLike()</code> call with the <code>startTransition</code>?</p>
<p>Without this, React gives us a warning:</p>
<blockquote>
<p>“An optimistic state update occurred outside a transition or action.”</p>
</blockquote>
<p>This is because optimistic updates are <strong>low-priority visual updates</strong>, not critical ones.</p>
<p>Using <code>startTransition()</code> ensures that:</p>
<ul>
<li><p>React doesn’t block rendering</p>
</li>
<li><p>We do not get the warning</p>
</li>
<li><p>We get a smooth, optimistic experience</p>
</li>
</ul>
<p>The transitions are part of React’s concurrency model that helps us improve the performance of React applications. If you are interested in learning various performance optimisation techniques, <a target="_blank" href="https://www.youtube.com/watch?v=G8Mk6lsSOcw">here is a two-part guide for you</a>.</p>
<h2 id="heading-the-pitfalls-and-anti-patterns">The Pitfalls and Anti-Patterns</h2>
<p>With any design pattern, we need to be aware of possible pitfalls, misuses, and anti-patterns. Here are a few things you should be aware of:</p>
<ul>
<li><p>Don’t assume that the server call will be successful. Network failure will happen, and you need to have a way to roll back. Rollback is the heart of optimistic UI. Omitting the rollback logic will cause adverse consequences.</p>
</li>
<li><p>Don’t try hiding the bad UX behind optimistic updates. The Optimistic UI is not a fix or replacement for poor designs.</p>
</li>
<li><p>Don’t perform any expensive work in optimistic updates. Keep the optimistic updater function lean, pure, and fast.</p>
</li>
</ul>
<h2 id="heading-15-days-of-react-design-patterns"><strong>15 Days of React Design Patterns</strong></h2>
<p>I have some great news for you: after my <em>40 days of the JavaScript</em> initiative, I have now started a brand new initiative called <a target="_blank" href="https://www.youtube.com/playlist?list=PLIJrr73KDmRyQVT__uFZvaVfWPdfyMFHC">15 Days of React Design Patterns</a>.</p>
<p>If you enjoyed learning from this article, I am sure you will love this series, featuring the 15+ most important React design patterns. Check it out and join for FREE:</p>
<p><a target="_blank" href="https://www.youtube.com/playlist?list=PLIJrr73KDmRyQVT__uFZvaVfWPdfyMFHC"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765439781697/751c2051-5dc2-4a88-bcc2-037f6ce0e91e.png" alt="https://www.youtube.com/playlist?list=PLIJrr73KDmRyQVT__uFZvaVfWPdfyMFHC" class="image--center mx-auto" width="1612" height="850" loading="lazy"></a></p>
<h2 id="heading-before-we-end"><strong>Before We End...</strong></h2>
<p>That’s all! I hope you found this article insightful. You can find all the source code used in this tutorial on the <a target="_blank" href="https://github.com/tapascript/15-days-of-react-design-patterns/tree/main/day-08">tapaScript GitHub</a>.</p>
<p><a target="_blank" href="https://github.com/tapascript/15-days-of-react-design-patterns/tree/main/day-03/compound-components-patterns">Let’s connect:</a></p>
<ul>
<li><p>Subscribe to my <a target="_blank" href="https://www.youtube.com/tapasadhikary?sub_confirmation=1">YouTube Channel</a>.</p>
</li>
<li><p>Grab the <a target="_blank" href="https://www.tapascript.io/books/react-hooks-cheatsheet">React Hooks Cheatsheet</a>.</p>
</li>
<li><p>Follow on <a target="_blank" href="https://www.linkedin.com/in/tapasadhikary/">LinkedIn</a> if you don't want to miss the daily dose of up-skilling tips.</p>
</li>
<li><p>Join my <a target="_blank" href="https://discord.gg/zHHXx4vc2H">Discord Server</a>, and let’s learn together.</p>
</li>
<li><p>Subscribe to my fortnightly newsletter, <a target="_blank" href="https://tapascript.substack.com/subscribe?utm_medium=fcc">The Commit Log</a>.</p>
</li>
</ul>
<p>See you soon with my next article. Until then, please take care of yourself and keep learning.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ React’s Critical "React2Shell" Vulnerability — What You Should Know, and How to Upgrade Your App ]]>
                </title>
                <description>
                    <![CDATA[ Web development is always evolving, and sometimes those changes happen a bit under the hood. One such change involved the shift to React Server Components (RSC). If you’re a NextJS or React developer, especially using the App Router, understanding th... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/reacts-critical-react2shell-vulnerability-what-you-should-know-and-how-to-upgrade-your-app/</link>
                <guid isPermaLink="false">6939b6ee10076c81dd6c3f49</guid>
                
                    <category>
                        <![CDATA[ React2Shell ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Next.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ hacking ]]>
                    </category>
                
                    <category>
                        <![CDATA[ cybersecurity ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Arunachalam B ]]>
                </dc:creator>
                <pubDate>Wed, 10 Dec 2025 18:07:42 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1765328805925/a9c016a1-90a9-4123-bbb0-17c7d46da035.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Web development is always evolving, and sometimes those changes happen a bit under the hood. One such change involved the shift to React Server Components (RSC). If you’re a NextJS or React developer, especially using the App Router, understanding the new security alert is really important for keeping your apps safe and secure.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-is-react2shell">What is "React2Shell"?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-why-is-this-happening-now">Why is this Happening Now?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-should-you-worry-about-this-change">Should You Worry About this Change?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-is-this-mandatory">Is this Mandatory?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-bad-can-it-get-the-extent-of-exploitation">How Bad Can It Get? The Extent of Exploitation</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-would-be-the-code-change-for-this">What Would be the Code Change for This?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-advanced-verify-with-the-original-exploit-poc">Advanced: Verify with the Original Exploit (PoC)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-emergency-response-what-if-you-were-already-compromised">Emergency Response: What If You Were Already Compromised?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-what-is-react2shell">What is "React2Shell"?</h2>
<p>Think of your server receiving data like a mailroom receiving packages.</p>
<p>Usually, a mailroom checks if a package is safe before opening it. But in vulnerable versions of React and NextJS, the "Flight" protocol (used to communicate between the server and client) acts like a mailroom that blindly opens every package and follows any instructions inside immediately.</p>
<p>This vulnerability (CVE-2025-55182) allows an attacker to send a specifically crafted "package" (HTTP request) that forces your server to execute malicious code – like stealing passwords or installing viruses –without even logging in.</p>
<h2 id="heading-why-is-this-happening-now">Why is this Happening Now?</h2>
<p>It's all about how modern frameworks handle data serialization. There are a few reasons why this was just discovered.</p>
<p>First, React has complex serialization. To make Server Components seamless, React sends complex data structures back and forth.</p>
<p>Second, it has the "Flight" protocol. The vulnerability was found in how this specific protocol de-serializes (unpacks) data. It was too trusting of the input it received from the client side.</p>
<h2 id="heading-should-you-worry-about-this-change">Should You Worry About this Change?</h2>
<p>You need to pay attention if your app qualifies for any of the below:</p>
<ul>
<li><p><strong>You are using NextJS App Router:</strong> This is the default in newer NextJS versions (v13+).</p>
</li>
<li><p><strong>You are using React 19:</strong> Specifically versions with Server Components enabled.</p>
</li>
<li><p><strong>You use Server Actions:</strong> If your app takes user input and processes it on the server using React's server actions.</p>
</li>
</ul>
<h2 id="heading-is-this-mandatory">Is this Mandatory?</h2>
<p><strong>Yes.</strong> This is a critical security update. If your app qualifies in any of the above scenarios, you need to act immediately. Because, this vulnerability is being exploited right now.</p>
<h2 id="heading-how-bad-can-it-get-the-extent-of-exploitation">How Bad Can It Get? The Extent of Exploitation</h2>
<p>You might be thinking, "My site is just a simple content wrapper, surely I'm not a target?" Unfortunately, with Remote Code Execution (RCE), the attacker doesn't just "break" your site – they own the server it runs on.</p>
<p>Here is exactly what a hacker can do once they exploit this vulnerability:</p>
<h3 id="heading-total-environment-theft">Total Environment Theft</h3>
<p>The most immediate risk is your <code>.env</code> file. Attackers can execute code to read your environment variables, instantly gaining access to your AWS Secret Keys, Database passwords, Stripe API keys, and OpenAI tokens.</p>
<h3 id="heading-the-shell-access">The "Shell" Access</h3>
<p>As the name "React2Shell" implies, attackers can open a reverse shell. This gives them a command-line interface on your server, allowing them to browse your file system as if they were sitting in front of your computer.</p>
<h3 id="heading-lateral-movement">Lateral Movement</h3>
<p>Once inside your NodeJS server, they are behind your firewall. They can now attack your internal services (like Redis, internal databases, or private micro-services) that are usually blocked from the outside world.</p>
<h3 id="heading-supply-chain-poisoning">Supply Chain Poisoning</h3>
<p>If your build server is vulnerable, an attacker could potentially inject malicious code into your deployment pipeline, affecting every user who visits your site in the future.</p>
<h3 id="heading-botnet-recruitment">Botnet Recruitment</h3>
<p>Hackers often automate these attacks to install crypto-miners, using your server's CPU (which you pay for!) to mine digital currency for them, often crashing your application in the process.</p>
<h2 id="heading-what-would-be-the-code-change-for-this">What Would be the Code Change for This?</h2>
<p>You don’t need to rewrite your application code, but you must update your dependencies in your release line.</p>
<p>The vulnerability is fully resolved in the following patched NextJS releases:</p>
<ul>
<li><p>15.0.5</p>
</li>
<li><p>15.1.9</p>
</li>
<li><p>15.2.6</p>
</li>
<li><p>15.3.6</p>
</li>
<li><p>15.4.8</p>
</li>
<li><p>15.5.7</p>
</li>
<li><p>16.0.7</p>
</li>
</ul>
<p>Patched canary releases for NextJS 15 and 16:</p>
<ul>
<li><p>15.6.0-canary.58 (for 15.x canary releases)</p>
</li>
<li><p>16.1.0-canary.12 (for 16.x canary releases)</p>
</li>
</ul>
<p>These versions include the hardened React Server Components implementation.</p>
<p>Here are the patched versions for React JS:</p>
<ul>
<li><p>19.0.1</p>
</li>
<li><p>19.1.2</p>
</li>
<li><p>19.2.1</p>
</li>
</ul>
<p>Frameworks and bundlers using the aforementioned packages should install the latest versions provided by their respective maintainers.</p>
<p>Alternatively, you can run <code>npx fix-react2shell-next</code> in your NextJS project to launch an interactive tool which can check versions and perform deterministic version bumps per the recommended versions above. See the <a target="_blank" href="https://github.com/vercel-labs/fix-react2shell-next">GitHub repository</a> for full details.</p>
<p><strong>There is no workaround other than upgrading to a patched version.</strong></p>
<p>It’s highly recommended to rotate all your application secrets, once you have patched your version and re-deployed your application.</p>
<h2 id="heading-advanced-verify-with-the-original-exploit-poc">Advanced: Verify with the Original Exploit (PoC)</h2>
<p>If you want to be 100% sure your patch is working, or if you want to understand how the attack actually works, you can use the original Proof of Concept (PoC) created by the security researcher (Lachlan Davidson) who found the bug.</p>
<p><strong>Repository:</strong> <a target="_blank" href="https://github.com/lachlan2k/React2Shell-CVE-2025-55182-original-poc">React2Shell-CVE-2025-55182-original-poc</a></p>
<p>Lachlan provided three variations of the exploit script. The most important one for testing is <code>01-submitted-poc.js</code>, which is the exact, simplified version submitted to Meta for the bug bounty.</p>
<h3 id="heading-how-the-exploit-works">How the Exploit Works</h3>
<p>According to the repository, the attack works by tricking the parser:</p>
<ol>
<li><p>The attacker sends a payload using <code>$@x</code> to access a specific data <code>Chunk</code>.</p>
</li>
<li><p>They "plant" a <code>.then</code> function on a fake object.</p>
</li>
<li><p>The JavaScript runtime thinks it is handling a Promise and tries to "unravel" it.</p>
</li>
<li><p>This allows the attacker to re-enter the parser with a malicious fake chunk, giving them access to internal server gadgets (like <code>_response</code>) to execute code (RCE).</p>
</li>
</ol>
<h3 id="heading-steps-to-recreate-the-issue">Steps to Recreate the Issue</h3>
<p><strong>⚠️ WARNING:</strong> Only run this against a local development server (<a target="_blank" href="http://localhost"><code>localhost</code></a>) that you own. Never run this against production servers or public websites.</p>
<p><strong>Note:</strong> I forked Lachlan’s repo and made minor changes to make it easy for you to run the script.</p>
<h4 id="heading-step-1-clone-the-repository">Step 1: Clone the Repository</h4>
<p>Run the following commands to clone the repository, navigate into the project, and install dependencies:</p>
<pre><code class="lang-bash">git <span class="hljs-built_in">clone</span> https://github.com/arunachalam-b/React2Shell-CVE-2025-55182-original-poc.git
<span class="hljs-built_in">cd</span> React2Shell-CVE-2025-55182-original-poc
npm i
</code></pre>
<h4 id="heading-step-2-run-a-vulnerable-local-server">Step 2: Run a Vulnerable Local Server</h4>
<p>Start your NextJS application locally (ensure it’s running a vulnerable version, for example NextJS 15.0.0, for the test to succeed).</p>
<pre><code class="lang-bash">npm run dev
<span class="hljs-comment"># usually runs on http://localhost:3000</span>
</code></pre>
<h4 id="heading-step-3-execute-the-test">Step 3: Execute the Test</h4>
<p>You will need to modify the script or use a tool like <code>curl</code> to send the payload structure found in <code>01-submitted-poc.js</code> to your server's endpoint (usually a Server Action endpoint). Or simply run the following command if your app is accessible at <code>http://localhost:3000</code>:</p>
<pre><code class="lang-bash">node 01-submitted-poc.js
</code></pre>
<p>If the exploit succeeds (on the vulnerable version), the console will log the execution of the code (RCE). If the exploit fails (after you patch), the server will either reject the request or error out safely.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765298324009/7a8158bb-30cc-4604-9591-4503a4c8d655.png" alt="This response on running the script indicates your server is vulnerable" class="image--center mx-auto" width="1160" height="461" loading="lazy"></p>
<p>You can also confirm if your infected web server prints <code>50</code> in the console. Because we inject the code to do a calculation (look at <code>_prefix</code> field in the below JSON) that results in <code>50</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765298178869/417d5f3c-15d2-4806-854c-f4216d336bd9.png" alt="The payload used to demonstrate this hack" class="image--center mx-auto" width="568" height="367" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765298285565/38bcbcfa-390f-4198-9db1-218d2ed0dd65.png" alt="The 50 in your NextJS console indicates the hackers code has been executed on your server" class="image--center mx-auto" width="707" height="208" loading="lazy"></p>
<p>After you apply the fix, you should see an error while running the script. In this case, as I’m using NextJS v15.1, the fix is upgrading the <code>next</code> package to version <code>15.1.9</code>. Here are the screenshots after upgrading the package and running the script.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765298558607/36103fd0-2a23-44f5-bff6-f979255a9765.png" alt="Response on running script after applying the fix" class="image--center mx-auto" width="1160" height="572" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765298580698/53bcd836-013a-4291-98df-2d9188512a2c.png" alt="The console does not print 50 while running same script which indicates the hackers code is not executed on your server after applying the fix" class="image--center mx-auto" width="1321" height="251" loading="lazy"></p>
<h4 id="heading-step-4-verification">Step 4: Verification</h4>
<p>Once you have confirmed the exploit works on the old version, update your packages (as shown in the section above) and run the script again. It should no longer trigger the code execution.</p>
<h2 id="heading-emergency-response-what-if-you-were-already-compromised">Emergency Response: What If You Were Already Compromised?</h2>
<p>If you suspect your server was exposed to the internet with a vulnerable version, assume the worst. A hacker may have already stolen your keys or left a "backdoor" to return later. Patching the code alone is NOT enough in this case.</p>
<p>Follow this <strong>"Nuke and Pave"</strong> protocol immediately:</p>
<h3 id="heading-step-1-isolate-and-shutdown">Step 1: Isolate and Shutdown</h3>
<p>Take the compromised server offline immediately. Do not try to "fix" it while it is running.</p>
<h3 id="heading-step-2-rotate-all-secrets-crucial-step">Step 2: Rotate ALL Secrets (Crucial Step)</h3>
<p>Assume every secret in your <code>.env</code> file is in the hands of a hacker. You must generate new ones:</p>
<ul>
<li><p>Change the password for your database users.</p>
</li>
<li><p>Rotate AWS Access Keys, Google Cloud Service Account keys, and so on.</p>
</li>
<li><p>Roll your Stripe/PayPal/Razorpay API keys.</p>
</li>
<li><p>Rotate your <code>NEXTAUTH_SECRET</code> or any JWT signing keys.</p>
</li>
</ul>
<h3 id="heading-step-3-do-not-clean-rebuild">Step 3: Do Not "Clean" — Rebuild</h3>
<p>Do not attempt to find and delete malware files on the server. Hackers are good at hiding.</p>
<ul>
<li><p>Destroy the existing container, droplet, or EC2 instance entirely.</p>
</li>
<li><p>Build a fresh instance from your source code (after applying the patch).</p>
</li>
</ul>
<h3 id="heading-step-4-audit-your-logs">Step 4: Audit Your Logs</h3>
<p>Look at your database and cloud provider logs. Did anyone download your entire user database? Did anyone spin up expensive GPU instances on your AWS account? Check for unusual activity that occurred before you patched.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this article, you learned about the "React2Shell" vulnerability, how to verify it using the original developer's tools, and how to upgrade your app to secure your Server Components. I hope you have a clear idea about why this update is urgent. By being proactive now, you can avoid a catastrophic data breach.</p>
<p>You can follow my <a target="_blank" href="https://x.com/AI_Techie_Arun">Twitter/X account</a> to receive the top AI news everyday. If you wish to learn more about cybersecurity, <a target="_blank" href="https://5minslearn.gogosoon.com/?ref=react2shell-vulnerability">subscribe to my email newsletter</a> and follow me on social media.</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
