<?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" version="2.0"><channel><title><![CDATA[Sazzadur's blog]]></title><description><![CDATA[👋 Hey there! I'm a developer with a knack for creating robust and user-friendly applications. My expertise spans across various technologies, including TypeScr]]></description><link>https://blog.sazzadur.site</link><generator>RSS for Node</generator><lastBuildDate>Fri, 24 Apr 2026 21:59:41 GMT</lastBuildDate><atom:link href="https://blog.sazzadur.site/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Soft Delete Is Easy. Data Consistency Is Not.]]></title><description><![CDATA[You add a column like isDeleted, flip it to true, and suddenly life feels safer. No data loss. Easy rollback. No angry product manager asking why something is gone forever.
At first, it works beautifully.
Until your data starts depending on other dat...]]></description><link>https://blog.sazzadur.site/soft-delete-is-easy-data-consistency-is-not</link><guid isPermaLink="true">https://blog.sazzadur.site/soft-delete-is-easy-data-consistency-is-not</guid><category><![CDATA[Software Engineering]]></category><category><![CDATA[Backend Development]]></category><category><![CDATA[database design]]></category><category><![CDATA[data-modeling]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Sazzadur Rahman]]></dc:creator><pubDate>Sat, 20 Dec 2025 15:01:34 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1766242803362/a5956db1-db1d-4049-812f-560fa9c1d5b3.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<hr />
<p>You add a column like <code>isDeleted</code>, flip it to <code>true</code>, and suddenly life feels safer. No data loss. Easy rollback. No angry product manager asking why something is gone forever.</p>
<p>At first, it works beautifully.</p>
<p>Until your data starts depending on other data.</p>
<p>That’s when soft delete stops being a convenience and starts quietly breaking things. Not with errors or crashes, but with something worse: incorrect history.</p>
<p>The real problem isn’t that records are deleted. It’s that <strong>mutable data is being treated as historical truth</strong>.</p>
<p>I ran into this while working on a simple-looking system. Dormitories, rooms, beds, bookings. Nothing fancy. But one small change exposed a flaw that soft delete alone cannot fix.</p>
<p>This article is not about how to implement soft delete. It’s about what happens <strong>after</strong> you do.</p>
<hr />
<h3 id="heading-the-illusion-of-safety">The Illusion of Safety</h3>
<p>Soft delete feels like the responsible choice.</p>
<p>Instead of removing rows from the database, you keep them around. You tell yourself this is safer. If something goes wrong, you can always bring the data back. Audits become easier. Mistakes feel reversible.</p>
<p>From a code perspective, it’s almost too easy:</p>
<pre><code class="lang-javascript">isDeleted <span class="hljs-built_in">Boolean</span> @<span class="hljs-keyword">default</span>(<span class="hljs-literal">false</span>)
</code></pre>
<p>Then you update your queries to filter out deleted records, and you move on.</p>
<p>Most of the time, this works. Especially when your data is flat or loosely connected.</p>
<p>The illusion starts when you believe that keeping records automatically preserves correctness.</p>
<p>Soft delete protects <em>existence</em>, not <em>meaning</em>.</p>
<p>A deleted record still exists in the database, but the moment you stop treating it as part of the active system, you’ve changed how the rest of your data behaves. Relationships don’t disappear. They just become awkward.</p>
<p>And the more your data model relies on relationships, the faster this illusion breaks.</p>
<ul>
<li><p>You won’t notice it during development.</p>
</li>
<li><p>You won’t see it in unit tests.</p>
</li>
<li><p>You’ll see it when someone asks a very simple question:</p>
</li>
</ul>
<blockquote>
<p><em>“Why does this old record look wrong?”</em></p>
</blockquote>
<p>That’s usually when you realize something deeper is broken.</p>
<hr />
<h3 id="heading-where-things-start-to-break">Where Things Start to Break</h3>
<p>Soft delete works best when records are independent.</p>
<ul>
<li><p>A user gets deleted.</p>
</li>
<li><p>A tag gets deleted.</p>
</li>
<li><p>A draft gets deleted.</p>
</li>
</ul>
<p>No other piece of data needs them to explain the past.</p>
<p>Problems begin when records stop being standalone and start representing <strong>structure</strong>.</p>
<p>Structure is different. Other data relies on it to make sense.</p>
<ul>
<li><p>Orders rely on products.</p>
</li>
<li><p>Bookings rely on rooms.</p>
</li>
<li><p>Seats rely on flights.</p>
</li>
<li><p>Beds rely on dormitories.</p>
</li>
</ul>
<p>When you soft delete something structural, you’re not just hiding a row. You’re changing the shape of the system that older data was built on.</p>
<p>This is where data consistency starts leaking.</p>
<p>At first, it’s subtle. A missing label here. A null value there. A join that suddenly returns less data than expected.</p>
<p>So you patch it.</p>
<p>You add conditional checks. You show placeholders. You ignore deleted records in reports.</p>
<p>But none of this fixes the core issue. It only hides it.</p>
<p>Because historical data is still asking a very simple question:</p>
<blockquote>
<p>“What did the world look like at the time this happened?”</p>
</blockquote>
<p>And your system can no longer answer that honestly.</p>
<p>This becomes painfully clear when a structural change is made for a perfectly valid reason.</p>
<p>Which brings me to the dormitory problem.</p>
<hr />
<h3 id="heading-the-dormitory-problem">The Dormitory Problem</h3>
<p>Imagine a dormitory with <strong>15 beds</strong>.</p>
<p>Each bed is a record in the database. Bookings reference beds. Everything is clean and relational.</p>
<p>Over time, a few guests stay in <strong>beds 13, 14, and 15</strong>. Their bookings are completed, paid for, and archived. This is now historical data.</p>
<p>Later, an admin decides to reduce the dormitory capacity to <strong>10 beds</strong>. Maybe the room layout changed. Maybe regulations changed. It’s a valid business decision.</p>
<p>To reflect this, beds <strong>11–15</strong> are soft deleted.</p>
<p>No rows are removed. <code>isDeleted</code> is set to <code>true</code>. The system still “has” the data.</p>
<p>But now ask a simple question:</p>
<blockquote>
<p>“What happens to the old bookings?”</p>
</blockquote>
<p>Those bookings still point to beds 13, 14, and 15. Beds that no longer exist in the active system.</p>
<p>On the surface, nothing crashes.</p>
<p>But cracks start to show:</p>
<ul>
<li><p>Booking history pages can’t display bed details properly</p>
</li>
<li><p>Reports skip deleted beds and show incomplete data</p>
</li>
<li><p>Admins see “missing” information and lose trust in the system</p>
</li>
<li><p>Developers add more conditions to patch the UI</p>
</li>
</ul>
<p>The bookings are correct.<br />The beds existed.<br />The stays happened.</p>
<p>Yet the system can no longer describe that reality accurately.</p>
<p>That’s the real failure.</p>
<hr />
<h3 id="heading-why-soft-delete-isnt-the-real-problem">Why Soft Delete Isn’t the Real Problem</h3>
<p>It’s tempting to blame soft delete here. But soft delete is just the messenger.</p>
<p>The real issue is treating <strong>mutable structure</strong> as <strong>historical truth</strong>.</p>
<p>Dormitory capacity is configuration.<br />Bed availability is configuration.<br />Room layout is configuration.</p>
<p>Bookings are not.</p>
<p>A booking is a fact. It already happened. It should never depend on something that can change later.</p>
<p>When a booking depends on live structural data, you’re asking the present to explain the past. And the present has already moved on.</p>
<p>Soft delete didn’t break your data.<br />Your mental model did.</p>
<hr />
<h3 id="heading-better-ways-to-model-this">Better Ways to Model This</h3>
<p>There’s no single “correct” solution, but there are safer patterns.</p>
<h4 id="heading-1-never-truly-remove-structural-entities">1. Never Truly Remove Structural Entities</h4>
<p>Beds don’t disappear. They become inactive.</p>
<p>You stop assigning them to new bookings, but you never treat them as gone. Historical records can still reference them without hacks or fallbacks.</p>
<p>This keeps joins simple and history intact.</p>
<h4 id="heading-2-version-your-structure">2. Version Your Structure</h4>
<p>Instead of one mutable dormitory, you create versions.</p>
<p>Bookings reference the version that was active at the time of booking. Capacity changes create a new version, not a rewrite of reality.</p>
<p>This is powerful, but heavier to implement.</p>
<h4 id="heading-3-snapshot-what-matters">3. Snapshot What Matters</h4>
<p>For historical records, store what you actually need.</p>
<p>Instead of depending on joins:</p>
<ul>
<li><p>Store bed number</p>
</li>
<li><p>Store room name</p>
</li>
<li><p>Store capacity at the time</p>
</li>
</ul>
<p>This accepts duplication in exchange for truth.</p>
<p>History becomes self-contained.</p>
<hr />
<h3 id="heading-the-rule-i-follow-now">The Rule I Follow Now</h3>
<p>I don’t decide soft delete strategies first anymore.</p>
<p>I ask one question:</p>
<blockquote>
<p><em>“Can this data change after something depends on it?”</em></p>
</blockquote>
<p>If the answer is yes, history should not rely on it.</p>
<p>A few simple rules guide me now:</p>
<ul>
<li><p>Historical records must survive structural changes</p>
</li>
<li><p>Soft delete is about visibility, not correctness</p>
</li>
<li><p>If data explains the past, freeze it or snapshot it</p>
</li>
</ul>
<hr />
<h3 id="heading-closing-thoughts">Closing Thoughts</h3>
<p>Soft delete solves deletion.</p>
<p>It does not solve meaning.</p>
<p>If your system cannot explain what happened yesterday after a change today, the problem isn’t missing rows. It’s broken history.</p>
<p>And no amount of <code>isDeleted = true</code> can fix that.</p>
]]></content:encoded></item><item><title><![CDATA[The Right Way to Upload Files to S3 from a Serverless Backend]]></title><description><![CDATA[Introduction
File uploads sound simple — until you try doing them in a serverless environment. Suddenly your “tiny upload feature” feels like it’s eating half your cloud budget.
On a normal server, you can just accept a file, push it to Amazon S3, an...]]></description><link>https://blog.sazzadur.site/upload-files-to-s3-from-serverless</link><guid isPermaLink="true">https://blog.sazzadur.site/upload-files-to-s3-from-serverless</guid><category><![CDATA[serverless]]></category><category><![CDATA[S3]]></category><category><![CDATA[AWS s3]]></category><category><![CDATA[upload]]></category><category><![CDATA[File Upload]]></category><dc:creator><![CDATA[Sazzadur Rahman]]></dc:creator><pubDate>Sat, 20 Sep 2025 04:05:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1758341488455/906d072e-a5a8-4d96-98a6-0c2a0867fc2c.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<hr />
<h3 id="heading-introduction">Introduction</h3>
<p>File uploads sound simple — until you try doing them in a serverless environment. Suddenly your “tiny upload feature” feels like it’s eating half your cloud budget.</p>
<p>On a normal server, you can just accept a file, push it to Amazon S3, and call it a day. But serverless backends (like AWS Lambda, Vercel Functions, or Netlify Functions) aren’t meant to babysit your uploads. They’re sprinters, not marathon runners.</p>
<p>That means:</p>
<ul>
<li><p><strong>Timeouts</strong> (big files take longer than your function’s max execution time).</p>
</li>
<li><p><strong>High costs</strong> (you pay for every second your backend is stuck watching progress bars).</p>
</li>
<li><p><strong>Unnecessary complexity</strong> (your backend turns into a middleman with trust issues).</p>
</li>
</ul>
<p>The good news? There’s a better way. Instead of playing “file courier,” let your backend hand out a <strong>VIP pass (presigned URL)</strong> so clients can walk right into S3 themselves.</p>
<hr />
<h3 id="heading-traditional-approach-uploading-through-backend">Traditional Approach: Uploading Through Backend</h3>
<p>Here’s the old-school method:</p>
<ol>
<li><p>Client uploads a file to your backend.</p>
</li>
<li><p>Backend temporarily holds it (like an awkward third wheel).</p>
</li>
<li><p>Backend uploads it to S3.</p>
</li>
</ol>
<p>A simple Node.js + Multer setup might look like this:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// server.js</span>
<span class="hljs-keyword">import</span> express <span class="hljs-keyword">from</span> <span class="hljs-string">"express"</span>;
<span class="hljs-keyword">import</span> multer <span class="hljs-keyword">from</span> <span class="hljs-string">"multer"</span>;
<span class="hljs-keyword">import</span> { S3Client, PutObjectCommand } <span class="hljs-keyword">from</span> <span class="hljs-string">"@aws-sdk/client-s3"</span>;

<span class="hljs-keyword">const</span> app = express();
<span class="hljs-keyword">const</span> upload = multer({ <span class="hljs-attr">storage</span>: multer.memoryStorage() });
<span class="hljs-keyword">const</span> s3 = <span class="hljs-keyword">new</span> S3Client({
  <span class="hljs-attr">credentials</span>: {
    <span class="hljs-attr">accessKeyId</span>: process.env.ACCESS_KEY_ID,
    <span class="hljs-attr">secretAccessKey</span>: process.env.SECRET_ACCESS_KEY,
  },
  <span class="hljs-attr">region</span>: process.env.AWS_REGION,
});

app.post(<span class="hljs-string">"/upload"</span>, upload.single(<span class="hljs-string">"file"</span>), <span class="hljs-keyword">async</span> (req, res) =&gt; {
  <span class="hljs-keyword">const</span> params = {
    <span class="hljs-attr">Bucket</span>: process.env.S3_BUCKET,
    <span class="hljs-attr">Key</span>: req.file.originalname,
    <span class="hljs-attr">Body</span>: req.file.buffer,
  };
  <span class="hljs-keyword">const</span> params = {
    <span class="hljs-attr">Bucket</span>: process.env.S3_BUCKET,
    <span class="hljs-attr">Key</span>: req.file.originalname,
    <span class="hljs-attr">Body</span>: req.file.buffer,
    <span class="hljs-attr">ContentType</span>: req.file.mimetype,
  };

  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> command = <span class="hljs-keyword">new</span> PutObjectCommand(params);
    <span class="hljs-keyword">await</span> s3Client.send(command);

    res.json({ <span class="hljs-attr">message</span>: <span class="hljs-string">"File uploaded successfully"</span> });
  } <span class="hljs-keyword">catch</span> (err) {
    res.status(<span class="hljs-number">500</span>).json({ <span class="hljs-attr">error</span>: err.message });
  }
});

app.listen(<span class="hljs-number">3000</span>);
</code></pre>
<p>Flow: <strong>Client → Backend → S3</strong></p>
<p>The problems are obvious:</p>
<ul>
<li><p><strong>Scalability</strong> → Large files keep your serverless function awake way past its bedtime.</p>
</li>
<li><p><strong>Costs</strong> → You’re billed while your backend plays middleman.</p>
</li>
<li><p><strong>Bottleneck</strong> → Every upload has to squeeze through your backend first.</p>
</li>
</ul>
<p>This is fine… until someone uploads a 2GB video. Then it’s <em>not fine</em>.</p>
<hr />
<h3 id="heading-alternative-external-upload-service">Alternative: External Upload Service</h3>
<p>Some developers think: <em>“Fine, I’ll just build a separate backend just for uploads!”</em></p>
<p>That means:</p>
<ul>
<li><p>Spinning up EC2, ECS, or a dedicated Node server.</p>
</li>
<li><p>Letting it handle streams and send them to S3.</p>
</li>
</ul>
<p>Pros:</p>
<ul>
<li><p>Offloads work from your serverless functions.</p>
</li>
<li><p>Can be tuned for file streaming.</p>
</li>
</ul>
<p>Cons:</p>
<ul>
<li><p>You’ve now adopted another backend baby to feed and maintain.</p>
</li>
<li><p>Public upload endpoints can be dangerous if not locked down.</p>
</li>
<li><p>Still, your backend is just a fancier middleman.</p>
</li>
</ul>
<p>So yes, this works — but it’s like renting a moving truck to deliver a pizza.</p>
<hr />
<h3 id="heading-the-right-way-using-presigned-urls">The Right Way: Using Presigned URLs</h3>
<p>Enter the hero: <strong>presigned URLs</strong>.</p>
<p>Here’s the magic:</p>
<ol>
<li><p>Client asks your backend for permission to upload.</p>
</li>
<li><p>Backend generates a presigned URL from S3.</p>
</li>
<li><p>Client uploads directly to S3.</p>
</li>
</ol>
<p>Backend never touches the file. Everyone’s happy.</p>
<h4 id="heading-backend-generate-presigned-url">Backend: Generate Presigned URL</h4>
<pre><code class="lang-javascript"><span class="hljs-comment">// getUploadUrl.js</span>
<span class="hljs-keyword">import</span> { S3Client, PutObjectCommand } <span class="hljs-keyword">from</span> <span class="hljs-string">"@aws-sdk/client-s3"</span>;
<span class="hljs-keyword">import</span> { getSignedUrl } <span class="hljs-keyword">from</span> <span class="hljs-string">"@aws-sdk/s3-request-presigner"</span>;

<span class="hljs-keyword">const</span> s3 = <span class="hljs-keyword">new</span> S3Client({
  <span class="hljs-attr">credentials</span>: {
    <span class="hljs-attr">accessKeyId</span>: process.env.ACCESS_KEY_ID,
    <span class="hljs-attr">secretAccessKey</span>: process.env.SECRET_ACCESS_KEY,
  },
  <span class="hljs-attr">region</span>: process.env.AWS_REGION,
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getUploadUrl</span>(<span class="hljs-params">filename, fileType</span>) </span>{
  <span class="hljs-keyword">const</span> command = <span class="hljs-keyword">new</span> PutObjectCommand({
    <span class="hljs-attr">Bucket</span>: process.env.S3_BUCKET,
    <span class="hljs-attr">Key</span>: filename,
    <span class="hljs-attr">ContentType</span>: fileType,
  });

  <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> getSignedUrl(s3, command, { <span class="hljs-attr">expiresIn</span>: <span class="hljs-number">60</span> }); <span class="hljs-comment">// 60s VIP pass</span>
}
</code></pre>
<p>Your backend responds with:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"url"</span>: <span class="hljs-string">"https://your-bucket.s3.amazonaws.com/file.png?..."</span>,
  <span class="hljs-attr">"key"</span>: <span class="hljs-string">"file.png"</span>
}
</code></pre>
<h4 id="heading-frontend-upload-file">Frontend: Upload File</h4>
<pre><code class="lang-javascript"><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">uploadFile</span>(<span class="hljs-params">file</span>) </span>{
  <span class="hljs-comment">// 1. Ask backend for presigned URL</span>
  <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">"/api/upload-url"</span>, {
    <span class="hljs-attr">method</span>: <span class="hljs-string">"POST"</span>,
    <span class="hljs-attr">body</span>: <span class="hljs-built_in">JSON</span>.stringify({ <span class="hljs-attr">filename</span>: file.name, <span class="hljs-attr">fileType</span>: file.type }),
    <span class="hljs-attr">headers</span>: { <span class="hljs-string">"Content-Type"</span>: <span class="hljs-string">"application/json"</span> },
  });
j
  <span class="hljs-keyword">const</span> { url } = <span class="hljs-keyword">await</span> res.json();

  <span class="hljs-comment">// 2. Upload directly to S3</span>
  <span class="hljs-keyword">await</span> fetch(url, {
    <span class="hljs-attr">method</span>: <span class="hljs-string">"PUT"</span>,
    <span class="hljs-attr">headers</span>: { <span class="hljs-string">"Content-Type"</span>: file.type },
    <span class="hljs-attr">body</span>: file,
  });

  alert(<span class="hljs-string">"Upload successful!"</span>);
}
</code></pre>
<p>It’s like your backend saying: <em>“Here’s your one-time backstage pass, don’t lose it. Now go talk to S3 directly.”</em></p>
<hr />
<h3 id="heading-benefits-of-presigned-urls">Benefits of Presigned URLs</h3>
<p>Why this approach wins:</p>
<ul>
<li><p><strong>No long-running backend requests</strong> → functions return fast.</p>
</li>
<li><p><strong>Scalable</strong> → clients upload big files directly to S3.</p>
</li>
<li><p><strong>Secure</strong> → presigned URLs expire quickly and can restrict file type/size.</p>
</li>
<li><p><strong>Flexible</strong> → works with private buckets.</p>
</li>
<li><p><strong>Cheaper</strong> → backend isn’t wasting execution time watching progress bars.</p>
</li>
</ul>
<p>Basically, presigned URLs let your backend do what it does best: hand out keys, not carry boxes.</p>
<hr />
<h3 id="heading-conclusion">Conclusion</h3>
<p>We compared three approaches:</p>
<ol>
<li><p><strong>Traditional backend upload</strong> → works for small files, cries with big ones.</p>
</li>
<li><p><strong>Dedicated upload service</strong> → better, but extra work to maintain.</p>
</li>
<li><p><strong>Presigned URLs</strong> → simple, secure, scalable, and cost-friendly.</p>
</li>
</ol>
<p>If you’re building on a serverless backend, presigned URLs are the <strong>right way</strong> to upload files to S3.</p>
<p>Happy coding!</p>
]]></content:encoded></item><item><title><![CDATA[How to Build a Serverless API That Actually Works (with SST, Prisma, TiDB, Bun & AWS Lambda)]]></title><description><![CDATA[Welcome, brave developer, to the wild world of modern web development where we combine more technologies than a NASA space mission! Today we’re building a serverless TypeScript application using SST (not the sonic boom), Prisma (not the crystal), TiD...]]></description><link>https://blog.sazzadur.site/how-to-build-a-serverless-api</link><guid isPermaLink="true">https://blog.sazzadur.site/how-to-build-a-serverless-api</guid><category><![CDATA[serverless]]></category><category><![CDATA[sst]]></category><category><![CDATA[prisma]]></category><category><![CDATA[tidb]]></category><category><![CDATA[aws lambda]]></category><dc:creator><![CDATA[Sazzadur Rahman]]></dc:creator><pubDate>Tue, 16 Sep 2025 19:01:23 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1758048878862/0e4d77d9-6e1c-4d57-8648-46e0bf70bee0.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<hr />
<p>Welcome, brave developer, to the wild world of modern web development where we combine more technologies than a NASA space mission! Today we’re building a serverless TypeScript application using <strong>SST</strong> (not the sonic boom), <strong>Prisma</strong> (not the crystal), <strong>TiDB</strong> (not a robot), <strong>Express.js</strong> (not the delivery service), and <strong>Bun</strong> (not the bread). We’ll deploy this beautiful chaos to <strong>AWS Lambda</strong> because apparently, we enjoy complicated relationships with cloud providers.</p>
<h3 id="heading-why-this-particular-flavor-of-madness">Why This Particular Flavor of Madness?</h3>
<p>Let me explain why we’re choosing this specific cocktail of technologies (spoiler: it’s actually pretty awesome):</p>
<ul>
<li><p><strong>SST</strong>: Because who doesn’t love infrastructure as code that doesn’t make you cry</p>
</li>
<li><p><strong>Prisma</strong>: Type-safe database queries that prevent your 3 AM “WHERE DID ALL THE DATA GO?!” moments</p>
</li>
<li><p><strong>TiDB</strong>: A database that scales horizontally because vertical scaling is so 2010</p>
</li>
<li><p><strong>Express</strong>: The reliable old friend that’s been there since the dinosaur age of Node.js</p>
</li>
<li><p><strong>Bun</strong>: The new kid on the block that makes everything ridiculously fast (yes, it’s named after a rabbit)</p>
</li>
<li><p><strong>AWS Lambda</strong>: Because paying for servers when they’re not doing anything is like paying rent for a ghost</p>
</li>
</ul>
<hr />
<h3 id="heading-prerequisites-aka-stuff-you-need-before-you-can-break-things">Prerequisites (AKA “Stuff You Need Before You Can Break Things”)</h3>
<ul>
<li><p>An AWS Account (and the emotional fortitude to handle the billing alerts)</p>
</li>
<li><p><strong>AWS CLI installed and configured</strong> (because apparently we still need command lines in 2025)</p>
</li>
<li><p>Node.js 18+ (anything older and you’re basically using stone tablets)</p>
</li>
<li><p>Basic knowledge of TypeScript (if you’re still writing vanilla JS, we need to have a talk)</p>
</li>
<li><p>A strong cup of coffee ☕</p>
</li>
</ul>
<h4 id="heading-important-aws-cli-setup-or-how-to-convince-aws-youre-not-a-robot">Important: AWS CLI Setup (Or How to Convince AWS You’re Not a Robot)</h4>
<p>Before we start building our magnificent creation, you need to authenticate with AWS. Think of it as showing your ID at a very expensive nightclub.</p>
<p>If you haven’t set up AWS CLI yet (and let’s be honest, who has time for documentation?):</p>
<ol>
<li><p>Install AWS CLI: <a target="_blank" href="https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html">Installation Guide</a></p>
</li>
<li><p>Configure your credentials (AWS wants to know who’s about to rack up the bill):</p>
</li>
</ol>
<pre><code class="lang-bash">aws configure
</code></pre>
<p>3. Enter your credentials like you’re entering a secret speakeasy</p>
<p>You can verify your setup with this magic incantation:</p>
<pre><code class="lang-bash">aws sts get-caller-identity
</code></pre>
<p>If it returns your identity without throwing an error, congratulations! AWS acknowledges your existence.</p>
<hr />
<h3 id="heading-lets-get-this-party-started">Let’s Get This Party Started</h3>
<h4 id="heading-step-1-install-bun-the-rabbit-that-will-change-your-life">Step 1: Install Bun (The Rabbit That Will Change Your Life)</h4>
<p>First, let’s install Bun globally because we’re living dangerously:</p>
<pre><code class="lang-bash">npm i -g bun
</code></pre>
<p>Yes, we’re using npm to install npm’s potential replacement. The irony is not lost on us.</p>
<p>For more information about why Bun is faster than your ex leaving you, visit <a target="_blank" href="https://bun.sh/">bun.sh</a>.</p>
<h4 id="heading-step-2-create-your-project-like-bob-ross-creating-happy-little-servers">Step 2: Create Your Project (Like Bob Ross Creating Happy Little Servers)</h4>
<p>Create a new directory and give it a name that you won’t be embarrassed to show your colleagues:</p>
<pre><code class="lang-bash">mkdir my-sst-app
<span class="hljs-built_in">cd</span> my-sst-app
</code></pre>
<p>Initialize a new Bun project (watch the magic happen):</p>
<pre><code class="lang-bash">bun init
</code></pre>
<p>Now let’s clean up the mess and create our proper structure:</p>
<pre><code class="lang-bash">rm index.ts <span class="hljs-comment"># Goodbye, you served us well for about 30 seconds</span>
mkdir src
touch src/app.ts <span class="hljs-comment"># Our future masterpiece</span>
</code></pre>
<h4 id="heading-step-3-install-dependencies-aka-dependency-hell-but-make-it-fashion">Step 3: Install Dependencies (AKA Dependency Hell, But Make It Fashion)</h4>
<p>Time to install more packages than a teenager’s skincare routine:</p>
<pre><code class="lang-bash">bun add express @prisma/client sst @tidbcloud/prisma-adapter @codegenie/serverless-express
bun add -D prisma @types/express @types/aws-lambda tsx
</code></pre>
<p>Let’s decode this alphabet soup:</p>
<ul>
<li><p><code>express</code>: The old reliable (like your favorite pair of jeans)</p>
</li>
<li><p><code>@prisma/client</code>: Your database’s new best friend</p>
</li>
<li><p><code>sst</code>: Infrastructure as code that doesn’t hate you</p>
</li>
<li><p><code>@tidbcloud/prisma-adapter</code>: The diplomatic translator between Prisma and TiDB</p>
</li>
<li><p><code>@codegenie/serverless-express</code>: The magic that makes Express work in Lambda land</p>
</li>
<li><p>Various TypeScript types because we’re not savages</p>
</li>
</ul>
<hr />
<h3 id="heading-database-setup-or-how-to-tame-the-data-beast">Database Setup (Or: How to Tame the Data Beast)</h3>
<h4 id="heading-step-4-initialize-prisma-your-new-database-overlord">Step 4: Initialize Prisma (Your New Database Overlord)</h4>
<p>Let Prisma set up shop in your project:</p>
<pre><code class="lang-bash">bun prisma init
</code></pre>
<p>This creates a <code>prisma</code> directory and a <code>.env</code> file. It’s like moving into a new apartment, but for databases.</p>
<h4 id="heading-step-5-configure-prisma-schema-the-blueprint-of-your-dreams">Step 5: Configure Prisma Schema (The Blueprint of Your Dreams)</h4>
<p>Update your <code>prisma/schema.prisma</code> file to work with TiDB:</p>
<pre><code class="lang-plaintext">generator client {
  provider = "prisma-client"
  output = "../src/generated/prisma"
  engineType = "client"
}

