<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Chidera Humphrey - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Chidera Humphrey - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Sun, 24 May 2026 22:23:52 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/author/chiderahumphrey/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Custom PDF Text Extractor with Node.js and TypeScript ]]>
                </title>
                <description>
                    <![CDATA[ Extracting text from PDFs sounds simple until you try to do it. And it can be even more challenging for JavaScript developers, with various libraries to choose from and so on. I encountered this problem while I was building my SaaS app. I scoured thr... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/build-a-custom-pdf-text-extractor-with-nodejs-and-typescript/</link>
                <guid isPermaLink="false">698e11a1f5e2fbcb44cccc0a</guid>
                
                    <category>
                        <![CDATA[ Node.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ TypeScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Chidera Humphrey ]]>
                </dc:creator>
                <pubDate>Thu, 12 Feb 2026 17:45:05 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1770918274288/fea825aa-9dc4-468b-abbf-04fb1e74ec22.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Extracting text from PDFs sounds simple until you try to do it. And it can be even more challenging for JavaScript developers, with various libraries to choose from and so on.</p>
<p>I encountered this problem while I was building my SaaS app. I scoured through StackOverflow, Reddit, and Quora, but didn't find a satisfying answer. Some solutions were impractical, while others needed complex configuration.</p>
<p>After going through the struggle, I said, “You know what? Screw it. Let me build my own little PDF parser”. With the help of Claude and Node.js, I built a custom PDF parser for my SaaS app.</p>
<p>In this tutorial, I’ll show you how I built my custom PDF parser using Node.js and how you can do the same.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-why-build-a-custom-pdf-text-extractor">Why Build a Custom PDF Text Extractor?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-sample-of-what-well-be-building">Sample of What We’ll Be Building</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-setting-up-the-project">Setting Up the Project</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-core-implementation-building-the-extractor">Core Implementation: Building the Extractor</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-adding-page-specific-extraction">Adding Page-Specific Extraction</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-adding-a-lightweight-metadata-only-endpoint">Adding a Lightweight Metadata-Only Endpoint</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-adding-searchfind-functionality">Adding Search/Find Functionality</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-creating-the-searchfind-function">Creating the Search/Find Function</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-handling-edge-cases-and-best-practices">Handling Edge Cases and Best Practices</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-best-practices">Best Practices</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-unit-testing-your-pdf-parser">Unit Testing Your PDF Parser</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-deploying-your-pdf-parser-api">Deploying Your PDF Parser API</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-next-steps-integrate-into-your-saas">Next Steps: Integrate Into Your SaaS</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-resources">Resources</a></p>
</li>
</ul>
<h2 id="heading-why-build-a-custom-pdf-text-extractor">Why Build a Custom PDF Text Extractor?</h2>
<p>You might ask yourself: "Why build a custom PDF parser when libraries already exist?"</p>
<p>Popular JavaScript PDF parsers have various trade-offs. Here's a quick comparison of common options:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Library</td><td>Text Extraction</td><td>TypeScript Support</td><td>Dependencies</td><td>Layout/Table Support</td><td>Best For</td></tr>
</thead>
<tbody>
<tr>
<td><strong>pdf-parse</strong></td><td>Basic only</td><td>Partial</td><td>None</td><td>Poor</td><td>Quick, simple text extraction</td></tr>
<tr>
<td><strong>pdfjs-dist</strong></td><td>Advanced</td><td>Full</td><td>None</td><td>Moderate</td><td>Custom parsing &amp; rendering</td></tr>
<tr>
<td><strong>pdf2json</strong></td><td>JSON output</td><td>Partial</td><td>None</td><td>Good for structure</td><td>Exporting structured data</td></tr>
<tr>
<td><strong>pdf-text-extract</strong></td><td>Text only</td><td>None</td><td>Requires Poppler</td><td>Basic</td><td>CLI or simple scripts</td></tr>
</tbody>
</table>
</div><p>These libraries work well for specific use cases, but building your own parser still has advantages:</p>
<ul>
<li><p>You choose the tech stack that fits your application</p>
</li>
<li><p>You add only the features your project needs</p>
</li>
</ul>
<p>And the good news is, you can build a JavaScript-native parser for your project's needs without having to rely on external dependencies or adapt libraries built for different ecosystems.</p>
<p>A custom parser gives you full control without the bloat of unnecessary functionality.</p>
<h2 id="heading-sample-of-what-well-be-building">Sample of What We’ll Be Building</h2>
<p>Here’s a screen recording of our text extractor in action:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770821775518/59ccda88-6913-4084-8d73-2141852530f7.gif" alt="Working demo of the PDF extractor" class="image--center mx-auto" width="400" height="186" loading="lazy"></p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>To follow along with this tutorial, I assume:</p>
<ul>
<li><p>You have Node.js installed on your machine. If you don’t have Node.js installed, you can install it from the <a target="_blank" href="https://nodejs.org/en/download">official Node.js website</a>.</p>
</li>
<li><p>You know how to write basic TypeScript code.</p>
</li>
</ul>
<h2 id="heading-setting-up-the-project">Setting Up the Project</h2>
<p>In this section, you’ll set up your project. This project uses TypeScript in Node.js rather than JavaScript.</p>
<p>Don’t worry if you don’t know how to configure TypeScript for Node.js. I’ll show you how to do it in this section.</p>
<h3 id="heading-initializing-a-nodejs-app">Initializing a Node.js app</h3>
<p>Open the folder where you want your project to live, and create a Node.js project:</p>
<pre><code class="lang-bash">npm init -y
</code></pre>
<p>Install the necessary packages:</p>
<pre><code class="lang-bash">npm install cors express-fileupload pdf-parse
</code></pre>
<ul>
<li><p><code>cors</code>: Enables Cross-Origin Resource Sharing, allowing your API to accept requests from different domains or ports.</p>
</li>
<li><p><code>express-fileupload</code>: Middleware for handling file uploads in Express, making it easy to process uploaded PDFs.</p>
</li>
<li><p><code>pdf-parse</code>: A lightweight PDF parsing library for extracting text and metadata from PDF files.</p>
</li>
<li><p><code>express</code>: The web framework for Node.js that handles routing, middleware, and server setup.</p>
</li>
</ul>
<p>Now let’s continue with our installs:</p>
<pre><code class="lang-bash">npm install -D typescript ts-node @types/node @types/express nodemon prettier dotenv @types/cors @types/express-fileupload
</code></pre>
<p>The <code>-D</code> flag directs <code>npm</code> to install these libraries as development dependencies.</p>
<ul>
<li><p><code>ts-node</code>: Lets you run TypeScript code directly in Node.js without compiling to JavaScript first</p>
</li>
<li><p><code>@types/node</code>: Adds TypeScript type definitions for Node.js core modules like <code>fs</code>, <code>path</code>, and <code>http</code></p>
</li>
<li><p><code>@types/express</code>: Provides TypeScript type definitions for the Express.js framework and its middleware</p>
</li>
<li><p><code>nodemon</code>: Automatically restarts your development server whenever you save changes to your code</p>
</li>
<li><p><code>prettier</code>: A code formatter that ensures consistent style and readability across your entire project</p>
</li>
</ul>
<h3 id="heading-configuring-typescript-in-the-nodejs-app">Configuring TypeScript in the Node.js app</h3>
<p>Let’s start by generating a <code>tsconfig.json</code> file:</p>
<pre><code class="lang-bash">npx tsc --init
</code></pre>
<p>TypeScript projects use the <code>tsconfig.json</code> file to manage the project’s settings. The configuration file is located in the root of your project.</p>
<p>After running the command, you should see a <code>tsconfig.json</code> file that looks like this:</p>
<pre><code class="lang-json">{
  <span class="hljs-comment">// Visit https://aka.ms/tsconfig to read more about this file</span>
  <span class="hljs-attr">"compilerOptions"</span>: {
    <span class="hljs-comment">// File Layout</span>
    <span class="hljs-comment">// "rootDir": "./src",</span>
    <span class="hljs-comment">// "outDir": "./dist",</span>

    <span class="hljs-comment">// Environment Settings</span>
    <span class="hljs-comment">// See also https://aka.ms/tsconfig/module</span>
    <span class="hljs-attr">"module"</span>: <span class="hljs-string">"nodenext"</span>,
    <span class="hljs-attr">"target"</span>: <span class="hljs-string">"esnext"</span>,
    <span class="hljs-attr">"types"</span>: [],
    <span class="hljs-comment">// For nodejs:</span>
    <span class="hljs-comment">// "lib": ["esnext"],</span>
    <span class="hljs-comment">// "types": ["node"],</span>
    <span class="hljs-comment">// and npm install -D @types/node</span>

    <span class="hljs-comment">// Other Outputs</span>
    <span class="hljs-attr">"sourceMap"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">"declaration"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">"declarationMap"</span>: <span class="hljs-literal">true</span>,

    <span class="hljs-comment">// Stricter Typechecking Options</span>
    <span class="hljs-attr">"noUncheckedIndexedAccess"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">"exactOptionalPropertyTypes"</span>: <span class="hljs-literal">true</span>,

    <span class="hljs-comment">// Style Options</span>
    <span class="hljs-comment">// "noImplicitReturns": true,</span>
    <span class="hljs-comment">// "noImplicitOverride": true,</span>
    <span class="hljs-comment">// "noUnusedLocals": true,</span>
    <span class="hljs-comment">// "noUnusedParameters": true,</span>
    <span class="hljs-comment">// "noFallthroughCasesInSwitch": true,</span>
    <span class="hljs-comment">// "noPropertyAccessFromIndexSignature": true,</span>

    <span class="hljs-comment">// Recommended Options</span>
    <span class="hljs-attr">"strict"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">"jsx"</span>: <span class="hljs-string">"react-jsx"</span>,
    <span class="hljs-attr">"verbatimModuleSyntax"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">"isolatedModules"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">"noUncheckedSideEffectImports"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">"moduleDetection"</span>: <span class="hljs-string">"force"</span>,
    <span class="hljs-attr">"skipLibCheck"</span>: <span class="hljs-literal">true</span>,
  }
}
</code></pre>
<p>Add <code>"node"</code> to the <code>types</code> array like this:</p>
<pre><code class="lang-json"><span class="hljs-string">"types"</span>: [<span class="hljs-string">"node"</span>]
</code></pre>
<p>Then modify your <code>package.json</code> file with the following code:</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"main"</span>: <span class="hljs-string">"index.ts"</span>,
    <span class="hljs-attr">"scripts"</span>: {
    <span class="hljs-attr">"test"</span>: <span class="hljs-string">"echo \"Error: no test specified\" &amp;&amp; exit 1"</span>,
    <span class="hljs-attr">"dev"</span>: <span class="hljs-string">"nodemon --watch src --ext ts,json --exec \"node --loader ts-node/esm src/server.ts\""</span>,
    <span class="hljs-attr">"build"</span>: <span class="hljs-string">"tsc"</span>,
    <span class="hljs-attr">"start"</span>: <span class="hljs-string">"node src/server.js"</span>
  },
    <span class="hljs-attr">"type"</span>: <span class="hljs-string">"module"</span>
}
</code></pre>
<p>This ensures that the entry point of your app is a TypeScript file, and you can use <code>import</code> statement instead of <code>require</code>.</p>
<p>In the next section, you’re going to build the PDF parser.</p>
<h2 id="heading-core-implementation-building-the-extractor">Core Implementation: Building the Extractor</h2>
<p>After configuring your Node.js app, the next step is to build the PDF parser.</p>
<p>Create a new directory in your Node.js app, and create a <code>server.ts</code> file.</p>
<p>Now import the necessary packages for building the PDF parser:</p>
<pre><code class="lang-tsx">import express, { type Request, type Response } from "express";
import fileUpload, { type UploadedFile } from "express-fileupload";
import { PDFParse } from "pdf-parse";
import cors from "cors";

const app = express();
const PORT = process.env.PORT || 8080;
</code></pre>
<p>Let’s understand what’s happening:</p>
<ul>
<li><p><code>fileUpload</code> is the module for uploading files in an Express app. The <code>UploadedFile</code> type is a TypeScript type for the uploaded file.</p>
</li>
<li><p><code>PDFParse</code> is the core parsing module. It provides the basic functionality of parsing PDF files.</p>
</li>
<li><p><code>cors</code> is the module for protecting the app from origins not specified.</p>
</li>
<li><p>You created an Express app with the following line: <code>const app = express();</code>.</p>
</li>
<li><p><code>PORT</code> is the port you want your app to be hosted on.</p>
</li>
</ul>
<h3 id="heading-configuring-cors-middleware">Configuring CORS Middleware</h3>
<p>Setting up CORS allows requests from specified origins. This protects your app from attacks.</p>
<pre><code class="lang-tsx">app.use(
  cors({
    origin: ["http://localhost:3000", "https://yourwebsite.com"],
  })
);
</code></pre>
<h3 id="heading-implementing-file-upload-middleware">Implementing File Upload Middleware</h3>
<p>To handle file uploads in your API, you’ll use the <code>express-fileupload</code> middleware. This middleware intercepts incoming file uploads and makes them accessible through <code>req.files</code>.</p>
<p>You can run checks on the incoming file, such as file size and number of files.</p>
<pre><code class="lang-tsx">import fileUpload, { type UploadedFile } from "express-fileupload";

app.use(
  fileUpload({
    limits: { fileSize: 50 * 1024 * 1024 }, // 50 MB limit
    abortOnLimit: true,
  })
);
</code></pre>
<p>Key options:</p>
<ul>
<li><p><code>fileSize</code>: Sets the maximum file size allowed (50 MB in this case)</p>
</li>
<li><p><code>abortOnLimit</code>: When <code>true</code>, automatically rejects uploads that exceed the size limit and prevents further processing</p>
</li>
</ul>
<p>Here’s why this is important:</p>
<ul>
<li><p><strong>Security</strong>: Limits prevent server overload from massive files.</p>
</li>
<li><p><strong>Performance</strong>: Automatically rejects oversized PDFs before processing.</p>
</li>
<li><p><strong>User Experience</strong>: Gives clear error messages for files that are too large.</p>
</li>
</ul>
<h3 id="heading-creating-the-parser-logic">Creating the Parser Logic</h3>
<p>The parser logic is the core function that parses the PDFs. It’s an asynchronous function that extracts text content and metadata from a PDF buffer.</p>
<pre><code class="lang-tsx">async function parsePDF(file: Uint8Array) {
  const parser = new PDFParse(file);
  const data = await parser.getText();
  const info = await parser.getInfo({ parsePageInfo: true });
  return { text: data?.text || "", info, numpages: info?.pages || 0 };
}
</code></pre>
<p>Let’s understand what’s happening in the code:</p>
<ul>
<li><p>The function accepts a <code>Uint8Array</code> buffer containing the raw PDF file data.</p>
</li>
<li><p>You initialized a new <code>PDFParse</code> object with the PDF buffer.</p>
</li>
<li><p>You called <code>getText()</code> to extract all text content from the PDF.</p>
</li>
<li><p>You called <code>getInfo()</code> with <code>parsePageInfo: true</code> to retrieve document information, including page count.</p>
</li>
<li><p>You returned an object containing:</p>
<ul>
<li><p><code>text</code>: The extracted text content (or empty string if none found)</p>
</li>
<li><p><code>info</code>: Document metadata (author, title, creation date, and so on)</p>
</li>
<li><p><code>numpages</code>: Total number of pages in the PDF</p>
</li>
</ul>
</li>
</ul>
<h4 id="heading-why-is-the-parser-logic-asynchronous">Why is the parser logic asynchronous?</h4>
<p>Both <code>getText()</code> and <code>getInfo()</code> are asynchronous operations. They require time to parse through the PDF document, so <code>await</code> ensures the operations complete before returning results. This prevents blocking your server while processing large PDF files.</p>
<h3 id="heading-creating-the-pdf-upload-and-processing-endpoint">Creating the PDF Upload and Processing Endpoint</h3>
<p>Now that you have the core <code>parsePDF()</code> function, you need an endpoint that accepts file uploads and processes them using this function.</p>
<pre><code class="lang-tsx">app.post("/upload", async (req: Request, res: Response) =&gt; {
  try {
    if (!req.files || !("file" in req.files)) {
      return res.status(400).json({
        error: "No PDF file shared.",
        body: `Body is ${JSON.stringify(req.body)}`,
      });
    }

    const pdfFile = req.files.file as UploadedFile;
    const unit8ArrayData = new Uint8Array(pdfFile?.data);
    const result = await parsePDF(unit8ArrayData);
    console.log("PDF parsed successfully: ", result);
    res.json({ result, success: true });
  } catch (error) {
    console.error("Error processing PDF:", error);
    if (error instanceof Error) {
      return res.status(500).json({ error: error.message, success: false });
    }
    res.status(500).json({
      error: "Failed to process PDF due to an unknown error.",
      success: false,
    });
  }
});
</code></pre>
<p>Let's break down what's happening in this code:</p>
<ul>
<li><p>You defined a POST route handler at <code>/upload</code> that processes PDF file uploads. The handler uses <code>req.files</code> to access uploaded files and validate that a "file" field exists in the request.</p>
</li>
<li><p>The handler extracts the uploaded PDF file and converts it to a <code>Uint8Array</code> buffer, which is the required format for the <code>parsePDF()</code> function that performs the actual PDF parsing.</p>
</li>
<li><p>You implemented comprehensive error handling with a try-catch block that:</p>
<ul>
<li><p>Logs errors to the console for debugging purposes</p>
</li>
<li><p>Returns specific error messages when the error is an instance of the <code>Error</code> class</p>
</li>
<li><p>Provides a generic error response for unexpected failures while maintaining the <code>success: false</code> flag for consistent client responses</p>
</li>
</ul>
</li>
</ul>
<p>This route handler creates a PDF processing endpoint that validates inputs, processes files efficiently, and provides clear error feedback.</p>
<h3 id="heading-starting-your-server">Starting Your Server</h3>
<p>The last step is to start your Express server and confirm it’s running correctly.</p>
<pre><code class="lang-tsx">const PORT = process.env.PORT || 8080;

app.listen(PORT, () =&gt; {
  console.log(`🚀 Server is running on http://localhost:${PORT}`);
});
</code></pre>
<ul>
<li><p><code>app.listen()</code>: Binds the Express server to the specified PORT and starts listening for incoming requests.</p>
</li>
<li><p>PORT configuration: The server uses the <code>PORT</code> environment variable if set, otherwise defaults to <code>8080</code>.</p>
</li>
<li><p>Callback function: Once the server starts, the callback logs a message to the console with the server URL.</p>
</li>
</ul>
<p>Use the following command to start your server:</p>
<pre><code class="lang-bash">npm run dev
</code></pre>
<p>When the server starts successfully, you'll see the message below in your console:</p>
<pre><code class="lang-bash">🚀 Server is running on http://localhost:8080
</code></pre>
<p>Your PDF parser API is now ready to accept file uploads and process PDFs.</p>
<p>You can verify that your PDF parser is working by visiting the URL using Postman or any API client of your choice.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770821817467/2afa85b4-2042-411a-abd2-410f9cab349f.gif" alt="Working demo of the custom PDF extractor" class="image--center mx-auto" width="400" height="186" loading="lazy"></p>
<p>Congratulations! You’ve built a custom PDF parser.</p>
<p>This PDF parser is sufficient for simple parsing tasks. But you can extend the functionality of the parser to make it more robust.</p>
<p>In the next sections, you’ll add extra features, such as handling corrupt files.</p>
<h2 id="heading-adding-page-specific-extraction">Adding Page-Specific Extraction</h2>
<p>When working with large PDF documents, extracting the entire file can be inefficient and unnecessary. This feature allows users to specify a page range and extract text only from those pages. This makes your parser more flexible and performant for real-world use cases.</p>
<p>For example, a user might want to extract only pages 5-10 from a 100-page report. By adding optional query parameters <code>startPage</code> and <code>endPage</code> to your endpoint, you give users fine-grained control over which portions of a PDF they want parsed.</p>
<p>In this section, you’ll create a page-specific extraction function and an endpoint to handle parameters from request queries.</p>
<h3 id="heading-creating-the-page-specific-extraction-function">Creating the Page-specific extraction function</h3>
<p>The page-specific extraction function is the core function that parses the specified pages.</p>
<pre><code class="lang-tsx">// function to extract text from a range of pages
async function parsePageRangeFromPDF(
  file: Uint8Array,
  startPage: number,
  endPage: number,
) {
  const parser = new PDFParse(file);
  const info = await parser.getInfo({ parsePageInfo: true });
  const totalPages = Array.isArray(info?.pages)
    ? info.pages.length
    : (info?.pages as number) || 0;

  if (startPage &lt; 1 || endPage &gt; totalPages || startPage &gt; endPage) {
    throw new Error(
      `Invalid page range. PDF has ${totalPages} pages. Please provide a valid range where start &gt;= 1, end &lt;= ${totalPages}, and start &lt;= end.`,
    );
  }

  const data = await parser.getText();
  const lines = data?.text?.split("\n") || [];

  // Note: pdf-parse doesn't provide direct page filtering, so getText() returns all text
  // For accurate page range extraction, consider using a different PDF library
  return { text: data?.text || "", startPage, endPage, totalPages };
}
</code></pre>
<p>Let's break down what's happening in this code:</p>
<ol>
<li><p>You defined an <code>async</code> function <code>parsePageRangeFromPDF</code> that extracts text from a specific range of pages within a PDF document. The function accepts a <code>Uint8Array</code> PDF file and two numeric parameters for the start and end page range.</p>
</li>
<li><p>The function uses the <code>PDFParse</code> library to analyze the PDF structure, first extracting metadata, including the total page count, using <code>parser.getInfo()</code>. It then validates that the requested page range falls within the actual PDF boundaries.</p>
</li>
<li><p>After successful validation, the function extracts all text from the PDF using <code>parser.getText()</code> and splits it into individual lines. The function returns an object containing the extracted text along with metadata about the requested range and total pages.</p>
</li>
</ol>
<p>This function creates a reusable utility for extracting specific page ranges from PDFs with proper validation and error handling.</p>
<h3 id="heading-creating-the-page-specific-extraction-endpoint">Creating the Page-Specific Extraction Endpoint</h3>
<p>Now that you’ve created the function for parsing specified pages of a PDF, you’ll create the endpoint for accepting uploads and parsing specified pages.</p>
<pre><code class="lang-tsx">// Page range PDF text extraction endpoint
app.post("/upload-page-range", async (req: Request, res: Response) =&gt; {
  try {
    if (!req.files || !("file" in req.files)) {
      return res.status(400).json({
        error: "No PDF file shared.",
      });
    }

    // Get page range from query params or body
    const startPage = parseInt(
      (req.query.startPage as string) || (req.body?.startPage as string) || "1"
    );
    const endPage = parseInt(
      (req.query.endPage as string) || (req.body?.endPage as string) || "1"
    );

    if (isNaN(startPage) || isNaN(endPage)) {
      return res.status(400).json({
        error:
          "Invalid page range. Please provide valid integers for startPage and endPage.",
      });
    }

    const pdfFile = req.files.file as UploadedFile;
    const unit8ArrayData = new Uint8Array(pdfFile?.data);
    const result = await parsePageRangeFromPDF(
      unit8ArrayData,
      startPage,
      endPage
    );
    console.log(
      `Pages ${startPage}-${endPage} extracted successfully: `,
      result
    );
    res.json({ result, success: true });
  } catch (error) {
    console.error("Error processing PDF: ", error);
    if (error instanceof Error) {
      return res.status(400).json({ error: error.message, success: false });
    }
    res.status(500).json({
      error: "Failed to process PDF due to an unknown error.",
      success: false,
    });
  }
});
</code></pre>
<p>Let's break down what's happening in this code:</p>
<ol>
<li><p>You defined a POST route handler at <code>/upload-page-range</code> that extracts text from a specific page range within uploaded PDF files. The handler first validates that a PDF file exists in the request using <code>req.files</code>, returning a 400 error if no file is provided.</p>
</li>
<li><p>The function extracts the <code>startPage</code> and <code>endPage</code> parameters from either query parameters or request body, providing default values of "1" if neither is specified. It then validates that both values are valid integers using <code>isNaN()</code> checks. This ensures robust input handling for page range requests.</p>
</li>
<li><p>Once the PDF is converted to a buffer, it's passed to <code>parsePageRangeFromPDF()</code> to extract the requested page range. The API responds with the extracted text and range details, while errors are clearly categorized: validation issues return 400, server problems return 500.</p>
</li>
</ol>
<p>This endpoint creates a specialized PDF processing route that allows clients to extract text from specific page ranges rather than entire documents.</p>
<p>Now, you can extract from specific pages using request parameters:</p>
<pre><code class="lang-bash">curl -F <span class="hljs-string">"file=@yourfile.pdf"</span> <span class="hljs-string">"http://localhost:8080/upload-page-range?startPage=5&amp;endPage=7"</span>
</code></pre>
<p>or using request body:</p>
<pre><code class="lang-bash">curl -X POST -F <span class="hljs-string">"file=@yourfile.pdf"</span> \
  -F <span class="hljs-string">"startPage=5"</span> \
  -F <span class="hljs-string">"endPage=7"</span> \
  http://localhost:8080/upload-page-range
</code></pre>
<p>In the next section, you’ll add an endpoint for getting only the metadata of an uploaded file.</p>
<h2 id="heading-adding-a-lightweight-metadata-only-endpoint">Adding a Lightweight Metadata-Only Endpoint</h2>
<p>Creating a lightweight metadata-only endpoint allows your users to quickly validate and inspect PDFs without fully processing the document.</p>
<p>This is useful for previewing document info before processing.</p>
<h3 id="heading-creating-the-metadata-extraction-function">Creating the Metadata Extraction Function</h3>
<p>Add a new function that retrieves only document information:</p>
<pre><code class="lang-tsx">async function getPDFMetadata(file: Uint8Array) {
  const parser = new PDFParse(file);
  const info = await parser.getInfo({ parsePageInfo: true });
  return {
    title: info?.info?.Title || "N/A",
    author: info?.info?.Author || "N/A",
    subject: info?.info?.Subject || "N/A",
    creator: info?.info?.Creator || "N/A",
    producer: info?.info?.Producer || "N/A",
    creationDate: convertPDFDateToReadable(info?.info?.CreationDate || "N/A"),
    modificationDate: convertPDFDateToReadable(info?.info?.ModDate || "N/A"),
    pages: info?.total || 0,
  };
}
</code></pre>
<p>Let's break down what's happening in this code:</p>
<ol>
<li><p>You defined an <code>async</code> function <code>getPDFMetadata</code> that extracts and processes metadata from PDF documents. The function accepts a <code>Uint8Array</code> PDF file buffer and uses the <code>PDFParse</code> library to retrieve document information.</p>
</li>
<li><p>The function extracts key PDF metadata fields, including title, author, subject, creator, and producer, providing fallback values of "N/A" when these fields are missing. This ensures the function always returns a complete metadata object, even for PDFs with missing information.</p>
</li>
<li><p>You implemented date processing using a <code>convertPDFDateToReadable</code> helper function to transform the PDF's specialized date formats into human-readable strings. The function returns a structured object containing all extracted metadata along with the total page count.</p>
</li>
</ol>
<p>This utility function provides a clean interface for extracting and normalizing PDF metadata. It makes it easy to access document information like authorship, creation dates, and page counts in a standardized format.</p>
<p>Here’s the <code>convertPDFDateToReadable</code> function:</p>
<pre><code class="lang-tsx">function convertPDFDateToReadable(pdfDateString: string): string {
  try {
    // Remove "D:" prefix if present
    let dateStr = pdfDateString.startsWith("D:")
      ? pdfDateString.slice(2)
      : pdfDateString;

    // Extract date and time components (format: YYYYMMDDHHmmss)
    const year = dateStr.substring(0, 4);
    const month = dateStr.substring(4, 6);
    const day = dateStr.substring(6, 8);
    const hour = dateStr.substring(8, 10);
    const minute = dateStr.substring(10, 12);
    const second = dateStr.substring(12, 14);

    // Validate date components
    const monthNum = parseInt(month);
    const dayNum = parseInt(day);

    if (monthNum &lt; 1 || monthNum &gt; 12 || dayNum &lt; 1 || dayNum &gt; 31) {
      throw new Error("Invalid date values");
    }

    // Return in dd/mm/yyyy format
    return `${day}/${month}/${year}`;
  } catch (error) {
    console.error("Error converting PDF date:", error);
    return "Invalid date";
  }
}
</code></pre>
<h3 id="heading-creating-the-metadata-endpoint">Creating the Metadata Endpoint</h3>
<p>Create a POST endpoint that accepts file uploads and returns only metadata:</p>
<pre><code class="lang-tsx">app.post("/metadata", async (req: Request, res: Response) =&gt; {
  try {
    if (!req.files || !("file" in req.files)) {
      return res.status(400).json({
        error: "No PDF file shared.",
      });
    }

    const pdfFile = req.files.file as UploadedFile;
    const unit8ArrayData = new Uint8Array(pdfFile?.data);
    const metadata = await getPDFMetadata(unit8ArrayData);

    console.log("PDF metadata extracted successfully: ", metadata);
    res.json({ metadata, success: true });
  } catch (error) {
    console.error("Error extracting metadata:", error);
    if (error instanceof Error) {
      return res.status(500).json({ error: error.message, success: false });
    }
    res.status(500).json({
      error: "Failed to extract metadata due to an unknown error.",
      success: false,
    });
  }
});
</code></pre>
<p>Let's break down what's happening in this code:</p>
<ol>
<li><p>You defined a POST route handler at <code>/metadata</code> that extracts and returns metadata from uploaded PDF files. The handler begins with validation to ensure a PDF file exists in the request using <code>req.files</code>, returning a 400 error with a clear message if no file is provided.</p>
</li>
<li><p>The function converts the uploaded PDF file to a <code>Uint8Array</code> buffer format, which is required by the <code>getPDFMetadata()</code> utility function you created earlier. This conversion ensures the PDF data is in the proper format for the PDF parsing library to process.</p>
</li>
<li><p>After successfully extracting metadata, the route logs the results and returns them in a structured JSON response. The comprehensive error handling catches any issues during processing and returns appropriate 500 errors with descriptive messages while maintaining the consistent response format.</p>
</li>
</ol>
<p>This endpoint provides a dedicated API for extracting PDF metadata like title, author, creation dates, and page counts. This provides your users with an easy way to analyze PDF document properties without needing to parse the entire file content.</p>
<p>Now, you can extract only the metadata of uploaded files:</p>
<pre><code class="lang-bash">curl -X POST -F <span class="hljs-string">"file=@document.pdf"</span> http://localhost:8080/metadata
</code></pre>
<p>Your response should look like this:</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"metadata"</span>: {
        <span class="hljs-attr">"title"</span>: <span class="hljs-string">"MSA"</span>,
        <span class="hljs-attr">"author"</span>: <span class="hljs-string">"N/A"</span>,
        <span class="hljs-attr">"subject"</span>: <span class="hljs-string">"N/A"</span>,
        <span class="hljs-attr">"creator"</span>: <span class="hljs-string">"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/144.0.0.0 Safari/537.36"</span>,
        <span class="hljs-attr">"producer"</span>: <span class="hljs-string">"Skia/PDF m144"</span>,
        <span class="hljs-attr">"creationDate"</span>: <span class="hljs-string">"22/01/2026"</span>,
        <span class="hljs-attr">"modificationDate"</span>: <span class="hljs-string">"22/01/2026"</span>,
        <span class="hljs-attr">"pages"</span>: <span class="hljs-number">26</span>
    },
    <span class="hljs-attr">"success"</span>: <span class="hljs-literal">true</span>
}
</code></pre>
<h2 id="heading-adding-searchfind-functionality">Adding Search/Find Functionality</h2>
<p>When working with large PDFs, finding specific information manually can be time-consuming. The search functionality allows users to locate keywords within a PDF and get immediate results showing which pages contain the keyword and how many times it appears.</p>
<p>This is especially valuable for research, compliance, or document analysis tasks.</p>
<p>For example, a user may wish to find all instances of "invoice" in a 50-page financial report, or locate "clause 3.2" in a legal document. By adding a dedicated search endpoint that accepts a PDF file and a keyword, you give clients the ability to quickly navigate large documents without reading through every page.</p>
<p>In this section, you'll create a search function that finds keywords within PDF text and an endpoint that accepts file uploads along with search queries.</p>
<h3 id="heading-creating-the-searchfind-function">Creating the Search/Find Function</h3>
<p>The search function is the core utility that finds keywords within PDF documents and returns detailed results about their locations.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">searchPDFText</span>(<span class="hljs-params">
  file: <span class="hljs-built_in">Uint8Array</span>,
  searchQuery: <span class="hljs-built_in">string</span>,
  caseSensitive: <span class="hljs-built_in">boolean</span> = <span class="hljs-literal">false</span>