datasource db {
  provider = "mysql"
  url = env("DATABASE_URL")
}

model User {
  id Int @id @default(autoincrement())
  email String @unique
  name String?
  age Int?
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}
</code></pre>
<p>Key points (because I know you skipped the code block):</p>
<ul>
<li><p>We’re using <code>provider = “prisma-client”</code> and <code>engineType = “client”</code> to generate the Prisma Client using the <strong>TypeScript-based engine</strong>. This provides better performance and smaller bundle sizes compared to the traditional binary-based engine, making it ideal for serverless deployments (translation: it’s fast and won’t break your Lambda)</p>
</li>
<li><p>We’re outputting the generated client to <code>src/generated/prisma</code> because organization is key to sanity</p>
</li>
<li><p>Using <code>mysql</code> provider since TiDB speaks MySQL (it’s bilingual!)</p>
</li>
<li><p>Our <code>User</code> model is simpler than your last relationship</p>
</li>
</ul>
<h4 id="heading-step-6-set-up-tidb-connection-database-speed-dating">Step 6: Set Up TiDB Connection (Database Speed Dating)</h4>
<p>Create a TiDB Serverless cluster at <a target="_blank" href="https://tidbcloud.com">tidbcloud.com</a>. Yes, another account to manage. Welcome to modern development!</p>
<p>Update your <code>.env</code> file with your TiDB connection string:</p>
<pre><code class="lang-plaintext">DATABASE_URL=”mysql://username:password@gateway01.region.prod.aws.tidbcloud.com:4000/database?sslaccept=strict”
</code></pre>
<p><em>Pro tip: Don’t commit this to Git unless you want to become famous on r/ProgrammerHumor</em></p>
<h4 id="heading-step-7-run-database-migration-the-moment-of-truth">Step 7: Run Database Migration (The Moment of Truth)</h4>
<p>Generate the Prisma Client and create database tables:</p>
<pre><code class="lang-bash">bun prisma migrate dev — name init
</code></pre>
<p>This command does more work than a junior developer on their first day:</p>
<ol>
<li><p>Creates migration files (like a diary of your database changes)</p>
</li>
<li><p>Applies the migration to your database (fingers crossed!)</p>
</li>
<li><p>Generates the Prisma Client with TypeScript types (because we’re fancy like that)</p>
</li>
</ol>
<hr />
<h3 id="heading-application-code-the-fun-part">Application Code (The Fun Part!)</h3>
<h4 id="heading-step-8-create-prisma-client-singleton-one-client-to-rule-them-all">Step 8: Create Prisma Client Singleton (One Client to Rule Them All)</h4>
<p>Create <code>src/lib/prisma.ts</code> to set up our database overlord:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { PrismaTiDBCloud } <span class="hljs-keyword">from</span> <span class="hljs-string">"@tidbcloud/prisma-adapter"</span>;
<span class="hljs-keyword">import</span> { PrismaClient } <span class="hljs-keyword">from</span> <span class="hljs-string">"../generated/prisma/client"</span>;

<span class="hljs-keyword">declare</span> <span class="hljs-built_in">global</span> {
  <span class="hljs-keyword">var</span> prisma: <span class="hljs-literal">undefined</span> | ReturnType&lt;<span class="hljs-keyword">typeof</span> prismaClientSingleton&gt;;
}

<span class="hljs-keyword">const</span> prismaClientSingleton = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> adapter = <span class="hljs-keyword">new</span> PrismaTiDBCloud({ url: process.env.DATABASE_URL });
  <span class="hljs-comment">// @ts-ignore: tidb adapter not in prisma types yet</span>
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> PrismaClient({ adapter });
};

<span class="hljs-keyword">const</span> db = globalThis.prisma ?? prismaClientSingleton();

<span class="hljs-keyword">if</span> (process.env.NODE_ENV !== <span class="hljs-string">"production"</span>) globalThis.prisma = db;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> db;
</code></pre>
<p>This pattern does several clever things:</p>
<ul>
<li><p>Uses the TiDB Cloud adapter (it’s like a universal translator)</p>
</li>
<li><p>Implements singleton pattern to avoid connection drama in serverless land</p>
</li>
<li><p>Reuses connections in development because we’re not made of money</p>
</li>
</ul>
<h4 id="heading-step-9-build-the-express-application-your-apis-main-character">Step 9: Build the Express Application (Your API’s Main Character)</h4>
<p>Create your <code>src/app.ts</code> file with all the Express goodness:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> express <span class="hljs-keyword">from</span> <span class="hljs-string">"express"</span>;
<span class="hljs-keyword">import</span> db <span class="hljs-keyword">from</span> <span class="hljs-string">"./lib/prisma"</span>;

<span class="hljs-keyword">const</span> app = express();
app.use(express.json());

app.get(<span class="hljs-string">"/health"</span>, <span class="hljs-function">(<span class="hljs-params">_req, res</span>) =&gt;</span> res.json({ ok: <span class="hljs-literal">true</span> }));

app.get(<span class="hljs-string">"/users"</span>, <span class="hljs-keyword">async</span> (_req, res) =&gt; {
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> users = <span class="hljs-keyword">await</span> db.user.findMany();
    <span class="hljs-keyword">return</span> res.json(users);
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">500</span>).json({ error: error });
  }
});

app.post(<span class="hljs-string">"/users"</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> { email, name, age } = req.body;

    <span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> db.user.create({
      data: { email, name, age },
    });
    <span class="hljs-keyword">return</span> res.json(user);
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">500</span>).json({ error: error });
  }
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> app;
</code></pre>
<p>Our Express app includes:</p>
<ul>
<li><p>A health check endpoint (for those anxious moments)</p>
</li>
<li><p>GET endpoint to fetch all users (because data hoarding is real)</p>
</li>
<li><p>POST endpoint to create new users (population growth!)</p>
</li>
<li><p>Proper error handling (because things go wrong, and that’s okay)</p>
</li>
<li><p>Type safety through Prisma (no more “undefined is not a function” at 3 AM)</p>
</li>
</ul>
<h4 id="heading-step-10-create-lambda-handler-the-serverless-magic-portal">Step 10: Create Lambda Handler (The Serverless Magic Portal)</h4>
<p>Create <code>src/lambda.ts</code> to handle AWS Lambda integration:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> serverlessExpress <span class="hljs-keyword">from</span> <span class="hljs-string">"@codegenie/serverless-express"</span>;
<span class="hljs-keyword">import</span> app <span class="hljs-keyword">from</span> <span class="hljs-string">"./app"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> handler = serverlessExpress({ app });
</code></pre>
<p>This tiny file is doing more heavy lifting than a gym enthusiast. It wraps our Express app to work with AWS Lambda’s event/callback model.</p>
<hr />
<h3 id="heading-infrastructure-with-sst-infrastructure-as-code-but-fun">Infrastructure with SST (Infrastructure as Code, But Fun)</h3>
<h4 id="heading-step-11-initialize-sst-your-infrastructure-butler">Step 11: Initialize SST (Your Infrastructure Butler)</h4>
<p>Set up SST in your project:</p>
<pre><code class="lang-bash">bun sst init
</code></pre>
<p>This creates <code>sst.config.ts</code> and other SST files. It’s like having a personal assistant for your AWS resources.</p>
<h4 id="heading-step-12-configure-sst-teaching-aws-how-to-behave">Step 12: Configure SST (Teaching AWS How to Behave)</h4>
<p>Update your <code>sst.config.ts</code> file to deploy your Express app:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">/// &lt;reference path="./.sst/platform/config.d.ts" /&gt;</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> $config({
  app(input) {
    <span class="hljs-keyword">return</span> {
      name: <span class="hljs-string">"aws-prisma"</span>,
      removal: input?.stage === <span class="hljs-string">"production"</span> ? <span class="hljs-string">"retain"</span> : <span class="hljs-string">"remove"</span>,
      protect: [<span class="hljs-string">"production"</span>].includes(input?.stage),
      home: <span class="hljs-string">"aws"</span>,
    };
  },

  <span class="hljs-keyword">async</span> run() {
    <span class="hljs-keyword">const</span> api = <span class="hljs-keyword">new</span> sst.aws.ApiGatewayV2(<span class="hljs-string">"AwsPrisma"</span>);

    api.route(<span class="hljs-string">"ANY /{proxy+}"</span>, {
      handler: <span class="hljs-string">"src/lambda.handler"</span>,
      runtime: <span class="hljs-string">"nodejs20.x"</span>,
      memory: <span class="hljs-string">"1024 MB"</span>,
      timeout: <span class="hljs-string">"60 seconds"</span>,
      environment: {
        DATABASE_URL: process.env.DATABASE_URL!,
      },
    });

    <span class="hljs-keyword">return</span> { url: api.url };
  },
});
</code></pre>
<p>This configuration is like a recipe for AWS:</p>
<ul>
<li><p>Creates an API Gateway V2 (faster and cheaper than v1, because we learn from our mistakes)</p>
</li>
<li><p>Routes all requests to our Lambda function (one function to handle them all)</p>
</li>
<li><p>Sets appropriate memory and timeout (because databases take time to think)</p>
</li>
<li><p>Passes environment variables securely (no hardcoded secrets here!)</p>
</li>
<li><p>Uses different settings for production vs development (because YOLO doesn’t work in prod)</p>
</li>
</ul>
<h4 id="heading-step-13-update-packagejson-the-command-center">Step 13: Update Package.json (The Command Center)</h4>
<p>Add useful scripts to your <code>package.json</code>:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"scripts"</span>: {
    <span class="hljs-attr">"dev"</span>: <span class="hljs-string">"sst dev"</span>,
    <span class="hljs-attr">"build"</span>: <span class="hljs-string">"sst build"</span>,
    <span class="hljs-attr">"deploy"</span>: <span class="hljs-string">"sst deploy"</span>,
    <span class="hljs-attr">"prisma:generate"</span>: <span class="hljs-string">"prisma generate"</span>,
    <span class="hljs-attr">"prisma:migrate"</span>: <span class="hljs-string">"prisma migrate dev"</span>,
    <span class="hljs-attr">"prisma:studio"</span>: <span class="hljs-string">"prisma studio"</span>
  }
}
</code></pre>
<hr />
<h3 id="heading-development-and-deployment-the-grand-finale">Development and Deployment (The Grand Finale)</h3>
<h4 id="heading-step-14-local-development-where-dreams-come-true">Step 14: Local Development (Where Dreams Come True)</h4>
<p>Start your development environment:</p>
<pre><code class="lang-bash">bun run dev
</code></pre>
<p>SST will work harder than a barista during finals week:</p>
<ol>
<li><p>Deploy your infrastructure in development mode</p>
</li>
<li><p>Set up live Lambda debugging (because print statements are so passé)</p>
</li>
<li><p>Provide hot reloads for your code changes (instant gratification!)</p>
</li>
<li><p>Give you a real AWS endpoint to test against (no more localhost!)</p>
</li>
</ol>
<h4 id="heading-step-15-test-your-api-moment-of-truth">Step 15: Test Your API (Moment of Truth)</h4>
<p>Once deployed, test your endpoints like you’re debugging your relationship:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Health check (is it alive?)</span>
curl https://your-api-gateway-url.amazonaws.com/health

<span class="hljs-comment"># Get all users (are there any humans here?)</span>
curl https://your-api-gateway-url.amazonaws.com/users

<span class="hljs-comment"># Create a user (let's make some friends!)</span>
curl -X POST https://your-api-gateway-url.amazonaws.com/users \
-H <span class="hljs-string">"Content-Type: application/json"</span> \
-d <span class="hljs-string">'{"email":"test@example.com","name":"John Doe","age":30}'</span>
</code></pre>
<h4 id="heading-step-16-production-deployment-the-big-leagues">Step 16: Production Deployment (The Big Leagues)</h4>
<p>Deploy to production (aka “the place where things need to actually work”):</p>
<pre><code class="lang-bash">bun run deploy - stage production
</code></pre>
<hr />
<h3 id="heading-best-practices-and-pro-tips-wisdom-from-the-trenches">Best Practices and Pro Tips (Wisdom from the Trenches)</h3>
<h4 id="heading-database-connection-management">Database Connection Management</h4>
<ul>
<li><p>Always use connection pooling in production (unless you enjoy 500 errors)</p>
</li>
<li><p>Consider using TiDB’s serverless tier for automatic scaling (it’s like having a database that goes to the gym for you)</p>
</li>
<li><p>Monitor connection usage to optimize performance (knowledge is power!)</p>
</li>
</ul>
<h4 id="heading-error-handling-because-murphys-law-is-real">Error Handling (Because Murphy’s Law is Real)</h4>
<pre><code class="lang-typescript">app.use(<span class="hljs-function">(<span class="hljs-params">err, req, res, next</span>) =&gt;</span> {
  <span class="hljs-built_in">console</span>.error(err.stack);
  res.status(<span class="hljs-number">500</span>).json({ error: <span class="hljs-string">"Something went wrong! But don't panic."</span> });
});
</code></pre>
<h4 id="heading-monitoring-and-logging-your-crystal-ball">Monitoring and Logging (Your Crystal Ball)</h4>
<p>Add structured logging for better observability:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Logger } <span class="hljs-keyword">from</span> <span class="hljs-string">"@aws-lambda-powertools/logger"</span>;
<span class="hljs-keyword">const</span> logger = <span class="hljs-keyword">new</span> Logger();

app.get(<span class="hljs-string">"/users"</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
  logger.info(<span class="hljs-string">"Fetching users"</span>, { userId: req.user?.id });
  <span class="hljs-comment">// … rest of your code</span>
});
</code></pre>
<hr />
<h3 id="heading-conclusion-we-made-it">Conclusion (We Made It! 🎉)</h3>
<p>Congratulations! You’ve successfully built a serverless API that uses more acronyms than a government document! You now have:</p>
<p>✅ <strong>Type-safe</strong> database operations (no more “oops, I dropped the table”)</p>
<p>✅ <strong>Scalable</strong> TiDB database that grows with your success</p>
<p>✅ <strong>Fast</strong> development experience that doesn’t make you want to switch careers</p>
<p>✅ <strong>Cost-effective</strong> serverless deployment (your wallet thanks you)</p>
<p>✅ <strong>Production-ready</strong> code that won’t wake you up at 3 AM</p>
<p>This stack is like assembling the Avengers of web development technologies. Each piece brings its superpower, and together they create something greater than the sum of their parts (and way cooler than your last WordPress site).</p>
<h4 id="heading-next-steps-because-were-never-really-done">Next Steps (Because We’re Never Really Done)</h4>
<p>Consider adding these upgrades to your masterpiece:</p>
<ul>
<li><p>Authentication (because not everyone should access your data)</p>
</li>
<li><p>Input validation with Zod (trust, but verify)</p>
</li>
<li><p>Rate limiting and caching (because users can be… enthusiastic)</p>
</li>
<li><p>Automated testing with Jest (because manual testing is for masochists)</p>
</li>
<li><p>CI/CD pipelines with GitHub Actions (automate all the things!)</p>
</li>
<li><p>Monitoring with AWS CloudWatch or DataDog (know thy application)</p>
</li>
</ul>
<p>Remember: the best code is the code that works, doesn’t break production, and makes your future self thank your past self instead of cursing your name.</p>
<hr />
<p>Thanks for reading! 🙌<br />If you enjoyed this guide:</p>
<ul>
<li><p>Leave a ❤️ or comment — it really helps me know what to write next</p>
</li>
<li><p>Follow me here on Hashnode for more</p>
</li>
<li><p>Connect with me on <a target="_blank" href="https://x.com/DevSazzadur">X</a>.</p>
</li>
</ul>
<p>Happy coding, you caffeinated developers! ☕</p>
]]></content:encoded></item><item><title><![CDATA[useForm Like a Pro: Mistakes You Might Be Making (And How to Fix Them)]]></title><description><![CDATA[Let’s face it — building forms in React is about as enjoyable as stepping on a LEGO at 3 AM. You start with the best intentions, telling yourself “it’s just a simple form,” and six hours later you’re surrounded by coffee cups, questioning your career...]]></description><link>https://blog.sazzadur.site/useform-like-a-pro</link><guid isPermaLink="true">https://blog.sazzadur.site/useform-like-a-pro</guid><category><![CDATA[React]]></category><category><![CDATA[react hooks]]></category><category><![CDATA[form validation]]></category><category><![CDATA[forms]]></category><category><![CDATA[State Management ]]></category><dc:creator><![CDATA[Sazzadur Rahman]]></dc:creator><pubDate>Mon, 31 Mar 2025 03:39:58 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1743392292074/6a2c924a-118e-49db-8edf-541430827f49.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<hr />
<p>Let’s face it — building forms in React is about as enjoyable as stepping on a LEGO at 3 AM. You start with the best intentions, telling yourself “it’s just a simple form,” and six hours later you’re surrounded by coffee cups, questioning your career choices.</p>
<p>This is your intervention. Put down that seventh cup of coffee and let me introduce you to <code>useForm</code> — the hook that will make you hate forms slightly less than you do now.</p>
<h3 id="heading-the-problem-or-why-forms-make-developers-cry"><strong>The Problem (Or: Why Forms Make Developers Cry)</strong></h3>
<p>If you’ve ever built a form in React, you’re probably familiar with code that looks like this monstrosity:</p>
<pre><code class="lang-typescript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">TraditionalForm</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [name, setName] = useState(<span class="hljs-string">""</span>);
  <span class="hljs-keyword">const</span> [email, setEmail] = useState(<span class="hljs-string">""</span>);
  <span class="hljs-keyword">const</span> [nameError, setNameError] = useState(<span class="hljs-string">""</span>);
  <span class="hljs-keyword">const</span> [emailError, setEmailError] = useState(<span class="hljs-string">""</span>);
  <span class="hljs-comment">// …27 more state variables because why not?</span>

  <span class="hljs-keyword">const</span> handleNameChange = <span class="hljs-function"><span class="hljs-params">e</span> =&gt;</span> {
    setName(e.target.value);
    <span class="hljs-keyword">if</span> (e.target.value.trim() === <span class="hljs-string">""</span>) {
      setNameError(<span class="hljs-string">"Name is required"</span>);
    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (e.target.value.length &lt; <span class="hljs-number">3</span>) {
      setNameError(<span class="hljs-string">"Name must be at least 3 characters"</span>);
    } <span class="hljs-keyword">else</span> {
      setNameError(<span class="hljs-string">""</span>);
    }
  };

  <span class="hljs-keyword">const</span> handleEmailChange = <span class="hljs-function"><span class="hljs-params">e</span> =&gt;</span> {
    <span class="hljs-comment">// Copy and paste the function above with minor tweaks</span>
    <span class="hljs-comment">// Developer skills at work!</span>
  };

  <span class="hljs-comment">// Several more nearly-identical functions here</span>
  <span class="hljs-keyword">const</span> handleSubmit = <span class="hljs-function"><span class="hljs-params">e</span> =&gt;</span> {
    e.preventDefault();
    <span class="hljs-comment">// Validate everything again because trust issues</span>
    <span class="hljs-comment">// Submit if the stars align and Mercury isn't in retrograde</span>
  };

  <span class="hljs-keyword">return</span> (
    &lt;form onSubmit={handleSubmit}&gt;
          {<span class="hljs-comment">/* A UI that took 30% of your development time */</span>}
    &lt;/form&gt;
  );
}
</code></pre>
<p>This approach has several drawbacks:</p>
<ul>
<li><p><strong>State Explosion</strong>: More state variables than you have fingers and toes</p>
</li>
<li><p><strong>Copy-Paste Engineering</strong>: Spread that validation logic like it’s going out of style</p>
</li>
<li><p><strong>Bug Breeding Ground</strong>: “Why isn’t my form validating?” (Narrator: It was.)</p>
</li>
<li><p><strong>TypeScript Nightmares</strong>: Type errors that make you question if you really understand programming after all</p>
</li>
</ul>
<hr />
<h3 id="heading-the-solution-useform-hook-aka-form-therapy"><strong>The Solution: useForm Hook (A.K.A. Form Therapy)</strong></h3>
<p>Our custom <code>useForm</code> hook provides a simple API that won’t make you want to become a sheep farmer:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useForm } <span class="hljs-keyword">from</span> <span class="hljs-string">"./lib/hooks/useForm"</span>;
<span class="hljs-keyword">import</span> { required, email } <span class="hljs-keyword">from</span> <span class="hljs-string">"./lib/validation"</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">SimpleForm</span>(<span class="hljs-params"></span>) </span>{
 <span class="hljs-keyword">const</span> { values, errors, handleChange, handleSubmit } = useForm({
  initialValues: {
   name: <span class="hljs-string">""</span>,
   email: <span class="hljs-string">""</span>,
  },
  validationSchema: {
   name: [required()],
   email: [required(), email()],
  },
  onSubmit: <span class="hljs-function"><span class="hljs-params">values</span> =&gt;</span> {
   <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Form submitted:"</span>, values);
   <span class="hljs-comment">// Time to celebrate with a victory dance</span>
  },
 });

 <span class="hljs-keyword">return</span> (
  &lt;form onSubmit={handleSubmit}&gt;
   &lt;div&gt;
    &lt;label htmlFor=<span class="hljs-string">"name"</span>&gt;Name&lt;/label&gt;
    &lt;input id=<span class="hljs-string">"name"</span> name=<span class="hljs-string">"name"</span> value={values.name} onChange={handleChange} /&gt;
    {errors.name &amp;&amp; &lt;div className=<span class="hljs-string">"error"</span>&gt;{errors.name}&lt;/div&gt;}
   &lt;/div&gt;

   &lt;div&gt;
    &lt;label htmlFor=<span class="hljs-string">"email"</span>&gt;Email&lt;/label&gt;
    &lt;input id=<span class="hljs-string">"email"</span> name=<span class="hljs-string">"email"</span> <span class="hljs-keyword">type</span>=<span class="hljs-string">"email"</span> value={values.email} onChange={handleChange} /&gt;
    {errors.email &amp;&amp; &lt;div className=<span class="hljs-string">"error"</span>&gt;{errors.email}&lt;/div&gt;}
   &lt;/div&gt;

   &lt;button <span class="hljs-keyword">type</span>=<span class="hljs-string">"submit"</span>&gt;Submit&lt;/button&gt;
  &lt;/form&gt;
 );
}
</code></pre>
<hr />
<h3 id="heading-key-features-or-why-youll-delete-your-old-form-code"><strong>Key Features (Or: Why You’ll Delete Your Old Form Code)</strong></h3>
<p>The <code>useForm</code> hook provides:</p>
<ul>
<li><p><strong>One State to Rule Them All</strong>: All form values and errors in one place, like a neat little package that won’t explode in your face</p>
</li>
<li><p><strong>Validation That Actually Makes Sense</strong>: Declarative validation rules that don’t make you question your life choices</p>
</li>
<li><p><strong>TypeScript That Helps Instead of Hurts</strong>: Full type safety without wanting to throw your computer out the window</p>
</li>
<li><p><strong>Less Code, More Coffee Time</strong>: Do more with less so you can actually take that lunch break</p>
</li>
<li><p><strong>Field Tracking</strong>: Knows which fields have been touched, unlike your memory after debugging for 5 hours straight</p>
</li>
</ul>
<h3 id="heading-using-the-register-api-because-less-code-more-happiness"><strong>Using the Register API (Because Less Code = More Happiness)</strong></h3>
<p>Our hook provides a <code>register</code> function that helps you connect form fields with less code than your average tweet: The <code>register</code> function bundles everything together like a burrito of form functionality — all the good stuff wrapped in a neat package.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { FormField } <span class="hljs-keyword">from</span> <span class="hljs-string">"./components/FormField"</span>;
<span class="hljs-keyword">import</span> { useForm } <span class="hljs-keyword">from</span> <span class="hljs-string">"./lib/hooks/useForm"</span>;
<span class="hljs-keyword">import</span> { required, email, minLength } <span class="hljs-keyword">from</span> <span class="hljs-string">"./lib/validation"</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">RegisterAPIForm</span>(<span class="hljs-params"></span>) </span>{
 <span class="hljs-keyword">const</span> { handleSubmit, register } = useForm({
  initialValues: {
   name: <span class="hljs-string">""</span>,
   email: <span class="hljs-string">""</span>,
   password: <span class="hljs-string">""</span>,
  },
  validationSchema: {
   name: [required(), minLength(<span class="hljs-number">3</span>)],
   email: [required(), email()],
   password: [required(), minLength(<span class="hljs-number">8</span>)], <span class="hljs-comment">// Because "password" is not secure</span>
  },
  onSubmit: <span class="hljs-function"><span class="hljs-params">values</span> =&gt;</span> {
   <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Form submitted:"</span>, values);
   <span class="hljs-comment">// Tell your PM you spent days on this</span>
  },
 });

 <span class="hljs-keyword">return</span> (
  &lt;form onSubmit={handleSubmit}&gt;
   &lt;FormField {...register(<span class="hljs-string">"name"</span>)} label=<span class="hljs-string">"Name"</span> /&gt;
   &lt;FormField {...register(<span class="hljs-string">"email"</span>)} label=<span class="hljs-string">"Email"</span> <span class="hljs-keyword">type</span>=<span class="hljs-string">"email"</span> /&gt;
   &lt;FormField {...register(<span class="hljs-string">"password"</span>)} label=<span class="hljs-string">"Password"</span> <span class="hljs-keyword">type</span>=<span class="hljs-string">"password"</span> /&gt;

   &lt;button <span class="hljs-keyword">type</span>=<span class="hljs-string">"submit"</span>&gt;Submit&lt;/button&gt;
  &lt;/form&gt;
 );
}
</code></pre>
<p>The <code>register</code> function bundles everything together like a burrito of form functionality — all the good stuff wrapped in a neat package.</p>
<p>The <code>FormField</code> component is like the Swiss Army knife of form fields — it handles labels, inputs, and error messages so you don’t have to. For the full code (and to see how the magic happens), check out <a class="post-section-overview" href="#formfield-component">The FormField Component</a>.</p>
<hr />
<h3 id="heading-validation-rules-for-all-those-users-who-type-password123"><strong>Validation Rules (For All Those Users Who Type “password123”)</strong></h3>
<p>Our validation system has more rules than a bureaucratic handbook:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> {
 required,
 email,
 minLength,
 maxLength,
 pattern,
 url,
 numeric,
 alphanumeric,
 range,
 passwordStrength,
 passwordMatch,
 phoneNumber,
 date,
} <span class="hljs-keyword">from</span> <span class="hljs-string">"./lib/validation"</span>;

<span class="hljs-comment">// Example usage for those users who think "123" is a strong password:</span>
<span class="hljs-keyword">const</span> validationSchema = {
 username: [
  required(<span class="hljs-string">"Please provide a username, or we'll just call you 'Mystery Guest'"</span>),
  minLength(<span class="hljs-number">3</span>, <span class="hljs-string">"Your username needs at least 3 characters—brevity isn't always the soul of wit"</span>),
 ],
 email: [
  required(<span class="hljs-string">"We need your email, but don't worry, we won't send you cat memes... probably"</span>),
  email(<span class="hljs-string">"That doesn't look like a valid email—did you forget the '@'?"</span>),
 ],
 password: [
  required(<span class="hljs-string">"A password is required—security first!"</span>),
  minLength(<span class="hljs-number">8</span>, <span class="hljs-string">"Make it at least 8 characters—short passwords are so last decade"</span>),
  passwordStrength(<span class="hljs-string">"Your password needs to be stronger—think 'Fort Knox,' not '1234'"</span>),
 ],
 confirmPassword: [
  required(<span class="hljs-string">"Please confirm your password—we're not mind readers"</span>),
  passwordMatch(<span class="hljs-string">"password"</span>, <span class="hljs-string">"Your passwords don't match—try again, champ"</span>),
 ],
 age: [
  numeric(<span class="hljs-string">"Enter your age as a number, not 'forever young'"</span>),
  range(<span class="hljs-number">18</span>, <span class="hljs-number">99</span>, <span class="hljs-string">"You must be between 18 and 99—no exceptions for time travelers"</span>),
 ],
};
</code></pre>
<hr />
<h3 id="heading-conditional-validation-for-when-life-gets-complicated"><strong>Conditional Validation (For When Life Gets Complicated)</strong></h3>
<p>Sometimes validation depends on other form values. The <code>when</code> helper lets you add conditions dynamically, making your forms smarter and more adaptable:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { when, required } <span class="hljs-keyword">from</span> <span class="hljs-string">"./lib/validation"</span>;

<span class="hljs-keyword">const</span> validationSchema = {
 shippingAddress: [
  when(
   <span class="hljs-function"><span class="hljs-params">values</span> =&gt;</span> values.needsShipping === <span class="hljs-literal">true</span>,
   required(<span class="hljs-string">"We need to know where to send your stuff unless you're telepathic"</span>)
  ),
 ],
 companyName: [
  when(
   <span class="hljs-function"><span class="hljs-params">values</span> =&gt;</span> values.accountType === <span class="hljs-string">"business"</span>,
   required(<span class="hljs-string">"Businesses typically have names, unless you're in the witness protection program"</span>)
  ),
 ],
};
</code></pre>
<hr />
<h3 id="heading-complete-sign-up-form-example-that-actually-works"><strong>Complete Sign-Up Form Example (That Actually Works!)</strong></h3>
<p>Here’s a real-world example of a sign-up form that won’t make you question your career choices:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { FormField } <span class="hljs-keyword">from</span> <span class="hljs-string">"./components/FormField"</span>;
<span class="hljs-keyword">import</span> { useForm } <span class="hljs-keyword">from</span> <span class="hljs-string">"./lib/hooks/useForm"</span>;
<span class="hljs-keyword">import</span> { email, minLength, passwordMatch, passwordStrength, required } <span class="hljs-keyword">from</span> <span class="hljs-string">"./lib/validation"</span>;

<span class="hljs-keyword">const</span> SignUpForm = <span class="hljs-function">() =&gt;</span> {
 <span class="hljs-keyword">const</span> { handleSubmit, register } = useForm({
  initialValues: {
   name: <span class="hljs-string">""</span>,
   email: <span class="hljs-string">""</span>,
   password: <span class="hljs-string">""</span>,
   confirmPassword: <span class="hljs-string">""</span>,
  },
  validateOnBlur: <span class="hljs-literal">true</span>,
  validateOnChange: <span class="hljs-literal">true</span>,
  validationSchema: {
   name: [required(), minLength(<span class="hljs-number">3</span>)],
   email: [required(), email()],
   password: [required(), minLength(<span class="hljs-number">8</span>), passwordStrength()],
   confirmPassword: [required(), passwordMatch(<span class="hljs-string">"password"</span>)],
  },
  onSubmit: <span class="hljs-function"><span class="hljs-params">values</span> =&gt;</span> {
   <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Form submitted with values:"</span>, values);
   <span class="hljs-comment">// In a real app, you would call an API here</span>
   alert(<span class="hljs-string">"Sign up successful! We now own your data!"</span>);
  },
 });

 <span class="hljs-keyword">return</span> (
  &lt;div className=<span class="hljs-string">"flex flex-col items-center justify-center min-h-screen bg-slate-200"</span>&gt;
   &lt;form onSubmit={handleSubmit} className=<span class="hljs-string">"w-full max-w-md p-6 bg-white rounded-lg shadow-md space-y-4"</span>&gt;
    &lt;h2 className=<span class="hljs-string">"text-2xl font-bold text-center mb-6"</span>&gt;Join Our Cult (Er, Platform)&lt;/h2&gt;

    &lt;FormField {...register(<span class="hljs-string">"name"</span>)} label=<span class="hljs-string">"Name"</span> placeholder=<span class="hljs-string">"Your name, not your gamer tag"</span> <span class="hljs-keyword">type</span>=<span class="hljs-string">"text"</span> /&gt;

    &lt;FormField {...register(<span class="hljs-string">"email"</span>)} label=<span class="hljs-string">"Email"</span> placeholder=<span class="hljs-string">"The one you actually check"</span> <span class="hljs-keyword">type</span>=<span class="hljs-string">"email"</span> /&gt;

    &lt;FormField {...register(<span class="hljs-string">"password"</span>)} label=<span class="hljs-string">"Password"</span> placeholder=<span class="hljs-string">"Not 'password123' please"</span> <span class="hljs-keyword">type</span>=<span class="hljs-string">"password"</span> /&gt;

    &lt;FormField
     {...register(<span class="hljs-string">"confirmPassword"</span>)}
     label=<span class="hljs-string">"Confirm Password"</span>
     placeholder=<span class="hljs-string">"Same as above, we're checking"</span>
     <span class="hljs-keyword">type</span>=<span class="hljs-string">"password"</span>
    /&gt;

    &lt;button
     <span class="hljs-keyword">type</span>=<span class="hljs-string">"submit"</span>
     className=<span class="hljs-string">"w-full py-2 px-4 bg-purple-500 text-white cursor-pointer rounded-md hover:bg-purple-500/90 transition-colors mt-6"</span>
    &gt;
     Sign Up (No Soul Required)
    &lt;/button&gt;
   &lt;/form&gt;
  &lt;/div&gt;
 );
};
</code></pre>
<hr />
<h3 id="heading-best-practices-from-developers-whove-made-all-the-mistakes"><strong>Best Practices (From Developers Who’ve Made All the Mistakes)</strong></h3>
<ul>
<li><strong>Type your form values</strong> (unless you enjoy debugging at 2 AM):</li>
</ul>
<pre><code class="lang-typescript"><span class="hljs-keyword">interface</span> LoginFormValues {
 email: <span class="hljs-built_in">string</span>;
 password: <span class="hljs-built_in">string</span>;
 rememberMe: <span class="hljs-built_in">boolean</span>; <span class="hljs-comment">// For those who trust their browser more than their memory</span>
}

<span class="hljs-keyword">const</span> { values } = useForm&lt;LoginFormValues&gt;({
 initialValues: {
  email: <span class="hljs-string">""</span>,
  password: <span class="hljs-string">""</span>,
  rememberMe: <span class="hljs-literal">false</span>,
 },
 <span class="hljs-comment">// ...other stuff that makes forms work</span>
});
</code></pre>
<ul>
<li><strong>Customize validation timing</strong> to balance user experience and your sanity:</li>
</ul>
<pre><code class="lang-typescript">useForm({
 <span class="hljs-comment">// ...</span>
 validateOnChange: <span class="hljs-literal">true</span>, <span class="hljs-comment">// For the anxious who need immediate feedback</span>
 validateOnBlur: <span class="hljs-literal">true</span>, <span class="hljs-comment">// For the reflective who prefer feedback after thinking</span>
});
</code></pre>
<ul>
<li><strong>Use custom error messages</strong> that don’t make users feel like they’re being scolded by a robot:</li>
</ul>
<pre><code class="lang-typescript">minLength(<span class="hljs-number">8</span>, <span class="hljs-string">"Think of a password as a toothbrush: longer is better and don't share it with friends"</span>);
</code></pre>
<ul>
<li><strong>Separate form logic from UI</strong> because mixing concerns is like putting pineapple on pizza — technically possible but fundamentally wrong.</li>
</ul>
<hr />
<h3 id="heading-full-implementation-reference"><strong>Full Implementation Reference</strong></h3>
<p>For those who want to copy-paste their way to productivity (we don’t judge), here are the full implementations of the core components:</p>
<h4 id="heading-the-useform-hook"><strong>The useForm Hook</strong></h4>
<pre><code class="lang-typescript"><span class="hljs-comment">// lib/hooks/useForm.ts</span>

<span class="hljs-keyword">import</span> { ChangeEvent, FormEvent, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { ValidationRule, validateField, validateForm } <span class="hljs-keyword">from</span> <span class="hljs-string">"../validation"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> UseFormOptions&lt;T <span class="hljs-keyword">extends</span> Record&lt;string, any&gt;&gt; {
 initialValues: T;
 onSubmit: <span class="hljs-function">(<span class="hljs-params">values: T</span>) =&gt;</span> <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">void</span>&gt; | <span class="hljs-built_in">void</span>;
 validationSchema?: {
  [K <span class="hljs-keyword">in</span> keyof Partial&lt;T&gt;]?: ValidationRule&lt;T, T[K]&gt;[];
 };
 validateOnChange?: <span class="hljs-built_in">boolean</span>;
 validateOnBlur?: <span class="hljs-built_in">boolean</span>;
}

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">useForm</span>&lt;<span class="hljs-title">T</span> <span class="hljs-title">extends</span> <span class="hljs-title">Record</span>&lt;<span class="hljs-title">string</span>, <span class="hljs-title">any</span>&gt;&gt;(<span class="hljs-params">{
 initialValues,
 onSubmit,
 validationSchema = {},
 validateOnChange = <span class="hljs-literal">false</span>,
 validateOnBlur = <span class="hljs-literal">false</span>,
}: UseFormOptions&lt;T&gt;</span>) </span>{
 <span class="hljs-keyword">const</span> [values, setValues] = useState&lt;T&gt;(initialValues);
 <span class="hljs-keyword">const</span> [errors, setErrors] = useState&lt;Partial&lt;Record&lt;keyof T, <span class="hljs-built_in">string</span>&gt;&gt;&gt;({});
 <span class="hljs-keyword">const</span> [touched, setTouched] = useState&lt;Partial&lt;Record&lt;keyof T, <span class="hljs-built_in">boolean</span>&gt;&gt;&gt;({});
 <span class="hljs-keyword">const</span> [isSubmitting, setIsSubmitting] = useState(<span class="hljs-literal">false</span>);

 <span class="hljs-keyword">const</span> validate = <span class="hljs-function">(<span class="hljs-params">fieldName?: keyof T</span>) =&gt;</span> {
  <span class="hljs-keyword">if</span> (fieldName) {
   <span class="hljs-comment">// Validate single field</span>
   <span class="hljs-keyword">const</span> fieldRules = validationSchema[fieldName] <span class="hljs-keyword">as</span> ValidationRule&lt;T, T[<span class="hljs-keyword">typeof</span> fieldName]&gt;[] | <span class="hljs-literal">undefined</span>;

   <span class="hljs-keyword">if</span> (fieldRules) {
    <span class="hljs-keyword">const</span> error = validateField(values[fieldName], fieldRules, values);
    setErrors(<span class="hljs-function"><span class="hljs-params">prev</span> =&gt;</span> ({
     ...prev,
     [fieldName]: error,
    }));
    <span class="hljs-keyword">return</span> !error;
   }
   <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
  } <span class="hljs-keyword">else</span> {
   <span class="hljs-comment">// Validate all fields</span>
   <span class="hljs-keyword">const</span> newErrors = validateForm(values, validationSchema);
   setErrors(newErrors);
   <span class="hljs-keyword">return</span> <span class="hljs-built_in">Object</span>.keys(newErrors).length === <span class="hljs-number">0</span>;
  }
 };

 <span class="hljs-keyword">const</span> handleChange = <span class="hljs-function">(<span class="hljs-params">e: ChangeEvent&lt;HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement&gt;</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> { name, value, <span class="hljs-keyword">type</span> } = e.target <span class="hljs-keyword">as</span> HTMLInputElement;
  <span class="hljs-keyword">const</span> fieldName = name <span class="hljs-keyword">as</span> keyof T;

  <span class="hljs-comment">// Handle checkbox inputs</span>
  <span class="hljs-keyword">const</span> newValue = <span class="hljs-keyword">type</span> === <span class="hljs-string">"checkbox"</span> ? (e.target <span class="hljs-keyword">as</span> HTMLInputElement).checked : value;

  <span class="hljs-comment">// Update values first</span>
  setValues(<span class="hljs-function"><span class="hljs-params">prev</span> =&gt;</span> ({
   ...prev,
   [fieldName]: newValue,
  }));

  <span class="hljs-keyword">if</span> (validateOnChange || errors[fieldName]) {
   <span class="hljs-keyword">const</span> fieldRules = validationSchema[fieldName] <span class="hljs-keyword">as</span> ValidationRule&lt;T, T[<span class="hljs-keyword">typeof</span> fieldName]&gt;[] | <span class="hljs-literal">undefined</span>;

   <span class="hljs-keyword">if</span> (fieldRules) {
    <span class="hljs-keyword">const</span> updatedValues = { ...values, [fieldName]: newValue };
    <span class="hljs-keyword">const</span> error = validateField(newValue <span class="hljs-keyword">as</span> T[<span class="hljs-keyword">typeof</span> fieldName], fieldRules, updatedValues);

    setErrors(<span class="hljs-function"><span class="hljs-params">prev</span> =&gt;</span> ({
     ...prev,
     [fieldName]: error,
    }));

    <span class="hljs-keyword">if</span> (!touched[fieldName]) {
     setTouched(<span class="hljs-function"><span class="hljs-params">prev</span> =&gt;</span> ({
      ...prev,
      [fieldName]: <span class="hljs-literal">true</span>,
     }));
    }
   }
  }
 };

 <span class="hljs-keyword">const</span> handleBlur = <span class="hljs-function">(<span class="hljs-params">e: React.FocusEvent&lt;HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement&gt;</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> fieldName = e.target.name <span class="hljs-keyword">as</span> keyof T;

  setTouched(<span class="hljs-function"><span class="hljs-params">prev</span> =&gt;</span> ({
   ...prev,
   [fieldName]: <span class="hljs-literal">true</span>,
  }));

  <span class="hljs-keyword">if</span> (validateOnBlur) {
   validate(fieldName);
  }
 };

 <span class="hljs-keyword">const</span> handleSubmit = <span class="hljs-keyword">async</span> (e: FormEvent&lt;HTMLFormElement&gt;) =&gt; {
  e.preventDefault();

  <span class="hljs-comment">// Mark all fields as touched</span>
  <span class="hljs-keyword">const</span> touchedFields = <span class="hljs-built_in">Object</span>.keys(validationSchema).reduce(
   <span class="hljs-function">(<span class="hljs-params">acc, key</span>) =&gt;</span> ({ ...acc, [key]: <span class="hljs-literal">true</span> }),
   {} <span class="hljs-keyword">as</span> Record&lt;keyof T, <span class="hljs-built_in">boolean</span>&gt;
  );

  setTouched(touchedFields <span class="hljs-keyword">as</span> Partial&lt;Record&lt;keyof T, <span class="hljs-built_in">boolean</span>&gt;&gt;);

  <span class="hljs-comment">// Validate all fields</span>
  <span class="hljs-keyword">const</span> isValid = validate();

  <span class="hljs-keyword">if</span> (isValid) {
   setIsSubmitting(<span class="hljs-literal">true</span>);

   <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">await</span> onSubmit(values);
   } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Form submission error:"</span>, error);
   } <span class="hljs-keyword">finally</span> {
    setIsSubmitting(<span class="hljs-literal">false</span>);
   }
  }
 };

 <span class="hljs-keyword">const</span> setFieldValue = <span class="hljs-function">(<span class="hljs-params">fieldName: keyof T, value: T[keyof T]</span>) =&gt;</span> {
  setValues(<span class="hljs-function"><span class="hljs-params">prev</span> =&gt;</span> ({
   ...prev,
   [fieldName]: value,
  }));

  <span class="hljs-keyword">if</span> (validateOnChange &amp;&amp; touched[fieldName]) {
   validate(fieldName);
  }
 };

 <span class="hljs-keyword">const</span> setFieldError = <span class="hljs-function">(<span class="hljs-params">fieldName: keyof T, error: <span class="hljs-built_in">string</span></span>) =&gt;</span> {
  setErrors(<span class="hljs-function"><span class="hljs-params">prev</span> =&gt;</span> ({
   ...prev,
   [fieldName]: error,
  }));
 };

 <span class="hljs-keyword">const</span> resetForm = <span class="hljs-function">() =&gt;</span> {
  setValues(initialValues);
  setErrors({});
  setTouched({});
  setIsSubmitting(<span class="hljs-literal">false</span>);
 };

 <span class="hljs-keyword">const</span> register = <span class="hljs-function">(<span class="hljs-params">fieldName: keyof T</span>) =&gt;</span> {
  <span class="hljs-keyword">return</span> {
   name: fieldName,
   value: values[fieldName],
   onChange: handleChange,
   onBlur: handleBlur,
   error: errors[fieldName],
  };
 };

 <span class="hljs-keyword">return</span> {
  values,
  errors,
  touched,
  isSubmitting,
  handleChange,
  handleBlur,
  handleSubmit,
  setFieldValue,
  setFieldError,
  resetForm,
  validate,
  register,
 };
}
</code></pre>
<h4 id="heading-validation-library"><strong>Validation Library</strong></h4>
<pre><code class="lang-typescript"><span class="hljs-comment">// lib/validation.ts</span>

<span class="hljs-comment">/**
 * Type-safe validation utilities for form fields
 */</span>

<span class="hljs-comment">// Generic validation rule type that can handle different value types</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> ValidationRule&lt;T <span class="hljs-keyword">extends</span> Record&lt;<span class="hljs-built_in">string</span>, unknown&gt;, V = unknown&gt; = {
 validate: <span class="hljs-function">(<span class="hljs-params">value: V, formValues?: T</span>) =&gt;</span> <span class="hljs-built_in">boolean</span>;
 message: <span class="hljs-built_in">string</span> | (<span class="hljs-function">(<span class="hljs-params">params: Record&lt;<span class="hljs-built_in">string</span>, unknown&gt;</span>) =&gt;</span> <span class="hljs-built_in">string</span>);
 params?: Record&lt;<span class="hljs-built_in">string</span>, unknown&gt;;
};