</span>) </span>{
  <span class="hljs-keyword">const</span> parser = <span class="hljs-keyword">new</span> PDFParse(file);
  <span class="hljs-keyword">const</span> info = <span class="hljs-keyword">await</span> parser.getInfo({ parsePageInfo: <span class="hljs-literal">true</span> });
  <span class="hljs-keyword">const</span> totalPages = <span class="hljs-built_in">Array</span>.isArray(info?.pages)
    ? info.pages.length
    : (info?.pages <span class="hljs-keyword">as</span> <span class="hljs-built_in">number</span>) || <span class="hljs-number">0</span>;

  <span class="hljs-keyword">const</span> results = {
    query: searchQuery,
    caseSensitive,
    matchCount: <span class="hljs-number">0</span>,
    matches: [] <span class="hljs-keyword">as</span> <span class="hljs-built_in">Array</span>&lt;{
      page: <span class="hljs-built_in">number</span>;
      text: <span class="hljs-built_in">string</span>;
      position: <span class="hljs-built_in">number</span>;
    }&gt;,
  };

  <span class="hljs-comment">// Extract text from all pages</span>
  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> page = <span class="hljs-number">1</span>; page &lt;= totalPages; page++) {
    <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> parser.getText();
    <span class="hljs-keyword">const</span> pageText = data?.text || <span class="hljs-string">""</span>;

    <span class="hljs-comment">// Determine search text based on case sensitivity</span>
    <span class="hljs-keyword">const</span> searchText = caseSensitive ? searchQuery : searchQuery.toLowerCase();
    <span class="hljs-keyword">const</span> compareText = caseSensitive ? pageText : pageText.toLowerCase();

    <span class="hljs-keyword">let</span> searchIndex = <span class="hljs-number">0</span>;
    <span class="hljs-keyword">while</span> ((searchIndex = compareText.indexOf(searchText, searchIndex)) !== <span class="hljs-number">-1</span>) {
      <span class="hljs-comment">// Extract context (100 characters before and after)</span>
      <span class="hljs-keyword">const</span> startContext = <span class="hljs-built_in">Math</span>.max(<span class="hljs-number">0</span>, searchIndex - <span class="hljs-number">50</span>);
      <span class="hljs-keyword">const</span> endContext = <span class="hljs-built_in">Math</span>.min(pageText.length, searchIndex + searchQuery.length + <span class="hljs-number">50</span>);
      <span class="hljs-keyword">const</span> contextText = pageText.substring(startContext, endContext);

      results.matches.push({
        page,
        text: contextText.trim(),
        position: searchIndex,
      });

      results.matchCount++;
      searchIndex += searchText.length;
    }
  }

  <span class="hljs-keyword">return</span> results;
}
</code></pre>
<p>Let's break down what's happening in this code:</p>
<ol>
<li><p>You defined an <code>async</code> function <code>searchPDFText</code> that performs text search within PDF documents with optional case sensitivity. The function accepts a PDF file buffer, a search query string, and a <code>caseSensitive</code> parameter with a default value of <code>false</code> for more flexible searching.</p>
</li>
<li><p>The function uses the <code>PDFParse</code> library to first extract the total page count from the PDF metadata. It then initializes a results object to track the search query, case sensitivity setting, total match count, and individual matches with their page numbers, context text, and positions.</p>
</li>
<li><p>For each page in the PDF, the function extracts text and performs the search using either case-sensitive or case-insensitive comparison based on the parameter. When matches are found, it captures a 100-character context window around each match (50 characters before and after) and records the page number, position, and contextual text in the results.</p>
</li>
</ol>
<p>This function creates a comprehensive PDF search utility that can locate specific text within documents, while providing contextual snippets for each match. This makes it useful for document analysis and content retrieval applications.</p>
<h3 id="heading-creating-the-searchfind-endpoint">Creating the Search/Find Endpoint</h3>
<p>Now that you've created the search function, you'll create an endpoint that accepts file uploads along with a search query. The endpoint also supports case-sensitive searches.</p>
<pre><code class="lang-typescript">app.post(<span class="hljs-string">"/search"</span>, <span class="hljs-keyword">async</span> (req: Request, res: Response) =&gt; {
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">if</span> (!req.files || !(<span class="hljs-string">"file"</span> <span class="hljs-keyword">in</span> req.files)) {
      <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">400</span>).json({
        error: <span class="hljs-string">"No PDF file shared."</span>,
      });
    }

    <span class="hljs-comment">// Get search query and options</span>
    <span class="hljs-keyword">const</span> query = (req.query.query <span class="hljs-keyword">as</span> <span class="hljs-built_in">string</span>) || (req.body?.query <span class="hljs-keyword">as</span> <span class="hljs-built_in">string</span>);
    <span class="hljs-keyword">const</span> caseSensitive =
      (req.query.caseSensitive <span class="hljs-keyword">as</span> <span class="hljs-built_in">string</span>) === <span class="hljs-string">"true"</span> ||
      req.body?.caseSensitive === <span class="hljs-literal">true</span>;

    <span class="hljs-keyword">if</span> (!query || query.trim() === <span class="hljs-string">""</span>) {
      <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">400</span>).json({
        error: <span class="hljs-string">"Search query is required."</span>,
      });
    }

    <span class="hljs-keyword">const</span> pdfFile = req.files.file <span class="hljs-keyword">as</span> UploadedFile;
    <span class="hljs-keyword">const</span> unit8ArrayData = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Uint8Array</span>(pdfFile?.data);
    <span class="hljs-keyword">const</span> results = <span class="hljs-keyword">await</span> searchPDFText(unit8ArrayData, query, caseSensitive);

    <span class="hljs-keyword">if</span> (results.matchCount === <span class="hljs-number">0</span>) {
      <span class="hljs-keyword">return</span> res.json({
        result: results,
        success: <span class="hljs-literal">true</span>,
        message: <span class="hljs-string">"No matches found."</span>,
      });
    }

    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Found <span class="hljs-subst">${results.matchCount}</span> matches for "<span class="hljs-subst">${query}</span>"`</span>);
    res.json({ result: results, success: <span class="hljs-literal">true</span> });
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Error searching PDF:"</span>, error);
    <span class="hljs-keyword">if</span> (error <span class="hljs-keyword">instanceof</span> <span class="hljs-built_in">Error</span>) {
      <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">400</span>).json({ error: error.message, success: <span class="hljs-literal">false</span> });
    }
    res.status(<span class="hljs-number">500</span>).json({
      error: <span class="hljs-string">"Failed to search PDF due to an unknown error."</span>,
      success: <span class="hljs-literal">false</span>,
    });
  }
});
</code></pre>
<p>Let's break down what's happening in this code:</p>
<ol>
<li><p>You defined a POST route handler at <code>/search</code> that enables full-text search within uploaded PDF documents. The handler begins with validation to ensure that both a PDF file and a search query are provided, returning 400 errors with descriptive messages if either is missing or empty.</p>
</li>
<li><p>The function extracts the search query and <code>caseSensitive</code> option from either query parameters or the request body, with proper type conversion for the boolean flag. It converts the uploaded PDF to a <code>Uint8Array</code> buffer and passes it to your <code>searchPDFText()</code> utility function along with the search parameters.</p>
</li>
<li><p>The handler provides informative responses based on search results: returning a success response with a "No matches found" message when no matches are detected, or returning the full results when matches exist. Error handling differentiates between client errors (400) for invalid inputs and server errors (500) for processing failures.</p>
</li>
</ol>
<p>This endpoint creates a powerful PDF search API that allows clients to locate specific text within documents with configurable case sensitivity, providing contextual matches and comprehensive results for document analysis applications.</p>
<p>Now, you can search for keywords within PDFs using query parameters.</p>
<p>Search for “example” (case-insensitive):</p>
<pre><code class="lang-bash">curl -F <span class="hljs-string">"file=@document.pdf"</span> <span class="hljs-string">"http://localhost:8080/search?query=example"</span>
</code></pre>
<p>Search for “Example” (case-sensitive):</p>
<pre><code class="lang-bash">curl -F <span class="hljs-string">"file=@document.pdf"</span> <span class="hljs-string">"http://localhost:8080/search?query=Example&amp;caseSensitive=true"</span>
</code></pre>
<p>You can use the request body:</p>
<pre><code class="lang-bash">curl -X POST -F <span class="hljs-string">"file=@document.pdf"</span> \
  -F <span class="hljs-string">"query=PDF"</span> \
  -F <span class="hljs-string">"caseSensitive=true"</span> \
  http://localhost:8080/search
</code></pre>
<p>Your response should look like this:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"result"</span>: {
    <span class="hljs-attr">"query"</span>: <span class="hljs-string">"PDF"</span>,
    <span class="hljs-attr">"caseSensitive"</span>: <span class="hljs-literal">false</span>,
    <span class="hljs-attr">"matchCount"</span>: <span class="hljs-number">3</span>,
    <span class="hljs-attr">"matches"</span>: [
      {
        <span class="hljs-attr">"page"</span>: <span class="hljs-number">1</span>,
        <span class="hljs-attr">"text"</span>: <span class="hljs-string">"...This is a PDF document. The PDF format is..."</span>,
        <span class="hljs-attr">"position"</span>: <span class="hljs-number">10</span>
      },
      {
        <span class="hljs-attr">"page"</span>: <span class="hljs-number">2</span>,
        <span class="hljs-attr">"text"</span>: <span class="hljs-string">"...Learn more about PDF standards..."</span>,
        <span class="hljs-attr">"position"</span>: <span class="hljs-number">25</span>
      }
    ]
  },
  <span class="hljs-attr">"success"</span>: <span class="hljs-literal">true</span>
}
</code></pre>
<p>You’ve now added three important features to your PDF parser.</p>
<p>In the next section, we’ll look at handling edge cases.</p>
<h2 id="heading-handling-edge-cases-and-best-practices">Handling Edge Cases and Best Practices</h2>
<p>When building your custom PDF parser, there are some edge cases you should keep in mind if you want to build a more robust and reliable parser.</p>
<p>Below are some edge cases to watch out for:</p>
<h3 id="heading-corrupted-or-malformed-pdfs">Corrupted or Malformed PDFs</h3>
<p>Some users may upload corrupted PDFs – that is, PDFs with invalid structure or corrupted headers. This can cause errors during processing.</p>
<p>You can wrap your parsing operations in a <code>try-catch</code> block to handle the parsing errors gracefully. Also, you’ll want to provide clear error messages that distinguish corrupted files from other errors.</p>
<h3 id="heading-password-protected-pdfs">Password-Protected PDFs</h3>
<p>PDFs can be encrypted with user or owner passwords. This poses a challenge as <code>pdf-parse</code> has limited support for password-protected files.</p>
<p>There are two ways you can solve this problem:</p>
<ul>
<li><p>Implement a mechanism that rejects password-protected files.</p>
</li>
<li><p>Accept a password query you can use to decrypt the files.</p>
</li>
</ul>
<h3 id="heading-scanned-pdfs-image-based">Scanned PDFs (Image-Based)</h3>
<p>PDFs created from scanned documents are images with no extractable text. If you try to parse these documents as is, you’ll get empty or minimal text.</p>
<p>You can implement OCR (Optical Character Recognition) to extract text from scanned PDFs.</p>
<h3 id="heading-special-characters-and-encoding">Special Characters and Encoding</h3>
<p>Your users may upload PDFs that contain special characters, Unicode symbols, or non-Latin scripts. If your extraction function doesn’t support such characters, your users may lose a good chunk of their files.</p>
<p>You’ll want to make sure that your text extraction can handle UTF-8 encoding and various character sets.</p>
<h2 id="heading-best-practices">Best Practices</h2>
<p>Below are some best practices to adopt when building your own custom PDF parser:</p>
<p>1. Validate incoming files before processing:</p>
<pre><code class="lang-tsx">function validatePDFFile(pdfFile: UploadedFile): { valid: boolean; error?: string } {
  // Check MIME type
  if (pdfFile.mimetype !== "application/pdf") {
    return { valid: false, error: "Invalid MIME type. Expected application/pdf" };
  }

  // Check file size (already limited by middleware, but double-check)
  const maxSize = 50 * 1024 * 1024; // 50 MB
  if (pdfFile.size &gt; maxSize) {
    return { valid: false, error: "File exceeds maximum size of 50 MB" };
  }

  // Check for empty file
  if (pdfFile.size === 0) {
    return { valid: false, error: "File is empty" };
  }

  // Check file signature (PDF magic bytes)
  const data = new Uint8Array(pdfFile.data as ArrayBuffer);
  const header = String.fromCharCode(...data.slice(0, 4));
  if (header !== "%PDF") {
    return { valid: false, error: "Invalid PDF file format" };
  }

  return { valid: true };
}
</code></pre>
<p>2. Implement request timeouts to avoid server hangs</p>
<pre><code class="lang-tsx">// Set timeout for long-running PDF operations
const parseWithTimeout = (file: Uint8Array, timeoutMs = 30000) =&gt; {
  return Promise.race([
    parsePDF(file),
    new Promise((_, reject) =&gt;
      setTimeout(() =&gt; reject(new Error("PDF parsing timeout")), timeoutMs)
    ),
  ]);
};
</code></pre>
<p>3. Implement rate limiting to avoid abuse. You can use the <code>express-rate-limit</code> library to apply rate limiting to your Express apps.</p>
<pre><code class="lang-tsx">import rateLimit from 'express-rate-limit';

const app = express();

// Create the rate limiting middleware
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // Limit each IP to 100 requests per `windowMs`
  message:
    'Too many requests from this IP, please try again after 15 minutes',
  standardHeaders: true, // Enable standard RateLimit headers (draft-7)
  legacyHeaders: false, // Disable legacy X-RateLimit-* headers
});

// Apply the rate limiting middleware to all requests
app.use(limiter);
</code></pre>
<p>4. Sanitize each keyword or search query to avoid injection attacks.</p>
<h2 id="heading-unit-testing-your-pdf-parser"><strong>Unit Testing Your PDF Parser</strong></h2>
<p>Testing is critical when building PDF processing tools, as real-world PDFs vary widely in structure, encoding, and complexity. Jest provides an excellent framework for testing Express endpoints and ensuring your extraction logic works reliably across different scenarios.</p>
<h3 id="heading-setting-up-jest-tests">Setting Up Jest Tests</h3>
<p>The test suite I've created uses Jest with Supertest (an HTTP assertion library) to simulate requests to your API endpoints without running a server.</p>
<p>To start, install Jest, Supertest, and their types:</p>
<pre><code class="lang-bash">npm install --save-dev jest @types/jest supertest @types/supertest ts-jest
</code></pre>
<p>Then update your package.json to include Jest configuration:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"scripts"</span>: {
    <span class="hljs-attr">"test"</span>: <span class="hljs-string">"jest"</span>,
    <span class="hljs-attr">"test:watch"</span>: <span class="hljs-string">"jest --watch"</span>
  },
  <span class="hljs-attr">"jest"</span>: {
    <span class="hljs-attr">"preset"</span>: <span class="hljs-string">"ts-jest"</span>,
    <span class="hljs-attr">"testEnvironment"</span>: <span class="hljs-string">"node"</span>,
    <span class="hljs-attr">"extensionsToTreatAsEsm"</span>: [<span class="hljs-string">".ts"</span>],
    <span class="hljs-attr">"moduleNameMapper"</span>: {
      <span class="hljs-attr">"^(\\.{1,2}/.*)\\.js$"</span>: <span class="hljs-string">"$1"</span>
    },
    <span class="hljs-attr">"transform"</span>: {
      <span class="hljs-attr">"^.+\\.tsx?$"</span>: [
        <span class="hljs-string">"ts-jest"</span>,
        {
          <span class="hljs-attr">"useESM"</span>: <span class="hljs-literal">true</span>,
          <span class="hljs-attr">"tsconfig"</span>: {
            <span class="hljs-attr">"module"</span>: <span class="hljs-string">"esnext"</span>
          }
        }
      ]
    }
  }
}
</code></pre>
<h3 id="heading-understanding-the-test-structure">Understanding the Test Structure</h3>
<p>The test file includes comprehensive coverage for all your endpoints. For example, the <code>/upload-page-range</code> endpoint tests verify both happy paths and error handling:</p>
<pre><code class="lang-tsx">describe("POST /upload-page-range", () =&gt; {
  it("should return error when no file is provided", async () =&gt; {
    const response = await request(app)
      .post("/upload-page-range")
      .query({ startPage: 1, endPage: 2 });
    expect(response.status).toBe(400);
    expect(response.body.error).toBe("No PDF file shared.");
  });

  it("should return error for invalid page range", async () =&gt; {
    const mockPdfBuffer = Buffer.from("%PDF-1.4 mock pdf");
    const response = await request(app)
      .post("/upload-page-range")
      .query({ startPage: "invalid", endPage: 2 })
      .attach("file", mockPdfBuffer, "test.pdf");

    expect(response.status).toBe(400);
    expect(response.body.error).toContain("valid integers");
  });

  it("should extract text from page range", async () =&gt; {
    const mockPdfBuffer = Buffer.from("%PDF-1.4 mock pdf");
    const response = await request(app)
      .post("/upload-page-range")
      .query({ startPage: 1, endPage: 2 })
      .attach("file", mockPdfBuffer, "test.pdf");

    expect(response.status).toBe(200);
    expect(response.body.success).toBe(true);
    expect(response.body.result.startPage).toBe(1);
    expect(response.body.result.endPage).toBe(2);
  });
});
</code></pre>
<p>Notice how the tests mock the PDFParse library rather than requiring actual PDF files. This approach makes tests:</p>
<ul>
<li><p><strong>Fast</strong>: No disk I/O, tests run in milliseconds</p>
</li>
<li><p><strong>Reliable</strong>: No dependency on external files that might change</p>
</li>
<li><p><strong>Focused</strong>: Each test verifies specific behavior, not file handling</p>
</li>
</ul>
<p>The mock returns consistent data for all test cases, allowing you to verify your endpoint logic, handle responses correctly, validate parameters properly, and return appropriate error messages.</p>
<h3 id="heading-running-tests">Running Tests</h3>
<p>Execute your test suite with:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Run tests once</span>
npm <span class="hljs-built_in">test</span>

<span class="hljs-comment"># Run tests in watch mode for development</span>
npm run <span class="hljs-built_in">test</span>:watch

<span class="hljs-comment"># Generate coverage report</span>
npm <span class="hljs-built_in">test</span> -- --coverage
</code></pre>
<p>A successful test run confirms that all endpoints, <code>/upload</code>, /metadata, <code>/search</code>, and <code>/upload-page-range</code>, handle valid requests, reject invalid inputs, and return data in the expected format.</p>
<h2 id="heading-deploying-your-pdf-parser-api"><strong>Deploying Your PDF Parser API</strong></h2>
<p>Once your tests pass, you're ready to deploy your Express app. The deployment process depends on your hosting platform, but here are the essentials:</p>
<h3 id="heading-running-locally">Running Locally</h3>
<p>Start your development server with:</p>
<pre><code class="lang-bash">npm run dev
</code></pre>
<p>This runs the server from <code>server.ts</code> using <code>ts-node</code> and Nodemon. The API will be available at <code>http://localhost:8080</code>.</p>
<p>Test your endpoints with curl:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Test the health check</span>
curl http://localhost:8080/

<span class="hljs-comment"># Upload and parse a PDF</span>
curl -F <span class="hljs-string">"file=@sample.pdf"</span> http://localhost:8080/upload

<span class="hljs-comment"># Extract specific pages</span>
curl -F <span class="hljs-string">"file=@sample.pdf"</span> <span class="hljs-string">"http://localhost:8080/upload-page-range?startPage=1&amp;endPage=5"</span>

<span class="hljs-comment"># Search for text</span>
curl -F <span class="hljs-string">"file=@sample.pdf"</span> <span class="hljs-string">"http://localhost:8080/search?query=invoice"</span>

<span class="hljs-comment"># Get metadata only</span>
curl -F <span class="hljs-string">"file=@sample.pdf"</span> http://localhost:8080/metadata
</code></pre>
<h3 id="heading-production-deployment">Production Deployment</h3>
<p>Before deploying to production, build your TypeScript:</p>
<pre><code class="lang-bash">npm run build
</code></pre>
<p>Then start the compiled server:</p>
<pre><code class="lang-bash">npm start
</code></pre>
<p>For cloud platforms like Heroku, AWS, or DigitalOcean, ensure your environment variables are set (particularly the <code>PORT</code> variable). The API is designed to scale horizontally, since it doesn't maintain state. Each request processes independently.</p>
<p>Consider adding these production improvements:</p>
<ul>
<li><p><strong>Rate limiting</strong>: Prevent abuse with express-rate-limit</p>
</li>
<li><p><strong>Logging</strong>: Use Winston or Pino for structured logging</p>
</li>
<li><p><strong>Monitoring</strong>: Set up error tracking with Sentry or similar services</p>
</li>
<li><p><strong>Database</strong>: Store extraction results in MongoDB or PostgreSQL for historical access</p>
</li>
<li><p><strong>Caching</strong>: Cache metadata for frequently accessed PDFs to reduce processing overhead</p>
</li>
</ul>
<h2 id="heading-next-steps-integrate-into-your-saas"><strong>Next Steps: Integrate Into Your SaaS</strong></h2>
<p>This PDF parser is now a production-ready API that you can integrate into any SaaS platform needing document processing capabilities. Here's how to get started:</p>
<p>Fork the repository and customize it for your use case. Add features like:</p>
<ul>
<li><p>Support for additional document formats (DOCX, XLSX, images)</p>
</li>
<li><p>Batch processing endpoints for handling multiple files</p>
</li>
<li><p>Webhook support for asynchronous processing</p>
</li>
<li><p>User authentication and per-user quotas</p>
</li>
<li><p>Advanced text extraction options (tables, forms, structured data)</p>
</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Building a production-ready PDF parser gives you complete control over document processing while maintaining modularity for future extensions.</p>
<p>You've learned to build an Express API that handles full extraction, page ranges, text search, and metadata retrieval – all with robust error handling and validation patterns that apply to any document processing tool.</p>
<p>This tested, deployable foundation is ready to scale in real applications, whether you're building a SaaS product or adding PDF capabilities to existing systems.</p>
<p>As you integrate these patterns into your projects, consider exploring advanced libraries like <code>pdfjs-dist</code> or <code>pdf-lib</code> while applying the same validation and modular design principles you've mastered here.</p>
<h3 id="heading-resources">Resources</h3>
<ul>
<li><a target="_blank" href="https://github.com/DeraCodings/custom-pdf-parser">GitHub repo</a></li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Integrate RTK Query with Redux Toolkit: A Step-by-Step Guide for React Developers ]]>
                </title>
                <description>
                    <![CDATA[ Redux is a state management library for JavaScript applications. It lets you create applications that behave in a predictable manner and run on different environments, including server and native environments. Redux Toolkit is the recommended way to ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-integrate-rtk-query-with-redux-toolkit/</link>
                <guid isPermaLink="false">67a4fbc311e2c2609ca60771</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ redux-toolkit ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Chidera Humphrey ]]>
                </dc:creator>
                <pubDate>Thu, 06 Feb 2025 18:13:23 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1738854615563/3357bd11-3fcd-43b3-b459-b0e8b60e853d.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Redux is a state management library for JavaScript applications. It lets you create applications that behave in a predictable manner and run on different environments, including server and native environments. Redux Toolkit is the recommended way to write Redux logic, and was created to make working with Redux easier.</p>
<p>Traditionally, writing Redux logic required a lot of boilerplate code, configuration, and dependency installations. This made Redux difficult to work with. RTK was created to solve these issues. RTK contains utilities that simplify common Redux tasks such as store configuration, creation of reducers, and immutable state update logic.</p>
<p>Redux Toolkit Query (RTK Query) is an optional add-on included in the Redux ToolKit package. It was created to simplify data fetching and caching in web applications. RTK Query is built on top of Redux Toolkit and employs Redux for its internal architectural design.</p>
<p>In this article, you'll learn how to integrate RTK Query with Redux Toolkit in your React applications by building a simple CRUD Movie app.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-understanding-rtk-query-and-core-concepts">Understanding RTK Query and core concepts</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-integrating-rtk-query-with-redux-toolkit">Integrating RTK Query with Redux Toolkit</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-handling-data-caching-with-rtk-query">Handling Data Caching with RTK Query</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-error-handling-and-loading-states">Error Handling and Loading States</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-best-practices">Best Practices</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ol>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>For this article, I assume that you are familiar with React.</p>
<h2 id="heading-understanding-rtk-query-and-core-concepts">Understanding RTK Query and Core Concepts</h2>
<p>At the core of RTK Query is the <code>createApi</code> function. This function allows you to define an API slice, which includes the server's base URL and a set of endpoints that describe how to fetch and mutate data from the server.</p>
<p>RTK Query automatically generates a custom hook for each of the defined endpoints. These custom hooks can be used in your React component to conditionally render content based on the state of the API request.</p>
<p>The code below shows how to create an API slice using the <code>createApi</code> function:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { createApi, fetchBaseQuery } <span class="hljs-keyword">from</span> <span class="hljs-string">'@reduxjs/toolkit/query/react'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> apiSlice = createApi({
    <span class="hljs-attr">reducerPath</span>: <span class="hljs-string">'api'</span>,
    <span class="hljs-attr">baseQuery</span>: fetchBaseQuery({ <span class="hljs-attr">baseUrl</span>: <span class="hljs-string">'https://server.co/api/v1/'</span>}),
    <span class="hljs-attr">endpoints</span>: <span class="hljs-function">(<span class="hljs-params">builder</span>) =&gt;</span> ({
        <span class="hljs-attr">getData</span>: builder.query({
            <span class="hljs-attr">query</span>: <span class="hljs-function">() =&gt;</span> <span class="hljs-string">'/data'</span>,
        })
    })
})

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> { useGetDataQuery } = apiSlice;
</code></pre>
<p><code>fetchBaseQuery</code> is a lightweight wrapper around the native JavaScript <code>fetch</code> function that simplifies API requests. The <code>reducerPath</code> property specifies the directory where your API slice is stored. A common convention is to name the directory <code>api</code>. The <code>baseQuery</code> property uses the <code>fetchBaseQuery</code> function to specify the base URL of your server. You can think of it as the root URL in which your endpoints are appended.</p>
<p><code>useGetDataQuery</code> is an auto-generated hook that you can use in your components.</p>
<h2 id="heading-how-to-integrate-rtk-query-with-redux-toolkit">How to Integrate RTK Query with Redux Toolkit</h2>
<p>In this section, you will learn how to integrate RTK Query with Redux Toolkit by building a simple Movie app. In this app, users will be able to view movies stored in your backend (though it's a mock backend), add movies, and update and delete any movie. In essence, you will build a CRUD app using RTK Query.</p>
<p>Also, I will be using TypeScript for this tutorial. If you're using JavaScript, skip the type annotations and/or <code>interface</code>s and replace <code>.tsx</code>/<code>.ts</code> with <code>.jsx</code>/<code>.js</code>.</p>
<h3 id="heading-setting-up-the-development-environment"><strong>Setting up the development environment</strong></h3>
<p>Create a new React project using the following command:</p>
<pre><code class="lang-sh">npm create vite@latest
</code></pre>
<p>Follow the prompts to create your React app.</p>
<p>Install the <code>react-redux</code> and <code>@reduxjs/toolkit</code> packages using the following command:</p>
<pre><code class="lang-sh"><span class="hljs-comment"># npm</span>
npm install @reduxjs/toolkit react-redux

<span class="hljs-comment"># yarn</span>
yarn add @reduxjs/toolkit react-redux
</code></pre>
<p>For the backend, you're going to use <code>json-server</code>. <code>json-server</code> is a light-weight Node.js tool that simulates a RESTful API using JSON files as the data source. It lets frontend developers create mock APIs without writing any server-side code.</p>
<p>You can read more about <code>json-server</code> <a target="_blank" href="https://github.com/typicode/json-server/tree/v0">here</a>.</p>
<p>Use the following command to install <code>json-server</code>:</p>
<pre><code class="lang-sh">npm install -g json-server
</code></pre>
<h3 id="heading-folder-structure"><strong>Folder structure</strong></h3>
<p>In the root directory of your application, create a <strong>data</strong> folder. Inside this folder, create a <code>db.json</code> file. This will be where your "backend" is stored.</p>
<p>In the <code>src</code> directory, create two folders: <strong>component</strong> and <strong>state</strong>.</p>
<p>Inside the <code>component</code> folder, create two folders: <strong>CardComponent</strong> and <strong>Modal,</strong> and a file:<code>Movies.tsx</code>.</p>
<p>Inside the state folder, create a <strong>movies</strong> folder and a file: <code>store.ts</code>.</p>
<p>After creating the folders and files, your app structure should look like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734786998116/7708adad-06b1-41bd-ab22-d6efb745246b.png" alt="7708adad-06b1-41bd-ab22-d6efb745246b" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h3 id="heading-building-the-app"><strong>Building the app</strong></h3>
<p>First, you're going to set up your <strong>JSON server</strong>.</p>
<p>Open the <code>db.json</code> file and paste in the following code:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"movies"</span>: [
    {
      <span class="hljs-attr">"title"</span>: <span class="hljs-string">"John Wick"</span>,
      <span class="hljs-attr">"description"</span>: <span class="hljs-string">"Retired assassin John Wick is pulled back into the criminal underworld when gangsters kill his beloved dog, a gift from his late wife. With his unmatched combat skills and a thirst for vengeance, Wick single-handedly takes on an entire criminal syndicate."</span>,
      <span class="hljs-attr">"year"</span>: <span class="hljs-number">2014</span>,
      <span class="hljs-attr">"thumbnail"</span>: <span class="hljs-string">"https://m.media-amazon.com/images/M/MV5BNTBmNWFjMWUtYWI5Ni00NGI2LWFjN2YtNDE2ODM1NTc5NGJlXkEyXkFqcGc@._V1_.jpg"</span>,
      <span class="hljs-attr">"id"</span>: <span class="hljs-string">"2"</span>
    },
    {
      <span class="hljs-attr">"id"</span>: <span class="hljs-string">"3"</span>,
      <span class="hljs-attr">"title"</span>: <span class="hljs-string">"The Dark Knight"</span>,
      <span class="hljs-attr">"year"</span>: <span class="hljs-number">2008</span>,
      <span class="hljs-attr">"description"</span>: <span class="hljs-string">"Batman faces off against his archenemy, the Joker, a criminal mastermind who plunges Gotham City into chaos. As the Joker tests Batman’s limits, the hero must confront his own ethical dilemmas to save the city from destruction."</span>,
      <span class="hljs-attr">"thumbnail"</span>: <span class="hljs-string">"https://m.media-amazon.com/images/M/MV5BMTMxNTMwODM0NF5BMl5BanBnXkFtZTcwODAyMTk2Mw@@._V1_FMjpg_UX1000_.jpg"</span>
    },
    {
      <span class="hljs-attr">"title"</span>: <span class="hljs-string">"Die Hard"</span>,
      <span class="hljs-attr">"description"</span>: <span class="hljs-string">"NYPD officer John McClane finds himself in a deadly hostage situation when a group of terrorists takes control of a Los Angeles skyscraper during a Christmas party. Armed only with his wit and a handgun, McClane must outsmart the heavily armed intruders to save his wife and others."</span>,
      <span class="hljs-attr">"year"</span>: <span class="hljs-number">1988</span>,
      <span class="hljs-attr">"thumbnail"</span>: <span class="hljs-string">"https://m.media-amazon.com/images/M/MV5BMGNlYmM1NmQtYWExMS00NmRjLTg5ZmEtMmYyYzJkMzljYWMxXkEyXkFqcGc@._V1_.jpg"</span>,
      <span class="hljs-attr">"id"</span>: <span class="hljs-string">"4"</span>
    },
    {
      <span class="hljs-attr">"title"</span>: <span class="hljs-string">"Mission: Impossible – Fallout"</span>,
      <span class="hljs-attr">"description"</span>: <span class="hljs-string">"Ethan Hunt and his IMF team must track down stolen plutonium while being hunted by assassins and former allies. With incredible stunts and non-stop action sequences, Hunt races against time to prevent a global catastrophe."</span>,
      <span class="hljs-attr">"year"</span>: <span class="hljs-number">2018</span>,
      <span class="hljs-attr">"thumbnail"</span>: <span class="hljs-string">"https://m.media-amazon.com/images/M/MV5BMTk3NDY5MTU0NV5BMl5BanBnXkFtZTgwNDI3MDE1NTM@._V1_.jpg"</span>,
      <span class="hljs-attr">"id"</span>: <span class="hljs-string">"5"</span>
    },
    {
      <span class="hljs-attr">"title"</span>: <span class="hljs-string">"Gladiator"</span>,
      <span class="hljs-attr">"description"</span>: <span class="hljs-string">"Betrayed by the Emperor’s son and left for dead, former Roman General Maximus rises as a gladiator to seek vengeance and restore honor to his family. His journey from slavery to becoming a champion captures the hearts of Rome’s citizens."</span>,
      <span class="hljs-attr">"year"</span>: <span class="hljs-number">2010</span>,
      <span class="hljs-attr">"thumbnail"</span>: <span class="hljs-string">"https://m.media-amazon.com/images/M/MV5BZmExODVmMjItNzFlZC00MDA0LWJkYjctMmQ0ZTNkYTcwYTMyXkEyXkFqcGc@._V1_.jpg"</span>,
      <span class="hljs-attr">"id"</span>: <span class="hljs-string">"6"</span>
    }
  ]
}
</code></pre>
<p>Start up your JSON server using the following command:</p>
<pre><code class="lang-sh">json-server --watch data\db.json --port 8080
</code></pre>
<p>This command will start up your JSON server and wrap the API endpoint running on port 8080. Your terminal should look like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734787039082/8331fca3-74ac-45aa-9fac-904af53cc961.png" alt="8331fca3-74ac-45aa-9fac-904af53cc961" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Next, you are going to create an API slice. This API slice will be used to configure your Redux store.</p>
<p>Navigate to the <strong>movies</strong> folder and create a <code>movieApiSlice.ts</code> file. Open the<code>movieApiSlice.ts</code> file and paste in the following code:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> { createApi, fetchBaseQuery } <span class="hljs-keyword">from</span> <span class="hljs-string">"@reduxjs/toolkit/query/react"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> moviesApiSlice = createApi({
  reducerPath: <span class="hljs-string">"movies"</span>,
  baseQuery: fetchBaseQuery({
    baseUrl: <span class="hljs-string">"http://localhost:8080"</span>,
  }),
  endpoints: <span class="hljs-function">(<span class="hljs-params">builder</span>) =&gt;</span> {
    <span class="hljs-keyword">return</span> {
      getMovies: builder.query({
        query: <span class="hljs-function">() =&gt;</span> <span class="hljs-string">`/movies`</span>,
      }),

      addMovie: builder.mutation({
        query: <span class="hljs-function">(<span class="hljs-params">movie</span>) =&gt;</span> ({
          url: <span class="hljs-string">"/movies"</span>,
          method: <span class="hljs-string">"POST"</span>,
          body: movie,
        }),
      }),

      updateMovie: builder.mutation({
        query: <span class="hljs-function">(<span class="hljs-params">movie</span>) =&gt;</span> {
          <span class="hljs-keyword">const</span> { id, ...body } = movie;
          <span class="hljs-keyword">return</span> {
            url: <span class="hljs-string">`movies/<span class="hljs-subst">${id}</span>`</span>,
            method: <span class="hljs-string">"PUT"</span>,
            body
          }
        },
      }),

      deleteMovie: builder.mutation({
        query: <span class="hljs-function">(<span class="hljs-params">{id}</span>) =&gt;</span> ({
          url: <span class="hljs-string">`/movies/<span class="hljs-subst">${id}</span>`</span>,
          method: <span class="hljs-string">"DELETE"</span>,
          body: id,
        }),
      }),
    };
  },
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> {
  useGetMoviesQuery,
  useAddMovieMutation,
  useDeleteMovieMutation,
  useUpdateMovieMutation,
} = moviesApiSlice;
</code></pre>
<p>In the code above, you created a <code>movieApiSlice</code> using the <code>createApi</code> function from RTK Query, which takes in an object as a parameter.</p>
<p>The <code>reducerPath</code> property specifies the path of the API slice.</p>
<p>The <code>baseQuery</code> uses the <code>fetchBaseQuery</code>. The <code>fetchBaseQuery</code> function takes in an object as a parameter, which has a <code>baseURL</code> property. The <code>baseURL</code> property specifies the root URL of our API.</p>
<p>In this case, you are using <a target="_blank" href="http://localhost:8080"><code>http://localhost:8080</code></a>, which is the URL of the JSON server.</p>
<p>The <code>endpoints</code> property is what your API interacts with. It’s a function that takes in a <code>builder</code> parameter and returns an object with methods (<code>getMovies</code>, <code>addMovie</code>, <code>updateMovie</code>, and <code>deleteMovie</code>) for interacting with your API.</p>
<p>Lastly, you are exporting custom hooks generated automatically by RTK Query. The custom hook starts with "use" and ends with "query" and is named based on the methods defined in the <code>endpoints</code> property.</p>
<p>These custom hooks let you interact with the API from your functional components.</p>
<p>Next, you are going to set up your Redux store. Navigate to the <code>store.ts</code> file located in the state folder and paste in the following code:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> { configureStore } <span class="hljs-keyword">from</span> <span class="hljs-string">"@reduxjs/toolkit"</span>;
<span class="hljs-keyword">import</span> { moviesApiSlice } <span class="hljs-keyword">from</span> <span class="hljs-string">"./movies/moviesApiSlice"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> store = configureStore({
    reducer: {
        [moviesApiSlice.reducerPath]: moviesApiSlice.reducer,
    },
    middleware: <span class="hljs-function">(<span class="hljs-params">getDefaultMiddleware</span>) =&gt;</span> {
        <span class="hljs-keyword">return</span> getDefaultMiddleware().concat(moviesApiSlice.middleware);
    }
})
</code></pre>
<p>In the code above, you are setting up a Redux store using the <code>configureStore</code> function from Redux Toolkit. The <code>reducer</code> property specifies a reducer for updating the state in the Redux store. The <code>moviesApiSlice.reducer</code> is the reducer for updating the state of your API.</p>
<p>For the <code>middleware</code> property, you are creating a middleware for handling asynchronous state updates. You don't have to worry too much about this part and what it does. This is required for all the caching functionality and all the other benefits that RTK Query provides.</p>
<p>Before we move further, you have to add your Redux store to your application. To do this, navigate to your <code>main.tsx</code> or <code>index.tsx</code> file (depending on what it is called in your application) and replace the code with the following code:</p>
<pre><code class="lang-tsx">// main.tsx

import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App.tsx";
import { Provider } from "react-redux";
import { store } from "./state/store.ts";

createRoot(document.getElementById("root")!).render(
  &lt;StrictMode&gt;
    &lt;Provider store={store}&gt;
      &lt;App /&gt;
    &lt;/Provider&gt;
  &lt;/StrictMode&gt;
);
</code></pre>
<p>In the code above, you are importing the <code>Provider</code> component from <code>react-redux</code> and the <code>store</code> you created earlier. Also, you are wrapping the <code>Provider</code> component around your <code>App</code> component. The <code>store</code> prop is used to pass your Redux store to your application.</p>
<h3 id="heading-building-the-movie-component"><strong>Building the Movie component</strong></h3>
<p>In this section, you're going to build out the <code>Movies.tsx</code> component, which is where all of your application logic lives.</p>
<p>Navigate to your <code>Movies.tsx</code> file and paste in the following code:</p>
<pre><code class="lang-tsx">import "../movie.css";
import { ChangeEvent, FormEvent, useState } from "react";

import {
  useGetMoviesQuery,
  useAddMovieMutation,
  useDeleteMovieMutation,
} from "../state/movies/moviesApiSlice";
import MovieCard from "./CardComponent/MovieCard";

export interface Movie {
  title: string;
  description: string;
  year: number;
  thumbnail: string;
  id: string;
}


export default function Movies() {
  // Form input states
  const [title, setTitle] = useState&lt;string&gt;("");
  const [year, setYear] = useState&lt;string&gt;("");
  const [thumbnail, setThumbnail] = useState&lt;string&gt;("");
  const [description, setDescription] = useState&lt;string&gt;("");

  const { data: movies = [], isLoading, isError } = useGetMoviesQuery({});

  const [ addMovie ] = useAddMovieMutation();
  const [ deleteMovie ] = useDeleteMovieMutation();

  // Handle form submission to add a new movie
  const handleSubmit = (e: FormEvent&lt;HTMLFormElement&gt;): void =&gt; {
    e.preventDefault();
    console.log("New movie submitted:", { title, thumbnail, description, year });
    addMovie({ title, description, year: Number(year), thumbnail, id: String(movies.length + 1) })
    // Reset form inputs after submission
    setTitle("");
    setThumbnail("");
    setDescription("");
    setYear("");
  };

  if (isError) {
    return &lt;div&gt;Error&lt;/div&gt;;
  }

  if (isLoading) {
    return &lt;div&gt;Loading...&lt;/div&gt;;
  }

  return (
    &lt;div className="movie-container"&gt;
      &lt;h2&gt;Movies to Watch&lt;/h2&gt;

      {/* Form to add a new movie */}
      &lt;div className="new-movie-form"&gt;
        &lt;form onSubmit={handleSubmit}&gt;
          &lt;div className="form-group"&gt;
            &lt;label htmlFor="title"&gt;Title&lt;/label&gt;
            &lt;input
              type="text"
              name="title"
              id="title"
              placeholder="Enter movie title"
              value={title}
              onChange={(e: ChangeEvent&lt;HTMLInputElement&gt;) =&gt; setTitle(e.target.value)}
              required
            /&gt;
          &lt;/div&gt;

          &lt;div className="form-group"&gt;
            &lt;label htmlFor="imageAddress"&gt;Image Link:&lt;/label&gt;
            &lt;input
              type="text"
              name="imageAddress"
              id="imageAddress"
              placeholder="Enter image link address"
              value={thumbnail}
              onChange={(e: ChangeEvent&lt;HTMLInputElement&gt;) =&gt; setThumbnail(e.target.value)}
              required
            /&gt;
          &lt;/div&gt;

          &lt;div className="form-group"&gt;
            &lt;label htmlFor="year"&gt;Year of release:&lt;/label&gt;
            &lt;input
              type="text"
              name="year"
              id="year"
              placeholder="Enter year of release"
              value={year}
              onChange={(e: ChangeEvent&lt;HTMLInputElement&gt;) =&gt; setYear(e.target.value)}
            /&gt;
          &lt;/div&gt;

          &lt;div className="form-group"&gt;
            &lt;label htmlFor="description"&gt;Description&lt;/label&gt;
            &lt;textarea
              name="description"
              id="description"
              placeholder="Enter movie description"
              value={description}
              onChange={(e: ChangeEvent&lt;HTMLTextAreaElement&gt;) =&gt; setDescription(e.target.value)}
              required
            &gt;&lt;/textarea&gt;
          &lt;/div&gt;

          &lt;button type="submit"&gt;Add Movie&lt;/button&gt;
        &lt;/form&gt;
      &lt;/div&gt;

      {/* Render list of movies */}
      &lt;div className="movie-list"&gt;
        {movies.length === 0 ? (
          &lt;p&gt;No movies added yet.&lt;/p&gt;
        ) : (
          movies.map((movie: Movie) =&gt; (
            &lt;div key={movie.id}&gt;
              &lt;MovieCard movie={movie} deleteMovie={deleteMovie} /&gt;
            &lt;/div&gt;
          ))
        )}
      &lt;/div&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p>In the code above, you're creating a <code>Movies</code> component and using RTK Query to handle CRUD operations.</p>
<p>Let's go step-by-step through what each part of the code does.</p>
<p>In the top part, you imported the <code>useGetMoviesQuery</code>, <code>useAddMovieMutation</code>, and <code>useDeleteMovie</code> functions from the <code>moviesApiSlice</code> you created earlier. The functions will be used for fetching, adding, and deleting movies, respectively.</p>
<p>You also imported a <code>MovieCard</code> component, which you'll use to display each movie. You'll create the <code>MovieCard</code> component in a second.</p>
<p>The <code>Movie</code> interface defines the shape of each movie object. It ensures consistency in the structure of movie data across the component. Again, ignore if you're using JavaScript.</p>
<p>You defined some state variables: <code>title</code>, <code>year</code>, <code>thumbnail</code>, and <code>description</code> to store form input values.</p>
<p>The <code>useGetMoviesQuery</code> hook fetches the movie data when the component mounts. The hook returns an object with several properties, but we're focusing on three properties: <code>data</code> aliased as <code>movies</code>, <code>isLoading</code>, and <code>isError</code>.</p>
<p>The <code>useAddMovieMutation</code> and <code>useDeleteMovieMutation</code> hooks return two functions: <code>addMovie</code> and <code>deleteMovie</code>, respectively.</p>
<p>The <code>handleSubmit</code> function handles the submission of the form. When the form is submitted, the <code>addMovie</code> function is called with the new movie details. The <code>year</code> is converted to a number, and the <code>id</code> is generated based on the current length of the movie array.</p>
<p>If an error occurs while fetching the movies (<code>isError</code>), a simple error message is displayed.</p>
<p>If the API request is still loading (<code>isLoading</code>), a loading message is shown.</p>
<p>If everything goes well, the main JSX structure of the component is returned, which includes:</p>
<ul>
<li><p>a form for adding new movies.</p>
</li>
<li><p>a list of movies rendered using the <code>MovieCard</code> component. Each<code>MovieCard</code> is passed the individual <code>movie</code> data along with the <code>deleteMovie</code> function to handle deletions.</p>
</li>
</ul>
<p>Now, let's create our <code>MovieCard</code> component.</p>
<p>Inside the <strong>CardComponent</strong> folder, create a <code>MovieCard.tsx</code> file. Open the <code>MovieCard.tsx</code> and paste in the following code:</p>
<pre><code class="lang-tsx">import { useRef, useState } from "react";
import EditModal from "../Modal/EditModal";
import { Movie } from "../Movies";

type DeleteMovie = (movie:{id:string}) =&gt; void;

interface MovieCardProps {
  movie: Movie;
  deleteMovie: DeleteMovie;
}

function MovieCard({ movie, deleteMovie }: MovieCardProps) {

  const dialogRef = useRef&lt;HTMLDialogElement | null&gt;(null);
  const [selectedMovie, setSelectedMovie] = useState&lt;Movie&gt;(movie);

  const handleSelectedMovie = () =&gt; {
    setSelectedMovie(movie);
    dialogRef.current?.showModal();
    document.body.style.overflow = 'hidden';
  }

  const closeDialog = (): void =&gt; {
    dialogRef.current?.close();
    document.body.style.overflow = 'visible';
  }

  return (
    &lt;div className="movie-wrapper" key={movie.id}&gt;
      &lt;div className="img-wrapper"&gt;
        &lt;img src={movie.thumbnail} alt={`${movie.title} poster`} /&gt;
      &lt;/div&gt;
      &lt;h3&gt;
        {movie.title} ({movie.year})
      &lt;/h3&gt;
      &lt;p&gt;{movie.description}&lt;/p&gt;
      &lt;div className="button-wrapper"&gt;
        &lt;button onClick={handleSelectedMovie}&gt;Edit&lt;/button&gt;
        &lt;button onClick={() =&gt; deleteMovie({ id: movie.id })}&gt;Delete&lt;/button&gt;
      &lt;/div&gt;

      &lt;EditModal dialogRef={dialogRef} selectedMovie={selectedMovie} closeDialog={closeDialog} /&gt;

    &lt;/div&gt;
  );
}

export default MovieCard;
</code></pre>
<p>In the code above, you're creating a <code>MovieCard</code> component for displaying the movies on the screen.</p>
<p>You're importing the <code>useRef</code> and <code>useState</code> hooks from React to manage the component’s state and references. You also import the <code>EditModal</code> component, which will handle editing the movie details, and the<code>Movie</code> type to enforce the shape of the movie object (this is for TypeScript).</p>
<p>The <code>MovieCard</code> component accepts two props: <code>movie</code> and <code>deleteMovie</code>.</p>
<p>The <code>dialogRef</code> variable is used to manage the reference to the modal dialog element.</p>
<p>The <code>selectedMovie</code> state is initialized with the <code>movie</code> prop. This will be used to track the currently selected movie for editing purposes.</p>
<p>The <code>handleSelectedMovie</code> function is called when the <strong>Edit</strong> button is clicked. It does the following:</p>
<ul>
<li><p>Sets <code>selectedMovie</code> to the current movie object.</p>
</li>
<li><p>Opens the <code>EditModal</code> dialog using <code>dialogRef.current?.showModal()</code>.</p>
</li>
<li><p>Prevents the page from scrolling while the modal is open by setting <code>document.body.style.overflow</code> to <code>'hidden'</code>.</p>
</li>
</ul>
<p>The <code>closeDialog</code> function closes the modal dialog using <code>dialogRef.current?.close()</code> and resets the page’s scroll behavior by setting <code>document.body.style.overflow</code> back to <code>'visible'</code>.</p>
<p>In the <code>return</code> statement, a JSX structure is returned that displays:</p>
<ul>
<li><p>an image for the movie's thumbnail,</p>
</li>
<li><p>the movie's title and year of release in an <code>h3</code> element,</p>
</li>
<li><p>a short description of the movie,</p>
</li>
<li><p>two buttons:</p>
<ul>
<li><p>The "Edit" button triggers the <code>handleSelectedMovie</code> function to open the <code>EditModal</code>.</p>
</li>
<li><p>The "Delete" button calls the <code>deleteMovie</code> function, passing the movie’s queryID to delete the specified movie from your API.</p>
</li>
</ul>
</li>
</ul>
<p>The <code>EditModal</code> component is also rendered, passing <code>dialogRef</code>, <code>closeDialog</code>, and <code>selectedMovie</code> as props. This ensures that the <code>EditModal</code> has access to the selected movie's details and a function to close itself.</p>
<p>Next up, you're going to create the <code>EditModal</code> component.</p>
<p>Inside the <strong>Modal</strong> folder, create a file: <code>EditModal.tsx</code>, that will house the modal component.</p>
<p>Open the <code>EditModal.tsx</code> file and paste in the following code:</p>
<pre><code class="lang-tsx">import { useUpdateMovieMutation } from "../../state/movies/moviesApiSlice";
import { Movie } from "../Movies";
import "./modal.css";
import { useState, RefObject, FormEvent } from "react";

interface EditModalProps {
  dialogRef: RefObject&lt;HTMLDialogElement&gt;;
  selectedMovie: Movie;
  closeDialog: () =&gt; void;
}

function EditModal({ dialogRef, selectedMovie, closeDialog }: EditModalProps) {
  const [title, setTitle] = useState&lt;string&gt;(selectedMovie.title);
  const [year, setYear] = useState&lt;string | number&gt;(selectedMovie.year);
  const [description, setDescription] = useState&lt;string&gt;(selectedMovie.description);
  const [thumbnail, setThumbnail] = useState&lt;string&gt;(selectedMovie.thumbnail);

  const [updateMovie] = useUpdateMovieMutation();

  async function handleUpdateMovie(e: FormEvent&lt;HTMLFormElement&gt;){
    e.preventDefault();
    try {
      await updateMovie({title, description, year: Number(year), thumbnail, id: selectedMovie.id});
      closeDialog();
    } catch (error) {
      alert(`${error} occurred`);
    }
  }

  return (
    &lt;dialog ref={dialogRef} className="modal-dialog"&gt;
      &lt;form onSubmit={handleUpdateMovie}&gt;
        &lt;div className="form-group"&gt;
          &lt;label htmlFor="title"&gt;Title:&lt;/label&gt;
          &lt;input
            type="text"
            id="title"
            value={title}
            onChange={(e) =&gt; setTitle(e.target.value)}
          /&gt;
        &lt;/div&gt;

        &lt;div className="form-group"&gt;
          &lt;label htmlFor="year"&gt;Year of release:&lt;/label&gt;
          &lt;input
            type="text"
            id="year"
            value={year}
            onChange={(e) =&gt; setYear(e.target.value)}
          /&gt;
        &lt;/div&gt;

        &lt;div className="form-group"&gt;
          &lt;label htmlFor="thumbnail"&gt;Image URL:&lt;/label&gt;
          &lt;input
            type="text"
            id="thumbnail"
            value={thumbnail}
            onChange={(e) =&gt; setThumbnail(e.target.value)}
          /&gt;
        &lt;/div&gt;

        &lt;div className="form-group"&gt;
          &lt;label htmlFor="description"&gt;Description:&lt;/label&gt;
          &lt;textarea
            id="description"
            value={description}
            onChange={(e) =&gt; setDescription(e.target.value)}
          &gt;&lt;/textarea&gt;
        &lt;/div&gt;
        &lt;button type="submit"&gt;Save&lt;/button&gt;
      &lt;/form&gt;
      &lt;button className="close-btn" onClick={closeDialog}&gt;
        Close
      &lt;/button&gt;
    &lt;/dialog&gt;
  );
}

export default EditModal;
</code></pre>
<p>In the code above, you're simply creating a modal dialog using the native HTML <code>&lt;dialog&gt;</code> element. Inside the <code>dialog</code> element is a <code>form</code> field populated with the details of the selected movie, obtained from the state variables: <code>title</code>, <code>year</code>, <code>description</code>, and <code>thumbnail</code>.</p>
<p>You imported the <code>useUpdateMovieMutation</code> hook from your <code>moviesApiSlice</code>. The <code>useUpdateMovieMutation</code> hook returns an <code>updateMovie</code> function you can use to update movie details.</p>
<p>The <code>handleUpdateMovie</code> is called when the <strong>Save</strong> button is clicked. It does the following:</p>
<ul>
<li><p>updates the movie details by calling the <code>updateMovie</code> function</p>
</li>
<li><p>closes the dialog using the <code>closeDialog</code> function</p>
</li>
</ul>
<h3 id="heading-mounting-our-component"><strong>Mounting our component</strong></h3>
<p>Navigate to your <code>App.tsx</code> file and add in your <code>Movies</code> component the following code:</p>
<pre><code class="lang-tsx">import "./App.css";
import Movies from "./components/Movies";

function App() {
  return (
    &lt;div&gt;
      &lt;Movies /&gt;
    &lt;/div&gt;
  );
}

export default App;
</code></pre>
<p>In your browser, open your <a target="_blank" href="http://localhost">localhost</a> and you should see something like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734787096281/f4f87b33-d5ba-4537-acd1-39dfa740410a.gif" alt="f4f87b33-d5ba-4537-acd1-39dfa740410a" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Congratulations! You've successfully integrated RTK Query with the Redux Toolkit.</p>
<p>In the next section, you'll learn how caching in RTK Query works and how to invalidate caches.</p>
<h2 id="heading-how-to-handle-data-caching-with-rtk-query">How to Handle Data Caching with RTK Query</h2>
<p>In this section, you'll learn how caching works in RTK Query and how to invalidate caches.</p>
<p>In programming, caching is one of the hardest things to do. But RTK Query makes handling caching easier for us.</p>
<p>When you call your API, RTK Query automatically caches the result of successfully calling your API. This means that subsequent calls to the API return the cached result.</p>
<p>For example, if you try editing any movie in your app, you'll notice that nothing changes. This doesn't mean that it's not working – in fact, it is working. And the results returned are the cached version (the results when you first called the API, that is on component mount).</p>
<p>To stop this behaviour, you need to invalidate the cache each time you make changes to your backend. This will cause RTK Query to automatically refetch the data to reflect your changes.</p>
<p>Navigate to your <code>moviesApiSlice.ts</code> file and replace that code with the following code:</p>
<pre><code class="lang-tsx">
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";

export const moviesApiSlice = createApi({
  reducerPath: "movies",
  baseQuery: fetchBaseQuery({
    baseUrl: "http://localhost:8080",
  }),
  tagTypes: ['Movies'],
  endpoints: (builder) =&gt; {
    return {
      getMovies: builder.query({
        query: () =&gt; `/movies`,
        providesTags: ['Movies']
      }),

      addMovie: builder.mutation({
        query: (movie) =&gt; ({
          url: "/movies",
          method: "POST",
          body: movie,
        }),
        invalidatesTags: ['Movies']
      }),

      updateMovie: builder.mutation({
        query: (movie) =&gt; {
          const { id, ...body } = movie;
          return {
            url: `movies/${id}`,
            method: "PUT",
            body
          }
        },
        invalidatesTags: ['Movies']
      }),

      deleteMovie: builder.mutation({
        query: ({id}) =&gt; ({
          url: `/movies/${id}`,
          method: "DELETE",
          body: id,
        }),
        invalidatesTags: ['Movies']
      }),
    };
  },
});

export const {
  useGetMoviesQuery,
  useAddMovieMutation,
  useDeleteMovieMutation,
  useUpdateMovieMutation,
} = moviesApiSlice;
</code></pre>
<p>In the code above, you added the <code>tagTypes</code> property to your <code>moviesApiSlice</code> and set it to<code>[Movies]</code>. This will be used to invalidate the cached results when you make changes to your backend.</p>
<p>In the <code>getMovies</code> function, you added the <code>providesTags</code> property. This means that you're providing a tag to your API call, which you can invalidate with the mutation functions.</p>
<p>In the mutation functions (<code>addMovie</code>, <code>updateMovie</code>, and <code>deleteMovie</code>), you added the <code>invalidatesTags</code> property set to the value of the <code>tagTypes</code> property. This invalidates the cache whenever each of these mutation functions are called, which causes RTK Query to automatically refetch the data.</p>
<p>With these changes, you can update and delete movies and see the result of your changes.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734787129856/983cd55d-9714-4c0e-a038-2b7c9f60f881.gif" alt="983cd55d-9714-4c0e-a038-2b7c9f60f881" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h2 id="heading-error-handling-and-loading-states">Error Handling and Loading States</h2>
<p>When you were building your app, you handled any errors that might arise from calling your API by simply displaying a "Error..." text.</p>
<p>In real-world applications, you want to display something meaningful, such as a UI that tells your users what went wrong exactly.</p>
<p>Similarly, when your API request is loading, you want to display a loading spinner or a loading skeleton UI so that your users know that your app data is loading.</p>
<p>For the purposes of this article, we are not going to dive into advanced error handling or managing loading states – but these would be things you’d want to look into.</p>
<h2 id="heading-best-practices">Best Practices</h2>
<p>Below are some of the best practices to consider when working with RTK Query:</p>
<ol>
<li><p><strong>Separate multiple API slices</strong>: if you have multiple API slices for different APIs, consider separating them into different API slices. This keeps your API slices modular, making it easier to maintain and debug.</p>
</li>
<li><p><strong>Use the Redux Devtools</strong>: the Redux Devtools let you get an inside look at what is going on in your Redux store as well as your queries and mutations. This makes debugging much easier. The Redux Devtools are available as a Chrome extension.</p>
</li>
<li><p><strong>Prefetch data</strong>: use the <code>usePrefetch</code> hook to make a data fetch before a user navigates to a page on your website or loads some known content. This reduces load time and makes the UI feel faster.</p>
</li>
<li><p><strong>Use middleware for complex logic</strong>: implement middleware when you need to intercept and modify actions or responses, such as adding authentication tokens to headers or logging specific errors.</p>
</li>
<li><p><strong>Use optimistic updates</strong>: when using <code>useMutation</code> to update or change existing data, you can implement an optimistic update to the UI. This helps to give the impression of immediate changes. If the request fails, you can roll back the update.</p>
</li>
</ol>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this article, you learned what RTK Query is and how to integrate RTK Query with Redux Toolkit by building a CRUD React Movie app. You also learned about the caching strategies of RTK Query and how to invalidate the caches.</p>
<p>Thanks for reading!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Handle Forms in Next.js with Server Actions and Zod for Validation ]]>
                </title>
                <description>
                    <![CDATA[ Forms are essential in modern websites, as they help you collect your users’ information. So knowing how to handle forms properly is crucial when you’re building web applications. In this article, you will learn how to handle forms in Next.js using s... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/handling-forms-nextjs-server-actions-zod/</link>
                <guid isPermaLink="false">6740b5ae31fc8f5b09184849</guid>
                
                    <category>
                        <![CDATA[ Next.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Frontend Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ form validation ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Chidera Humphrey ]]>
                </dc:creator>
                <pubDate>Fri, 22 Nov 2024 16:47:42 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1732137561737/293681e0-d2f4-4d88-9fbe-f7e5e9113554.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Forms are essential in modern websites, as they help you collect your users’ information. So knowing how to handle forms properly is crucial when you’re building web applications.</p>
<p>In this article, you will learn how to handle forms in Next.js using server actions and zod.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#introduction-to-server-actions-in-nextjs">Introduction to Server Actions in Next.js</a></p>
</li>
<li><p><a class="post-section-overview" href="#introduction-to-zod-for-validation">Introduction to zod for Validation</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-build-the-contact-form-component">How to Build the Contact Form Component</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-create-the-server-actions-and-validate-the-form-data-with-zod">How to Create the Server Actions and Validate the Form Data with zod</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-integrate-the-server-action-into-our-contact-form">How to Integrate the Server Action into Our Contact Form</a></p>
</li>
<li><p><a class="post-section-overview" href="#conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-prerequisites-and-setting-up-the-project">Prerequisites and Setting Up the Project</h2>
<p>For this tutorial, I assume that you know JavaScript and how to set up a Next.js project (I'm not going to walk through that set up here).</p>
<p>If you haven’t yet set up your Next.js project, use the following command and follow the prompts:</p>
<pre><code class="lang-sh">npx create-next-app
</code></pre>
<p>This is what we are going to build in this tutorial:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732051133068/960bd90a-cb22-4e8b-86e2-fdb78b5cf330.gif" alt="working form" class="image--center mx-auto" width="400" height="212" loading="lazy"></p>
<p><strong>Note</strong>: this tutorial mainly focuses on the logic and not the design. For the complete design, you can visit the GitHub repository which I’ve linked to at the end.</p>
<h2 id="heading-introduction-to-server-actions-in-nextjs">Introduction to Server Actions in Next.js</h2>
<p>So what are server actions? Server actions are pretty much what they sound like—actions or functions that run on the server. With server actions, you can make calls to external APIs or fetch data from a database.</p>
<p>Prior to Next.js 13, you had to use routes to handle API calls and form submissions. This was complex and cumbersome.</p>
<p>But the introduction of server actions lets you communicate with external APIs and databases directly in your Next.js components.</p>
<p>By running on the server, server actions enable secure handling of data processing, mitigating security risks.</p>
<p>Server actions are also useful in handling forms as they let you communicate directly with your server and limit the exposure of important credentials to the client.</p>
<p>There are two ways to create server actions:</p>
<ul>
<li>The first method is using the <code>"use server"</code> directive at the top level of a function. You can only use this method inside a server component. Using it inside a client component will result in an error.</li>
</ul>
<p>For example:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getPosts</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-string">"use server"</span>; <span class="hljs-comment">// this makes getPosts a server actions</span>

  <span class="hljs-comment">// rest of code</span>
}
</code></pre>
<ul>
<li>The other method is to create a separate file and add <strong>"use server"</strong> at the top of the file. This ensures that any async function exported from the file is a server action.</li>
</ul>
<pre><code class="lang-ts"><span class="hljs-comment">// action.ts</span>

<span class="hljs-string">"use server"</span>;

<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">getPosts</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">"https:..."</span>);
  <span class="hljs-keyword">const</span> data = res.json();

  <span class="hljs-keyword">return</span> data;
}
</code></pre>
<p>In the code example above, <code>getPosts</code> is a server action.</p>
<h2 id="heading-introduction-to-zod-for-validation">Introduction to Zod for Validation</h2>
<p>Zod is a validation library that you can use to validate form entries on the server side. This ensures consistency across both the client and server.</p>
<p>Zod is a TypeScript-first library, which means that it comes with type safety out of the box.</p>
<p>To install Zod in your Next.js application, use the following command:</p>
<pre><code class="lang-sh">npm install zod
</code></pre>
<p>At the core of the Zod library are schemas. You can use schemas to validate inputs.</p>
<p>Here's how to define a schema:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> { z } <span class="hljs-keyword">from</span> <span class="hljs-string">"zod"</span>;

<span class="hljs-keyword">const</span> contactSchema = z.object({
  name: z.string().min(<span class="hljs-number">2</span>, { message: <span class="hljs-string">"Name must be at least 2 characters"</span> }),
  email: z.string().email({ message: <span class="hljs-string">"Invalid email address"</span> }),
  message: z
    .string()
    .min(<span class="hljs-number">10</span>, { message: <span class="hljs-string">"Message must be at least 10 characters"</span> }),
});
</code></pre>
<p>Inside the <code>contactSchema</code>, we are specifying that:</p>
<ul>
<li><p><code>name</code> is of type <code>string</code> and should be a minimum of 2 characters,</p>
</li>
<li><p><code>email</code> is of type <code>string</code> and <code>email</code>, and</p>
</li>
<li><p><code>message</code> is of type <code>string</code> and should be a minimum of 10 characters.</p>
</li>
</ul>
<p>The <code>message</code> property is what will be displayed on the screen when all or any of the validation fails.</p>
<p>In the next section, we are going to build the contact form.</p>
<h2 id="heading-how-to-build-the-contact-form-component">How to Build the Contact Form Component</h2>
<p>In this section, we are going to build the UI of the contact form.</p>
<p>Inside the <code>app</code> directory, create a folder called "components.<strong>"</strong></p>
<p>Inside of the <code>components</code> folder, create a new file, <code>contactForm.tsx</code>, and add the following code:</p>
<pre><code class="lang-typescript"><span class="hljs-string">"use client"</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">ContactForm</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    &lt;form action=<span class="hljs-string">""</span>&gt;
      &lt;input <span class="hljs-keyword">type</span>=<span class="hljs-string">"text"</span> name=<span class="hljs-string">"name"</span> placeholder=<span class="hljs-string">"Enter your name"</span> /&gt;
      &lt;input <span class="hljs-keyword">type</span>=<span class="hljs-string">"email"</span> name=<span class="hljs-string">"email"</span> placeholder=<span class="hljs-string">"Enter your email"</span> /&gt;
      &lt;textarea name=<span class="hljs-string">"message"</span> cols={<span class="hljs-number">30</span>} rows={<span class="hljs-number">10</span>} placeholder=<span class="hljs-string">"Type in your message"</span>&gt;&lt;/textarea&gt;
      &lt;button <span class="hljs-keyword">type</span>=<span class="hljs-string">"submit"</span>&gt;Send Message&lt;/button&gt;
    &lt;/form&gt;
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> ContactForm;
</code></pre>
<p>In the code above, we are creating a simple contact form. We made it a client component – you’ll see why in a bit.</p>
<p>Import the <code>ContactForm</code> component in your <code>page.tsx</code> file:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> ContactForm <span class="hljs-keyword">from</span> <span class="hljs-string">"./components/contactForm.tsx"</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Home</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    &lt;div&gt;
      &lt;h2&gt;Contact Form&lt;/h2&gt;
      &lt;ContactForm /&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p>You should have something like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732048786231/694ac568-9d71-4597-ba61-962483740320.png" alt="contact form image" class="image--center mx-auto" width="838" height="632" loading="lazy"></p>
<p>Next, we are going to validate our form data using zod.</p>
<h2 id="heading-how-to-create-the-server-actions-and-validate-the-form-data-with-zod">How to Create the Server Actions and Validate the Form Data with zod</h2>
<p>In this section, we are going to create our server action and validate our form entries with zod.</p>
<p>In the <strong>app</strong> folder, create another folder, <code>api</code>.</p>
<p>Inside the <code>api</code> folder, create a file called <code>action.ts</code> and paste in the following code:</p>
<pre><code class="lang-ts"><span class="hljs-string">"use server"</span>;

<span class="hljs-keyword">import</span> { z } <span class="hljs-keyword">from</span> <span class="hljs-string">"zod"</span>;

<span class="hljs-keyword">const</span> contactFormSchema = z.object({
  name: z.string().trim().min(<span class="hljs-number">1</span>, { message: <span class="hljs-string">"Name field is required"</span> }),
  email: z.string().email({ message: <span class="hljs-string">"Invalid email address"</span> }),
  message: z.string().trim().min(<span class="hljs-number">1</span>, { message: <span class="hljs-string">"Please type in a message"</span> }),
});

<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">sendEmail</span>(<span class="hljs-params">prevState: <span class="hljs-built_in">any</span>, formData: FormData</span>) </span>{
  <span class="hljs-keyword">const</span> contactFormData = <span class="hljs-built_in">Object</span>.fromEntries(formData);
  <span class="hljs-keyword">const</span> validatedContactFormData = contactFormSchema.safeParse(contactFormData);


  <span class="hljs-keyword">if</span> (!validatedContactFormData.success) {
    <span class="hljs-keyword">const</span> formFieldErrors =
      validatedContactFormData.error.flatten().fieldErrors;

    <span class="hljs-keyword">return</span> {
      errors: {
        name: formFieldErrors?.name,
        email: formFieldErrors?.email,
        message: formFieldErrors?.message,
      },
    };
  }

  <span class="hljs-keyword">return</span> {
    success: <span class="hljs-string">"Your message was sent successfully!"</span>,
  };
}
</code></pre>
<p>In the code above, we defined a <code>contactFormSchema</code> for validating our form entries.</p>
<p>The <code>sendEmail</code> function (which is our server action) accepts two arguments:</p>
<ul>
<li><p><code>prevState</code> which will be used in to display our error and success messages, and</p>
</li>
<li><p><code>formData</code> which is the entries from our form</p>
</li>
</ul>
<p>FormData makes it possible for our function to have access to the form fields without using <code>useState</code> and it relies on the <code>name</code> attribute.</p>
<p>We are using <code>Object.fromEntries()</code> to convert the raw <code>formData</code> into a regular JavaScript object and we’re storing it in the <code>contactFormData</code> variable.</p>
<p>Next, we are validating the <code>contactFormData</code> using the <code>safeParse()</code> method of our zod schema, <code>contactFormSchema</code>.</p>
<p>As a good programming practice, we return early by checking if the validation fails. If the validation fails, we return an object with an <code>error</code> property, which is an object containing the error message of each form field.</p>
<p><code>formFieldsError</code> is assigned the value of the error object from zod, which contains the error message of each form field.</p>
<p>If everything goes well, we simply return an object with a <code>success</code> property.</p>
<p><strong>Note:</strong> this is where you send the message to your email using any email service provider of your choice. For the sake of the article, we are simply returning an object.</p>
<p>In the next section, we are going to integrate the server action in our contact form.</p>
<h2 id="heading-how-to-integrate-the-server-action-into-our-contact-form">How to Integrate the Server Action into Our Contact Form</h2>
<p>In this section, we are going to integrate the server action into our contact form.</p>
<p>Navigate to the <code>contactForm.tsx</code> file and replace the content with the following code:</p>
<pre><code class="lang-typescript"><span class="hljs-string">"use client"</span>;

<span class="hljs-keyword">import</span> { useFormState, useFormStatus } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-dom"</span>;
<span class="hljs-keyword">import</span> { sendEmail } <span class="hljs-keyword">from</span> <span class="hljs-string">"../api/action"</span>;

<span class="hljs-keyword">const</span> initialState = {
  success: <span class="hljs-string">""</span>,
  errors: {
    name: <span class="hljs-string">""</span>,
    email: <span class="hljs-string">""</span>,
    message: <span class="hljs-string">""</span>,
  }
};

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">ContactForm</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [state, formAction] = useFormState(sendEmail, initialState);

  <span class="hljs-keyword">return</span> (
    &lt;div&gt;
      &lt;div className=<span class="hljs-string">"py-6"</span>&gt;
        &lt;form action={formAction}&gt;
          &lt;div className=<span class="hljs-string">"mb-4"</span>&gt;
            &lt;label htmlFor=<span class="hljs-string">"name"</span>&gt;Your name&lt;/label&gt;
            &lt;br /&gt;
            &lt;input
              <span class="hljs-keyword">type</span>=<span class="hljs-string">"text"</span>
              name=<span class="hljs-string">"name"</span>
              id=<span class="hljs-string">"name"</span>
              <span class="hljs-comment">// required</span>
              className=<span class="hljs-string">"border w-full md:w-3/4 py-2 pl-2 rounded-lg rounded-l-lg block md:inline focus:outline-slate-500 border-gray-500"</span>
              placeholder=<span class="hljs-string">"Enter your name..."</span>
            /&gt;
            {state.errors?.name &amp;&amp; (
              &lt;p className=<span class="hljs-string">"text-red-500"</span>&gt;{state.errors.name}&lt;/p&gt;
            )}
          &lt;/div&gt;
          &lt;div className=<span class="hljs-string">"mb-4"</span>&gt;
            &lt;label htmlFor=<span class="hljs-string">"email"</span>&gt;Your email&lt;/label&gt;
            &lt;br /&gt;
            &lt;input
              <span class="hljs-keyword">type</span>=<span class="hljs-string">"email"</span>
              name=<span class="hljs-string">"email"</span>
              id=<span class="hljs-string">"email"</span>
              <span class="hljs-comment">// required</span>
              className=<span class="hljs-string">"border w-full md:w-3/4 py-2 pl-2 rounded-lg rounded-l-lg block md:inline focus:outline-slate-500 border-gray-500"</span>
              placeholder=<span class="hljs-string">"Enter your email..."</span>
            /&gt;
            {state.errors?.email &amp;&amp; (
              &lt;p className=<span class="hljs-string">"text-red-500"</span>&gt;{state.errors.email}&lt;/p&gt;
            )}
          &lt;/div&gt;
          &lt;div&gt;
            &lt;label htmlFor=<span class="hljs-string">"message"</span>&gt;Message&lt;/label&gt;
            &lt;br /&gt;
            &lt;textarea
              name=<span class="hljs-string">"message"</span>
              id=<span class="hljs-string">"message"</span>
              <span class="hljs-comment">// required</span>
              cols={<span class="hljs-number">100</span>}
              rows={<span class="hljs-number">10</span>}
              className=<span class="hljs-string">"border w-full md:w-3/4 py-3 pl-2 rounded-lg focus:outline-slate-500 border-gray-500"</span>
              placeholder=<span class="hljs-string">"Enter your message..."</span>
            &gt;&lt;/textarea&gt;
            {state.errors?.message &amp;&amp; (
              &lt;p className=<span class="hljs-string">"text-red-500"</span>&gt;{state.errors.message}&lt;/p&gt;
            )}
          &lt;/div&gt;
          &lt;SubmitButton /&gt;
        &lt;/form&gt;
      &lt;/div&gt;
      {state?.success &amp;&amp; &lt;p className=<span class="hljs-string">"text-green-600"</span>&gt;{state.success}&lt;/p&gt;}
    &lt;/div&gt;
  );
}

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

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">SubmitButton</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> { pending } = useFormStatus();

  <span class="hljs-keyword">return</span> (
    &lt;button
      <span class="hljs-keyword">type</span>=<span class="hljs-string">"submit"</span>
      disabled={pending ? <span class="hljs-literal">true</span> : <span class="hljs-literal">false</span>}
      className=<span class="hljs-string">"bg-green-600 text-white font-semibold px-3 py-2 rounded-lg"</span>
    &gt;
      {pending ? (
        &lt;span&gt;
          Submitting &lt;RiLoader5Fill className=<span class="hljs-string">"animate-spin"</span> /&gt;
        &lt;/span&gt;
      ) : (
        <span class="hljs-string">"Submit"</span>
      )}
    &lt;/button&gt;
  );
}
</code></pre>
<p>In the updated code above, we imported two hooks: <code>useFormState</code> and <code>useFormStatus</code> from "react-dom" and <code>sendEmail</code> from "api/action.ts".</p>
<p>Next, we created a <code>initialState</code> variable to hold our initial state. This will be used in the <code>useFormState</code> hook.</p>
<p><code>initialState</code> is an object with:</p>
<ul>
<li><p>a <code>success</code> property for the success message of our server action, and</p>
</li>
<li><p>an <code>errors</code> object, which is equal to the <code>errors</code> object we return in our server action if the validation fails.</p>
</li>
</ul>
<p>Inside our <code>ContactForm</code> component, we are using the <code>useFormState</code> hook. This hook accepts two arguments: a server action and an initial state and returns an array with two values: current state and <code>formAction</code>.</p>
<p><code>formAction</code> will be passed into the <code>action</code> prop of the <strong>form</strong> element. This will handle the submission of our form, which incorporates the zod validation.</p>
<p>Below each form field, we conditionally render the error message of each of the form field respectively.</p>
<p>Below the <strong>form</strong> element, we render the success message if the form was successfully submitted.</p>
<p>The submit button is put into a different component, <code>SubmitButton</code> so we can make use of the <code>useFormStatus</code> hook.</p>
<p>The <code>useFormStatus</code> hook returns an object with a <code>pending</code> property, which we can use to disable the submit button when the form is submitted.</p>
<p>Assuming everything went correctly, you should have a working contact form like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732051093066/c6cd1da7-fe24-4eea-85db-a6845efc501d.gif" alt="working form" class="image--center mx-auto" width="400" height="212" loading="lazy"></p>
<p>Congratulations! You have just created a contact form using server actions and the zod validation library.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this article, you learned what server actions are and how to use the zod library. You also used server actions and zod to build a contact form.</p>
<p>Server actions are not limited to form submission and can also be used for fetching data from external APIs and databases.</p>
<p>You can learn more with these resources:</p>
<ul>
<li><p><a target="_blank" href="https://zod.dev/">zod documentation</a></p>
</li>
<li><p><a target="_blank" href="https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations">server action documentation</a></p>
</li>
</ul>
<p>Here's the <a target="_blank" href="https://github.com/DeraCodings/server-action-zod">GitHub repository</a> of the complete project.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Scope, Closures, and Hoisting in JavaScript – Explained with Code Examples ]]>
                </title>
                <description>
                    <![CDATA[ In the dynamic world of JavaScript, understanding the intricacies of scope, closures, and hoisting is fundamental for mastering the language and building robust applications. These concepts, though often misunderstood, play a crucial role in determin... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/scope-closures-and-hoisting-in-javascript/</link>
                <guid isPermaLink="false">66d45dd58812486a37369c61</guid>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Chidera Humphrey ]]>
                </dc:creator>
                <pubDate>Wed, 26 Jun 2024 08:13:12 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/06/How-to-connect-Firebase-Authentication-with-Golang-app_20240625_101105_0000-1.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In the dynamic world of JavaScript, understanding the intricacies of scope, closures, and hoisting is fundamental for mastering the language and building robust applications.</p>
<p>These concepts, though often misunderstood, play a crucial role in determining how variables and functions behave within the code.</p>
<p>Scope dictates the accessibility of variables, closures enable powerful programming patterns, and hoisting can lead to unexpected results if not understood properly.</p>
<p>In this comprehensive guide, we will delve deep into the realms of scope, closures, and hoisting in JavaScript, unraveling their complexities, providing practical examples, and offering best practices to empower you in your journey as a JavaScript developer.</p>
<p>So, buckle up as we embark on this enlightening exploration of JavaScript's dynamic trio.</p>
<h2 id="heading-table-of-contents">Table Of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#scope-in-javascript">Scope in JavaScript</a></p>
</li>
<li><p><a class="post-section-overview" href="#types-of-scope-in-javascript">Types of scope in JavaScript</a></p>
</li>
<li><p><a class="post-section-overview" href="#closures">Closures</a></p>
</li>
<li><p><a class="post-section-overview" href="#hoisting">Hoisting</a></p>
<ul>
<li><p><a class="post-section-overview" href="#variable-hoisting">Variable hoisting</a></p>
</li>
<li><p><a class="post-section-overview" href="#function-hoisting">Function hoisting</a></p>
</li>
<li><p><a class="post-section-overview" href="#class-hoisting">Class hoisting</a></p>
</li>
<li><p><a class="post-section-overview" href="#import-hoisting">Import hoisting</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#best-practices">Best practices</a></p>
</li>
<li><p><a class="post-section-overview" href="#conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>You should have a basic understanding of the JavaScript language to follow along with this article.</p>
<h2 id="heading-scope-in-javascript">Scope in JavaScript</h2>
<p>In programming, scope refers to the context in which variables and functions are declared and accessed.</p>
<p>Scope determines the visibility and lifecycle of these variables and functions within a program, ensuring that they are used in the intended context.</p>
<p>In JavaScript, scope follows the concept of lexical scope. In lexical scope, the visibility of variables and functions are determined by the context in which the variables and functions are defined.</p>
<h2 id="heading-types-of-scope-in-javascript">Types of scope in JavaScript</h2>
<p>In JavaScript, there are three main types of scope:</p>
<h3 id="heading-global-scope">Global scope</h3>
<p>Variables and functions defined in the global scope can be accessed by any part of the program. Variables and functions that are declared in the global scope are said to be global-scoped.</p>
<pre><code class="lang-js">
<span class="hljs-keyword">let</span> globalScopeVariable = <span class="hljs-string">"I'm in the global scope"</span>;



<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">logScope</span>(<span class="hljs-params"></span>)</span>{

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

}

logScope(); <span class="hljs-comment">// I'm in the global scope</span>



<span class="hljs-keyword">for</span>(<span class="hljs-keyword">let</span> i=<span class="hljs-number">0</span>; i&lt;<span class="hljs-number">3</span>; i++){

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

}

<span class="hljs-comment">// I'm in the global scope</span>

<span class="hljs-comment">// I'm in the global scope</span>

<span class="hljs-comment">// I'm in the global scope</span>



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

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

}

<span class="hljs-comment">// I'm in the global scope</span>



<span class="hljs-built_in">console</span>.log(globalScopeVariable); <span class="hljs-comment">// "I'm in the global scope"</span>
</code></pre>
<p>In the code above, the <code>globalScopeVariable</code> can be accessed by any part of the program, whether it's inside a function, loop, conditional statements, or in the global scope itself.</p>
<p>You can think of global scope as your local supermarket – everyone has access to it.</p>
<p><strong>Note</strong>: when building real-world applications, it is recommended to minimize the number of variables that are global-scoped. This is to reduce unpredictability in your code which can lead to bugs.</p>
<h3 id="heading-function-scope">Function scope</h3>
<p>When variables and functions are declared within functions, the variables and functions are in the function scope.</p>
<p>These variables and functions can only be accessed within the function they were declared in.</p>
<p>Variables declared in function scope are said to be function-scoped.</p>
<pre><code class="lang-js">
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">doubleNum</span>(<span class="hljs-params"></span>)</span>{

<span class="hljs-keyword">let</span> num = <span class="hljs-number">23</span>;

<span class="hljs-built_in">console</span>.log(num * <span class="hljs-number">2</span>)

}

doubleNum(); <span class="hljs-comment">// 46</span>



<span class="hljs-built_in">console</span>.log(num); <span class="hljs-comment">// Reference error: "num" is not defined</span>
</code></pre>
<p>In the code above, logging <code>num</code> will result in a <code>Reference error</code> as <code>num</code> can only be accessed within <code>doubleNum</code> function.</p>
<p>You can think of function scope as a message sent to a group chat – only the group participants can view and interact with the message.</p>
<h3 id="heading-block-scope">Block scope</h3>
<p>Curly braces, <code>{}</code>, denote a code block. Variables declared within these curly braces cannot be accessed outside the curly braces.</p>
<pre><code class="lang-js">
{

<span class="hljs-keyword">let</span> blockScopedVariable = <span class="hljs-string">"I'm block-scoped"</span>;

<span class="hljs-built_in">console</span>.log(blockScopedVariable); <span class="hljs-comment">// I'm block-scoped</span>

}



<span class="hljs-built_in">console</span>.log(blockScopedVariable); <span class="hljs-comment">// ReferenceError: blockScopedVariable is not defined</span>
</code></pre>
<p>In the code above, <code>blockScopedVariable</code> can only be accessed within the curly braces as it was defined inside the curly braces.</p>
<p>Though block scope seems similar with function scope, there's a little difference.</p>
<p>The key difference between block scope and function scope is that function scope refers to variables defined within functions, while block scope refers to variables defined in a pair of curly braces.</p>
<p>You can say that function scope is a subset of block scope.</p>
<p><strong>Note</strong>: variables declared within a function using <code>var</code> cannot be accessed outside that function.</p>
<pre><code class="lang-js">
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">logScope</span>(<span class="hljs-params"></span>)</span>{

<span class="hljs-keyword">var</span> x = <span class="hljs-number">4</span>;

}

<span class="hljs-built_in">console</span>.log(x); <span class="hljs-comment">// ReferenceError: x is not defined</span>
</code></pre>
<h2 id="heading-closures">Closures</h2>
<p>A closure is the combination of a function and its lexical scope. In other words, a closure is a function defined in another function that remembers its lexical environment.</p>
<p>Remembering its lexical environment means that closure function has access to variables declared within the parent function, even after the parent function has finished executing.</p>
<pre><code class="lang-js">
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">parentFunction</span>(<span class="hljs-params"></span>)</span>{

<span class="hljs-keyword">let</span> x = <span class="hljs-number">3</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">childFunction</span>(<span class="hljs-params">y</span>)</span>{

<span class="hljs-keyword">return</span> x + y

}

<span class="hljs-keyword">return</span> childFunction

}



<span class="hljs-keyword">let</span> res = parentFunction();




<span class="hljs-built_in">console</span>.log(res(<span class="hljs-number">6</span>));
</code></pre>
<p>In the code above, <code>childFunction</code> forms a closure inside <code>parentFunction</code>. <code>childFunction</code> has access to variables defined in the <code>childFunction</code>'s lexical environment even after <code>parentFunction</code> has finished executing in <code>let res = parentFunction()</code>. This is why <code>console.log(res(6))</code> gives <code>9</code>.</p>
<h2 id="heading-hoisting">Hoisting</h2>
<p>Hoisting in JavaScript refers to the process by which the JavaScript interpreter moves the declaration of variables, functions, classes, and imports to the top of the code before execution.</p>
<p>You can view hoisting as declarations being "lifted" up before code execution.</p>
<h3 id="heading-variable-hoisting">Variable hoisting</h3>
<p>Only variables declared using <code>var</code> are hoisted. This is because <code>var</code> is not block-scoped, meaning that the <code>var</code>-declared variable can be referred to anywhere in its scope regardless of the position of the variable's declaration.</p>
<pre><code class="lang-js">
<span class="hljs-built_in">console</span>.log(x); <span class="hljs-comment">// undefined</span>

<span class="hljs-keyword">var</span> x = <span class="hljs-number">4</span>;
</code></pre>
<p>Running the code above will log <code>undefined</code> to the console. This is because only variable declarations are hoisted or 'lifted up' and not the initializations.</p>
<p>Prior to code execution, the code will look like this:</p>
<pre><code class="lang-js">
<span class="hljs-keyword">var</span> x;

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

x = <span class="hljs-number">4</span>
</code></pre>
<p><strong>Tip</strong>: <code>var x</code> is the variable declaration. <code>x = 4</code> is the initialization.</p>
<p>Variables declared with <code>let</code> and <code>const</code> are not hoisted. This means that referring to the variables before declaration results in <code>ReferenceError</code>.</p>
<pre><code class="lang-js">
<span class="hljs-built_in">console</span>.log(y); <span class="hljs-comment">// ReferenceError: Cannot access "y" before initialization</span>

<span class="hljs-keyword">let</span> y = <span class="hljs-number">3</span>;
</code></pre>
<h3 id="heading-function-hoisting">Function hoisting</h3>
<p>Functions are hoisted just like <code>var</code>-declared variables.</p>
<pre><code class="lang-js">
<span class="hljs-built_in">console</span>.log(addNums(<span class="hljs-number">1</span>,<span class="hljs-number">3</span>)); <span class="hljs-comment">// 4</span>



<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">addNums</span>(<span class="hljs-params">a,b</span>)</span>{

<span class="hljs-keyword">return</span> a + b;

}
</code></pre>
<p>During execution, the code looks like this:</p>
<pre><code class="lang-js">
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">addNums</span>(<span class="hljs-params">a,b</span>)</span>{

<span class="hljs-keyword">return</span> a + b;

}



<span class="hljs-built_in">console</span>.log(addNums(<span class="hljs-number">1</span>,<span class="hljs-number">3</span>));
</code></pre>
<p>However, it's important to know that only function declarations are hoisted. Function expressions are not hoisted.</p>
<pre><code class="lang-js">
<span class="hljs-built_in">console</span>.log(addNums(<span class="hljs-number">1</span>,<span class="hljs-number">3</span>)); <span class="hljs-comment">// ReferenceError: cannot access "addNums" before initialization</span>



<span class="hljs-keyword">const</span> addNums = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">a,b</span>) </span>{

<span class="hljs-keyword">return</span> a + b;

}
</code></pre>
<p>Running the code above will result in a <code>ReferenceError</code>.</p>
<h3 id="heading-class-hoisting">Class hoisting</h3>
<p>Unlike function declaration, class declarations are not hoisted. This means you cannot accessed a class before its declaration.</p>
<pre><code class="lang-js">
<span class="hljs-keyword">new</span> Car(); <span class="hljs-comment">// ReferenceError: cannot access "Car" before initialization</span>

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Car</span></span>{}
</code></pre>
<h3 id="heading-import-hoisting">Import hoisting</h3>
<p>Import declarations are hoisted. This means that all methods and functions of an imported value are accessible in another module even before its declaration.</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> sum = f.add(<span class="hljs-number">2</span>+<span class="hljs-number">3</span>);

<span class="hljs-keyword">import</span> f <span class="hljs-keyword">from</span> <span class="hljs-string">'./library/package'</span>
</code></pre>
<p>In the code above, <code>f</code> functions and methods are accessible even though the declaration comes later.</p>
<h2 id="heading-best-practices">Best practices</h2>
<h3 id="heading-keep-scope-as-local-as-possible">Keep scope as local as possible</h3>
<p>You should keep your scope local as possible.</p>
<p>When creating variables, you should aim to create the variables where you want to use them. This is especially true if you are going to be using the variables in only one or few parts of your code.</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> num = <span class="hljs-number">3</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">addNum</span>(<span class="hljs-params"></span>)</span>{
    <span class="hljs-keyword">return</span> <span class="hljs-number">2</span> + num; <span class="hljs-comment">// 3</span>
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">multiplyNum</span>(<span class="hljs-params">a</span>)</span>{
    <span class="hljs-keyword">return</span> <span class="hljs-number">3</span> * a;
}
</code></pre>
<p>In the code above, <code>num</code> is used only once, in the <code>addNum</code> function. It is a better practice to declare <code>num</code> inside of the <code>addNum</code> function.</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">addNum</span>(<span class="hljs-params"></span>)</span>{
    <span class="hljs-keyword">const</span> num = <span class="hljs-number">3</span>;
    <span class="hljs-keyword">return</span> <span class="hljs-number">2</span> + num;
}

<span class="hljs-comment">// rest of code</span>
</code></pre>
<p>For better modularity, you can pass <code>num</code> as an argument to the <code>addNum</code> function.</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">addNum</span>(<span class="hljs-params">num</span>)</span>{
    <span class="hljs-keyword">return</span> <span class="hljs-number">2</span> + num
}
addNum(<span class="hljs-number">3</span>); <span class="hljs-comment">//5</span>
</code></pre>
<h3 id="heading-use-closures-to-protect-data">Use closures to protect data</h3>
<p>In programming, there are times you may want to protect some variables from being accessed from outside of an object. This is where closures can be very useful.</p>
<p>Use closures to protect private data from outside functions and other parts of your code.</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">encapsulateData</span>(<span class="hljs-params"></span>)</span>{
    <span class="hljs-keyword">const</span> user = {
        <span class="hljs-attr">name</span>: <span class="hljs-string">'Chidera'</span>,
        <span class="hljs-attr">age</span>: <span class="hljs-number">23</span>
    }
    <span class="hljs-keyword">return</span> updateUserAge(){
        <span class="hljs-keyword">return</span> data.age++;
    }
}

<span class="hljs-keyword">const</span> updateHandler = encapsulateData();
<span class="hljs-keyword">const</span> updatedAge = updateHandler(); <span class="hljs-comment">// 24</span>
<span class="hljs-built_in">console</span>.log(user); <span class="hljs-comment">// undefined</span>
</code></pre>
<p>In the code above, <code>updateAge</code> increases the age of the user without <code>user</code> being accessible from the outside.</p>
<h3 id="heading-declare-variables-and-functions-before-using-them">Declare variables and functions before using them</h3>
<p>It is recommended to always declare variables before using them. This helps to avoid unpredictability and unwanted bugs in your code.</p>
<h3 id="heading-always-use-let-and-const-to-create-variables">Always use <code>let</code> and <code>const</code> to create variables</h3>
<p><code>let</code> and <code>const</code> are the standard way of declaring variables in JavaScript. They remove the unpredictable code behavior that comes with using <code>var</code>.</p>
<p>There is almost no reason to use <code>var</code> to declare variables in modern JavaScript.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In summary, scopes determine where a variable can be accessed.</p>
<p>Scope can be divided into three: global, local, and block scopes.</p>
<p>Closures are functions inside a function. Closure functions have access to parent function variables, even after the parent function has returned. Closure is a crucial part of asynchronous JavaScript.</p>
<p>Hoisting makes variables accessible even before their creation.</p>
<p>Remember to adhere to best practices when working with closures and hoisting. Declaring variables before usage and using closures to encapsulate data can help to prevent code unpredictability and protect private data.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Work with Third-Party APIs in React by Building a Crypto Exchange Rate Web App ]]>
                </title>
                <description>
                    <![CDATA[ Working with APIs is crucial to any web application. And as a frontend developer, knowing how to connect your apps with third-party APIs is an important skill to have. In this article, you'll learn how to connect your app and fetch data from a third-... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/work-with-third-party-libraries-in-react/</link>
                <guid isPermaLink="false">66d45dd7c7632f8bfbf1e3e7</guid>
                
                    <category>
                        <![CDATA[ api ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Applications ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Chidera Humphrey ]]>
                </dc:creator>
                <pubDate>Wed, 10 Jan 2024 15:24:13 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/01/20240105_040821_0000-3.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Working with APIs is crucial to any web application. And as a frontend developer, knowing how to connect your apps with third-party APIs is an important skill to have.</p>
<p>In this article, you'll learn how to connect your app and fetch data from a third-party API by building a crypto exchange web application in React.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#how-to-set-up-the-project">How to Set Up the Project</a></p>
<ul>
<li><p><a class="post-section-overview" href="#initialize-a-new-react-project">Initialize a new React project</a></p>
</li>
<li><p><a class="post-section-overview" href="#install-the-necessary-dependencies">Install the necessary dependencies</a></p>
</li>
<li><p><a class="post-section-overview" href="#set-up-your-project-structure">Set up your project structure</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#how-to-build-the-ui">How to Build the UI</a></p>
</li>
<li><p><a class="post-section-overview" href="#how-to-fetch-data-with-react-query">How to Fetch Data with React Query</a></p>
</li>
<li><p><a class="post-section-overview" href="#how-to-display-data-in-the-ui">How to Display Data in the UI</a></p>
</li>
<li><p><a class="post-section-overview" href="#error-handling">Error Handling</a></p>
</li>
<li><p><a class="post-section-overview" href="#conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>This tutorial assumes you have foundational knowledge of React. Also, you should be able to work with Axios, specifically for making API calls.</p>
<p>Also, you will need an API key from <a target="_blank" href="https://rapidapi.com">RapidAPI</a> to follow along with this tutorial.</p>
<h2 id="heading-how-to-set-up-the-project">How to Set Up the Project</h2>
<h3 id="heading-initialize-a-new-react-project">Initialize a new React project</h3>
<p>Use the following command to initialize a new React project using Vite: <code>npm create vite@latest</code></p>
<p>Then follow the prompts that come after.</p>
<p><a target="_blank" href="https://vitejs.dev/guide/">Vite</a> is a build tool that enables fast reloading of your React apps. For more information on Vite, you can check out their official documentation.</p>
<h3 id="heading-install-the-necessary-dependencies">Install the necessary dependencies</h3>
<p>For this tutorial, you will need a few packages:</p>
<ul>
<li><p>Axios: a library for making promise-based API calls.</p>
</li>
<li><p>react-query: a data fetching library that handles caching, loading, and error state of our API call.</p>
</li>
<li><p>antd (Ant Design): a UI library of pre-built React components.</p>
</li>
</ul>
<p>Use the following command to install these packages: <code>npm install react-query antd axios</code></p>
<h3 id="heading-set-up-your-project-structure">Set up your project structure</h3>
<p>In this section, you are going to create some folders inside the <code>src</code> folder. This is to keep your app structure clean and easy to work with.</p>
<p>Inside the <code>src</code> folder, create a new folder called <code>components</code>.</p>
<p>Inside of the <code>components</code> folder, create three folders: <code>fetchData</code>, <code>UI</code>, and <code>currencies</code>.</p>
<p>With the project setup out of the way, let's move to building the application.</p>
<h2 id="heading-how-to-build-the-ui">How to Build the UI</h2>
<p>In this section, you are going to create the UI of the app. There will be little to no logic.</p>
<p>Navigate to the <code>UI</code> folder inside the <code>components</code> folder.</p>
<p>Create a new file, <code>ExchangeRateUI.jsx</code>, inside of the <code>UI</code> folder.</p>
<p>Add the following code inside the <code>ExchangeRateUI.jsx</code> file:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { Typography, Card } <span class="hljs-keyword">from</span> <span class="hljs-string">"antd"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">ExchangeRateUI</span>(<span class="hljs-params">props</span>) </span>{
 <span class="hljs-keyword">return</span> (
   <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"exchange-rate-ui"</span>&gt;</span>
     <span class="hljs-tag">&lt;<span class="hljs-name">Card</span>
       <span class="hljs-attr">extra</span>=<span class="hljs-string">{3}</span>
       <span class="hljs-attr">bordered</span>=<span class="hljs-string">{false}</span>
       <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">width:</span> <span class="hljs-attr">350</span>, <span class="hljs-attr">backgroundColor:</span> "#<span class="hljs-attr">4d4add</span>", <span class="hljs-attr">color:</span> "#<span class="hljs-attr">fff</span>" }}
       <span class="hljs-attr">size</span>=<span class="hljs-string">"default"</span>
     &gt;</span>
       <span class="hljs-tag">&lt;<span class="hljs-name">Typography.Paragraph</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">color:</span> "#<span class="hljs-attr">fff</span>" }}&gt;</span>Bitcoin<span class="hljs-tag">&lt;/<span class="hljs-name">Typography.Paragraph</span>&gt;</span>
     <span class="hljs-tag">&lt;/<span class="hljs-name">Card</span>&gt;</span>
   <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
 );
}
</code></pre>
<p>In the code above, you used the <code>Card</code> and <code>Typography</code> components of the Ant Design UI library to create the UI of our app.</p>
<p>Next, navigate to the <code>currencies</code> folder and create a <code>currencies.jsx</code> file.</p>
<p>Add the following code inside <code>currencies.jsx</code>:</p>
<pre><code class="lang-js"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> cryptocurrencies = [
  { <span class="hljs-attr">value</span>: <span class="hljs-string">"BTC"</span>, <span class="hljs-attr">label</span>: <span class="hljs-string">"Bitcoin"</span> },
  { <span class="hljs-attr">value</span>: <span class="hljs-string">"ETH"</span>, <span class="hljs-attr">label</span>: <span class="hljs-string">"Ethereum"</span> },
  { <span class="hljs-attr">value</span>: <span class="hljs-string">"BCH"</span>, <span class="hljs-attr">label</span>: <span class="hljs-string">"Bitcoin Cash"</span> },
  { <span class="hljs-attr">value</span>: <span class="hljs-string">"XRP"</span>, <span class="hljs-attr">label</span>: <span class="hljs-string">"Ripple"</span> },
  { <span class="hljs-attr">value</span>: <span class="hljs-string">"SOL"</span>, <span class="hljs-attr">label</span>: <span class="hljs-string">"Solana"</span> },
  { <span class="hljs-attr">value</span>: <span class="hljs-string">"ADA"</span>, <span class="hljs-attr">label</span>: <span class="hljs-string">"Cardano"</span> },
  { <span class="hljs-attr">value</span>: <span class="hljs-string">"BNB"</span>, <span class="hljs-attr">label</span>: <span class="hljs-string">"Binance Coin"</span> },
];

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> fiatCurrencies = [
  { <span class="hljs-attr">value</span>: <span class="hljs-string">"USD"</span>, <span class="hljs-attr">label</span>: <span class="hljs-string">"US Dollar"</span> },
  { <span class="hljs-attr">value</span>: <span class="hljs-string">"GBP"</span>, <span class="hljs-attr">label</span>: <span class="hljs-string">"British Pound"</span> },
  { <span class="hljs-attr">value</span>: <span class="hljs-string">"EUR"</span>, <span class="hljs-attr">label</span>: <span class="hljs-string">"Euro"</span> },
  { <span class="hljs-attr">value</span>: <span class="hljs-string">"NGN"</span>, <span class="hljs-attr">label</span>: <span class="hljs-string">"Naira"</span> },
  { <span class="hljs-attr">value</span>: <span class="hljs-string">"CNY"</span>, <span class="hljs-attr">label</span>: <span class="hljs-string">"Chinese Yuan"</span> },
  { <span class="hljs-attr">value</span>: <span class="hljs-string">"RUB"</span>, <span class="hljs-attr">label</span>: <span class="hljs-string">"Russian Ruble"</span> },
  { <span class="hljs-attr">value</span>: <span class="hljs-string">"SGD"</span>, <span class="hljs-attr">label</span>: <span class="hljs-string">"Singaporean Dollar"</span> },
];
</code></pre>
<p>In the code above, you created two arrays, <code>fiatCurrencies</code> and <code>cryptocurrencies</code>, and exported those arrays. They will be used by the Ant Design <code>Select</code> components.</p>
<p>Next, create a file, <code>ExchangeRate.jsx</code>, inside the <code>components</code> folder.</p>
<p>Add the following code inside the <code>ExchangeRate.jsx</code> file:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { Typography, Select } <span class="hljs-keyword">from</span> <span class="hljs-string">"antd"</span>;
<span class="hljs-keyword">import</span> { cryptocurrencies, fiatCurrencies } <span class="hljs-keyword">from</span> <span class="hljs-string">"./currencies/currencies.jsx"</span>;
<span class="hljs-keyword">import</span> { ExchangeRateUI } <span class="hljs-keyword">from</span> <span class="hljs-string">"./UI/ExchangeRateUI.jsx"</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">ExchangeRate</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [fromCurrency, setFromCurrency] = useState(cryptocurrencies[<span class="hljs-number">0</span>].value);
  <span class="hljs-keyword">const</span> [toCurrency, setToCurrency] = useState(fiatCurrencies[<span class="hljs-number">0</span>].value);

  <span class="hljs-keyword">const</span> handleFromCurrencyChange = <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
    setFromCurrency(e);
    <span class="hljs-built_in">console</span>.log(e);
  };

  <span class="hljs-keyword">const</span> handleToCurrencyChange = <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
    setToCurrency(e);
    <span class="hljs-built_in">console</span>.log(e);
  };

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">section</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"exchange-rate"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Typography.Title</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">color:</span> "#<span class="hljs-attr">4d4add</span>" }} <span class="hljs-attr">level</span>=<span class="hljs-string">{2}</span>&gt;</span>
        Exchange Rate
      <span class="hljs-tag">&lt;/<span class="hljs-name">Typography.Title</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Typography.Text</span>&gt;</span>
        Get the latest exchange rate of cryptocurrencies in your favorite
        currency
      <span class="hljs-tag">&lt;/<span class="hljs-name">Typography.Text</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">section</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"select-group"</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">display:</span> "<span class="hljs-attr">flex</span>", <span class="hljs-attr">marginTop:</span> "<span class="hljs-attr">1rem</span>", <span class="hljs-attr">gap:</span> "<span class="hljs-attr">1rem</span>", <span class="hljs-attr">justifyContent:</span> "<span class="hljs-attr">space-around</span>" }}&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Select</span> <span class="hljs-attr">defaultValue</span>=<span class="hljs-string">{cryptocurrencies[0].value}</span> <span class="hljs-attr">options</span>=<span class="hljs-string">{cryptocurrencies}</span> <span class="hljs-attr">onChange</span>=<span class="hljs-string">{handleFromCurrencyChange}</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Select</span> <span class="hljs-attr">defaultValue</span>=<span class="hljs-string">{fiatCurrencies[0].value}</span> <span class="hljs-attr">options</span>=<span class="hljs-string">{fiatCurrencies}</span> <span class="hljs-attr">onChange</span>=<span class="hljs-string">{handleToCurrencyChange}</span> /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">section</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">section</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">marginTop:</span> "<span class="hljs-attr">1rem</span>" }}&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">ExchangeRateUI</span> /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">section</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">section</span>&gt;</span></span>
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> ExchangeRate;
</code></pre>
<p>In the <code>ExchahgeRate</code> component, you imported the <code>fiatCurrencies</code> and <code>cryptocurrencies</code> arrays from the <code>currencies</code> folder. The <code>fiatCurrencies</code> and <code>cryptocurrencies</code> arrays are used by the <code>Select</code> component to render the selected currencies you wish to convert.</p>
<p>The <code>useState</code> hook is used to keep track of the selected currencies.</p>
<p>The <code>return</code> statement renders the <code>ExchangeRateUI</code> component.</p>
<p>Update your <code>App.jsx</code> file with the following code: <code>import ExchangeRate from './components/ExchangeRate.jsx' function App(){ return ( &lt;ExchangeRate/&gt; ) } export default App;</code></p>
<p>If you followed each step correctly, your web app should look like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/GIF-240109_045936-1-.gif" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>GIF of the crypto app UI</em></p>
<p>Now that the app's UI is set up, let's move on to implementing the data fetching logic.</p>
<h2 id="heading-how-to-fetch-data-with-react-query">How to Fetch Data with React Query</h2>
<p>In this section, you are going to implement the data fetching logic.</p>
<p>Replace the code in the <code>App.jsx</code> file with the following code:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { QueryClient, QueryClientProvider } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-query"</span>;
<span class="hljs-keyword">import</span> ExchangeRate <span class="hljs-keyword">from</span> <span class="hljs-string">"./components/ExchangeRate.jsx"</span>;

<span class="hljs-keyword">const</span> queryClient = <span class="hljs-keyword">new</span> QueryClient(); <span class="hljs-comment">// Instantiate a new QueryClient instance</span>

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">QueryClientProvider</span> <span class="hljs-attr">client</span>=<span class="hljs-string">{queryClient}</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">ExchangeRate</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">QueryClientProvider</span>&gt;</span></span>
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<p>In the code above, you created a <code>queryClient</code> using <code>QueryClient()</code> from <code>react-query</code>. The <code>queryClient</code> is passed to the <code>QueryClientProvider</code> via the <code>client</code> props. This allows the child components to have access to the <code>queryClient</code>, which will be used for data fetching.</p>
<p>React-query comes with good default configurations, but you are going to alter some of these default configurations. This is to avoid rate limiting due to excessive requests to the API.</p>
<p>Replace the <code>queryClient</code> declaration with the following code in your <code>App.jsx</code>:</p>
<pre><code class="lang-js"><span class="hljs-comment">// previous code</span>

<span class="hljs-keyword">const</span> queryClient = <span class="hljs-keyword">new</span> QueryClient({
  <span class="hljs-attr">defaultOptions</span>: {
    <span class="hljs-attr">queries</span>: {
      <span class="hljs-attr">method</span>: <span class="hljs-string">"GET"</span>, <span class="hljs-comment">// Default HTTP method for queries</span>
      <span class="hljs-attr">refetchOnWindowFocus</span>: <span class="hljs-literal">false</span>, <span class="hljs-comment">// Disable automatic refetching on window focus</span>
      <span class="hljs-attr">refetchInterval</span>: <span class="hljs-number">60000</span>, <span class="hljs-comment">// Refetch queries every 60 seconds</span>
    },
  },
});

<span class="hljs-comment">// remaining code</span>
</code></pre>
<p>In the code above, you are stopping the component from refetching when you refocus on the web page. Also, you are increasing the background refetching time to six seconds.</p>
<p>After making the changes, your <code>App.jsx</code> should look like this:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { QueryClient, QueryClientProvider } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-query"</span>;

<span class="hljs-keyword">const</span> queryClient = <span class="hljs-keyword">new</span> QueryClient({
  <span class="hljs-attr">defaultOptions</span>: {
    <span class="hljs-attr">queries</span>: {
      <span class="hljs-attr">refetchOnWindowFocus</span>: <span class="hljs-literal">false</span>, <span class="hljs-comment">// Disable automatic refetching on focus</span>
      <span class="hljs-attr">refetchInterval</span>: <span class="hljs-number">60000</span>, <span class="hljs-comment">// Refetch queries every 60 seconds</span>
    },
  },
});

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">QueryClientProvider</span> <span class="hljs-attr">client</span>=<span class="hljs-string">{queryClient}</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">ExchangeRate</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">QueryClientProvider</span>&gt;</span></span>
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<p>Next, you are going to create the data fetching function in the <code>fetchData</code> folder.</p>
<p>Create a new file called <code>fetchData.jsx</code> in the <code>fetchData</code> folder.</p>
<p>Add the following code in the <code>fetchData.jsx</code> file:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> axios <span class="hljs-keyword">from</span> <span class="hljs-string">'axios'</span> 

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getExchangeRate</span>(<span class="hljs-params">fromCurrency, toCurrency</span>)</span>{ 

    <span class="hljs-keyword">const</span> options = { 
        <span class="hljs-attr">method</span>: <span class="hljs-string">'GET'</span>, 
        <span class="hljs-attr">url</span>: <span class="hljs-string">'https://alpha-vantage.p.rapidapi.com/query'</span>, 
        <span class="hljs-attr">params</span>: { <span class="hljs-attr">from_currency</span>: fromCurrency, <span class="hljs-attr">function</span>: <span class="hljs-string">'CURRENCY_EXCHANGE_RATE'</span>, <span class="hljs-attr">to_currency</span>: toCurrency }, 
        <span class="hljs-attr">headers</span>: { <span class="hljs-string">'X-RapidAPI-Key'</span>: <span class="hljs-string">'YOUR API KEY'</span>, <span class="hljs-string">'X-RapidAPI-Host'</span>: <span class="hljs-string">'alpha-vantage.p.rapidapi.com'</span> } 
    }; 

    <span class="hljs-keyword">return</span> axios.request(options)
        .then(<span class="hljs-function"><span class="hljs-params">res</span> =&gt;</span> { <span class="hljs-keyword">return</span> res.data; })
        .catch(<span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> { <span class="hljs-keyword">return</span> err; }) 

}
</code></pre>
<p>In the code above, you used Axios to fetch data from the AlphaVantage API through RapidAPI.</p>
<p>Remember to replace <code>YOUR API KEY</code> with your API key from RapidAPI.</p>
<p>Navigate to your <code>ExchangeRate</code> component, and add the following code:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { useQuery } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-query'</span>;
<span class="hljs-keyword">import</span> { getExchangeRate } <span class="hljs-keyword">from</span> <span class="hljs-string">'./fetchData/fetchData.jsx'</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">ExchangeRate</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// previous code</span>

  <span class="hljs-keyword">const</span> dependencies = {
    <span class="hljs-attr">fromCurrency</span>: fromCurrency,
    <span class="hljs-attr">toCurrency</span>: toCurrency,
  };

  <span class="hljs-keyword">const</span> { data, isLoading, isError, error } = useQuery({
    <span class="hljs-attr">queryKey</span>: [<span class="hljs-string">"exchangeRate"</span>, dependencies],
    <span class="hljs-attr">queryFn</span>: <span class="hljs-function">() =&gt;</span> fetchData(fromCurrency, toCurrency),
    <span class="hljs-attr">staleTime</span>: <span class="hljs-number">1000</span> * <span class="hljs-number">60</span>,
    <span class="hljs-attr">retry</span>: <span class="hljs-number">1</span>,
    <span class="hljs-attr">retryDelay</span>: <span class="hljs-number">6000</span>,
  });

  <span class="hljs-comment">// further code</span>
}
</code></pre>
<p>In the code above, you used the <code>useQuery</code> hook to fetch the exchange rate data with specified parameters.</p>
<p>The <code>useQuery</code> hook returns an object with many properties – but the following are the properties that are of interest:</p>
<ul>
<li><p><code>data</code>: holds the fetched data.</p>
</li>
<li><p><code>isLoading</code>: indicates if the data is still being fetched.</p>
</li>
<li><p><code>isError</code>: indicates if an error occurred during fetching.</p>
</li>
<li><p><code>error</code>: contains the error details if there's an issue.</p>
</li>
</ul>
<p>The <code>queryKey</code> is an array representing a unique identifier for this query, and <code>staleTime</code> sets the cache to consider data stale after a minute. <code>retry</code> and <code>retryDelay</code> control retry attempts.</p>
<p>Let's look at the <code>data</code> property.</p>
<p>The <code>data</code> property is the response object that is successfully returned from our call to the AlphaVantage API.</p>
<p>The <code>data</code> property looks like this:</p>
<pre><code class="lang-sh">{
  <span class="hljs-string">"Realtime Currency Exchange Rate"</span>: {
    <span class="hljs-string">"1. From_Currency Code"</span>: <span class="hljs-string">"BTC"</span>,
    <span class="hljs-string">"2. From_Currency Name"</span>: <span class="hljs-string">"Bitcoin"</span>,
    <span class="hljs-string">"3. To_Currency Code"</span>: <span class="hljs-string">"USD"</span>,
    <span class="hljs-string">"4. To_Currency Name"</span>: <span class="hljs-string">"United States Dollar"</span>,
    <span class="hljs-string">"5. Exchange Rate"</span>: <span class="hljs-string">"44138.96000000"</span>,
    <span class="hljs-string">"6. Last Refreshed"</span>: <span class="hljs-string">"2024-01-05 00:16:03"</span>,
    <span class="hljs-string">"7. Time Zone"</span>: <span class="hljs-string">"UTC"</span>,
    <span class="hljs-string">"8. Bid Price"</span>: <span class="hljs-string">"44138.96000000"</span>,
    <span class="hljs-string">"9. Ask Price"</span>: <span class="hljs-string">"44138.97000000"</span>
  }
}
</code></pre>
<p>We are only interested in the <code>5. Exchange Rate</code> property of the <code>data</code> property as it contains the exchange rate.</p>
<p>Since we only need the <code>5. Exchange Rate</code>, pass it as props to the <code>ExchangeRateUI</code> component.</p>
<p>After making the changes, your <code>ExchangeRate</code> component should look like this:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> React, { useState, useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> { Typography, Select, Spin } <span class="hljs-keyword">from</span> <span class="hljs-string">'antd'</span>;
<span class="hljs-keyword">import</span> { cryptocurrencies, fiatCurrencies } <span class="hljs-keyword">from</span> <span class="hljs-string">'./currencies/currencies.jsx'</span>;
<span class="hljs-keyword">import</span> { ExchangeRateUI } <span class="hljs-keyword">from</span> <span class="hljs-string">'./UI/ExchangeRateUI.jsx'</span>;
<span class="hljs-keyword">import</span> { useQuery } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-query'</span>;
<span class="hljs-keyword">import</span> { fetchData } <span class="hljs-keyword">from</span> <span class="hljs-string">'./fetchData/fetchData.jsx'</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">ExchangeRate</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [fromCurrency, setFromCurrency] = useState(cryptocurrencies[<span class="hljs-number">0</span>].value);
  <span class="hljs-keyword">const</span> [toCurrency, setToCurrency] = useState(fiatCurrencies[<span class="hljs-number">0</span>].value);
  <span class="hljs-keyword">const</span> [currencySymbol, setCurrencySymbol] = useState(<span class="hljs-string">"Bitcoin"</span>);

  <span class="hljs-keyword">const</span> handleFromCurrencyChange = <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
    setFromCurrency(e);
    <span class="hljs-built_in">console</span>.log(e);
  };

  <span class="hljs-keyword">const</span> handleToCurrencyChange = <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
    setToCurrency(e);
    <span class="hljs-built_in">console</span>.log(e);
  };

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> fromCurrencyLabel = cryptocurrencies.find(<span class="hljs-function"><span class="hljs-params">currency</span> =&gt;</span> currency.value === fromCurrency)?.label;
    setCurrencySymbol(fromCurrencyLabel);
  }, [fromCurrency]);

  <span class="hljs-keyword">const</span> dependencies = { <span class="hljs-attr">fromCurrency</span>: fromCurrency, <span class="hljs-attr">toCurrency</span>: toCurrency };
  <span class="hljs-keyword">const</span> { data, isLoading, isError, error } = useQuery({
    <span class="hljs-attr">queryKey</span>: [<span class="hljs-string">"exchangeRate"</span>, dependencies],
    <span class="hljs-attr">queryFn</span>: <span class="hljs-function">() =&gt;</span> fetchData(fromCurrency, toCurrency),
    <span class="hljs-attr">staleTime</span>: <span class="hljs-number">1000</span> * <span class="hljs-number">60</span>,
    <span class="hljs-attr">retry</span>: <span class="hljs-number">1</span>,
    <span class="hljs-attr">retryDelay</span>: <span class="hljs-number">60000</span>
  });

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

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">section</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"exchange-rate"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Typography.Title</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">color:</span> "#<span class="hljs-attr">4d4add</span>" }} <span class="hljs-attr">level</span>=<span class="hljs-string">{2}</span>&gt;</span>Exchange Rate<span class="hljs-tag">&lt;/<span class="hljs-name">Typography.Title</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Typography.Text</span>&gt;</span>Get the latest exchange rate of cryptocurrencies in your favorite currency<span class="hljs-tag">&lt;/<span class="hljs-name">Typography.Text</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">section</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"select-group"</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">display:</span> "<span class="hljs-attr">flex</span>", <span class="hljs-attr">marginTop:</span> "<span class="hljs-attr">1rem</span>", <span class="hljs-attr">gap:</span> "<span class="hljs-attr">1rem</span>", <span class="hljs-attr">justifyContent:</span> "<span class="hljs-attr">space-around</span>" }}&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Select</span> <span class="hljs-attr">defaultValue</span>=<span class="hljs-string">{cryptocurrencies[0].value}</span> <span class="hljs-attr">options</span>=<span class="hljs-string">{cryptocurrencies}</span> <span class="hljs-attr">onChange</span>=<span class="hljs-string">{handleFromCurrencyChange}</span> /&gt;</span>{' '}
        <span class="hljs-tag">&lt;<span class="hljs-name">Select</span> <span class="hljs-attr">defaultValue</span>=<span class="hljs-string">{fiatCurrencies[0].value}</span> <span class="hljs-attr">options</span>=<span class="hljs-string">{fiatCurrencies}</span> <span class="hljs-attr">onChange</span>=<span class="hljs-string">{handleToCurrencyChange}</span> /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">section</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">section</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">marginTop:</span> '<span class="hljs-attr">1rem</span>' }}&gt;</span>
        {isLoading ? (
          <span class="hljs-tag">&lt;<span class="hljs-name">Spin</span> <span class="hljs-attr">tip</span>=<span class="hljs-string">"Fetching results"</span> <span class="hljs-attr">spinning</span> <span class="hljs-attr">size</span>=<span class="hljs-string">"large"</span> /&gt;</span>
        ) : isError ? (
          <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>Error: {error.message}<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        ) : (
          <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">ExchangeRateUI</span> <span class="hljs-attr">price</span>=<span class="hljs-string">{data[</span>"<span class="hljs-attr">Realtime</span> <span class="hljs-attr">Currency</span> <span class="hljs-attr">Exchange</span> <span class="hljs-attr">Rate</span>"]["<span class="hljs-attr">5.</span> <span class="hljs-attr">Exchange</span> <span class="hljs-attr">Rate</span>"]} <span class="hljs-attr">dataObj</span>=<span class="hljs-string">{dependencies}</span> <span class="hljs-attr">currencySymbol</span>=<span class="hljs-string">{currencySymbol}</span> /&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        )}
      <span class="hljs-tag">&lt;/<span class="hljs-name">section</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">section</span>&gt;</span></span>
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> ExchangeRate;
</code></pre>
<p>In the code above, you used the ternary operator to render different content based on the state of the data fetching.</p>
<p><code>currencySymbol</code> is used to keep track of the cryptocurrency you're checking the exchange rate of.</p>
<p>Open your console and you should see something like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/Screenshot_20240105-015827-1-.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Now that you're done implementing the data fetching logic, let's display the data in the UI.</p>
<h2 id="heading-how-to-display-the-data-in-the-ui">How to Display the data in the UI</h2>
<p>In this section, you are going to add the final touches to the web app.</p>
<p>Update your <code>ExchangeRateUI.jsx</code> file with the following code:</p>
<pre><code class="lang-js"><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> { Typography, Card } <span class="hljs-keyword">from</span> <span class="hljs-string">"antd"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">ExchangeRateUI</span>(<span class="hljs-params">props</span>) </span>{
  <span class="hljs-keyword">const</span> { price, dataObj, currencySymbol } = props;
  <span class="hljs-keyword">const</span> toCurrency = dataObj.toCurrency;
  <span class="hljs-keyword">let</span> value = <span class="hljs-built_in">Number</span>(price);
  <span class="hljs-keyword">let</span> currencyCode = toCurrency;

  <span class="hljs-keyword">let</span> currency = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Intl</span>.NumberFormat(<span class="hljs-string">'en-US'</span>, {
    <span class="hljs-attr">style</span>: <span class="hljs-string">'currency'</span>,
    <span class="hljs-attr">currency</span>: currencyCode,
  });

  <span class="hljs-keyword">let</span> formattedCurrency = currency.format(value);

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"exchange-rate-ui"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Card</span> <span class="hljs-attr">extra</span>=<span class="hljs-string">{currencySymbol}</span> <span class="hljs-attr">bordered</span>=<span class="hljs-string">{false}</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">width:</span> <span class="hljs-attr">350</span>, <span class="hljs-attr">backgroundColor:</span> "#<span class="hljs-attr">4d4add</span>", <span class="hljs-attr">color:</span> '#<span class="hljs-attr">fff</span>' }} <span class="hljs-attr">size</span>=<span class="hljs-string">"default"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Typography.Paragraph</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">color:</span> "#<span class="hljs-attr">fff</span>" }}&gt;</span>{formattedCurrency}<span class="hljs-tag">&lt;/<span class="hljs-name">Typography.Paragraph</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">Card</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}
</code></pre>
<p>Let's understand what's going on in the code above.</p>
<p>First, you take the raw price representing the exchange rate, ensure it's treated as a number, determine the currency code, and then format the numeric value into a user-friendly currency string. This formatted currency is used for display in the user interface to provide a clear and standardized representation of the exchange rate.</p>
<p>Next, the formatted currency is displayed within a styled <code>Card</code> component from Ant Design. The <code>Card</code> includes the <code>currencySymbol</code> as an extra element. The background color, width, and text color are styled for a visually appealing UI.</p>
<p>Your app should look like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/GIF-240109_050748-1-.gif" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>GIF of complete working crypto app</em></p>
<p>Congratulations. You have built a crypto exchange rate web app in React.</p>
<h2 id="heading-error-handling">Error Handling</h2>
<p>Error handling is a crucial aspect of developing web apps. This is because web apps and software in general can experience crashes and down time.</p>
<p>In React, when your app crashes, it usually shows a white/blank screen. This doesn't make for a good user experience. You'll want to display some kind of information to your users if your app crashes.</p>
<p>In this section, you are going to use the <code>ErrorBoundary</code> component to handle app crashes.</p>
<p>Create a file called <code>ErrorBoundary.jsx</code> in the <code>src</code> folder.</p>
<p>Add the following code in the <code>ErrorBoundary.jsx</code> file:</p>
<pre><code class="lang-js"><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> { Typography } <span class="hljs-keyword">from</span> <span class="hljs-string">"antd"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ErrorBoundary</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">React</span>.<span class="hljs-title">Component</span> </span>{
  <span class="hljs-keyword">constructor</span>(props) {
    <span class="hljs-built_in">super</span>(props);
    <span class="hljs-built_in">this</span>.state = { <span class="hljs-attr">hasError</span>: <span class="hljs-literal">false</span> };
  }

  <span class="hljs-keyword">static</span> getDerivedStateFromError(error) {
    <span class="hljs-comment">// Update state so the next render will show the fallback UI.</span>
    <span class="hljs-keyword">return</span> { <span class="hljs-attr">hasError</span>: <span class="hljs-literal">true</span> };
  }

  componentDidCatch(error, errorInfo) {
    <span class="hljs-comment">// You can also log the error to an error reporting service</span>
    <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Caught error: "</span>);
  }

  render() {
    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.state.hasError) {
      <span class="hljs-comment">// You can render any custom fallback UI</span>
      <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Typography.Title</span> <span class="hljs-attr">level</span>=<span class="hljs-string">{4}</span>&gt;</span>Something went wrong.<span class="hljs-tag">&lt;/<span class="hljs-name">Typography.Title</span>&gt;</span></span>;
    }

    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.props.children;
  }
}
</code></pre>
<p>In the code above, you are rendering the message "Something went wrong" if there's an error rendering your app.</p>
<p>Update your <code>index.jsx</code> file with the following code:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> ReactDOM <span class="hljs-keyword">from</span> <span class="hljs-string">'react-dom/client'</span>;
<span class="hljs-keyword">import</span> App <span class="hljs-keyword">from</span> <span class="hljs-string">'./App'</span>;
<span class="hljs-keyword">import</span> ErrorBoundary <span class="hljs-keyword">from</span> <span class="hljs-string">'./ErrorBoundary'</span>;

ReactDOM.createRoot(<span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'root'</span>)).render(
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">React.StrictMode</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">ErrorBoundary</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">App</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">ErrorBoundary</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">React.StrictMode</span>&gt;</span></span>
);
</code></pre>
<p>Visit the <a target="_blank" href="https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary">React docs</a> for more information on ErrorBoundary.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>This article walked you through how to work with third-party APIs in React by building a crypto exchange rate web app.</p>
<p>But don't stop here. You can improve this project by adding a news feature and styling it to your taste. Also, you can decide to support more currencies.</p>
<h3 id="heading-additional-resources">Additional resources</h3>
<ul>
<li><p><a target="_blank" href="https://tanstack.com/query/v3/docs/react/guides/paginated-queries">React-query documentation</a></p>
</li>
<li><p><a target="_blank" href="https://ant.design/components">Ant Design UI library</a></p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