<span class="hljs-comment">// Helper to create validation messages with parameters</span>
<span class="hljs-keyword">const</span> createMessage = (
 message: <span class="hljs-built_in">string</span> | (<span class="hljs-function">(<span class="hljs-params">params: Record&lt;<span class="hljs-built_in">string</span>, unknown&gt;</span>) =&gt;</span> <span class="hljs-built_in">string</span>),
 params?: Record&lt;<span class="hljs-built_in">string</span>, unknown&gt;
): <span class="hljs-function"><span class="hljs-params">string</span> =&gt;</span> {
 <span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> message === <span class="hljs-string">"function"</span>) {
  <span class="hljs-keyword">return</span> message(params || {});
 }

 <span class="hljs-keyword">if</span> (!params) <span class="hljs-keyword">return</span> message;

 <span class="hljs-keyword">return</span> <span class="hljs-built_in">Object</span>.entries(params).reduce(<span class="hljs-function">(<span class="hljs-params">msg, [key, value]</span>) =&gt;</span> msg.replace(<span class="hljs-keyword">new</span> <span class="hljs-built_in">RegExp</span>(<span class="hljs-string">`{<span class="hljs-subst">${key}</span>}`</span>, <span class="hljs-string">"g"</span>), <span class="hljs-built_in">String</span>(value)), message);
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> required = &lt;T <span class="hljs-keyword">extends</span> Record&lt;<span class="hljs-built_in">string</span>, unknown&gt;&gt;(message?: <span class="hljs-built_in">string</span>): ValidationRule&lt;T, <span class="hljs-built_in">string</span>&gt; =&gt; ({
 validate: <span class="hljs-function"><span class="hljs-params">value</span> =&gt;</span> value !== <span class="hljs-literal">undefined</span> &amp;&amp; value !== <span class="hljs-literal">null</span> &amp;&amp; value.trim() !== <span class="hljs-string">""</span>,
 message: message || <span class="hljs-string">"This field is required"</span>,
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> email = &lt;T <span class="hljs-keyword">extends</span> Record&lt;<span class="hljs-built_in">string</span>, unknown&gt;&gt;(message?: <span class="hljs-built_in">string</span>): ValidationRule&lt;T, <span class="hljs-built_in">string</span>&gt; =&gt; ({
 validate: <span class="hljs-function"><span class="hljs-params">value</span> =&gt;</span> {
  <span class="hljs-keyword">const</span> emailRegex = <span class="hljs-regexp">/^[^\s@]+@[^\s@]+\.[^\s@]+$/</span>;
  <span class="hljs-keyword">return</span> !value || emailRegex.test(value);
 },
 message: message || <span class="hljs-string">"Please enter a valid email address"</span>,
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> minLength = &lt;T <span class="hljs-keyword">extends</span> Record&lt;<span class="hljs-built_in">string</span>, unknown&gt;&gt;(length: <span class="hljs-built_in">number</span>, message?: <span class="hljs-built_in">string</span>): ValidationRule&lt;T, <span class="hljs-built_in">string</span>&gt; =&gt; ({
 validate: <span class="hljs-function"><span class="hljs-params">value</span> =&gt;</span> !value || value.length &gt;= length,
 message: message || <span class="hljs-string">"Must be at least {length} characters long"</span>,
 params: { length },
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> maxLength = &lt;T <span class="hljs-keyword">extends</span> Record&lt;<span class="hljs-built_in">string</span>, unknown&gt;&gt;(length: <span class="hljs-built_in">number</span>, message?: <span class="hljs-built_in">string</span>): ValidationRule&lt;T, <span class="hljs-built_in">string</span>&gt; =&gt; ({
 validate: <span class="hljs-function"><span class="hljs-params">value</span> =&gt;</span> !value || value.length &lt;= length,
 message: message || <span class="hljs-string">"Cannot exceed {length} characters"</span>,
 params: { length },
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> pattern = &lt;T <span class="hljs-keyword">extends</span> Record&lt;<span class="hljs-built_in">string</span>, unknown&gt;&gt;(regex: <span class="hljs-built_in">RegExp</span>, message?: <span class="hljs-built_in">string</span>): ValidationRule&lt;T, <span class="hljs-built_in">string</span>&gt; =&gt; ({
 validate: <span class="hljs-function"><span class="hljs-params">value</span> =&gt;</span> !value || regex.test(value),
 message: message || <span class="hljs-string">"Invalid format"</span>,
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> url = &lt;T <span class="hljs-keyword">extends</span> Record&lt;<span class="hljs-built_in">string</span>, unknown&gt;&gt;(message?: <span class="hljs-built_in">string</span>): ValidationRule&lt;T, <span class="hljs-built_in">string</span>&gt; =&gt; ({
 validate: <span class="hljs-function"><span class="hljs-params">value</span> =&gt;</span> {
  <span class="hljs-keyword">if</span> (!value) <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
  <span class="hljs-keyword">try</span> {
   <span class="hljs-keyword">new</span> URL(value);
   <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
  } <span class="hljs-keyword">catch</span> {
   <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
  }
 },
 message: message || <span class="hljs-string">"Please enter a valid URL"</span>,
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> numeric = &lt;T <span class="hljs-keyword">extends</span> Record&lt;<span class="hljs-built_in">string</span>, unknown&gt;&gt;(message?: <span class="hljs-built_in">string</span>): ValidationRule&lt;T, <span class="hljs-built_in">string</span>&gt; =&gt; ({
 validate: <span class="hljs-function"><span class="hljs-params">value</span> =&gt;</span> !value || <span class="hljs-regexp">/^[0-9]+$/</span>.test(value),
 message: message || <span class="hljs-string">"Must contain only numbers"</span>,
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> alphanumeric = &lt;T <span class="hljs-keyword">extends</span> Record&lt;<span class="hljs-built_in">string</span>, unknown&gt;&gt;(message?: <span class="hljs-built_in">string</span>): ValidationRule&lt;T, <span class="hljs-built_in">string</span>&gt; =&gt; ({
 validate: <span class="hljs-function"><span class="hljs-params">value</span> =&gt;</span> !value || <span class="hljs-regexp">/^[a-zA-Z0-9]+$/</span>.test(value),
 message: message || <span class="hljs-string">"Must contain only letters and numbers"</span>,
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> range = &lt;T <span class="hljs-keyword">extends</span> Record&lt;<span class="hljs-built_in">string</span>, unknown&gt;&gt;(
 min: <span class="hljs-built_in">number</span>,
 max: <span class="hljs-built_in">number</span>,
 message?: <span class="hljs-built_in">string</span>
): ValidationRule&lt;T, <span class="hljs-built_in">string</span> | <span class="hljs-built_in">number</span>&gt; =&gt; ({
 validate: <span class="hljs-function"><span class="hljs-params">value</span> =&gt;</span> {
  <span class="hljs-keyword">if</span> (value === <span class="hljs-literal">undefined</span> || value === <span class="hljs-literal">null</span> || value === <span class="hljs-string">""</span>) <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
  <span class="hljs-keyword">const</span> numValue = <span class="hljs-keyword">typeof</span> value === <span class="hljs-string">"string"</span> ? <span class="hljs-built_in">parseFloat</span>(value) : value;
  <span class="hljs-keyword">return</span> !<span class="hljs-built_in">isNaN</span>(numValue <span class="hljs-keyword">as</span> <span class="hljs-built_in">number</span>) &amp;&amp; numValue &gt;= min &amp;&amp; numValue &lt;= max;
 },
 message: message || <span class="hljs-string">"Value must be between {min} and {max}"</span>,
 params: { min, max },
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> passwordStrength = &lt;T <span class="hljs-keyword">extends</span> Record&lt;<span class="hljs-built_in">string</span>, unknown&gt;&gt;(message?: <span class="hljs-built_in">string</span>): ValidationRule&lt;T, <span class="hljs-built_in">string</span>&gt; =&gt; ({
 validate: <span class="hljs-function"><span class="hljs-params">value</span> =&gt;</span> {
  <span class="hljs-keyword">if</span> (!value) <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
  <span class="hljs-keyword">const</span> hasLetter = <span class="hljs-regexp">/[a-zA-Z]/</span>.test(value);
  <span class="hljs-keyword">const</span> hasNumber = <span class="hljs-regexp">/\d/</span>.test(value);
  <span class="hljs-keyword">const</span> hasSpecialChar = <span class="hljs-regexp">/[!@#$%^&amp;*(),.?":{}|&lt;&gt;]/</span>.test(value);
  <span class="hljs-keyword">return</span> hasLetter &amp;&amp; hasNumber &amp;&amp; hasSpecialChar;
 },
 message: message || <span class="hljs-string">"Password must contain letters, numbers, and special characters"</span>,
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> passwordMatch = &lt;T <span class="hljs-keyword">extends</span> Record&lt;<span class="hljs-built_in">string</span>, unknown&gt;&gt;(
 matchField: keyof T,
 message?: <span class="hljs-built_in">string</span>
): ValidationRule&lt;T, <span class="hljs-built_in">string</span>&gt; =&gt; ({
 validate: <span class="hljs-function">(<span class="hljs-params">value, formValues</span>) =&gt;</span> !value || value === (formValues?.[matchField] <span class="hljs-keyword">as</span> <span class="hljs-built_in">string</span>),
 message: message || <span class="hljs-string">"Passwords do not match"</span>,
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> phoneNumber = &lt;T <span class="hljs-keyword">extends</span> Record&lt;<span class="hljs-built_in">string</span>, unknown&gt;&gt;(message?: <span class="hljs-built_in">string</span>): ValidationRule&lt;T, <span class="hljs-built_in">string</span>&gt; =&gt; ({
 validate: <span class="hljs-function"><span class="hljs-params">value</span> =&gt;</span> {
  <span class="hljs-keyword">if</span> (!value) <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
  <span class="hljs-keyword">const</span> phoneRegex = <span class="hljs-regexp">/^\+?[0-9]{10,15}$/</span>;
  <span class="hljs-keyword">return</span> phoneRegex.test(value.replace(<span class="hljs-regexp">/[-()\s]/g</span>, <span class="hljs-string">""</span>));
 },
 message: message || <span class="hljs-string">"Please enter a valid phone number"</span>,
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> date = &lt;T <span class="hljs-keyword">extends</span> Record&lt;<span class="hljs-built_in">string</span>, unknown&gt;&gt;(message?: <span class="hljs-built_in">string</span>): ValidationRule&lt;T, <span class="hljs-built_in">string</span>&gt; =&gt; ({
 validate: <span class="hljs-function"><span class="hljs-params">value</span> =&gt;</span> {
  <span class="hljs-keyword">if</span> (!value) <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
  <span class="hljs-keyword">const</span> date = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(value);
  <span class="hljs-keyword">return</span> !<span class="hljs-built_in">isNaN</span>(date.getTime());
 },
 message: message || <span class="hljs-string">"Please enter a valid date"</span>,
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> fileType = &lt;T <span class="hljs-keyword">extends</span> Record&lt;<span class="hljs-built_in">string</span>, unknown&gt;&gt;(
 allowedTypes: <span class="hljs-built_in">string</span>[],
 message?: <span class="hljs-built_in">string</span>
): ValidationRule&lt;T, File | <span class="hljs-literal">null</span>&gt; =&gt; ({
 validate: <span class="hljs-function"><span class="hljs-params">file</span> =&gt;</span> {
  <span class="hljs-keyword">if</span> (!file) <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
  <span class="hljs-keyword">const</span> extension = file.name.split(<span class="hljs-string">"."</span>).pop()?.toLowerCase() || <span class="hljs-string">""</span>;
  <span class="hljs-keyword">return</span> allowedTypes.includes(extension);
 },
 message: message || <span class="hljs-string">"File type not allowed. Allowed types: {types}"</span>,
 params: { types: allowedTypes.join(<span class="hljs-string">", "</span>) },
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> fileSize = &lt;T <span class="hljs-keyword">extends</span> Record&lt;<span class="hljs-built_in">string</span>, unknown&gt;&gt;(
 maxSizeInMB: <span class="hljs-built_in">number</span>,
 message?: <span class="hljs-built_in">string</span>
): ValidationRule&lt;T, File | <span class="hljs-literal">null</span>&gt; =&gt; ({
 validate: <span class="hljs-function"><span class="hljs-params">file</span> =&gt;</span> {
  <span class="hljs-keyword">if</span> (!file) <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
  <span class="hljs-keyword">const</span> sizeInMB = file.size / (<span class="hljs-number">1024</span> * <span class="hljs-number">1024</span>);
  <span class="hljs-keyword">return</span> sizeInMB &lt;= maxSizeInMB;
 },
 message: message || <span class="hljs-string">"File size cannot exceed {size}MB"</span>,
 params: { size: maxSizeInMB },
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> when = &lt;T <span class="hljs-keyword">extends</span> Record&lt;<span class="hljs-built_in">string</span>, unknown&gt;, V&gt;(
 condition: <span class="hljs-function">(<span class="hljs-params">formValues?: T</span>) =&gt;</span> <span class="hljs-built_in">boolean</span>,
 rule: ValidationRule&lt;T, V&gt;
): ValidationRule&lt;T, V&gt; =&gt; ({
 validate: <span class="hljs-function">(<span class="hljs-params">value, formValues</span>) =&gt;</span> {
  <span class="hljs-keyword">return</span> !condition(formValues) || rule.validate(value, formValues);
 },
 message: rule.message,
 params: rule.params,
});

<span class="hljs-comment">/**
 * Validates a field against provided rules
 */</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> validateField = &lt;T <span class="hljs-keyword">extends</span> Record&lt;<span class="hljs-built_in">string</span>, unknown&gt;, V&gt;(
 value: V,
 rules: ValidationRule&lt;T, V&gt;[],
 formValues?: T
): <span class="hljs-function"><span class="hljs-params">string</span> =&gt;</span> {
 <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> rule <span class="hljs-keyword">of</span> rules) {
  <span class="hljs-keyword">if</span> (!rule.validate(value, formValues)) {
   <span class="hljs-keyword">return</span> createMessage(rule.message, rule.params);
  }
 }
 <span class="hljs-keyword">return</span> <span class="hljs-string">""</span>;
};

<span class="hljs-comment">/**
 * Validates all fields in a form
 */</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> validateForm = &lt;T <span class="hljs-keyword">extends</span> Record&lt;<span class="hljs-built_in">string</span>, unknown&gt;&gt;(
 formValues: T,
 validationSchema: { [K <span class="hljs-keyword">in</span> keyof T]?: ValidationRule&lt;T, T[K]&gt;[] }
): { [K <span class="hljs-keyword">in</span> keyof T]?: <span class="hljs-built_in">string</span> } =&gt; {
 <span class="hljs-keyword">const</span> errors: Partial&lt;Record&lt;keyof T, <span class="hljs-built_in">string</span>&gt;&gt; = {};

 <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> field <span class="hljs-keyword">in</span> validationSchema) {
  <span class="hljs-keyword">if</span> (validationSchema[field]) {
   <span class="hljs-keyword">const</span> error = validateField(
    formValues[field],
    validationSchema[field] <span class="hljs-keyword">as</span> ValidationRule&lt;T, T[<span class="hljs-keyword">typeof</span> field]&gt;[],
    formValues
   );
   <span class="hljs-keyword">if</span> (error) {
    errors[field] = error;
   }
  }
 }

 <span class="hljs-keyword">return</span> errors;
};
</code></pre>
<h4 id="heading-formfield-component"><strong>FormField Component</strong></h4>
<pre><code class="lang-typescript"><span class="hljs-comment">// components/FormField.tsx</span>

<span class="hljs-keyword">interface</span> FormFieldProps <span class="hljs-keyword">extends</span> React.InputHTMLAttributes&lt;HTMLInputElement&gt; {
 label: <span class="hljs-built_in">string</span>;
 error?: <span class="hljs-built_in">string</span>;
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> FormField: React.FC&lt;FormFieldProps&gt; = <span class="hljs-function">(<span class="hljs-params">{ label, error, ...props }</span>) =&gt;</span> {
 <span class="hljs-keyword">return</span> (
  &lt;div className={<span class="hljs-string">`mb-4 <span class="hljs-subst">${props.className}</span>`</span>}&gt;
   &lt;label htmlFor={props.name} className=<span class="hljs-string">"block text-sm font-medium text-gray-700 mb-1"</span>&gt;
    {label}
   &lt;/label&gt;
   &lt;input
    id={props.name}
    className={<span class="hljs-string">`w-full px-3 py-2 border rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:border-transparent <span class="hljs-subst">${
     error ? <span class="hljs-string">"border-red-500 focus:ring-red-200"</span> : <span class="hljs-string">"border-gray-300 focus:ring-blue-200 focus:border-blue-500"</span>
    }</span> <span class="hljs-subst">${props.disabled ? <span class="hljs-string">"bg-gray-100 text-gray-500"</span> : <span class="hljs-string">""</span>}</span>`</span>}
    {...props}
   /&gt;
   {error &amp;&amp; &lt;p className=<span class="hljs-string">"mt-1 text-sm text-red-600"</span>&gt;{error}&lt;/p&gt;}
  &lt;/div&gt;
 );
};
</code></pre>
<hr />
<h3 id="heading-conclusion"><strong>Conclusion</strong></h3>
<p>The <code>useForm</code> hook won’t fix all your problems (sorry about that receding hairline), but it will make your form development experience significantly less painful. No more state management nightmares, no more validation logic sprawling across your components like an invasive species.</p>
<p>With our beautifully crafted, lovingly maintained, and slightly over-engineered form solution, you can focus on the important things — like arguing about whether dark mode is superior or if that button should be 2 pixels to the left.</p>
<p>Now go forth and create forms that don’t make users (or developers) want to throw their devices out the window. Your users will thank you, your therapist will see you less often, and your code reviewers might actually smile for once.</p>
<hr />
<p><em>This article was written by a developer who has spent way too much time dealing with forms and has the therapy bills to prove it. No forms were harmed in the making of this hook, though several keyboards suffered minor damage from frustrated typing.</em></p>
]]></content:encoded></item><item><title><![CDATA[How to Create a Custom React Hook for LocalStorage Using React Context]]></title><description><![CDATA[Managing local storage in React applications can become cumbersome, especially when you need to synchronize data across multiple components. By creating a custom hook combined with React Context, you can simplify this process and make your code more ...]]></description><link>https://blog.sazzadur.site/custom-react-hook-for-localstorage-using-react-context</link><guid isPermaLink="true">https://blog.sazzadur.site/custom-react-hook-for-localstorage-using-react-context</guid><category><![CDATA[React]]></category><category><![CDATA[ReactHooks]]></category><category><![CDATA[React context]]></category><category><![CDATA[React Context API]]></category><category><![CDATA[localstorage]]></category><category><![CDATA[State Management ]]></category><dc:creator><![CDATA[Sazzadur Rahman]]></dc:creator><pubDate>Sun, 28 Jul 2024 08:33:40 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1722156719180/b7468456-8ed0-422d-8a94-fb1a2a22f641.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<hr />
<p>Managing local storage in React applications can become cumbersome, especially when you need to synchronize data across multiple components. By creating a custom hook combined with React Context, you can simplify this process and make your code more reusable and maintainable. In this article, I’ll guide you through creating a custom React hook for local storage and demonstrate how to integrate it using React Context.</p>
<h3 id="heading-setting-up-the-project">Setting Up the Project</h3>
<p>First, let’s set up a new React project using Vite with <code>TypeScript</code> for better type safety.</p>
<h4 id="heading-open-your-terminal-and-execute-the-following-command-to-create-a-new-vite-project">Open your terminal and execute the following command to create a new Vite project:</h4>
<pre><code class="lang-bash">pnpm create vite@latest react-store-hook
</code></pre>
<blockquote>
<p>During the setup, select <code>react</code> and then <code>typescript</code> to ensure your project uses TypeScript.</p>
</blockquote>
<h4 id="heading-navigate-to-the-project-directory-and-install-the-dependencies">Navigate to the project directory and install the dependencies:</h4>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> react-store-hook
pnpm install
</code></pre>
<hr />
<h3 id="heading-setting-up-the-react-context">Setting Up the React Context</h3>
<p>Next, we’ll set up a React Context to provide our custom hook across the application. This way, we can access the local storage state in any component.</p>
<h4 id="heading-first-define-the-type-for-the-context">First, define the type for the context:</h4>
<p>Create a folder <code>types</code> inside the <code>src</code> folder and a file <code>index.d.ts</code> inside the folder.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/types/index.d.ts</span>

<span class="hljs-keyword">declare</span> <span class="hljs-keyword">type</span> StoreContextType = {
  [key: <span class="hljs-built_in">string</span>]: unknown;
};
</code></pre>
<p>Now create a folder named <code>hooks</code> inside <code>src</code> folder.</p>
<h4 id="heading-create-the-context-file">Create the context file:</h4>
<p>Create a <code>StoreContext.ts</code> file. The <code>StoreContext</code> will provide the global state and a method to update it across your application.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/hooks/StoreContext.ts</span>

<span class="hljs-keyword">import</span> { createContext, Dispatch, SetStateAction } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> StoreContext = createContext&lt;{
  store: StoreContextType;
  setStore: Dispatch&lt;SetStateAction&lt;StoreContextType&gt;&gt;;
}&gt;({
  store: {},
  setStore: <span class="hljs-function">() =&gt;</span> {},
});
</code></pre>
<h4 id="heading-next-create-the-provider-component">Next, create the provider component:</h4>
<p>Create a <code>StoreProvider.tsx</code> file. The <code>StoreProvider</code> component will wrap your application and provide the <code>StoreContext</code> to its children.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/hooks/StoreProvider.tsx</span>

<span class="hljs-keyword">import</span> { useState, ReactNode, FC } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { StoreContext } <span class="hljs-keyword">from</span> <span class="hljs-string">"./StoreContext"</span>;

<span class="hljs-keyword">const</span> StoreProvider: FC&lt;{ children: ReactNode }&gt; = <span class="hljs-function">(<span class="hljs-params">{ children }</span>) =&gt;</span> {
 <span class="hljs-keyword">const</span> [store, setStore] = useState&lt;StoreContextType&gt;({});

 <span class="hljs-keyword">return</span> &lt;StoreContext.Provider value={{ store, setStore }}&gt;{children}&lt;/StoreContext.Provider&gt;;
};

<span class="hljs-keyword">export</span> { StoreProvider };
</code></pre>
<h4 id="heading-creating-the-custom-hook-usestore">Creating the Custom Hook: useStore</h4>
<p>Now let’s create the custom hook <code>useStore</code>. This hook will interact with both the <code>StoreContext</code> and local storage. Create a <code>useStore.ts</code> file.</p>
<p><strong>Add the following code step by step to</strong><code>useStore.ts</code><strong>:</strong></p>
<p><strong>Step 1: Import Necessary Modules</strong></p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useContext, useEffect, useCallback, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { StoreContext } <span class="hljs-keyword">from</span> <span class="hljs-string">"./StoreContext"</span>;
</code></pre>
<p>Here, we import React hooks and the <code>StoreContext</code>. We'll use these to manage state and context.</p>
<p><strong>Step 2: Define the Hook Signature</strong></p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> useStore = &lt;T&gt;(
    key: <span class="hljs-built_in">string</span>,
    initialValue?: T | <span class="hljs-literal">null</span>,
    storeInLocalStorage: <span class="hljs-built_in">boolean</span> = <span class="hljs-literal">true</span>
): [T, <span class="hljs-function">(<span class="hljs-params">value: T</span>) =&gt;</span> <span class="hljs-built_in">void</span>, <span class="hljs-built_in">boolean</span>] =&gt; {
</code></pre>
<p>The <code>useStore</code> hook takes three parameters:</p>
<ul>
<li><p><code>key</code>: The key to store the value under.</p>
</li>
<li><p><code>initialValue</code>: The initial value to set.</p>
</li>
<li><p><code>storeInLocalStorage</code>: A flag to determine if the value should be stored in local storage.</p>
</li>
</ul>
<p>It returns a array containing:</p>
<ul>
<li><p>The stored value.</p>
</li>
<li><p>A function to update the value.</p>
</li>
<li><p>A loading state.</p>
</li>
</ul>
<p><strong>Step 3: Initialize the State</strong></p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> { store, setStore } = useContext(StoreContext);

<span class="hljs-keyword">const</span> initializeState = useCallback(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">if</span> (storeInLocalStorage &amp;&amp; <span class="hljs-keyword">typeof</span> <span class="hljs-built_in">window</span> !== <span class="hljs-string">"undefined"</span>) {
        <span class="hljs-keyword">const</span> storedValue = <span class="hljs-built_in">localStorage</span>.getItem(key);
        <span class="hljs-keyword">return</span> storedValue !== <span class="hljs-literal">null</span> ? <span class="hljs-built_in">JSON</span>.parse(storedValue) : initialValue;
    }
    <span class="hljs-keyword">return</span> initialValue || <span class="hljs-literal">null</span>;
}, [key, initialValue, storeInLocalStorage]);

<span class="hljs-keyword">const</span> [isLoading, setIsLoading] = useState&lt;<span class="hljs-built_in">boolean</span>&gt;(<span class="hljs-literal">true</span>);
<span class="hljs-keyword">const</span> [localValue, setLocalValue] = useState&lt;T | <span class="hljs-literal">undefined</span>&gt;(initializeState);
</code></pre>
<ul>
<li><p>We use <code>useContext</code> to get the <code>store</code> and <code>setStore</code> from <code>StoreContext</code>.</p>
</li>
<li><p><code>initializeState</code> is a function that gets the initial state from local storage if the flag is set and the window object is available. Otherwise, it returns the initial value.</p>
</li>
<li><p><code>isLoading</code> indicates if the state is being initialized.</p>
</li>
<li><p><code>localValue</code> holds the current value.</p>
</li>
</ul>
<p><strong>Step 4: Sync State with Local Storage and Context</strong></p>
<pre><code class="lang-typescript">useEffect(<span class="hljs-function">() =&gt;</span> {
    setIsLoading(<span class="hljs-literal">true</span>);

    <span class="hljs-keyword">if</span> (storeInLocalStorage &amp;&amp; localValue !== <span class="hljs-literal">undefined</span>) {
        <span class="hljs-built_in">localStorage</span>.setItem(key, <span class="hljs-built_in">JSON</span>.stringify(localValue));
    }
    setStore(<span class="hljs-function"><span class="hljs-params">prevStore</span> =&gt;</span> ({
        ...prevStore,
        [key]: localValue,
    }));

    setIsLoading(<span class="hljs-literal">false</span>);
}, [key, localValue, storeInLocalStorage, setStore]);
</code></pre>
<ul>
<li><p>The <code>useEffect</code> hook syncs the <code>localValue</code> with local storage and the context whenever <code>localValue</code>, <code>key</code>, or <code>storeInLocalStorage</code> changes.</p>
</li>
<li><p>It updates local storage if the flag is set and sets the value in the context.</p>
</li>
</ul>
<p><strong>Step 5: Create the Set Value Function</strong></p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> setValue = useCallback(
    <span class="hljs-function">(<span class="hljs-params">value: T</span>) =&gt;</span> {
        setLocalValue(value);
        setStore(<span class="hljs-function"><span class="hljs-params">prevStore</span> =&gt;</span> ({
            ...prevStore,
            [key]: value,
        }));

        <span class="hljs-keyword">if</span> (storeInLocalStorage) {
            <span class="hljs-built_in">localStorage</span>.setItem(key, <span class="hljs-built_in">JSON</span>.stringify(value));
        }
    },
    [key, storeInLocalStorage, setStore]
);
</code></pre>
<ul>
<li><p>The <code>setValue</code> function updates the <code>localValue</code> and the context.</p>
</li>
<li><p>It also updates local storage if the flag is set.</p>
</li>
</ul>
<p><strong>Step 6: Return the Hook Values</strong></p>
<pre><code class="lang-typescript">    <span class="hljs-keyword">return</span> [store[key] !== <span class="hljs-literal">undefined</span> ? (store[key] <span class="hljs-keyword">as</span> T) : (localValue <span class="hljs-keyword">as</span> T), setValue, isLoading];
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> useStore;
</code></pre>
<p>The hook returns the current value, the <code>setValue</code> function, and the loading state.</p>
<p><strong>Full</strong><code>useStore.ts</code><strong>File:</strong></p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/hooks/useStore.ts</span>

<span class="hljs-keyword">import</span> { useContext, useEffect, useCallback, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { StoreContext } <span class="hljs-keyword">from</span> <span class="hljs-string">"./StoreContext"</span>;

<span class="hljs-keyword">const</span> useStore = &lt;T&gt;(
 key: <span class="hljs-built_in">string</span>,
 initialValue?: T | <span class="hljs-literal">null</span>,
 storeInLocalStorage: <span class="hljs-built_in">boolean</span> = <span class="hljs-literal">true</span>
): [T, <span class="hljs-function">(<span class="hljs-params">value: T</span>) =&gt;</span> <span class="hljs-built_in">void</span>, <span class="hljs-built_in">boolean</span>] =&gt; {
 <span class="hljs-keyword">const</span> { store, setStore } = useContext(StoreContext);

 <span class="hljs-keyword">const</span> initializeState = useCallback(<span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">if</span> (storeInLocalStorage &amp;&amp; <span class="hljs-keyword">typeof</span> <span class="hljs-built_in">window</span> !== <span class="hljs-string">"undefined"</span>) {
   <span class="hljs-keyword">const</span> storedValue = <span class="hljs-built_in">localStorage</span>.getItem(key);
   <span class="hljs-keyword">return</span> storedValue !== <span class="hljs-literal">null</span> ? <span class="hljs-built_in">JSON</span>.parse(storedValue) : initialValue;
  }

  <span class="hljs-keyword">return</span> initialValue || <span class="hljs-literal">null</span>;
 }, [key, initialValue, storeInLocalStorage]);

 <span class="hljs-keyword">const</span> [isLoading, setIsLoading] = useState&lt;<span class="hljs-built_in">boolean</span>&gt;(<span class="hljs-literal">true</span>);
 <span class="hljs-keyword">const</span> [localValue, setLocalValue] = useState&lt;T | <span class="hljs-literal">undefined</span>&gt;(initializeState);

 useEffect(<span class="hljs-function">() =&gt;</span> {
  setIsLoading(<span class="hljs-literal">true</span>);

  <span class="hljs-keyword">if</span> (storeInLocalStorage &amp;&amp; localValue !== <span class="hljs-literal">undefined</span>) {
   <span class="hljs-built_in">localStorage</span>.setItem(key, <span class="hljs-built_in">JSON</span>.stringify(localValue));
  }
  setStore(<span class="hljs-function"><span class="hljs-params">prevStore</span> =&gt;</span> ({
   ...prevStore,
   [key]: localValue,
  }));

  setIsLoading(<span class="hljs-literal">false</span>);
 }, [key, localValue, storeInLocalStorage, setStore]);

 <span class="hljs-keyword">const</span> setValue = useCallback(
  <span class="hljs-function">(<span class="hljs-params">value: T</span>) =&gt;</span> {
   setLocalValue(value);
   setStore(<span class="hljs-function"><span class="hljs-params">prevStore</span> =&gt;</span> ({
    ...prevStore,
    [key]: value,
   }));

   <span class="hljs-keyword">if</span> (storeInLocalStorage) {
    <span class="hljs-built_in">localStorage</span>.setItem(key, <span class="hljs-built_in">JSON</span>.stringify(value));
   }
  },
  [key, storeInLocalStorage, setStore]
 );

 <span class="hljs-keyword">return</span> [store[key] !== <span class="hljs-literal">undefined</span> ? (store[key] <span class="hljs-keyword">as</span> T) : (localValue <span class="hljs-keyword">as</span> T), setValue, isLoading];
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> useStore;
</code></pre>
<hr />
<h3 id="heading-putting-it-all-together">Putting It All Together</h3>
<p>Finally, let’s see how to use the <code>StoreProvider</code> and <code>useStore</code> hook in your application.</p>
<h4 id="heading-in-the-maintsx-file">In the <code>main.tsx</code> file:</h4>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/main.tsx</span>

<span class="hljs-keyword">import</span> { StoreProvider } <span class="hljs-keyword">from</span> <span class="hljs-string">"./hooks/StoreProvider"</span>;
<span class="hljs-comment">// other imports</span>

ReactDOM.createRoot(<span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"root"</span>)!).render(
 &lt;React.StrictMode&gt;
  &lt;StoreProvider&gt;
   &lt;App /&gt;
  &lt;/StoreProvider&gt;
 &lt;/React.StrictMode&gt;
);
</code></pre>
<h4 id="heading-in-the-component-where-you-want-to-use-the-hook">In the component where you want to use the hook:</h4>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/MyComponent.tsx</span>

<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> useStore <span class="hljs-keyword">from</span> <span class="hljs-string">'./hooks/useStore'</span>;

<span class="hljs-keyword">const</span> MyComponent: React.FC = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> [value, setValue, isLoading] = useStore&lt;<span class="hljs-built_in">string</span>&gt;(<span class="hljs-string">'myKey'</span>, <span class="hljs-string">'defaultValue'</span>);

    <span class="hljs-keyword">if</span> (isLoading) {
        <span class="hljs-keyword">return</span> &lt;div&gt;Loading...&lt;/div&gt;;
    }

    <span class="hljs-keyword">return</span> (
        &lt;div&gt;
            &lt;h1&gt;Stored Value: {value}&lt;/h1&gt;
            &lt;button onClick={<span class="hljs-function">() =&gt;</span> setValue(<span class="hljs-string">'newValue'</span>)}&gt;<span class="hljs-built_in">Set</span> New Value&lt;/button&gt;
        &lt;/div&gt;
    );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> MyComponent;
</code></pre>
<hr />
<h3 id="heading-conclusion">Conclusion</h3>
<p>In this article, we’ve walked through the process of creating a custom React hook for managing local storage, integrating it with React Context to provide a clean and efficient solution for state management across your application. By encapsulating local storage logic within a hook, we can keep our components simpler and more focused on their core responsibilities. Using TypeScript enhances type safety and makes the code more robust.</p>
<p>The <code>useStore</code> hook, combined with <code>StoreContext</code> and <code>StoreProvider</code>, provides a flexible and reusable way to manage local storage, ensuring that data is synchronized across components and reducing the need for repetitive code. This approach not only improves maintainability but also makes your application more scalable and easier to understand.</p>
<p>Implementing custom hooks and contexts in your React applications can significantly improve your development workflow. We encourage you to try integrating these patterns into your projects and experience the benefits of a cleaner, more maintainable codebase.</p>
<p>By following this guide, you’ll have an excellent tool for managing state in your React development toolbox. Feel free to experiment with this to tailor the useStore hook to your specific needs.</p>
<p>Happy coding!</p>
]]></content:encoded></item><item><title><![CDATA[Building a Responsive Layout with Tailwind CSS: Sidebars and Bottom Navigation]]></title><description><![CDATA[Introduction
In today’s multi-device world, creating responsive web layouts is essential for delivering a seamless user experience across different screen sizes. A well-designed responsive layout adapts gracefully to various devices, ensuring that co...]]></description><link>https://blog.sazzadur.site/building-a-responsive-layout</link><guid isPermaLink="true">https://blog.sazzadur.site/building-a-responsive-layout</guid><category><![CDATA[Tailwind CSS]]></category><category><![CDATA[HTML5]]></category><category><![CDATA[Responsive Web Design]]></category><dc:creator><![CDATA[Sazzadur Rahman]]></dc:creator><pubDate>Tue, 02 Jul 2024 11:46:12 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1719914822492/6aaf5d97-15d0-490b-9d1d-aae13be90b9a.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<hr />
<h3 id="heading-introduction">Introduction</h3>
<p>In today’s multi-device world, creating responsive web layouts is essential for delivering a seamless user experience across different screen sizes. A well-designed responsive layout adapts gracefully to various devices, ensuring that content is easily accessible and navigable whether viewed on a desktop, tablet, or smartphone. In this article, we’ll explore how to build a versatile and responsive website layout featuring a left sidebar, a right sidebar for larger screens, and a bottom navigation bar for smaller screens. Using the powerful utility-first CSS framework <code>Tailwind CSS</code>, we’ll simplify the process of crafting a dynamic and adaptive design that enhances usability and aesthetics. Let’s dive in and see how <code>Tailwind CSS</code>can help us achieve a modern, responsive layout effortlessly.</p>
<h3 id="heading-visual-overview">Visual Overview</h3>
<p>To help visualize the layout we’ll be building, here are three images showing how it will appear on different screen sizes:</p>
<h4 id="heading-large-screen-devices">Large Screen Devices</h4>
<p><img src="https://cdn-images-1.medium.com/max/1600/1*4Y40Fhrzwk6v4Nrz20MzNg.png" alt /></p>
<p>On large screens, the layout includes both the left and right sidebars along with the main content area.</p>
<h4 id="heading-medium-screen-devices">Medium Screen Devices</h4>
<p><img src="https://cdn-images-1.medium.com/max/1600/1*dwyF73Dedpy1QCj6yLeUZA.png" alt /></p>
<p>On medium screens, the layout simplifies to just the left sidebar and the main content area.</p>
<h4 id="heading-small-screen-devices">Small Screen Devices</h4>
<p><img src="https://cdn-images-1.medium.com/max/1600/1*4CMe1ZGrXlf6dXQinrE2fA.png" alt /></p>
<p>On small screens, the layout transforms to have a bottom navigation bar and the main content area.</p>
<blockquote>
<p>The main content area for each layout consists of a header, the main content, and a footer, ensuring a consistent structure across all devices.</p>
</blockquote>
<h3 id="heading-setting-up-the-project">Setting Up the Project</h3>
<p>To get started, let’s create an empty HTML file and configure Tailwind CSS. Follow these steps:</p>
<h4 id="heading-step-1-create-an-html-file">Step 1: Create an HTML File</h4>
<p>First, create an empty HTML file named <code>index.html</code> and open it in your preferred code editor. We’ll use the basic HTML boilerplate to set up our file.</p>
<pre><code class="lang-bash">&lt;!DOCTYPE html&gt;
&lt;html lang=<span class="hljs-string">"en"</span>&gt;
  &lt;head&gt;
    &lt;meta charset=<span class="hljs-string">"UTF-8"</span> /&gt;
    &lt;meta name=<span class="hljs-string">"viewport"</span> content=<span class="hljs-string">"width=device-width, initial-scale=1.0"</span> /&gt;
    &lt;title&gt;Resposive Layout&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<h4 id="heading-step-2-configure-tailwind-css">Step 2: Configure Tailwind CSS</h4>
<p>To configure <code>Tailwind CSS</code>, we’ll use the following steps:</p>
<p><strong>1. Add the Tailwind CSS Script Tag:</strong></p>
<p>Add the following script tag inside the <code>&lt;head&gt;</code> section of your <code>index.html</code> file to include <code>Tailwind CSS</code> from the CDN:</p>
<pre><code class="lang-bash">&lt;script src=<span class="hljs-string">"https://cdn.tailwindcss.com"</span>&gt;&lt;/script&gt;
</code></pre>
<p><strong>2. Test the Tailwind CSS Configuration:</strong></p>
<p>To ensure that Tailwind CSS is working correctly, add the following HTML tag inside the <code>&lt;body&gt;</code> section:</p>
<pre><code class="lang-bash">&lt;h1 class=<span class="hljs-string">"text-3xl font-bold underline"</span>&gt;Hello world!&lt;/h1&gt;
</code></pre>
<p>Your <code>index.html</code> file should now look like this:</p>
<pre><code class="lang-bash">&lt;!DOCTYPE html&gt;
&lt;html lang=<span class="hljs-string">"en"</span>&gt;
 &lt;head&gt;
  &lt;meta charset=<span class="hljs-string">"UTF-8"</span> /&gt;
  &lt;meta name=<span class="hljs-string">"viewport"</span> content=<span class="hljs-string">"width=device-width, initial-scale=1.0"</span> /&gt;
  &lt;script src=<span class="hljs-string">"https://cdn.tailwindcss.com"</span>&gt;&lt;/script&gt;
  &lt;title&gt;Resposive Layout&lt;/title&gt;
 &lt;/head&gt;
 &lt;body&gt;
  &lt;h1 class=<span class="hljs-string">"text-3xl font-bold underline"</span>&gt;Hello world!&lt;/h1&gt;
 &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p><strong>3. Check the Result in the Browser:</strong></p>
<p>Open <code>index.html</code> in your web browser. You should see the text “Hello world!” displayed with bold, underlined, and larger font size, confirming that Tailwind CSS is working correctly.</p>
<blockquote>
<p>Now that we have our project set up and Tailwind CSS configured, we can start building our responsive layout.</p>
</blockquote>
<hr />
<h3 id="heading-creating-the-html-structure">Creating the HTML Structure</h3>
<p>For large screen devices, our layout will include a left sidebar, a main content area with a header and footer, and a right sidebar. We’ll divide the page into three sections and place them side by side using <code>display: flex</code>. Here's how we can achieve this:</p>
<p><strong>Step 1: Create the Main Container</strong></p>
<p>We’ll start by creating a main container that takes up the full width of the page and uses Flexbox for layout.</p>
<pre><code class="lang-bash">&lt;div class=<span class="hljs-string">"w-full flex h-svh max-h-svh"</span>&gt;
&lt;/div&gt;
</code></pre>
<ul>
<li><p><code>w-full</code>: Ensures the container takes up the full width of the viewport.</p>
</li>
<li><p><code>flex</code>: Applies Flexbox layout to the container.</p>
</li>
<li><p><code>h-svh max-h-svh</code>: Ensures the container takes up the full height of the viewport.</p>
</li>
</ul>
<p><strong>Step 2: Add the Left Sidebar</strong></p>
<p>Next, we’ll add a div for the left sidebar inside the main container. This sidebar will take up 30% of the available space.</p>
<pre><code class="lang-bash">&lt;div class=<span class="hljs-string">"w-full flex h-svh max-h-svh"</span>&gt;
    &lt;div class=<span class="hljs-string">"h-full flex-[0.3]"</span>&gt;
        &lt;!-- Left Sidebar --&gt;
    &lt;/div&gt;
&lt;/div&gt;
</code></pre>
<ul>
<li><p><code>h-full</code>: Ensures the sidebar takes up the full height of the container.</p>
</li>
<li><p><code>flex-[0.3]</code>: Allocates 30% of the available width to the sidebar.</p>
</li>
</ul>
<h4 id="heading-step-3-add-the-main-content-area">Step 3: Add the Main Content Area</h4>
<p>We’ll add another div for the main content area, which will take up the remaining space.</p>
<pre><code class="lang-bash">&lt;div class=<span class="hljs-string">"w-full flex h-svh max-h-svh"</span>&gt;
    &lt;div class=<span class="hljs-string">"h-full flex-[0.3]"</span>&gt;
        &lt;!-- Left Sidebar --&gt;
    &lt;/div&gt;

    &lt;div class=<span class="hljs-string">"h-full flex-1"</span>&gt;
        &lt;!-- Main Content Area --&gt;
    &lt;/div&gt;
&lt;/div&gt;
</code></pre>
<ul>
<li><code>flex-1</code>: Allocates the remaining space (70%) to the main content area.</li>
</ul>
<h4 id="heading-step-4-add-the-right-sidebar">Step 4: Add the Right Sidebar</h4>
<p>Finally, we’ll add a div for the right sidebar, which will also take up 30% of the available space.</p>
<pre><code class="lang-bash">&lt;div class=<span class="hljs-string">"w-full flex h-svh max-h-svh"</span>&gt;
    &lt;div class=<span class="hljs-string">"h-full flex-[0.3]"</span>&gt;
        &lt;!-- Left Sidebar --&gt;
    &lt;/div&gt;

    &lt;div class=<span class="hljs-string">"h-full flex-1"</span>&gt;
        &lt;!-- Main Content Area --&gt;
    &lt;/div&gt;

    &lt;div class=<span class="hljs-string">"h-full flex-[0.3]"</span>&gt;
        &lt;!-- Right Sidebar --&gt;
    &lt;/div&gt;
&lt;/div&gt;
</code></pre>
<ul>
<li><code>flex-[0.3]</code>: Allocates 30% of the available width to the right sidebar.</li>
</ul>
<h4 id="heading-adding-contents-to-visualize">Adding Contents to Visualize</h4>
<p>Let’s integrate the demo content into our large screen layout, including the left sidebar, main content area with header, content, and footer, and the right sidebar.</p>
<p><strong>Step 1: Adding Content to Left Sidebar</strong></p>
<p>First, add the demo content inside the left sidebar:</p>
<pre><code class="lang-bash">&lt;div class=<span class="hljs-string">"w-full flex h-svh max-h-svh"</span>&gt;
  &lt;div class=<span class="hljs-string">"h-full flex-[0.3]"</span>&gt;
    &lt;!-- Left Sidebar --&gt;
    &lt;div class=<span class="hljs-string">"grid h-full place-content-center bg-green-600"</span>&gt;
      &lt;h1 class=<span class="hljs-string">"text-xl"</span>&gt;Left&lt;/h1&gt;
      &lt;h1 class=<span class="hljs-string">"text-xl"</span>&gt;Sidebar&lt;/h1&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=<span class="hljs-string">"h-full flex-1"</span>&gt;
    &lt;!-- Main Content --&gt;
  &lt;/div&gt;

  &lt;div class=<span class="hljs-string">"h-full flex-[0.3]"</span>&gt;
    &lt;!-- Right Sidebar --&gt;
  &lt;/div&gt;
&lt;/div&gt;
</code></pre>
<ul>
<li><p><code>bg-green-600</code>: Sets the background color of the left sidebar to green.</p>
</li>
<li><p><code>grid place-content-center</code>: Centers the content vertically and horizontally within the sidebar.</p>
</li>
</ul>
<p><strong>Step 2: Adding Content to Left Sidebar</strong></p>
<p>First, add the demo content inside the left sidebar:</p>
<pre><code class="lang-bash">&lt;div class=<span class="hljs-string">"w-full flex h-svh max-h-svh"</span>&gt;
  &lt;div class=<span class="hljs-string">"h-full flex-[0.3]"</span>&gt;
    &lt;!-- Left Sidebar --&gt;
    &lt;div class=<span class="hljs-string">"grid h-full place-content-center bg-green-600"</span>&gt;
      &lt;h1 class=<span class="hljs-string">"text-xl"</span>&gt;Left&lt;/h1&gt;
      &lt;h1 class=<span class="hljs-string">"text-xl"</span>&gt;Sidebar&lt;/h1&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=<span class="hljs-string">"h-full flex-1"</span>&gt;
    &lt;!-- Main Content --&gt;
  &lt;/div&gt;

  &lt;div class=<span class="hljs-string">"h-full flex-[0.3]"</span>&gt;
    &lt;!-- Right Sidebar --&gt;
    &lt;div class=<span class="hljs-string">"grid h-full place-content-center bg-blue-600"</span>&gt;
      &lt;h1 class=<span class="hljs-string">"text-xl"</span>&gt;Right&lt;/h1&gt;
      &lt;h1 class=<span class="hljs-string">"text-xl"</span>&gt;Sidebar&lt;/h1&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
</code></pre>
<ul>
<li><p><code>bg-blue-600</code>: Sets the background color of the right sidebar to blue.</p>
</li>
<li><p><code>grid place-content-center</code>: Centers the content vertically and horizontally within the sidebar.</p>
</li>
</ul>
<p><strong>Step 3: Dividing Main Content Area</strong></p>
<p>Next, divide the main content area into sections for header, content, and footer:</p>
<pre><code class="lang-bash">&lt;div class=<span class="hljs-string">"h-full flex-1"</span>&gt;
  &lt;!-- Main Content Area--&gt;
  &lt;div class=<span class="hljs-string">"flex h-full flex-col justify-between overflow-y-scroll"</span>&gt;
    &lt;div&gt;
      &lt;!-- Header --&gt;
    &lt;/div&gt;

    &lt;div&gt;
      &lt;!-- Content --&gt;
    &lt;/div&gt;

    &lt;div&gt;
      &lt;!-- Footer --&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
</code></pre>
<ul>
<li><p><code>flex-col</code>: Arranges the content in a column layout.</p>
</li>
<li><p><code>justify-between</code>: Positions the header and footer at the top and bottom of the main content area, respectively.</p>
</li>
<li><p><code>overflow-y-scroll</code>: Enables vertical scrolling within the main content area if needed.</p>
</li>
</ul>
<blockquote>
<p>Now let’s integrate the sticky header, content, and footer into our layout.</p>
</blockquote>
<p><strong>Step 1: Adding Sticky Header</strong></p>
<p>Add the sticky header section at the top of the main content area:</p>
<pre><code class="lang-bash">&lt;div class=<span class="hljs-string">"sticky top-0 w-full"</span>&gt;
    &lt;!-- Header --&gt;
    &lt;div class=<span class="hljs-string">"bg-orange-500 py-5"</span>&gt;
        &lt;h1 class=<span class="hljs-string">"text-center text-xl"</span>&gt;Header&lt;/h1&gt;
    &lt;/div&gt;
&lt;/div&gt;
</code></pre>
<ul>
<li><p><code>sticky top-0</code>: Makes the header sticky at the top of the viewport when scrolling.</p>
</li>
<li><p><code>bg-orange-500</code>: Sets the background color of the header to orange.</p>
</li>
<li><p><code>py-5</code>: Adds padding vertically to the header for spacing.</p>
</li>
</ul>
<p><strong>Step 2: Content and Footer</strong></p>
<p>Ensure the content and footer sections are appropriately placed within the main content area:</p>
<pre><code class="lang-bash">&lt;div&gt;
  &lt;!-- Content --&gt;
  &lt;h1 class=<span class="hljs-string">"text-center text-xl"</span>&gt;Content&lt;/h1&gt;
&lt;/div&gt;

&lt;div class=<span class="hljs-string">"w-full"</span>&gt;
  &lt;!-- Footer --&gt;
  &lt;div class=<span class="hljs-string">"bg-yellow-600 py-12"</span>&gt;
    &lt;h1 class=<span class="hljs-string">"text-center text-xl"</span>&gt;Footer&lt;/h1&gt;
  &lt;/div&gt;
&lt;/div&gt;
</code></pre>
<p><strong>Full Main Content Area Code</strong></p>
<pre><code class="lang-bash">&lt;div class=<span class="hljs-string">"h-full flex-1"</span>&gt;
  &lt;!-- Main Content Area--&gt;
  &lt;div class=<span class="hljs-string">"flex h-full flex-col justify-between overflow-y-scroll"</span>&gt;
    &lt;div class=<span class="hljs-string">"sticky top-0 w-full"</span>&gt;
      &lt;!-- Header --&gt;
      &lt;div class=<span class="hljs-string">"bg-orange-500 py-5"</span>&gt;
      &lt;h1 class=<span class="hljs-string">"text-center text-xl"</span>&gt;Header&lt;/h1&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div&gt;
    &lt;!-- Content --&gt;
    &lt;h1 class=<span class="hljs-string">"text-center text-xl"</span>&gt;Content&lt;/h1&gt;
  &lt;/div&gt;

  &lt;div class=<span class="hljs-string">"w-full"</span>&gt;
    &lt;!-- Footer --&gt;
    &lt;div class=<span class="hljs-string">"bg-yellow-600 py-12"</span>&gt;
      &lt;h1 class=<span class="hljs-string">"text-center text-xl"</span>&gt;Footer&lt;/h1&gt;
    &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
</code></pre>
<hr />
<h3 id="heading-current-layout-explanation">Current Layout Explanation</h3>
<p>Till now, our layout includes a left sidebar, a main content area divided into three sections (header, content, and footer), and a right sidebar. Here’s a visual representation and an explanation of what it looks like:</p>
<p><img src="https://cdn-images-1.medium.com/max/1600/1*pFeClfKxeKHr63rtkKW_jQ.png" alt /></p>
<h4 id="heading-image-explanation">Image Explanation</h4>
<p><strong>1. Left Sidebar:</strong></p>
<ul>
<li><strong>Appearance</strong>: It has a green background with the words “Left” and “Sidebar” centered both vertically and horizontally.</li>
</ul>
<p><strong>2. Main Content Area:</strong></p>
<ul>
<li><p><strong>Header</strong>: It has an orange background with the word “Header” centered. It is sticky and remains at the top of the viewport when scrolling.</p>
</li>
<li><p><strong>Content</strong>: It has the word “Content” centered within this section.</p>
</li>
<li><p><strong>Footer</strong>: It has a yellow background with the word “Footer” centered.</p>
</li>
</ul>
<p><strong>3. Right Sidebar</strong>:</p>
<ul>
<li><strong>Appearance</strong>: It has a blue background with the words “Right” and “Sidebar” centered both vertically and horizontally.</li>
</ul>
<h4 id="heading-layout-structure">Layout Structure</h4>
<ul>
<li><p><strong>Flexbox Layout</strong>: The overall layout uses Flexbox to arrange the left sidebar, main content area, and right sidebar side by side.</p>
</li>
<li><p><strong>Width Distribution</strong>:</p>
</li>
<li><p>Left Sidebar: 30% width of the container.</p>
</li>
<li><p>Main Content Area: 40% width of the container.</p>
</li>
<li><p>Right Sidebar: 30% width of the container.</p>
</li>
<li><p><strong>Height Distribution</strong>:</p>
</li>
<li><p>The entire layout takes up the full height of the viewport (<code>h-svh</code> and <code>max-h-svh</code>).</p>
</li>
<li><p>The main content area is divided vertically into header, content, and footer sections using Flexbox (<code>flex-col</code> and <code>justify-between</code>).</p>
</li>
</ul>
<h4 id="heading-full-code-for-reference">Full Code for Reference</h4>
<pre><code class="lang-bash">&lt;!DOCTYPE html&gt;
&lt;html lang=<span class="hljs-string">"en"</span>&gt;
 &lt;head&gt;
  &lt;meta charset=<span class="hljs-string">"UTF-8"</span> /&gt;
  &lt;meta name=<span class="hljs-string">"viewport"</span> content=<span class="hljs-string">"width=device-width, initial-scale=1.0"</span> /&gt;
  &lt;script src=<span class="hljs-string">"https://cdn.tailwindcss.com"</span>&gt;&lt;/script&gt;
  &lt;title&gt;Resposive Layout&lt;/title&gt;
 &lt;/head&gt;
 &lt;body&gt;
  &lt;div class=<span class="hljs-string">"w-full flex h-svh max-h-svh"</span>&gt;
   &lt;div class=<span class="hljs-string">"h-full flex-[0.3]"</span>&gt;
    &lt;!-- Left Sidebar --&gt;
    &lt;div class=<span class="hljs-string">"grid h-full place-content-center bg-green-600"</span>&gt;
     &lt;h1 class=<span class="hljs-string">"text-xl"</span>&gt;Left&lt;/h1&gt;
     &lt;h1 class=<span class="hljs-string">"text-xl"</span>&gt;Sidebar&lt;/h1&gt;
    &lt;/div&gt;
   &lt;/div&gt;

   &lt;div class=<span class="hljs-string">"h-full flex-1"</span>&gt;
    &lt;!-- Main Content Area--&gt;
    &lt;div class=<span class="hljs-string">"flex h-full flex-col justify-between overflow-y-scroll"</span>&gt;
     &lt;div class=<span class="hljs-string">"sticky top-0 w-full"</span>&gt;
      &lt;!-- Header --&gt;
      &lt;div class=<span class="hljs-string">"bg-orange-500 py-5"</span>&gt;
       &lt;h1 class=<span class="hljs-string">"text-center text-xl"</span>&gt;Header&lt;/h1&gt;
      &lt;/div&gt;
     &lt;/div&gt;

     &lt;div&gt;
      &lt;!-- Content --&gt;
      &lt;h1 class=<span class="hljs-string">"text-center text-xl"</span>&gt;Content&lt;/h1&gt;
     &lt;/div&gt;

     &lt;div class=<span class="hljs-string">"w-full"</span>&gt;
      &lt;!-- Footer --&gt;
      &lt;div class=<span class="hljs-string">"bg-yellow-600 py-12"</span>&gt;
       &lt;h1 class=<span class="hljs-string">"text-center text-xl"</span>&gt;Footer&lt;/h1&gt;
      &lt;/div&gt;
     &lt;/div&gt;
    &lt;/div&gt;
   &lt;/div&gt;

   &lt;div class=<span class="hljs-string">"h-full flex-[0.3]"</span>&gt;
    &lt;!-- Right Sidebar --&gt;
    &lt;div class=<span class="hljs-string">"grid h-full place-content-center bg-blue-600"</span>&gt;
     &lt;h1 class=<span class="hljs-string">"text-xl"</span>&gt;Right&lt;/h1&gt;
     &lt;h1 class=<span class="hljs-string">"text-xl"</span>&gt;Sidebar&lt;/h1&gt;
    &lt;/div&gt;
   &lt;/div&gt;
  &lt;/div&gt;
 &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<hr />
<h3 id="heading-making-the-layout-responsive">Making the Layout Responsive</h3>
<p>Let’s modify the existing layout to be responsive based on the given requirements:</p>
<ol>
<li><p><strong>Left Sidebar</strong>: Hidden on small screens and visible on medium screens and above.</p>
</li>
<li><p><strong>Right Sidebar</strong>: Hidden on small and medium screens, visible on large screens and above.</p>
</li>
<li><p><strong>Bottom Navigation</strong>: Visible on small screens, hidden on medium screens and above.</p>
</li>
</ol>
<p>Here’s the modified code:</p>
<h4 id="heading-step-1-updating-left-sidebar">Step 1: Updating Left Sidebar</h4>
<p>Add <code>hidden md:block</code> to the left sidebar to hide it on small screens and show it on medium and above:</p>
<pre><code class="lang-bash">&lt;div class=<span class="hljs-string">"hidden h-full flex-[0.3] md:block"</span>&gt;
  &lt;!-- Left Sidebar --&gt;
  &lt;div class=<span class="hljs-string">"grid h-full place-content-center bg-green-600"</span>&gt;
    &lt;h1 class=<span class="hljs-string">"text-xl"</span>&gt;Left&lt;/h1&gt;
    &lt;h1 class=<span class="hljs-string">"text-xl"</span>&gt;Sidebar&lt;/h1&gt;
  &lt;/div&gt;
&lt;/div&gt;
</code></pre>
<h4 id="heading-step-2-updating-right-sidebar">Step 2: Updating Right Sidebar</h4>
<p>Add <code>hidden lg:block</code> to the right sidebar to hide it on small and medium screens and show it on large and above:</p>
<pre><code class="lang-bash">&lt;div class=<span class="hljs-string">"hidden h-full flex-[0.3] lg:block"</span>&gt;
  &lt;!-- Right Sidebar --&gt;
  &lt;div class=<span class="hljs-string">"grid h-full place-content-center bg-blue-600"</span>&gt;
    &lt;h1 class=<span class="hljs-string">"text-xl"</span>&gt;Right&lt;/h1&gt;
    &lt;h1 class=<span class="hljs-string">"text-xl"</span>&gt;Sidebar&lt;/h1&gt;
  &lt;/div&gt;
&lt;/div&gt;
</code></pre>
<h4 id="heading-step-3-adding-bottom-navigation">Step 3: Adding Bottom Navigation</h4>
<p>Add the bottom navigation inside the main content area, visible on small screens and hidden on medium and above:</p>
<pre><code class="lang-bash">&lt;div class=<span class="hljs-string">"sticky top-0 block bg-pink-500 py-5 md:hidden"</span>&gt;
  &lt;h1 class=<span class="hljs-string">"text-center text-xl"</span>&gt;Bottom Navigation&lt;/h1&gt;
&lt;/div&gt;
</code></pre>
<h4 id="heading-step-4-modifying-main-content-area">Step 4: Modifying Main Content Area</h4>
<p>Update the main content area to include the bottom navigation:</p>
<pre><code class="lang-bash">&lt;div class=<span class="hljs-string">"flex h-full flex-1 flex-col"</span>&gt;
  &lt;!-- Main Content Area--&gt;
  &lt;div&gt;
    &lt;!-- Header, Content, Footer --&gt;
  &lt;/div&gt;

  &lt;div class=<span class="hljs-string">"sticky top-0 block bg-pink-500 py-5 md:hidden"</span>&gt;
    &lt;h1 class=<span class="hljs-string">"text-center text-xl"</span>&gt;Bottom Navigation&lt;/h1&gt;
  &lt;/div&gt;
&lt;/div&gt;
</code></pre>
<h3 id="heading-full-responsive-layout-code">Full Responsive Layout Code</h3>
<p>Here’s the complete HTML structure for the responsive layout:</p>
<pre><code class="lang-bash">&lt;!DOCTYPE html&gt;
&lt;html lang=<span class="hljs-string">"en"</span>&gt;
 &lt;head&gt;
  &lt;meta charset=<span class="hljs-string">"UTF-8"</span> /&gt;
  &lt;meta name=<span class="hljs-string">"viewport"</span> content=<span class="hljs-string">"width=device-width, initial-scale=1.0"</span> /&gt;
  &lt;script src=<span class="hljs-string">"https://cdn.tailwindcss.com"</span>&gt;&lt;/script&gt;
  &lt;title&gt;Resposive Layout&lt;/title&gt;
 &lt;/head&gt;
 &lt;body&gt;
  &lt;div class=<span class="hljs-string">"w-full flex h-svh max-h-svh"</span>&gt;
   &lt;div class=<span class="hljs-string">"hidden h-full flex-[0.3] md:block"</span>&gt;
    &lt;!-- Left Sidebar --&gt;
    &lt;div class=<span class="hljs-string">"grid h-full place-content-center bg-green-600"</span>&gt;
     &lt;h1 class=<span class="hljs-string">"text-xl"</span>&gt;Left&lt;/h1&gt;
     &lt;h1 class=<span class="hljs-string">"text-xl"</span>&gt;Sidebar&lt;/h1&gt;
    &lt;/div&gt;
   &lt;/div&gt;

   &lt;div class=<span class="hljs-string">"flex h-full flex-1 flex-col"</span>&gt;
    &lt;!-- Main Content Area--&gt;
    &lt;div class=<span class="hljs-string">"flex h-full flex-col justify-between overflow-y-scroll"</span>&gt;
     &lt;div class=<span class="hljs-string">"sticky top-0 w-full"</span>&gt;
      &lt;!-- Header --&gt;
      &lt;div class=<span class="hljs-string">"bg-orange-500 py-5"</span>&gt;
       &lt;h1 class=<span class="hljs-string">"text-center text-xl"</span>&gt;Header&lt;/h1&gt;
      &lt;/div&gt;
     &lt;/div&gt;

     &lt;div&gt;
      &lt;!-- Content --&gt;
      &lt;h1 class=<span class="hljs-string">"text-center text-xl"</span>&gt;Content&lt;/h1&gt;
     &lt;/div&gt;

     &lt;div class=<span class="hljs-string">"w-full"</span>&gt;
      &lt;!-- Footer --&gt;
      &lt;div class=<span class="hljs-string">"bg-yellow-600 py-12"</span>&gt;
       &lt;h1 class=<span class="hljs-string">"text-center text-xl"</span>&gt;Footer&lt;/h1&gt;
      &lt;/div&gt;
     &lt;/div&gt;
    &lt;/div&gt;

    &lt;div class=<span class="hljs-string">"sticky top-0 block bg-pink-500 py-5 md:hidden"</span>&gt;
     &lt;h1 class=<span class="hljs-string">"text-center text-xl"</span>&gt;Bottom Navigation&lt;/h1&gt;
    &lt;/div&gt;
   &lt;/div&gt;

   &lt;div class=<span class="hljs-string">"hidden h-full flex-[0.3] lg:block"</span>&gt;
    &lt;!-- Right Sidebar --&gt;
    &lt;div class=<span class="hljs-string">"grid h-full place-content-center bg-blue-600"</span>&gt;
     &lt;h1 class=<span class="hljs-string">"text-xl"</span>&gt;Right&lt;/h1&gt;
     &lt;h1 class=<span class="hljs-string">"text-xl"</span>&gt;Sidebar&lt;/h1&gt;
    &lt;/div&gt;
   &lt;/div&gt;
  &lt;/div&gt;
 &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<h3 id="heading-explanation-of-responsive-classes">Explanation of Responsive Classes</h3>
<ol>
<li><p><code>hidden md:block</code>:<br /> <strong>Left Sidebar</strong>: Hidden by default (<code>hidden</code>) and displayed as a block (<code>block</code>) on medium screens and above (<code>md</code>).</p>
</li>
<li><p><code>hidden lg:block</code>:<br /> <strong>Right Sidebar</strong>: Hidden by default (<code>hidden</code>) and displayed as a block (<code>block</code>) on large screens and above (<code>lg</code>).</p>
</li>
<li><p><code>block md:hidden</code>:<br /> <strong>Bottom Navigation</strong>: Displayed as a block (<code>block</code>) by default and hidden (<code>hidden</code>) on medium screens and above (<code>md</code>).</p>
</li>
</ol>
<p>This setup ensures the layout adapts seamlessly to different screen sizes, providing a user-friendly experience across devices.</p>
<h3 id="heading-visual-representation"><strong>Visual Representation</strong>:</h3>
<h4 id="heading-medium-screen-layout"><strong>Medium Screen Layout</strong></h4>
<p>On medium screens, the layout includes the left sidebar and the main content area, but the right sidebar is hidden.</p>
<p><img src="https://cdn-images-1.medium.com/max/1600/1*G6LsOTRlGQkLISDlZgOFVg.png" alt /></p>
<p><strong>Explanation</strong>:</p>
<ul>
<li><p><strong>Left Sidebar</strong>: Visible with the same green background as on large screens, taking up 30% of the width.</p>
</li>
<li><p><strong>Main Content Area</strong>: Takes up the remaining width (70%) and includes the header, content, and footer sections.</p>
</li>
<li><p><strong>Right Sidebar</strong>: Hidden (<code>hidden lg:block</code>).</p>
</li>
</ul>
<h4 id="heading-small-screen-layout">Small Screen Layout</h4>
<p>On small screens, the layout includes only the main content area and a bottom navigation bar, while both sidebars are hidden.</p>
<p><img src="https://cdn-images-1.medium.com/max/1600/1*Xsf4T7lCLDVJ2kIFuw610w.png" alt /></p>
<p><strong>Explanation</strong>:</p>
<ul>
<li><p><strong>Left Sidebar</strong>: Hidden (<code>hidden md:block</code>).</p>
</li>
<li><p><strong>Main Content Area</strong>: Takes up the full width and includes the header, content, and footer sections.</p>
</li>
<li><p><strong>Bottom Navigation</strong>: Visible at the bottom, with a pink background, displayed using <code>block md:hidden</code>.</p>
</li>
</ul>
<h3 id="heading-responsive-behavior-summary-table">Responsive Behavior Summary Table</h3>
<p>Here’s a table summarizing the responsive behavior for different screen sizes:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Component</td><td>Small Screens (&lt;768px)</td><td>Medium Screens (&gt;=768px)</td><td>Large Screens (&gt;=1024px)</td></tr>
</thead>
<tbody>
<tr>
<td>Left Sidebar</td><td>Hidden</td><td>Visible</td><td>Visible</td></tr>
<tr>
<td>Main Content</td><td>Visible</td><td>Visible</td><td>Visible</td></tr>
<tr>
<td>Right Sidebar</td><td>Hidden</td><td>Hidden</td><td>Visible</td></tr>
<tr>
<td>Bottom Navigation</td><td>Visible</td><td>Hidden</td><td>Hidden</td></tr>
</tbody>
</table>
</div><p>This table provides a clear and concise summary of how each component behaves across different screen sizes, ensuring that the layout adapts effectively to provide an optimal user experience on all devices.</p>
<hr />
<h3 id="heading-conclusion">Conclusion</h3>
<p>In this guide, we’ve walked through the process of creating a responsive layout for a website using Tailwind CSS. Our goal was to build a layout that adapts to different screen sizes, ensuring a seamless user experience across all devices. Here’s a quick recap of what we’ve accomplished:</p>
<h4 id="heading-1-initial-setup"><strong>1. Initial Setup</strong>:</h4>
<ul>
<li><p>Created an empty HTML file and included the Tailwind CSS script.</p>
</li>
<li><p>Verified the Tailwind CSS setup with a test element.</p>
</li>
</ul>
<h4 id="heading-2-building-the-layout"><strong>2. Building the Layout:</strong></h4>
<ul>
<li><p>Divided the page into three sections: a left sidebar, a main content area (with header, content, and footer), and a right sidebar.</p>
</li>
<li><p>Used Flexbox to arrange these sections side by side.</p>
</li>
</ul>
<h4 id="heading-3-adding-demo-content"><strong>3. Adding Demo Content</strong>:</h4>
<ul>
<li>Populated the left and right sidebars, as well as the main content area, with sample content to visualize the layout.</li>
</ul>
<h4 id="heading-4-making-the-layout-responsive"><strong>4. Making the Layout Responsive</strong>:</h4>
<ul>
<li><p>Utilized Tailwind CSS utility classes to hide or show elements based on the screen size:</p>
</li>
<li><p>Left Sidebar: Hidden on small screens, visible on medium and large screens.</p>
</li>
<li><p>Right Sidebar: Hidden on small and medium screens, visible on large screens.</p>
</li>
<li><p>Bottom Navigation: Visible on small screens, hidden on medium and large screens.</p>
</li>
<li><p>Ensured the main content area remained visible on all screen sizes.</p>
</li>
</ul>
<h4 id="heading-5-responsive-behavior-summary"><strong>5. Responsive Behavior Summary</strong>:</h4>
<ul>
<li>Provided a table summarizing the behavior of each component across different screen sizes.</li>
</ul>
<p>By following these steps, you can create a versatile and user-friendly layout that caters to various devices, from large desktop monitors to small mobile screens. Tailwind CSS simplifies the process, allowing you to focus on designing and building a responsive layout without getting bogged down in complex CSS. This approach not only enhances the visual appeal of your website but also improves accessibility and usability for all users.</p>
<p>Happy Coding!</p>
]]></content:encoded></item><item><title><![CDATA[Building an Infinite Scroll FlatList Component in ReactJS: A Comprehensive Guide]]></title><description><![CDATA[Introduction
A FlatList is a performant interface for rendering large lists of data. Commonly used in mobile applications, especially in React Native, Flatlist components help in managing complex lists efficiently. This article will guide you through...]]></description><link>https://blog.sazzadur.site/infinite-scroll-flatlist-in-reactjs</link><guid isPermaLink="true">https://blog.sazzadur.site/infinite-scroll-flatlist-in-reactjs</guid><category><![CDATA[Web Development]]></category><category><![CDATA[web app development]]></category><category><![CDATA[React]]></category><category><![CDATA[infinite scrolling]]></category><dc:creator><![CDATA[Sazzadur Rahman]]></dc:creator><pubDate>Mon, 17 Jun 2024 03:56:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1718586525389/0811e7df-510e-4a4f-82e3-ac3ae4d264e1.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<hr />
<h3 id="heading-introduction">Introduction</h3>
<p>A FlatList is a performant interface for rendering large lists of data. Commonly used in mobile applications, especially in React Native, Flatlist components help in managing complex lists efficiently. This article will guide you through creating a FlatList-like component in a web application using ReactJS.</p>
<h3 id="heading-setting-up-the-project">Setting Up the Project</h3>
<p>First, we’ll create a new React project using <code>Vite</code> and set up <code>Tailwind CSS</code> for styling. Follow these steps:</p>
<h4 id="heading-1-create-react-project">1. Create React Project:</h4>
<pre><code class="lang-bash">pnpm create vite@latest react-flatlist
</code></pre>
<h4 id="heading-2-navigate-to-the-project-directory-and-install-dependencies">2. Navigate to the project directory and install dependencies:</h4>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> react-flatlist
pnpm install
</code></pre>
<h4 id="heading-3-install-tailwind-css">3. Install Tailwind CSS:</h4>
<pre><code class="lang-bash">pnpm add -D tailwindcss postcss autoprefixer
pnpm dlx tailwindcss init -p
</code></pre>
<h4 id="heading-4-configure-tailwind-css">4. Configure Tailwind CSS:</h4>
<p>Update <code>tailwind.config.js</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {
  <span class="hljs-comment">// replace this</span>
  <span class="hljs-attr">content</span>: [<span class="hljs-string">"./index.html"</span>, <span class="hljs-string">"./src/**/*.{js,ts,jsx,tsx}"</span>],
  <span class="hljs-comment">// your other configs</span>
  <span class="hljs-attr">theme</span>: {
    <span class="hljs-attr">extend</span>: {},
  },
  <span class="hljs-attr">plugins</span>: [],
}
</code></pre>
<h4 id="heading-5-add-tailwind-css-directives">5. Add Tailwind CSS Directives:</h4>
<p>Add these lines to the top of your <code>index.css</code> file:</p>
<pre><code class="lang-css"><span class="hljs-keyword">@tailwind</span> base;
<span class="hljs-keyword">@tailwind</span> components;
<span class="hljs-keyword">@tailwind</span> utilities;
</code></pre>
<h4 id="heading-6-install-axios">6. Install Axios:</h4>
<pre><code class="lang-bash">pnpm add axios
</code></pre>
<h4 id="heading-7-run-the-project">7. Run the Project:</h4>
<pre><code class="lang-bash">pnpm dev
</code></pre>
<blockquote>
<p>Now, your React project is set up with <code>Tailwind CSS</code> for styling and <code>axios</code> for easy data fetching. You’re ready to start building the Flatlist Component!</p>
</blockquote>
<hr />
<h3 id="heading-creating-the-flatlist-component">Creating the Flatlist Component</h3>
<h4 id="heading-1-import-required-dependencies-and-define-props-interface"><strong>1. Import Required Dependencies and Define Props Interface</strong></h4>
<p>In <code>src/components/FlatList.tsx</code>, import the necessary modules and define the interface for the props:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Fragment, Key, ReactNode, useCallback, useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">interface</span> ListProps&lt;T&gt; {
 data: T[];
 keyExtractor: <span class="hljs-function">(<span class="hljs-params">item: T</span>) =&gt;</span> Key;
 renderItem: <span class="hljs-function">(<span class="hljs-params">item: T</span>) =&gt;</span> ReactNode;
 ItemSeparatorComponent?: <span class="hljs-function">() =&gt;</span> ReactNode;
 ListFooterComponent?: <span class="hljs-function">() =&gt;</span> ReactNode;
 onEndReached?: <span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">void</span>;
 onEndReachedThreshold?: <span class="hljs-built_in">number</span>;
}
</code></pre>
<h4 id="heading-2-create-the-flatlist-component"><strong>2. Create the FlatList Component</strong></h4>
<p>Implement the <code>FlatList</code> component using the provided structure:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> FlatList = &lt;T,&gt;<span class="hljs-function">(<span class="hljs-params">{
 data,
 keyExtractor,
 renderItem,
 ItemSeparatorComponent,
 ListFooterComponent,
 onEndReached,
 onEndReachedThreshold = <span class="hljs-number">0.5</span>,
}: ListProps&lt;T&gt;</span>) =&gt;</span> {
 <span class="hljs-keyword">const</span> handleScroll = useCallback(<span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> { scrollTop, scrollHeight, clientHeight } = <span class="hljs-built_in">document</span>.documentElement;

  <span class="hljs-keyword">if</span> (scrollTop + clientHeight &gt;= scrollHeight - onEndReachedThreshold) {
   <span class="hljs-keyword">if</span> (onEndReached) onEndReached();
  }
 }, [onEndReached, onEndReachedThreshold]);

 useEffect(<span class="hljs-function">() =&gt;</span> {
  <span class="hljs-built_in">window</span>.addEventListener(<span class="hljs-string">"scroll"</span>, handleScroll);
  <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">window</span>.removeEventListener(<span class="hljs-string">"scroll"</span>, handleScroll);
 }, [handleScroll]);

 <span class="hljs-keyword">return</span> (
  &lt;&gt;
   {data.map(<span class="hljs-function">(<span class="hljs-params">item, index</span>) =&gt;</span> (
    &lt;Fragment key={keyExtractor(item)}&gt;
     {renderItem(item)}
     {ItemSeparatorComponent &amp;&amp; index &lt; data.length - <span class="hljs-number">1</span> &amp;&amp; &lt;ItemSeparatorComponent /&gt;}
    &lt;/Fragment&gt;
   ))}
   {ListFooterComponent &amp;&amp; &lt;ListFooterComponent /&gt;}
  &lt;/&gt;
 );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> FlatList;
</code></pre>
<hr />
<h3 id="heading-using-the-flatlist-component">Using the FlatList Component</h3>
<h4 id="heading-1-basic-flatlist-rendering"><strong>1. Basic FlatList Rendering</strong></h4>
<p>In your <code>App.tsx</code>, import and use the FlatList component and render demo data:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> FlatList <span class="hljs-keyword">from</span> <span class="hljs-string">"./components/FlatList"</span>;

<span class="hljs-keyword">type</span> Item = { id: <span class="hljs-built_in">number</span>; title: <span class="hljs-built_in">string</span> };

<span class="hljs-keyword">const</span> data = <span class="hljs-built_in">Array</span>.from({ length: <span class="hljs-number">100</span> }).map(<span class="hljs-function">(<span class="hljs-params">_, index</span>) =&gt;</span> ({ id: index + <span class="hljs-number">1</span>, title: <span class="hljs-string">`Item <span class="hljs-subst">${index + <span class="hljs-number">1</span>}</span>`</span> }));

<span class="hljs-keyword">const</span> App = <span class="hljs-function">() =&gt;</span> {
 <span class="hljs-keyword">const</span> renderItem = <span class="hljs-function">(<span class="hljs-params">item: Item</span>) =&gt;</span> &lt;div&gt;{item.title}&lt;/div&gt;;
 <span class="hljs-keyword">const</span> keyExtractor = <span class="hljs-function">(<span class="hljs-params">item: { id: <span class="hljs-built_in">number</span> }</span>) =&gt;</span> item.id;

 <span class="hljs-keyword">return</span> (
  &lt;div className=<span class="hljs-string">"space-y-4 max-w-2xl mx-auto w-full"</span>&gt;
   &lt;h1 className=<span class="hljs-string">"mt-4 text-3xl font-bold text-center"</span>&gt;React FlatList&lt;/h1&gt;

   &lt;FlatList data={data} renderItem={renderItem} keyExtractor={keyExtractor} /&gt;
  &lt;/div&gt;
 );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<h4 id="heading-2-check-the-browser">2. Check the Browser:</h4>
<p>Open your browser and navigate to the local server (usually <a target="_blank" href="http://localhost:5173"><code>http://localhost:5173</code></a>). You should see the heading ‘React FlatList’, followed by 100 list items.</p>
<p><img src="https://cdn-images-1.medium.com/max/1600/1*TWgemATOgsh3H8j9HNLD5g.png" alt /></p>
<blockquote>
<p>This confirms that your project setup with <code>Tailwind CSS</code> and the <code>FlatList</code> component is rendering the items. Now you’re ready to implement the full features of <code>FlatList</code> component!</p>
</blockquote>
<hr />
<h3 id="heading-handling-data">Handling Data</h3>
<h4 id="heading-1-fetching-data"><strong>1. Fetching Data</strong></h4>
<p>Modify the <code>App.tsx</code> to fetch data from an API or use mock data. Let's use the JSONPlaceholder API to get some todos and display them in the list.</p>
<ul>
<li><strong>First, define the</strong> <code>Todo</code> <strong>type:</strong></li>
</ul>
<pre><code class="lang-typescript"><span class="hljs-keyword">type</span> Todo = { id: <span class="hljs-built_in">number</span>; title: <span class="hljs-built_in">string</span>; completed: <span class="hljs-built_in">boolean</span>; userId: <span class="hljs-built_in">number</span> };
</code></pre>
<ul>
<li><strong>Define a</strong> <code>todos</code> <strong>state:</strong></li>
</ul>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> [todos, setTodos] = useState&lt;Todo[]&gt;([]);
</code></pre>
<ul>
<li><strong>Write the function to fetch the todos</strong></li>
</ul>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> fetchData = useCallback(<span class="hljs-keyword">async</span> () =&gt; {
 <span class="hljs-keyword">try</span> {
  <span class="hljs-keyword">const</span> { data } = <span class="hljs-keyword">await</span> axios.get(<span class="hljs-string">"https://jsonplaceholder.typicode.com/todos"</span>);

  setTodos(data);
 } <span class="hljs-keyword">catch</span> (error) {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"error"</span>, error);
 }
}, []);
</code></pre>
<blockquote>
<p>Wrapping the fetch function with <code>useCallback</code> is necessary to ensure it doesn’t recreate the function on every render, which could cause unnecessary re-renders or re-fetches.</p>
</blockquote>
<ul>
<li><strong>Fetch Data on Component Mount</strong></li>
</ul>
<p>Use <code>useEffect</code> to fetch data when the component mounts.</p>
<pre><code class="lang-typescript">useEffect(<span class="hljs-function">() =&gt;</span> {
 fetchData();
}, [fetchData]);
</code></pre>
<ul>
<li><strong>Pass Todos to the FlatList Component</strong></li>
</ul>
<pre><code class="lang-typescript">&lt;FlatList
 data={todos}
 renderItem={renderItem}
 keyExtractor={keyExtractor}
 ItemSeparatorComponent={ItemSeparatorComponent}
/&gt;
</code></pre>
<ul>
<li><strong>Style the</strong> <code>renderItem</code> <strong>and</strong> <code>ItemSeparatorComponent</code></li>
</ul>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> renderItem = <span class="hljs-function">(<span class="hljs-params">item: Todo</span>) =&gt;</span> (
 &lt;div className=<span class="hljs-string">"bg-slate-900 rounded-lg overflow-hidden flex flex-col justify-between"</span>&gt;
  &lt;div className=<span class="hljs-string">"px-5 py-8 flex items-center gap-4"</span>&gt;
   &lt;p&gt;{item.id})&lt;/p&gt;
   &lt;h2 className=<span class="hljs-string">"text-2xl capitalize"</span>&gt;{item.title}&lt;/h2&gt;
  &lt;/div&gt;
  &lt;div className=<span class="hljs-string">"bg-slate-600 py-2"</span>&gt;
   &lt;p className=<span class="hljs-string">"text-center"</span>&gt;{item.completed ? <span class="hljs-string">"Completed"</span> : <span class="hljs-string">"Not completed"</span>}&lt;/p&gt;
  &lt;/div&gt;
 &lt;/div&gt;
);

<span class="hljs-keyword">const</span> ItemSeparatorComponent = <span class="hljs-function">() =&gt;</span> &lt;div className=<span class="hljs-string">"my-1 h-[1px] border-t border-t-zinc-700 border-dashed"</span> /&gt;;
</code></pre>
<h4 id="heading-2-whole-app-component">2. Whole <code>App</code> Component</h4>
<p>Here’s the complete code for the <code>App</code> component:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useCallback, useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> axios <span class="hljs-keyword">from</span> <span class="hljs-string">"axios"</span>;
<span class="hljs-keyword">import</span> FlatList <span class="hljs-keyword">from</span> <span class="hljs-string">"./components/FlatList"</span>;

<span class="hljs-keyword">type</span> Todo = { id: <span class="hljs-built_in">number</span>; title: <span class="hljs-built_in">string</span>; completed: <span class="hljs-built_in">boolean</span>; userId: <span class="hljs-built_in">number</span> };
<span class="hljs-keyword">const</span> App = <span class="hljs-function">() =&gt;</span> {
 <span class="hljs-keyword">const</span> [todos, setTodos] = useState&lt;Todo[]&gt;([]);

 <span class="hljs-keyword">const</span> fetchData = useCallback(<span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">try</span> {
   <span class="hljs-keyword">const</span> { data } = <span class="hljs-keyword">await</span> axios.get(<span class="hljs-string">"https://jsonplaceholder.typicode.com/todos"</span>);

   setTodos(data);
  } <span class="hljs-keyword">catch</span> (error) {
   <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"error"</span>, error);
  }
 }, []);

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

 <span class="hljs-keyword">const</span> renderItem = <span class="hljs-function">(<span class="hljs-params">item: Todo</span>) =&gt;</span> (
  &lt;div className=<span class="hljs-string">"bg-slate-900 rounded-lg overflow-hidden flex flex-col justify-between"</span>&gt;
   &lt;div className=<span class="hljs-string">"px-5 py-8 flex items-center gap-4"</span>&gt;
    &lt;p&gt;{item.id})&lt;/p&gt;
    &lt;h2 className=<span class="hljs-string">"text-2xl capitalize"</span>&gt;{item.title}&lt;/h2&gt;
   &lt;/div&gt;
   &lt;div className=<span class="hljs-string">"bg-slate-600 py-2"</span>&gt;
    &lt;p className=<span class="hljs-string">"text-center"</span>&gt;{item.completed ? <span class="hljs-string">"Completed"</span> : <span class="hljs-string">"Not completed"</span>}&lt;/p&gt;
   &lt;/div&gt;
  &lt;/div&gt;
 );

 <span class="hljs-keyword">const</span> ItemSeparatorComponent = <span class="hljs-function">() =&gt;</span> &lt;div className=<span class="hljs-string">"my-1 h-[1px] border-t border-t-zinc-700 border-dashed"</span> /&gt;;

 <span class="hljs-keyword">const</span> keyExtractor = <span class="hljs-function">(<span class="hljs-params">item: { id: <span class="hljs-built_in">number</span> }</span>) =&gt;</span> item.id;

 <span class="hljs-keyword">return</span> (
  &lt;div className=<span class="hljs-string">"space-y-4 max-w-2xl mx-auto w-full"</span>&gt;
   &lt;h1 className=<span class="hljs-string">"mt-4 text-3xl font-bold text-center"</span>&gt;React FlatList&lt;/h1&gt;

   &lt;FlatList
    data={todos}
    renderItem={renderItem}
    keyExtractor={keyExtractor}
    ItemSeparatorComponent={ItemSeparatorComponent}
   /&gt;
  &lt;/div&gt;
 );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<p><img src="https://cdn-images-1.medium.com/max/1600/1*9zSkdGSoDXVJq1PcBqKhjQ.png" alt /></p>
<blockquote>
<p>Now, when you run the project and view it in your browser, you should see a list of todos displayed in your browser.</p>
</blockquote>
<hr />
<h3 id="heading-adding-pagination-to-the-flatlist-component">Adding Pagination to the FlatList Component</h3>
<p>In this section, we’ll enhance our <code>FlatList</code> component by adding pagination, allowing the list to load more items as you scroll down. We'll use constants to manage the number of items per page, total items, and the threshold for triggering the end-of-list event.</p>
<h4 id="heading-1-define-constants">1. Define Constants</h4>
<p>First, let’s define some constants for our component:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> ITEM_PER_PAGE = <span class="hljs-number">10</span>;
<span class="hljs-keyword">const</span> TOTAL_ITEMS = <span class="hljs-number">200</span>;
<span class="hljs-keyword">const</span> ON_END_REACHED_THRESHOLD = <span class="hljs-number">0.2</span>;
</code></pre>
<h4 id="heading-2-define-states-and-a-ref">2. Define States and a Ref</h4>
<p>Next, we’ll define the states for managing the current page, the loading state, and a ref to track the initial render:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> [page, setPage] = useState(<span class="hljs-number">1</span>);
<span class="hljs-keyword">const</span> [hasMore, setHasMore] = useState(<span class="hljs-literal">true</span>);
<span class="hljs-keyword">const</span> [isLoading, setIsLoading] = useState(<span class="hljs-literal">false</span>);

<span class="hljs-keyword">const</span> initialRender = useRef(<span class="hljs-literal">true</span>); <span class="hljs-comment">// Ref to track the initial render</span>
</code></pre>
<h4 id="heading-3-modify-the-fetchdata-function">3. Modify the fetchData Function</h4>
<p>We’ll update the <code>fetchData</code> function to handle pagination. This function will fetch data based on the current page and append it to the existing list:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> fetchData = useCallback(<span class="hljs-keyword">async</span> () =&gt; {
  setIsLoading(<span class="hljs-literal">true</span>);

  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> { data } = <span class="hljs-keyword">await</span> axios.get(<span class="hljs-string">`https://jsonplaceholder.typicode.com/todos?_limit=<span class="hljs-subst">${ITEM_PER_PAGE}</span>&amp;_page=<span class="hljs-subst">${page}</span>`</span>);

    setTodos(<span class="hljs-function"><span class="hljs-params">prevData</span> =&gt;</span> {
      <span class="hljs-keyword">const</span> todos = [...prevData, ...data];

      <span class="hljs-keyword">if</span> (todos.length &gt;= TOTAL_ITEMS) {
        setHasMore(<span class="hljs-literal">false</span>);
      }

      <span class="hljs-keyword">return</span> todos;
    });
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"error"</span>, error);
  } <span class="hljs-keyword">finally</span> {
    setIsLoading(<span class="hljs-literal">false</span>);
  }
}, [page]);
</code></pre>
<h4 id="heading-4-modify-the-useeffect">4. Modify the useEffect</h4>
<p>We’ll modify the <code>useEffect</code> to call <code>fetchData</code> only if there are more items to load:</p>
<pre><code class="lang-typescript">useEffect(<span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">if</span> (initialRender.current) {
    initialRender.current = <span class="hljs-literal">false</span>;
    <span class="hljs-keyword">return</span>;
  }
  <span class="hljs-keyword">if</span> (hasMore) {
    fetchData();
  }
}, [fetchData, hasMore]);
</code></pre>
<h4 id="heading-5-add-the-listfootercomponent">5. Add the ListFooterComponent</h4>
<p>We’ll add a <code>ListFooterComponent</code> to display a loading indicator or an end-of-list message:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> ListFooterComponent = <span class="hljs-function">() =&gt;</span> (
  &lt;&gt;
    {isLoading &amp;&amp; &lt;div className=<span class="hljs-string">"text-center p-5"</span>&gt;Loading...&lt;/div&gt;}
    {!isLoading &amp;&amp; !hasMore &amp;&amp; &lt;div className=<span class="hljs-string">"text-center p-5"</span>&gt;End <span class="hljs-keyword">of</span> List&lt;/div&gt;}
  &lt;/&gt;
);
</code></pre>
<h4 id="heading-6-add-the-onendreached-function">6. Add the onEndReached Function</h4>
<p>The <code>onEndReached</code> function will increment the page number when the end of the list is reached:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> onEndReached = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">if</span> (!isLoading &amp;&amp; hasMore) {
    setPage(<span class="hljs-function"><span class="hljs-params">prevPage</span> =&gt;</span> prevPage + <span class="hljs-number">1</span>);
  }
};
</code></pre>
<h4 id="heading-7-pass-additional-props-to-flatlist">7. Pass Additional Props to FlatList</h4>
<p>Finally, we’ll pass <code>ListFooterComponent</code>, <code>onEndReached</code>, and <code>ON_END_REACHED_THRESHOLD</code> to the <code>FlatList</code> component:</p>
<pre><code class="lang-typescript">&lt;FlatList
  data={todos}
  renderItem={renderItem}
  keyExtractor={keyExtractor}
  ItemSeparatorComponent={ItemSeparatorComponent}
  ListFooterComponent={ListFooterComponent}
  onEndReached={onEndReached}
  onEndReachedThreshold={ON_END_REACHED_THRESHOLD}
/&gt;
</code></pre>
<h4 id="heading-8-complete-app-component">8. Complete <code>App</code> Component</h4>
<p>Here’s the complete <code>App</code> component with all the modifications:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useCallback, useEffect, useRef, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> axios <span class="hljs-keyword">from</span> <span class="hljs-string">"axios"</span>;
<span class="hljs-keyword">import</span> FlatList <span class="hljs-keyword">from</span> <span class="hljs-string">"./components/FlatList"</span>;

<span class="hljs-keyword">const</span> ITEM_PER_PAGE = <span class="hljs-number">10</span>;
<span class="hljs-keyword">const</span> TOTAL_ITEMS = <span class="hljs-number">200</span>;
<span class="hljs-keyword">const</span> ON_END_REACHED_THRESHOLD = <span class="hljs-number">0.2</span>;

<span class="hljs-keyword">type</span> Todo = { id: <span class="hljs-built_in">number</span>; title: <span class="hljs-built_in">string</span>; completed: <span class="hljs-built_in">boolean</span>; userId: <span class="hljs-built_in">number</span> };

<span class="hljs-keyword">const</span> App = <span class="hljs-function">() =&gt;</span> {
 <span class="hljs-keyword">const</span> [todos, setTodos] = useState&lt;Todo[]&gt;([]);
 <span class="hljs-keyword">const</span> [page, setPage] = useState(<span class="hljs-number">1</span>);
 <span class="hljs-keyword">const</span> [hasMore, setHasMore] = useState(<span class="hljs-literal">true</span>);
 <span class="hljs-keyword">const</span> [isLoading, setIsLoading] = useState(<span class="hljs-literal">false</span>);

 <span class="hljs-keyword">const</span> initialRender = useRef(<span class="hljs-literal">true</span>); <span class="hljs-comment">// Ref to track the initial render</span>

 <span class="hljs-keyword">const</span> fetchData = useCallback(<span class="hljs-keyword">async</span> () =&gt; {
  setIsLoading(<span class="hljs-literal">true</span>);

  <span class="hljs-keyword">try</span> {
   <span class="hljs-keyword">const</span> { data } = <span class="hljs-keyword">await</span> axios.get(<span class="hljs-string">`https://jsonplaceholder.typicode.com/todos?_limit=<span class="hljs-subst">${ITEM_PER_PAGE}</span>&amp;_page=<span class="hljs-subst">${page}</span>`</span>);

   setTodos(<span class="hljs-function"><span class="hljs-params">prevData</span> =&gt;</span> {
    <span class="hljs-keyword">const</span> todos = [...prevData, ...data];

    <span class="hljs-keyword">if</span> (todos.length &gt;= TOTAL_ITEMS) {
     setHasMore(<span class="hljs-literal">false</span>);
    }

    <span class="hljs-keyword">return</span> todos;
   });
  } <span class="hljs-keyword">catch</span> (error) {
   <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"error"</span>, error);
  } <span class="hljs-keyword">finally</span> {
   setIsLoading(<span class="hljs-literal">false</span>);
  }
 }, [page]);

 useEffect(<span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">if</span> (initialRender.current) {
   initialRender.current = <span class="hljs-literal">false</span>;
   <span class="hljs-keyword">return</span>;
  }
  <span class="hljs-keyword">if</span> (hasMore) {
   fetchData();
  }
 }, [fetchData, hasMore]);

 <span class="hljs-keyword">const</span> renderItem = <span class="hljs-function">(<span class="hljs-params">item: Todo</span>) =&gt;</span> (
  &lt;div className=<span class="hljs-string">"bg-slate-900 rounded-lg overflow-hidden flex flex-col justify-between"</span>&gt;
   &lt;div className=<span class="hljs-string">"px-5 py-8 flex items-center gap-4"</span>&gt;
    &lt;p&gt;{item.id})&lt;/p&gt;
    &lt;h2 className=<span class="hljs-string">"text-2xl capitalize"</span>&gt;{item.title}&lt;/h2&gt;
   &lt;/div&gt;
   &lt;div className=<span class="hljs-string">"bg-slate-600 py-2"</span>&gt;
    &lt;p className=<span class="hljs-string">"text-center"</span>&gt;{item.completed ? <span class="hljs-string">"Completed"</span> : <span class="hljs-string">"Not completed"</span>}&lt;/p&gt;
   &lt;/div&gt;
  &lt;/div&gt;
 );

 <span class="hljs-keyword">const</span> ItemSeparatorComponent = <span class="hljs-function">() =&gt;</span> &lt;div className=<span class="hljs-string">"my-1 h-[1px] border-t border-t-zinc-700 border-dashed"</span> /&gt;;

 <span class="hljs-keyword">const</span> ListFooterComponent = <span class="hljs-function">() =&gt;</span> (
   &lt;&gt;
     {isLoading &amp;&amp; &lt;div className=<span class="hljs-string">"text-center p-5"</span>&gt;Loading...&lt;/div&gt;}
     {!isLoading &amp;&amp; !hasMore &amp;&amp; &lt;div className=<span class="hljs-string">"text-center p-5"</span>&gt;End <span class="hljs-keyword">of</span> List&lt;/div&gt;}
   &lt;/&gt;
 );

 <span class="hljs-keyword">const</span> keyExtractor = <span class="hljs-function">(<span class="hljs-params">item: { id: <span class="hljs-built_in">number</span> }</span>) =&gt;</span> item.id;

 <span class="hljs-keyword">const</span> onEndReached = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">if</span> (!isLoading &amp;&amp; hasMore) {
   setPage(<span class="hljs-function"><span class="hljs-params">prevPage</span> =&gt;</span> prevPage + <span class="hljs-number">1</span>);
  }
 };

 <span class="hljs-keyword">return</span> (
  &lt;div className=<span class="hljs-string">"space-y-4 max-w-2xl mx-auto w-full"</span>&gt;
   &lt;h1 className=<span class="hljs-string">"mt-4 text-3xl font-bold text-center"</span>&gt;React FlatList&lt;/h1&gt;

   &lt;FlatList
    data={todos}
    renderItem={renderItem}
    keyExtractor={keyExtractor}
    ItemSeparatorComponent={ItemSeparatorComponent}
    ListFooterComponent={ListFooterComponent}
    onEndReached={onEndReached}
    onEndReachedThreshold={ON_END_REACHED_THRESHOLD}
   /&gt;
  &lt;/div&gt;
 );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<p><img src="https://cdn-images-1.medium.com/max/1600/1*DfGtxXPZSU0euilX6jnYhA.png" alt /></p>
<blockquote>
<p>After implementing these changes, you should be able to see the list of todos in your browser. As you scroll to the bottom, more items will load, and a loading indicator will appear. If there are no more items to load, an “End of List” message will be displayed.</p>
</blockquote>
<hr />
<h3 id="heading-conclusion">Conclusion</h3>
<p>Creating a FlatList-like component for a web application using ReactJS is a powerful way to efficiently manage and display large datasets with infinite scrolling. This tutorial walked through building such a component, emphasizing the importance of handling data fetching, pagination, and maintaining a responsive user experience.</p>
<h4 id="heading-key-steps-included">Key steps included:</h4>
<ol>
<li><p><strong>Setting Up the FlatList Component</strong>: We created a reusable FlatList component with essential props such as <code>data</code>, <code>keyExtractor</code>, <code>renderItem</code>, <code>ItemSeparatorComponent</code>, <code>ListFooterComponent</code>, <code>onEndReached</code>, and <code>onEndReachedThreshold</code>.</p>
</li>
<li><p><strong>Handling Data Fetching</strong>: By utilizing the <code>useCallback</code> and <code>useEffect</code> hooks, we ensured efficient data fetching and updating the state only when necessary.</p>
</li>
<li><p><strong>Adding Pagination</strong>: We implemented a pagination system that loads more items as the user scrolls down, using constants to manage the number of items per page and the total items.</p>
</li>
<li><p><strong>Enhancing User Experience</strong>: The <code>ListFooterComponent</code> provided visual feedback to users about the loading state and the end of the list.</p>
</li>
</ol>
<p>By following these steps, you can create a highly customizable and efficient infinite scroll component for any dataset. This approach not only improves the user experience by providing seamless data loading but also enhances performance by loading data incrementally.</p>
<p>Feel free to experiment with different styles and configurations to tailor the FlatList component to your specific needs. Happy coding!</p>
]]></content:encoded></item><item><title><![CDATA[Implementing Google Login in NextJS 14: A Complete Guide with AuthJS 5, MongoDB, and Prisma ORM]]></title><description><![CDATA[Introduction
In today’s digital landscape, secure user authentication is crucial for any web application. Google authentication provides a seamless and secure way for users to log in using their Google accounts. This tutorial will guide you through i...]]></description><link>https://blog.sazzadur.site/google-login-in-nextjs-with-authjs5-mongodb-and-prisma</link><guid isPermaLink="true">https://blog.sazzadur.site/google-login-in-nextjs-with-authjs5-mongodb-and-prisma</guid><category><![CDATA[Next.js]]></category><category><![CDATA[nextauth.js]]></category><category><![CDATA[authentication]]></category><category><![CDATA[prisma]]></category><category><![CDATA[MongoDB]]></category><category><![CDATA[ google guide login]]></category><dc:creator><![CDATA[Sazzadur Rahman]]></dc:creator><pubDate>Thu, 13 Jun 2024 07:30:35 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1718121816043/80ff83bf-06d4-4d56-9b23-401f4bb5c410.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-introduction">Introduction</h3>
<p>In today’s digital landscape, secure user authentication is crucial for any web application. Google authentication provides a seamless and secure way for users to log in using their Google accounts. This tutorial will guide you through implementing Google authentication in a NextJS 14 application using AuthJS 5, MongoDB, and Prisma ORM.</p>
<p>By the end of this article, you’ll have a robust authentication system that leverages the power of modern web technologies, ensuring both security and ease of use.</p>
<p>Let’s dive in and get started with setting up our NextJS project!</p>
<hr />
<h3 id="heading-setting-up-the-project">Setting Up the Project</h3>
<p><strong>1. Initialize a NextJS Project</strong></p>
<p>First, we’ll create a new NextJS project named <code>nextjs-google-auth</code> by running the following command:</p>
<pre><code class="lang-bash">npx create-next-app@latest nextjs-google-auth
</code></pre>
<p>Follow the prompts to set up the project according to your preferences.</p>
<p><strong>2. Navigate to the Project Directory</strong></p>
<p>Move into the newly created project directory:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> nextjs-google-auth
</code></pre>
<p>3. Install Necessary Dependencies<br />Next, install the required packages for authentication and database management:</p>
<pre><code class="lang-bash">npm install next-auth@beta @prisma/client @auth/prisma-adapter
npm install prisma --save-dev
</code></pre>
<blockquote>
<p><strong>Note:</strong> We use <code>next-auth@beta</code> because Auth.js version 5 is currently in beta. If you're reading this in the future when Auth.js 5 is fully released, you can install it with <code>npm install next-auth</code> .</p>
</blockquote>
<p><strong>4. Set Up Google Auth Credentials</strong><br />We will need a Google Auth Client ID and Client Secret. To generate these credentials:</p>
<ul>
<li><p>Go to the <a target="_blank" href="https://console.developers.google.com/apis/credentials">Google Cloud Console</a></p>
</li>
<li><p>Register a new application and create OAuth 2.0 credentials.</p>
</li>
<li><p>Set the Callback URL to: <code>https://&lt;your-site&gt;/api/auth/callback/google</code> .</p>
</li>
</ul>
<p>Copy the generated Client ID and Client Secret, and paste them into the <code>.env</code> file in your project directory:</p>
<pre><code class="lang-plaintext">AUTH_GOOGLE_ID="your-google-client-id"
AUTH_GOOGLE_SECRET="your-google-client-secret"
</code></pre>
<p><strong>5. Generate an Auth Secret</strong><br />Run the following command to generate a secret for authentication and add it to the <code>.env</code> file:</p>
<pre><code class="lang-bash">npx auth secret
</code></pre>
<p>Add the generated secret to your <code>.env</code> file:</p>
<pre><code class="lang-plaintext">AUTH_SECRET="your-auth-secret"
</code></pre>
<p><strong>6. Set Up the Database</strong></p>
<p>Go to the <a target="_blank" href="https://www.mongodb.com/">MongoDB website</a> and create a new database. Once the database is created, obtain the connection string.</p>
<p>Remember to include your database name in the connection string. Add this connection string to your <code>.env</code> file:</p>
<pre><code class="lang-plaintext">DATABASE_URL="mongodb+srv://&lt;username&gt;:&lt;password&gt;@&lt;cluster-url&gt;/&lt;your-db-name&gt;?retryWrites=true&amp;w=majority&amp;appName=&lt;app-name&gt;"
</code></pre>
<p>This sets up the basic configuration for your project, ensuring you have the necessary environment variables and dependencies to proceed with implementing Google authentication.</p>
<p>Next, we will configure Prisma to work with MongoDB.</p>
<hr />
<h3 id="heading-configuring-prisma-orm">Configuring Prisma ORM</h3>
<p><strong>1. Initialize Prisma</strong><br />Run the following command to initialize Prisma with MongoDB as the datasource provider:</p>
<pre><code class="lang-bash">npx prisma init --datasource-provider mongodb
</code></pre>
<p>This will generate a <code>prisma</code> folder and a <code>schema.prisma</code> file inside it.</p>
<p><strong>2. Configure the Prisma Schema</strong><br />Replace the contents of the <code>prisma/schema.prisma</code> file with the following:</p>
<pre><code class="lang-plaintext">// prisma/schema.prisma

datasource db {
  provider = "mongodb"
  url      = env("DATABASE_URL")
}

generator client {
  provider = "prisma-client-js"
}

model User {
  id            String    @id @default(auto()) @map("_id") @db.ObjectId
  name          String?
  email         String?   @unique
  emailVerified DateTime?
  image         String?
  accounts      Account[]
}

model Account {
  id                String   @id @default(auto()) @map("_id") @db.ObjectId
  userId            String   @db.ObjectId
  type              String
  provider          String
  providerAccountId String
  refresh_token     String?  @db.String
  access_token      String?  @db.String
  expires_at        Int?
  token_type        String?
  scope             String?
  id_token          String?  @db.String
  session_state     String?
  createdAt         DateTime @default(now())
  updatedAt         DateTime @updatedAt
  user              User     @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@unique([provider, providerAccountId])
}
</code></pre>
<p><strong>3. Generate Prisma Client</strong><br />Run the following command to generate the Prisma client based on the updated schema:</p>
<pre><code class="lang-bash">npx prisma generate
</code></pre>
<p>This configures Prisma to work with MongoDB and sets up the necessary models for handling user authentication data.</p>
<hr />
<h3 id="heading-configuring-the-database-connection-and-authentication">Configuring the Database Connection and Authentication</h3>
<p><strong>1. Create a</strong><code>lib</code><strong>Folder</strong><br />Inside the <code>src</code> folder, create a <code>lib</code> folder to store our database and authentication configurations.</p>
<p><strong>2. Create</strong><code>db.ts</code><strong>in the</strong><code>lib</code><strong>Folder</strong><br />Create a <code>db.ts</code> file inside the <code>lib</code> folder with the following content:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// @/lib/db.ts</span>

<span class="hljs-keyword">import</span> { PrismaClient } <span class="hljs-keyword">from</span> <span class="hljs-string">"@prisma/client"</span>;

<span class="hljs-keyword">const</span> prismaClientSingleton = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> PrismaClient();
};

<span class="hljs-keyword">declare</span> <span class="hljs-built_in">global</span> {
  <span class="hljs-keyword">var</span> prisma: <span class="hljs-literal">undefined</span> | ReturnType&lt;<span class="hljs-keyword">typeof</span> prismaClientSingleton&gt;;
}

<span class="hljs-keyword">const</span> db = globalThis.prisma ?? prismaClientSingleton();

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> db;

<span class="hljs-keyword">if</span> (process.env.NODE_ENV !== <span class="hljs-string">"production"</span>) globalThis.prisma = db;
</code></pre>
<p><strong>3. Create</strong><code>auth.config.ts</code><strong>in the</strong><code>lib</code><strong>Folder</strong><br />Create a <code>auth.config.ts</code>file inside the <code>lib</code> folder with the following content:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// @/lib/auth.config.ts</span>

<span class="hljs-keyword">import</span> { NextAuthConfig } <span class="hljs-keyword">from</span> <span class="hljs-string">"next-auth"</span>;
<span class="hljs-keyword">import</span> Google <span class="hljs-keyword">from</span> <span class="hljs-string">"next-auth/providers/google"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> authConfig: NextAuthConfig = {
  providers: [Google],
};
</code></pre>
<p><strong>4. Create</strong><code>auth.ts</code><strong>in the</strong><code>src</code><strong>Folder</strong><br />Create a <code>auth.ts</code> file inside the <code>src</code> folder with the following content:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// @/auth.ts</span>

<span class="hljs-keyword">import</span> NextAuth <span class="hljs-keyword">from</span> <span class="hljs-string">"next-auth"</span>;
<span class="hljs-keyword">import</span> { authConfig } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/lib/auth.config"</span>;
<span class="hljs-keyword">import</span> { PrismaAdapter } <span class="hljs-keyword">from</span> <span class="hljs-string">"@auth/prisma-adapter"</span>;
<span class="hljs-keyword">import</span> db <span class="hljs-keyword">from</span> <span class="hljs-string">"@/lib/db"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> { handlers, signIn, signOut, auth } = NextAuth({
 ...authConfig,
 adapter: PrismaAdapter(db),
 session: {
  strategy: <span class="hljs-string">"jwt"</span>,
 },
 pages: {
  signIn: <span class="hljs-string">"/login"</span>,
 },
 callbacks: {
  <span class="hljs-keyword">async</span> jwt({ token, user }) {
   <span class="hljs-keyword">if</span> (user) {
    <span class="hljs-comment">// get user from db with the email</span>
    <span class="hljs-comment">// if there is no user with the email, create new user</span>
    <span class="hljs-comment">// else set the user data to token</span>
   }

   <span class="hljs-keyword">return</span> token;
  },

  <span class="hljs-keyword">async</span> session({ session, token }) {
   <span class="hljs-keyword">if</span> (token) {
    <span class="hljs-comment">// set the token data to session</span>
   }

   <span class="hljs-keyword">return</span> session;
  },

  redirect() {
   <span class="hljs-keyword">return</span> <span class="hljs-string">"/login"</span>;
  },
 },
});
</code></pre>
<hr />
<h3 id="heading-setting-up-api-routes-and-middleware"><strong>Setting Up API Routes and Middleware</strong></h3>
<p><strong>1. Create API Route for Authentication</strong><br />Inside the <code>app</code> folder, create an <code>api</code> folder, then an <code>auth</code> folder, and finally a <code>[...nextauth]</code> folder inside it. Create a <code>route.ts</code> file inside the <code>[...nextauth]</code> folder with the following content:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// @/app/api/auth/[…nextauth]/route.ts</span>

<span class="hljs-keyword">import</span> { handlers } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/auth"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> { GET, POST } = handlers;
</code></pre>
<p><strong>2. Create</strong><code>routes.ts</code><strong>in the</strong><code>src</code><strong>Folder</strong><br />Create a <code>routes.ts</code> file inside the <code>src</code> folder with the following content:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// @/routes.ts</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> API_AUTH_PREFIX = <span class="hljs-string">"/api/auth"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> AUTH_ROUTES = [<span class="hljs-string">"/login"</span>];

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> PROTECTED_ROUTES = [
  <span class="hljs-string">"/"</span>,
  <span class="hljs-comment">// your other protected routes</span>
]
</code></pre>
<p><strong>3. Create</strong><code>middleware.ts</code><strong>in the</strong><code>src</code><strong>Folder</strong>Create a <code>middleware.ts</code> file inside the <code>src</code> folder with the following content:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// @/middleware.ts</span>

<span class="hljs-keyword">import</span> NextAuth <span class="hljs-keyword">from</span> <span class="hljs-string">"next-auth"</span>;
<span class="hljs-keyword">import</span> { NextResponse } <span class="hljs-keyword">from</span> <span class="hljs-string">"next/server"</span>;

<span class="hljs-keyword">import</span> { authConfig } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/lib/auth.config"</span>;
<span class="hljs-keyword">import</span> { API_AUTH_PREFIX, AUTH_ROUTES, PROTECTED_ROUTES } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/routes"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> { auth } = NextAuth(authConfig);

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> auth(<span class="hljs-function"><span class="hljs-params">req</span> =&gt;</span> {
 <span class="hljs-keyword">const</span> pathname = req.nextUrl.pathname;

 <span class="hljs-comment">// manage route protection</span>
 <span class="hljs-keyword">const</span> isAuth = req.auth;

 <span class="hljs-keyword">const</span> isAccessingApiAuthRoute = pathname.startsWith(API_AUTH_PREFIX);
 <span class="hljs-keyword">const</span> isAccessingAuthRoute = AUTH_ROUTES.some(<span class="hljs-function"><span class="hljs-params">route</span> =&gt;</span> pathname.startsWith(route));
 <span class="hljs-keyword">const</span> isAccessingProtectedRoute = PROTECTED_ROUTES.some(<span class="hljs-function"><span class="hljs-params">route</span> =&gt;</span> pathname.startsWith(route));

 <span class="hljs-keyword">if</span> (isAccessingApiAuthRoute) {
  <span class="hljs-keyword">return</span> NextResponse.next();
 }

 <span class="hljs-keyword">if</span> (isAccessingAuthRoute) {
  <span class="hljs-keyword">if</span> (isAuth) {
   <span class="hljs-keyword">return</span> NextResponse.redirect(<span class="hljs-keyword">new</span> URL(<span class="hljs-string">"/"</span>, req.url));
  }

  <span class="hljs-keyword">return</span> NextResponse.next();
 }

 <span class="hljs-keyword">if</span> (!isAuth &amp;&amp; isAccessingProtectedRoute) {
  <span class="hljs-keyword">return</span> NextResponse.redirect(<span class="hljs-keyword">new</span> URL(<span class="hljs-string">"/login"</span>, req.url));
 }
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> config = {
 matcher: [<span class="hljs-string">"/((?!.+\\.[\\w]+$|_next).*)"</span>, <span class="hljs-string">"/"</span>, <span class="hljs-string">"/(api|trpc)(.*)"</span>],
};
</code></pre>
<p>This setup configures the database connection, authentication system, API routes, and middleware for your NextJS application.</p>
<hr />
<h3 id="heading-implementing-authentication-pages">Implementing Authentication Pages</h3>
<p><strong>1. Create the Login Page</strong><br />Inside the <code>app</code> folder, create a <code>login</code> folder. Inside the <code>login</code> folder, create a <code>page.tsx</code> file with the following content:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// @/app/login/page.tsx</span>

<span class="hljs-keyword">import</span> { signIn } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/auth"</span>;

<span class="hljs-keyword">const</span> LoginPage = <span class="hljs-function">() =&gt;</span> {
 <span class="hljs-keyword">return</span> (
   &lt;form
     action={<span class="hljs-keyword">async</span> () =&gt; {
       <span class="hljs-string">"use server"</span>;
       <span class="hljs-keyword">await</span> signIn(<span class="hljs-string">"google"</span>);
     }}
   &gt;
     &lt;button <span class="hljs-keyword">type</span>=<span class="hljs-string">"submit"</span>&gt;Login <span class="hljs-keyword">with</span> Google&lt;/button&gt;
   &lt;/form&gt;
   );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> LoginPage;
</code></pre>
<p><strong>2. Create the Home Page</strong><br />Inside the <code>app</code> folder, create a <code>page.tsx</code> file with the following content:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// @/app/page.tsx</span>

<span class="hljs-keyword">import</span> { auth, signOut } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/auth"</span>;
<span class="hljs-keyword">import</span> { notFound } <span class="hljs-keyword">from</span> <span class="hljs-string">"next/navigation"</span>;

<span class="hljs-keyword">const</span> HomePage = <span class="hljs-keyword">async</span> () =&gt; {
 <span class="hljs-keyword">const</span> session = <span class="hljs-keyword">await</span> auth();
 <span class="hljs-keyword">if</span> (!session) <span class="hljs-keyword">return</span> notFound();
 <span class="hljs-keyword">return</span> (
   &lt;main&gt;
     &lt;h1&gt;Hello {session.user.name}&lt;/h1&gt;

     &lt;form
       action={<span class="hljs-keyword">async</span> () =&gt; {
         <span class="hljs-string">"use server"</span>;
         <span class="hljs-keyword">await</span> signOut();
       }}
     &gt;
       &lt;button <span class="hljs-keyword">type</span>=<span class="hljs-string">"submit"</span>&gt;Log Out&lt;/button&gt;
     &lt;/form&gt;
   &lt;/main&gt;
 );
};
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> HomePage;
</code></pre>
<p><strong>3. Run the Development Server</strong><br />Run the following command in your terminal:</p>
<pre><code class="lang-bash">npm run dev
</code></pre>
<p>If everything is set up correctly, you will be asked to log in with your Google account. Once logged in, you should be able to see the home page with a greeting and an option to log out.</p>
<hr />
<h3 id="heading-conclusion">Conclusion</h3>
<p>Implementing Google authentication in a Next.js 14 application using NextAuth.js 5 (beta), Prisma ORM, and MongoDB can be a streamlined and efficient process. By following the steps outlined in this article, you can set up a robust authentication system that leverages Google’s OAuth 2.0 for user authentication, Prisma for database management, and MongoDB as the database.</p>
<p>Here’s a brief recap of what we covered:</p>
<ol>
<li><p><strong>Project Setup</strong>: Initializing a Next.js project and installing necessary dependencies.</p>
</li>
<li><p><strong>Google Auth Credentials</strong>: Configuring Google OAuth 2.0 credentials.</p>
</li>
<li><p><strong>Database Setup</strong>: Setting up a MongoDB database and configuring Prisma ORM.</p>
</li>
<li><p><strong>Prisma Configuration</strong>: Initializing Prisma and defining schema models for <code>User</code> and <code>Account</code>.</p>
</li>
<li><p><strong>Authentication Configuration</strong>: Setting up NextAuth.js configuration with Prisma adapter and custom callbacks.</p>
</li>
<li><p><strong>API Routes and Middleware</strong>: Defining API routes for authentication and implementing middleware for route protection.</p>
</li>
<li><p><strong>Authentication Pages</strong>: Creating login and home pages with server actions for sign-in and sign-out.</p>
</li>
</ol>
<p>With this setup, you now have a fully functional authentication system integrated into your Next.js application. This foundation can be further extended to include more features such as authorization, role-based access control, and additional OAuth providers.</p>
<p>Feel free to customize and expand upon this setup to fit the specific needs of your application. For a complete reference and code implementation, you can visit the <a target="_blank" href="https://github.com/SSazzadur/nextjs-google-auth">GitHub Repository</a>.</p>
<p>Happy coding!</p>
<hr />
<h3 id="heading-github-repository">GitHub Repository</h3>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/SSazzadur/nextjs-google-auth">https://github.com/SSazzadur/nextjs-google-auth</a></div>
]]></content:encoded></item><item><title><![CDATA[Step-by-Step Guide to Building a Rating System in React]]></title><description><![CDATA[Introduction
User feedback is essential for improving products and services. A rating system is a simple yet powerful way to gather this feedback. In this article, we’ll walk you through creating a rating system with React. We’ll cover setting up the...]]></description><link>https://blog.sazzadur.site/building-a-rating-system-in-react</link><guid isPermaLink="true">https://blog.sazzadur.site/building-a-rating-system-in-react</guid><category><![CDATA[rating system]]></category><category><![CDATA[React]]></category><category><![CDATA[Rating]]></category><category><![CDATA[Tailwind CSS]]></category><category><![CDATA[React]]></category><dc:creator><![CDATA[Sazzadur Rahman]]></dc:creator><pubDate>Wed, 12 Jun 2024 16:42:02 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1718209881529/6b5e9dee-9147-4c4e-8540-59f3ddf29a0f.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-introduction">Introduction</h3>
<p>User feedback is essential for improving products and services. A rating system is a simple yet powerful way to gather this feedback. In this article, we’ll walk you through creating a rating system with React. We’ll cover setting up the project, building the rating component, and adding interactivity and styling. By the end, you’ll have a fully functional and customizable rating system ready to enhance your web application. This guide is perfect for developers with basic React knowledge. Let’s dive in!</p>
<h3 id="heading-setting-up-the-project">Setting Up the Project</h3>
<p>First, we’ll create a new React project using <code>Vite</code> and set up <code>Tailwind CSS</code> for styling. Follow these steps:</p>
<h4 id="heading-1-create-react-project">1. Create React Project:</h4>
<pre><code class="lang-bash">pnpm create vite@latest react-rating-system
</code></pre>
<h4 id="heading-2-navigate-to-the-project-directory-and-install-dependencies">2. Navigate to the project directory and install dependencies:</h4>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> react-rating-system
pnpm install
</code></pre>
<h4 id="heading-3-install-tailwind-css">3. Install Tailwind CSS:</h4>
<pre><code class="lang-bash">pnpm add -D tailwindcss postcss autoprefixer
pnpm dlx tailwindcss init -p
</code></pre>
<h4 id="heading-4-configure-tailwind-css">4. Configure Tailwind CSS:</h4>
<p>Update <code>tailwind.config.js</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {
  <span class="hljs-comment">// replace this</span>
  <span class="hljs-attr">content</span>: [<span class="hljs-string">"./index.html"</span>, <span class="hljs-string">"./src/**/*.{js,ts,jsx,tsx}"</span>],

  <span class="hljs-comment">// your other configs</span>
  <span class="hljs-attr">theme</span>: {
    <span class="hljs-attr">extend</span>: {},
  },
  <span class="hljs-attr">plugins</span>: [],
}
</code></pre>
<h4 id="heading-5-add-tailwind-css-directives">5. Add Tailwind CSS Directives:</h4>
<p>Add these lines to the top of your <code>index.css</code> file:</p>
<pre><code class="lang-css"><span class="hljs-keyword">@tailwind</span> base;
<span class="hljs-keyword">@tailwind</span> components;
<span class="hljs-keyword">@tailwind</span> utilities;
</code></pre>
<h4 id="heading-6-install-react-icons">6. Install React Icons:</h4>
<pre><code class="lang-bash">pnpm add react-icons
</code></pre>
<h4 id="heading-7-run-the-project">7. Run the Project:</h4>
<pre><code class="lang-bash">pnpm dev
</code></pre>
<blockquote>
<p>Now, your React project is set up with <code>Tailwind CSS</code> for styling and <code>React Icons</code> for easy icon integration. You’re ready to start building the rating system!</p>
</blockquote>
<hr />
<h3 id="heading-initial-setup-and-testing">Initial Setup and Testing</h3>
<p>To ensure everything is set up correctly, we’ll start by adding some initial code to display star icons in your <code>App.tsx</code> file. Follow these steps:</p>
<h4 id="heading-1-update-apptsx">1. Update App.tsx:</h4>
<p>Replace the contents of your <code>App.tsx</code> file with the following code:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { FaRegStar, FaStar } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-icons/fa"</span>;

<span class="hljs-keyword">import</span> <span class="hljs-string">"./App.css"</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> (
    &lt;div className=”flex items-center gap-x<span class="hljs-number">-4</span><span class="hljs-string">"&gt;
      &lt;FaStar className=”h-8 w-auto text-white cursor-pointer” /&gt;
      &lt;FaRegStar className=”h-8 w-auto text-white cursor-pointer” /&gt;
    &lt;/div&gt;
  );
}

export default App;</span>
</code></pre>
<h4 id="heading-2-check-the-browser">2. Check the Browser:</h4>
<p>Open your browser and navigate to the local server (usually <a target="_blank" href="http://localhost:5173"><code>http://localhost:5173</code></a>). You should see two stars: one filled and one unfilled.</p>
<p><img src="https://cdn-images-1.medium.com/max/1600/1*wATb-N_EiWplPWzU_9eNLg.png" alt /></p>
<blockquote>
<p>This confirms that your project setup with <code>Tailwind CSS</code> and <code>React Icons</code> is working correctly. Now you’re ready to build out the full rating system!</p>
</blockquote>
<hr />
<h3 id="heading-adding-rating-state-and-dynamic-rendering">Adding Rating State and Dynamic Rendering</h3>
<p>To implement dynamic rendering based on the selected rating, follow these steps:</p>
<h4 id="heading-1-define-total-ratings">1. Define Total Ratings:</h4>
<p>Set the total number of stars you want to display:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> TOTAL_RATINGS = <span class="hljs-number">5</span>;
</code></pre>
<h4 id="heading-2-import-usestate">2. Import useState:</h4>
<p>Import the <code>useState</code> hook from React:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> React, { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
</code></pre>
<h4 id="heading-3-add-rating-state">3. Add Rating State:</h4>
<p>Define a state variable to store the current rating:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> [rating, setRating] = useState(<span class="hljs-number">0</span>);
</code></pre>
<h4 id="heading-4-replace-icon-rendering">4. Replace Icon Rendering:</h4>
<p>Update the JSX code to dynamically render filled and unfilled stars based on the rating state:</p>
<pre><code class="lang-typescript">{<span class="hljs-built_in">Array</span>.from({ length: rating || <span class="hljs-number">0</span> }, <span class="hljs-function">(<span class="hljs-params">_, idx</span>) =&gt;</span> (
  &lt;FaStar key={idx} className=<span class="hljs-string">"h-8 w-auto text-white cursor-pointer"</span> /&gt;
))}

{<span class="hljs-built_in">Array</span>.from({ length: TOTAL_RATINGS — (rating || <span class="hljs-number">0</span>) }, <span class="hljs-function">(<span class="hljs-params">_, idx</span>) =&gt;</span> (
  &lt;FaRegStar key={idx} className=<span class="hljs-string">"h-8 w-auto text-white cursor-pointer"</span> /&gt;
))}
</code></pre>
<p><img src="https://cdn-images-1.medium.com/max/1600/1*5Qy0ZJwJLs3WDRsL5O9bVw.png" alt /></p>
<blockquote>
<p>Now, when you run the project and view it in your browser, you should see <code>TOTAL_RATINGS</code> (in my case 5) unfilled stars.</p>
</blockquote>
<hr />
<h3 id="heading-adding-rating-functionality">Adding Rating Functionality</h3>
<p>To add functionality to increase and decrease the rating when a star is clicked, follow these steps:</p>
<h4 id="heading-1-add-handle-rating-function">1. Add Handle Rating Function:</h4>
<p>Define a function that handles the rating change based on the type of action (increase or decrease) and the index of the star clicked:</p>
<pre><code class="lang-typescript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handleRating</span>(<span class="hljs-params"><span class="hljs-keyword">type</span>: <span class="hljs-built_in">string</span>, idx: <span class="hljs-built_in">number</span></span>) </span>{
  <span class="hljs-keyword">let</span> count = <span class="hljs-number">0</span>;

  <span class="hljs-keyword">if</span> (<span class="hljs-keyword">type</span> === <span class="hljs-string">"increase"</span>) {
    count = rating + idx + <span class="hljs-number">1</span>;
  } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-keyword">type</span> === <span class="hljs-string">"decrease"</span>) {
    count = idx + <span class="hljs-number">1</span>;
  }

  setRating(count);
 }
</code></pre>
<h4 id="heading-2-update-star-rendering">2. Update Star Rendering:</h4>
<p>Update the JSX code to call the <code>handleRating</code> function when a star is clicked, passing the appropriate type and index:</p>
<pre><code class="lang-typescript">{<span class="hljs-built_in">Array</span>.from({ length: rating || <span class="hljs-number">0</span> }, <span class="hljs-function">(<span class="hljs-params">_, idx</span>) =&gt;</span> (
  &lt;FaStar
    key={idx}
    onClick={<span class="hljs-function">() =&gt;</span> handleRating(<span class="hljs-string">"decrease"</span>, idx)}
    className=<span class="hljs-string">"h-8 w-auto text-white cursor-pointer"</span>
  /&gt;
))}

{<span class="hljs-built_in">Array</span>.from({ length: TOTAL_RATINGS — (rating || <span class="hljs-number">0</span>) }, <span class="hljs-function">(<span class="hljs-params">_, idx</span>) =&gt;</span> (
  &lt;FaRegStar
    key={idx}
    onClick={<span class="hljs-function">() =&gt;</span> handleRating(<span class="hljs-string">"increase"</span>, idx)}
    className=<span class="hljs-string">"h-8 w-auto text-white cursor-pointer"</span>
  /&gt;
))}
</code></pre>
<p><img src="https://cdn-images-1.medium.com/max/1600/1*CtHhvaVBZ5CtRXvDFHhKkA.png" alt /></p>
<blockquote>
<p>Now, when you click on a star, the rating should increase or decrease accordingly. This completes the basic functionality of your rating system.</p>
</blockquote>
<hr />
<h3 id="heading-code">Code</h3>
<h4 id="heading-final-apptsx">Final App.tsx</h4>
<p>Your final <code>App.tsx</code> file should look like this:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> React, { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { FaRegStar, FaStar } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-icons/fa"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"./App.css"</span>;

<span class="hljs-keyword">const</span> TOTAL_RATINGS = <span class="hljs-number">5</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> [rating, setRating] = useState(<span class="hljs-number">0</span>);

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handleRating</span>(<span class="hljs-params"><span class="hljs-keyword">type</span>: <span class="hljs-built_in">string</span>, idx: <span class="hljs-built_in">number</span></span>) </span>{
  <span class="hljs-keyword">let</span> count = <span class="hljs-number">0</span>;

  <span class="hljs-keyword">if</span> (<span class="hljs-keyword">type</span> === <span class="hljs-string">"increase"</span>) {
    count = rating + idx + <span class="hljs-number">1</span>;
  } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-keyword">type</span> === <span class="hljs-string">"decrease"</span>) {
    count = idx + <span class="hljs-number">1</span>;
  }

  setRating(count);
}

<span class="hljs-keyword">return</span> (
  &lt;div className=<span class="hljs-string">"flex items-center gap-x-4"</span>&gt;
    {<span class="hljs-built_in">Array</span>.from({ length: rating || <span class="hljs-number">0</span> }, <span class="hljs-function">(<span class="hljs-params">_, idx</span>) =&gt;</span> (
      &lt;FaStar
        key={idx}
        onClick={<span class="hljs-function">() =&gt;</span> handleRating(<span class="hljs-string">"decrease"</span>, idx)}
        className=<span class="hljs-string">"h-8 w-auto text-white cursor-pointer"</span>
      /&gt;
    ))}

    {<span class="hljs-built_in">Array</span>.from({ length: TOTAL_RATINGS - (rating || <span class="hljs-number">0</span>) }, <span class="hljs-function">(<span class="hljs-params">_, idx</span>) =&gt;</span> (
      &lt;FaRegStar
        key={idx}
        onClick={<span class="hljs-function">() =&gt;</span> handleRating(<span class="hljs-string">"increase"</span>, idx)}
        className=<span class="hljs-string">"h-8 w-auto text-white cursor-pointer"</span>
      /&gt;
    ))}
  &lt;/div&gt;
 );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<blockquote>
<p>This code defines a React component <code>App</code> that renders a rating system with filled and unfilled stars. Clicking on a star increases or decreases the rating accordingly.</p>
</blockquote>
<hr />
<h3 id="heading-conclusion">Conclusion</h3>
<p>In this tutorial, we’ve learned how to create a simple yet powerful rating system using React. By utilizing state management with the <code>useState</code> hook, we were able to dynamically render filled and unfilled stars based on the user’s selection.</p>
<p>Through the <code>handleRating</code> function, we implemented the logic to increase or decrease the rating when a star is clicked. This functionality provides a user-friendly way for visitors to rate products, services, or content on your website.</p>
<p>Additionally, by combining <code>Tailwind CSS</code> for styling and <code>React Icons</code> for easily integrating icons, we achieved a visually appealing and functional rating system.</p>
<p>Feel free to further customize this rating system by adding features such as half-star ratings, average rating calculation, or saving ratings to a backend server. The possibilities are endless, and we hope this tutorial serves as a solid foundation for your future projects.</p>
<p>Thank you for following along, and happy coding!</p>
]]></content:encoded></item></channel></rss>