<?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[ Olaleye Blessing - 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[ Olaleye Blessing - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Wed, 13 May 2026 17:46:41 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/author/Jongbo/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Simplify Your React Components with Derived State ]]>
                </title>
                <description>
                    <![CDATA[ React simplifies building user interfaces with hooks like useState for managing dynamic values. But it's common to overuse useState. This often leads to duplicated data and unnecessary complexity. For instance, you might store a full name in state wh... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/simplify-react-components-with-derived-state/</link>
                <guid isPermaLink="false">6924c6fb01ae89275dfb14f6</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ TypeScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ ReactHooks ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Olaleye Blessing ]]>
                </dc:creator>
                <pubDate>Mon, 24 Nov 2025 20:58:35 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1764017894421/6bbd5cf2-c221-490c-891a-bf984cfaa92c.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>React simplifies building user interfaces with hooks like <code>useState</code> for managing dynamic values. But it's common to overuse <code>useState</code>. This often leads to duplicated data and unnecessary complexity.</p>
<p>For instance, you might store a full name in state when it can be calculated from first and last name props, or duplicate fetched data from a library like React Query. This creates issues like harder debugging, extra re-renders, and synchronization problems.</p>
<p>In this tutorial, you'll learn how to use derived state to improve your components. By the end, you'll know when to derive state instead of storing it, making your React code cleaner and more maintainable.</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-what-is-a-derived-state">What Is a Derived State?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-derive-state-from-existing-data">How to Derive State From Existing Data</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-how-to-derive-state-from-props-or-other-state">How to Derive State From Props or Other State</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-derive-state-from-a-url">How to Derive State From a URL</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-derive-state-from-external-data">How to Derive State From External Data</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-prevent-recalculation-of-a-derived-state">How to Prevent Recalculation of a Derived State</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-when-to-use-usestate">When to Use useState</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-controlled-input">Controlled Input</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-independent-state-changes">Independent State Changes</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ol>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before you move on, make sure you have:</p>
<ul>
<li><p>Basic knowledge of JavaScript, TypeScript, React, and React hooks</p>
</li>
<li><p>Understanding of asynchronous calls</p>
</li>
<li><p>A simple React development setup.</p>
</li>
</ul>
<p>If you don’t have a React development setup, you can head over to the <a target="_blank" href="https://github.com/Olaleye-Blessing/freecodecamp-derived-states">derived-state repo</a>. The repo has been set up with <a target="_blank" href="https://reactrouter.com/">React Router</a> and <a target="_blank" href="https://tanstack.com/query/latest/docs/framework/react/overview">React Query</a>. Run the commands below to set it up:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># clone the repo</span>
git <span class="hljs-built_in">clone</span> &lt;https://github.com/Olaleye-Blessing/freecodecamp-derived-states.git&gt;

<span class="hljs-comment"># navigate to the folder</span>
<span class="hljs-built_in">cd</span> freecodecamp-derived-states

<span class="hljs-comment"># install the packages</span>
pnpm install

<span class="hljs-comment"># start development</span>
pnpm dev
</code></pre>
<h2 id="heading-what-is-a-derived-state">What Is a Derived State?</h2>
<p>A derived state is any value that can be calculated from existing data. This existing data can be from:</p>
<ul>
<li><p><strong>Props:</strong> Data passed from a parent component.</p>
</li>
<li><p><strong>Existing state:</strong> Other state variables already in your component.</p>
</li>
<li><p><strong>URL parameters:</strong> Data from routes or query strings.</p>
</li>
<li><p><strong>External data:</strong> Data from a fetching library like React Query.</p>
</li>
</ul>
<p>Storing derivable state in <code>useState</code> can create several issues. First, it can cause debugging problems: more state variables make it harder to trace data flow. The more states you have, the more the number of state changes you need to keep track of while debugging.</p>
<p>It can also cause unnecessary re-renders. React will trigger a re-render every time you call the state setter function.</p>
<p>Finally, there can be synchronization issues, as you’re forced to update the “derived state” anytime the source data changes manually. When similar data exists in multiple states, they can get out of sync.</p>
<h2 id="heading-how-to-derive-state-from-existing-data">How to Derive State From Existing Data</h2>
<p>Moving forward, we’ll explore common scenarios where you can derive state instead of storing it in another <code>useState</code>.</p>
<p>Note: All the code in this article is run in <a target="_blank" href="https://react.dev/reference/react/StrictMode">non-StrictMode</a>.</p>
<h3 id="heading-how-to-derive-state-from-props-or-other-state">How to Derive State From Props or Other State</h3>
<p>In this section, we’ll first examine the problems caused by using <code>useState</code> for derivable values. We’ll look at a form component that stores unnecessary states like full name, adult status, and a local copy of an email. You’ll see the re-renders in different scenarios and then learn how to refactor using derived state to remove these problems.</p>
<h4 id="heading-the-problem-with-extra-states">The Problem With Extra States</h4>
<p>Any value that comes directly from props or other state can be derived on the fly. Take the below as an example, where you pass an <code>email</code> prop to a form component:</p>
<pre><code class="lang-typescript">&lt;DetailForm email=<span class="hljs-string">"olaleyedev@gmail.com"</span> /&gt;
</code></pre>
<p>Here is how the form receives the email prop and also manages its own state:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useEffect, useState, <span class="hljs-keyword">type</span> FormEventHandler } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

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

<span class="hljs-keyword">const</span> DetailForm = <span class="hljs-function">(<span class="hljs-params">{ email }: DetailFormProps</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> [lastName, setLastName] = useState(<span class="hljs-string">""</span>);
  <span class="hljs-keyword">const</span> [fullName, setFullName] = useState(<span class="hljs-string">""</span>);
  <span class="hljs-keyword">const</span> [firstName, setFirstName] = useState(<span class="hljs-string">""</span>);
  <span class="hljs-keyword">const</span> [age, setAge] = useState(<span class="hljs-number">0</span>);
  <span class="hljs-keyword">const</span> [isAdult, setIsAdult] = useState(<span class="hljs-literal">false</span>);
  <span class="hljs-keyword">const</span> [localEmail, setLocalEmail] = useState(email);

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

  useEffect(<span class="hljs-function">() =&gt;</span> {
    setFullName(<span class="hljs-string">`<span class="hljs-subst">${firstName}</span> <span class="hljs-subst">${lastName}</span>`</span>.trim());
  }, [firstName, lastName]);

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

  <span class="hljs-keyword">const</span> submitForm: FormEventHandler&lt;HTMLFormElement&gt; = <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
    e.preventDefault();
    <span class="hljs-built_in">console</span>.log({ fullName, age, isAdult });
  };

  <span class="hljs-built_in">console</span>.count(<span class="hljs-string">"-- Form render --"</span>);

  <span class="hljs-keyword">return</span> (
    &lt;form onSubmit={submitForm}&gt;
      &lt;div style={{ marginBottom: <span class="hljs-string">"1rem"</span> }}&gt;
        &lt;label
          htmlFor=<span class="hljs-string">"firstName"</span>
          style={{
            display: <span class="hljs-string">"inline-block"</span>,
            marginRight: <span class="hljs-string">"1rem"</span>,
            width: <span class="hljs-string">"10rem"</span>,
          }}
        &gt;
          First Name
        &lt;/label&gt;
        &lt;input
          id=<span class="hljs-string">"firstName"</span>
          <span class="hljs-keyword">type</span>=<span class="hljs-string">"text"</span>
          onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> setFirstName(e.target.value)}
        /&gt;
      &lt;/div&gt;
      &lt;div style={{ marginBottom: <span class="hljs-string">"1rem"</span> }}&gt;
        &lt;label
          htmlFor=<span class="hljs-string">"lastName"</span>
          style={{
            display: <span class="hljs-string">"inline-block"</span>,
            marginRight: <span class="hljs-string">"1rem"</span>,
            width: <span class="hljs-string">"10rem"</span>,
          }}
        &gt;
          Last Name
        &lt;/label&gt;
        &lt;input
          id=<span class="hljs-string">"lastName"</span>
          <span class="hljs-keyword">type</span>=<span class="hljs-string">"text"</span>
          onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> setLastName(e.target.value)}
        /&gt;
      &lt;/div&gt;
      &lt;div style={{ marginBottom: <span class="hljs-string">"1rem"</span> }}&gt;
        &lt;label
          htmlFor=<span class="hljs-string">"age"</span>
          style={{
            display: <span class="hljs-string">"inline-block"</span>,
            marginRight: <span class="hljs-string">"1rem"</span>,
            width: <span class="hljs-string">"10rem"</span>,
          }}
        &gt;
          Age
        &lt;/label&gt;
        &lt;input <span class="hljs-keyword">type</span>=<span class="hljs-string">"number"</span> onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> setAge(<span class="hljs-built_in">Number</span>(e.target.value))} /&gt;
      &lt;/div&gt;
      &lt;p&gt;
        Your receipt will have{<span class="hljs-string">" "</span>}
        &lt;span
          style={{
            borderBottom: <span class="hljs-string">"1px solid #fffa"</span>,
            paddingBottom: <span class="hljs-string">"1px"</span>,
          }}
        &gt;
          {fullName.trim() || <span class="hljs-string">"-----"</span>}
        &lt;/span&gt;{<span class="hljs-string">" "</span>}
        <span class="hljs-keyword">as</span> the recipient<span class="hljs-string">'s name and will be sent to {localEmail || "-----"}.
      &lt;/p&gt;
      &lt;p&gt;
        {isAdult
          ? "You are allowed to order a drink."
          : "You are not allowed to order any drinks."}
      &lt;/p&gt;
      &lt;button type="submit" disabled={!isAdult}&gt;
        Submit
      &lt;/button&gt;
    &lt;/form&gt;
  );
};

export default DetailForm;</span>
</code></pre>
<p>The form component collects information from a user. It shows them more information based on their input and the provided email. It maintains some states to keep track of the user’s input.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762625903293/c952e785-2b37-4411-987f-01686db8f54e.png" alt="c952e785-2b37-4411-987f-01686db8f54e" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>While it looks simple, there are some issues with the way state is being used here.</p>
<p>First of all, a React component re-renders whenever any of its states change. Looking at the UI, the form should only re-render when users input their first name, last name, and age, or when the email prop changes from the parent. This means the form should keep track of just these three states internally, while deriving from the prop and other states.</p>
<p>But the form keeps track of three extra states (<code>fullName</code>, <code>isAdult</code>, and <code>localEmail</code>). This means that the form will re-render each time these extra states change.</p>
<p>Let’s see this in action:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762626688959/f381aa1f-6244-4065-aa36-123134f26b1b.gif" alt="f381aa1f-6244-4065-aa36-123134f26b1b" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>As you can see, the form re-rendered twice when the input field changed, instead of once. The form re-rendered the first time the user updated their first name. But for each time the first and last name changed, the second <code>useEffect</code> gets triggered.</p>
<pre><code class="lang-typescript">useEffect(<span class="hljs-function">() =&gt;</span> {
  setFullName(<span class="hljs-string">`<span class="hljs-subst">${firstName}</span> <span class="hljs-subst">${lastName}</span>`</span>.trim());
}, [firstName, lastName]);
</code></pre>
<p>This is to sync the <code>fullName</code> with the <code>firstName</code> and <code>lastName</code>. Updating the <code>fullName</code> causes the form to re-render the second time.</p>
<p>The same applies to the <code>isAdult</code> state. When the user updates their <code>age</code>, <code>setAge()</code> re-renders the form, and the third <code>useEffect</code> also causes another re-render.</p>
<h4 id="heading-handling-email-changes">Handling Email Changes</h4>
<p>Let’s see what happens when you update the email from the parent component. This is the code that allows you to update the email:</p>
<pre><code class="lang-typescript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [email, setEmail] = useState(<span class="hljs-string">"olaleyedev@gmail.com"</span>);

  <span class="hljs-keyword">return</span> (
    &lt;&gt;
      &lt;div
        style={{
          marginBottom: <span class="hljs-string">"1rem"</span>,
          border: <span class="hljs-string">"1px solid #fff"</span>,
          paddingBottom: <span class="hljs-string">"1rem"</span>,
        }}
      &gt;
        &lt;h3&gt;Parent Component&lt;/h3&gt;
        &lt;input
          style={{ padding: <span class="hljs-string">"0.4rem 0.8rem"</span> }}
          <span class="hljs-keyword">type</span>=<span class="hljs-string">"email"</span>
          value={email}
          onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> setEmail(e.target.value)}
        /&gt;
      &lt;/div&gt;
      &lt;DetailForm email={email} /&gt;
    &lt;/&gt;
  );
}
</code></pre>
<p>The parent component keeps track of the user’s email and also updates it through an <code>input</code> element.</p>
<p>Note: In the <code>&lt;Detail /&gt;</code> component, I’ve removed the <code>useEffect</code>s that sync the <code>fullName</code> and <code>isAdult</code>. This is to focus mainly on the <code>email</code> prop. The form component now has this:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useEffect, useState, <span class="hljs-keyword">type</span> FormEventHandler } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

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

<span class="hljs-keyword">const</span> DetailForm = <span class="hljs-function">(<span class="hljs-params">{ email }: DetailFormProps</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> [lastName, setLastName] = useState(<span class="hljs-string">""</span>);
  <span class="hljs-keyword">const</span> [fullName, setFullName] = useState(<span class="hljs-string">""</span>);
  <span class="hljs-keyword">const</span> [firstName, setFirstName] = useState(<span class="hljs-string">""</span>);
  <span class="hljs-keyword">const</span> [age, setAge] = useState(<span class="hljs-number">0</span>);
  <span class="hljs-keyword">const</span> [isAdult, setIsAdult] = useState(<span class="hljs-literal">false</span>);
  <span class="hljs-keyword">const</span> [localEmail, setLocalEmail] = useState(email);

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

  <span class="hljs-keyword">const</span> submitForm: FormEventHandler&lt;HTMLFormElement&gt; = <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
    e.preventDefault();
    <span class="hljs-built_in">console</span>.log({ fullName, age, isAdult });
  };

  <span class="hljs-built_in">console</span>.count(<span class="hljs-string">"-- Form render --"</span>);

  <span class="hljs-keyword">return</span> &lt;form onSubmit={submitForm}&gt;{<span class="hljs-comment">/* Rest */</span>}&lt;/form&gt;;
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> DetailForm;
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762626841744/9beffe7d-2513-4d3e-9970-21477b16161e.gif" alt="9beffe7d-2513-4d3e-9970-21477b16161e" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Updating the email from the parent component causes the detail form to have an extra re-render. Because you are saving the email to a local state, any change to the <code>email</code> prop triggers the <code>useEffect</code> to update the state, causing an extra re-render. Your UI will show stale data without <code>useEffect</code>.</p>
<h4 id="heading-how-to-solve-this-with-derived-states">How to Solve This with Derived States</h4>
<p>You can solve all these extra re-renders by using derived states. Neither <code>fullName</code> nor <code>isAdult</code> directly needs the user’s input for us to know their value. You can calculate their value when their dependent states change without using <code>useEffect</code>. Likewise, you can derive the <code>email</code> directly by using the prop value on the fly.</p>
<pre><code class="lang-diff">import { useEffect, useState, type FormEventHandler } from "react";

interface DetailFormProps {
  email: string;
}

const DetailForm = ({ email }: DetailFormProps) =&gt; {
  const [lastName, setLastName] = useState("");
  const [firstName, setFirstName] = useState("");
<span class="hljs-deletion">- const [fullName, setFullName] = useState("");</span>
  const [age, setAge] = useState(0);
<span class="hljs-deletion">- const [isAdult, setIsAdult] = useState(false);</span>
<span class="hljs-deletion">- const [localEmail, setLocalEmail] = useState(email);</span>

<span class="hljs-deletion">- useEffect(() =&gt; {</span>
<span class="hljs-deletion">-   setFullName(`${firstName} ${lastName}`.trim());</span>
<span class="hljs-deletion">- }, [firstName, lastName]);</span>

<span class="hljs-deletion">- useEffect(() =&gt; {</span>
<span class="hljs-deletion">-   setIsAdult(age &gt; 18);</span>
<span class="hljs-deletion">- }, [age]);</span>

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

<span class="hljs-addition">+ const fullName = `${firstName} ${lastName}`.trim();</span>
<span class="hljs-addition">+ const isAdult = age &gt; 18;</span>

  const submitForm: FormEventHandler&lt;HTMLFormElement&gt; = (e) =&gt; {
    e.preventDefault();
<span class="hljs-deletion">-   console.log({ fullName, age, isAdult, email: localEmail });</span>
<span class="hljs-addition">+   console.log({ fullName, age, isAdult, email });</span>
  };
};

export default DetailForm;
</code></pre>
<p>The form now stores the necessary state, <code>lastName</code>, <code>firstName</code>, and <code>age</code>. The <code>fullName</code> and <code>isAdult</code> values are derived directly from their dependent values.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762627041623/69004769-ddd9-470c-9212-ba7711350b57.gif" alt="69004769-ddd9-470c-9212-ba7711350b57" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>As you can see with the derived state, we have 0 extra re-renders. The form re-rendered only when it should. The form re-rendered once when each input value changed, not twice. Also, when the email changes in the parent component, there is only one re-render.</p>
<h3 id="heading-how-to-derive-state-from-a-url">How to Derive State From a URL</h3>
<p>You can also use a URL to save dynamic data through route parameters or query strings. Every routing library provides some hooks to access this data.</p>
<p>For example, React Router provides <code>useParams</code> or <code>useSearchParams</code>. In most cases, the returned values from these hooks don’t need to go into <code>useState</code> again – as this would mean keeping track of two storage, <code>useState</code> and <code>URL</code>.</p>
<p>Now, we’ll look at some examples using search parameters from React Router. You’ll first see an example that overuses <code>useState</code> and causes extra re-renders and sync issues. Then you’ll see its derived solution.</p>
<h4 id="heading-syncing-with-useeffect">Syncing with <code>useEffect</code></h4>
<p>The component you will be using is a filter component:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { useSearchParams } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-router"</span>;

<span class="hljs-keyword">const</span> Products = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> [searchParams, setSearchParams] = useSearchParams();
  <span class="hljs-keyword">const</span> [searchQuery, setSearchQuery] = useState(<span class="hljs-string">""</span>);
  <span class="hljs-keyword">const</span> [selectedCategory, setSelectedCategory] = useState(<span class="hljs-string">""</span>);
  <span class="hljs-keyword">const</span> [selectedMaterial, setSelectedMaterial] = useState(<span class="hljs-string">""</span>);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    setSearchQuery(searchParams.get(<span class="hljs-string">"search"</span>) || <span class="hljs-string">""</span>);
    setSelectedCategory(searchParams.get(<span class="hljs-string">"category"</span>) || <span class="hljs-string">""</span>);
    setSelectedMaterial(searchParams.get(<span class="hljs-string">"material"</span>) || <span class="hljs-string">""</span>);
  }, [searchParams]);

  <span class="hljs-built_in">console</span>.count(<span class="hljs-string">"__ RENDERDED ___"</span>);

  <span class="hljs-keyword">return</span> (
    &lt;div
      style={{
        display: <span class="hljs-string">"flex"</span>,
        alignItems: <span class="hljs-string">"center"</span>,
        justifyContent: <span class="hljs-string">"center"</span>,
        gap: <span class="hljs-string">"1rem"</span>,
      }}
    &gt;
      &lt;input value={searchQuery} /&gt;
      &lt;select value={selectedCategory}&gt;
        &lt;option value=<span class="hljs-string">""</span>&gt;All Categories&lt;/option&gt;
        &lt;option value=<span class="hljs-string">"electronics"</span>&gt;Electronics&lt;/option&gt;
        &lt;option value=<span class="hljs-string">"clothing"</span>&gt;Clothing&lt;/option&gt;
      &lt;/select&gt;
      &lt;select value={selectedMaterial}&gt;
        &lt;option value=<span class="hljs-string">""</span>&gt;All Material&lt;/option&gt;
        &lt;option value=<span class="hljs-string">"leather"</span>&gt;Leather&lt;/option&gt;
        &lt;option value=<span class="hljs-string">"silk"</span>&gt;Silk&lt;/option&gt;
      &lt;/select&gt;
      &lt;button&gt;Clear Filters&lt;/button&gt;
    &lt;/div&gt;
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Products;
</code></pre>
<p>This component allows users to search for a product and/or filter by category and materials. While it looks simple, this component has all the problems mentioned earlier.</p>
<p>Let’s see what happens when the component mounts with no filter and predefined filters.</p>
<h5 id="heading-with-no-filter">With no filter:</h5>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762627432720/fb1ffe32-b3ae-4506-9a33-cdd42cffb5dd.gif" alt="fb1ffe32-b3ae-4506-9a33-cdd42cffb5dd" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>The component only renders once when there's no filter. This is good.</p>
<h5 id="heading-with-a-predefined-filter">With a predefined filter:</h5>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762627673969/3a29eb6d-694d-4499-8f78-06cd8702ae3d.gif" alt="3a29eb6d-694d-4499-8f78-06cd8702ae3d" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>It renders twice because you are trying to make sure your <code>useState</code>s are in sync with the <code>URL</code> by using <code>useEffect</code>. The component renders the first time it mounts, then re-renders because of the dependency in <code>useEffect</code>.</p>
<h4 id="heading-initializing-usestate-directly">Initializing <code>useState</code> Directly</h4>
<p>You could be thinking that it’s better to move the getters into the <code>useState</code>s directly, which truly solves the re-rendering case when the component mounts.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> Products = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> [searchParams, setSearchParams] = useSearchParams();
  <span class="hljs-keyword">const</span> [searchQuery, setSearchQuery] = useState(
    searchParams.get(<span class="hljs-string">"search"</span>) || <span class="hljs-string">""</span>,
  );
  <span class="hljs-keyword">const</span> [selectedCategory, setSelectedCategory] = useState(
    searchParams.get(<span class="hljs-string">"category"</span>) || <span class="hljs-string">""</span>,
  );
  <span class="hljs-keyword">const</span> [selectedMaterial, setSelectedMaterial] = useState(
    searchParams.get(<span class="hljs-string">"material"</span>) || <span class="hljs-string">""</span>,
  );

  <span class="hljs-comment">// prev code</span>
};
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762627883127/76b4af17-a0c6-4f7d-bc40-fbf013fb2ad9.gif" alt="76b4af17-a0c6-4f7d-bc40-fbf013fb2ad9" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>As you can see, the component only renders once. There is no more <code>useEffect</code> to trigger another re-render.</p>
<p>Just keep in mind that this isn’t a solution yet. It has other problems you’ll see soon.</p>
<p>What happens when any of the filtering changes or when you enter a search query?</p>
<h4 id="heading-handling-changes">Handling Changes</h4>
<p>Both solution shows that you have unnecessary re-renders. This is because you have to keep two states in sync: both the <code>useState</code>s and the <code>URL</code>.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { useSearchParams } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-router"</span>;

<span class="hljs-keyword">const</span> Products = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-comment">// prev code</span>

  <span class="hljs-keyword">const</span> handleSearchChange = <span class="hljs-function">(<span class="hljs-params">query: <span class="hljs-built_in">string</span></span>) =&gt;</span> {
    setSearchQuery(query);
    setSearchParams(<span class="hljs-function">(<span class="hljs-params">prev</span>) =&gt;</span> {
      <span class="hljs-keyword">const</span> newParams = <span class="hljs-keyword">new</span> URLSearchParams(prev);
      <span class="hljs-keyword">if</span> (query) {
        newParams.set(<span class="hljs-string">"search"</span>, query);
      } <span class="hljs-keyword">else</span> {
        newParams.delete(<span class="hljs-string">"search"</span>);
      }
      <span class="hljs-keyword">return</span> newParams;
    });
  };

  <span class="hljs-keyword">const</span> handleCategoryChange = <span class="hljs-function">(<span class="hljs-params">category: <span class="hljs-built_in">string</span></span>) =&gt;</span> {
    setSelectedCategory(category);
    setSearchParams(<span class="hljs-function">(<span class="hljs-params">prev</span>) =&gt;</span> {
      <span class="hljs-keyword">const</span> newParams = <span class="hljs-keyword">new</span> URLSearchParams(prev);
      <span class="hljs-keyword">if</span> (category) {
        newParams.set(<span class="hljs-string">"category"</span>, category);
      } <span class="hljs-keyword">else</span> {
        newParams.delete(<span class="hljs-string">"category"</span>);
      }
      <span class="hljs-keyword">return</span> newParams;
    });
  };

  <span class="hljs-keyword">const</span> handleMaterialChange = <span class="hljs-function">(<span class="hljs-params">material: <span class="hljs-built_in">string</span></span>) =&gt;</span> {
    setSelectedMaterial(material);
    setSearchParams(<span class="hljs-function">(<span class="hljs-params">prev</span>) =&gt;</span> {
      <span class="hljs-keyword">const</span> newParams = <span class="hljs-keyword">new</span> URLSearchParams(prev);
      <span class="hljs-keyword">if</span> (material) {
        newParams.set(<span class="hljs-string">"material"</span>, material);
      } <span class="hljs-keyword">else</span> {
        newParams.delete(<span class="hljs-string">"material"</span>);
      }
      <span class="hljs-keyword">return</span> newParams;
    });
  };

  <span class="hljs-keyword">const</span> clearFilters = <span class="hljs-function">() =&gt;</span> {
    setSearchQuery(<span class="hljs-string">""</span>);
    setSelectedCategory(<span class="hljs-string">""</span>);
    setSelectedMaterial(<span class="hljs-string">""</span>);
    setSearchParams({});
  };

  <span class="hljs-built_in">console</span>.count(<span class="hljs-string">"__ RENDERDED ___"</span>);

  <span class="hljs-keyword">return</span> (
    &lt;div
      style={{
        display: <span class="hljs-string">"flex"</span>,
        alignItems: <span class="hljs-string">"center"</span>,
        justifyContent: <span class="hljs-string">"center"</span>,
        gap: <span class="hljs-string">"1rem"</span>,
      }}
    &gt;
      &lt;input
        value={searchQuery}
        onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> handleSearchChange(e.target.value)}
      /&gt;
      &lt;select
        value={selectedCategory}
        onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> handleCategoryChange(e.target.value)}
      &gt;
        {<span class="hljs-comment">/* prev options */</span>}
      &lt;/select&gt;
      &lt;select
        value={selectedMaterial}
        onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> handleMaterialChange(e.target.value)}
      &gt;
        {<span class="hljs-comment">/* prev options */</span>}
      &lt;/select&gt;
      &lt;button onClick={clearFilters}&gt;Clear Filters&lt;/button&gt;
    &lt;/div&gt;
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Products;
</code></pre>
<p>Because you are tracking the values in <code>useState</code>, you need to update the <code>useState</code> first before updating the <code>URL</code>. The UI won’t get updated if you update the <code>URL</code> without updating the <code>useState</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762628106580/c28d2453-9faa-460d-9cfa-d000b9ed41a4.gif" alt="c28d2453-9faa-460d-9cfa-d000b9ed41a4" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Notice that each change leads to 2-3 re-renders.</p>
<h4 id="heading-using-derived-state">Using Derived State</h4>
<p>Using a derived state will solve all the issues you encountered. Your state can be derived directly from the <code>URL</code> without using <code>useState</code> and/or <code>useEffect</code>.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useSearchParams } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-router"</span>;

<span class="hljs-keyword">const</span> Products = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> [searchParams, setSearchParams] = useSearchParams();

  <span class="hljs-keyword">const</span> searchQuery = searchParams.get(<span class="hljs-string">"search"</span>) || <span class="hljs-string">""</span>;
  <span class="hljs-keyword">const</span> selectedCategory = searchParams.get(<span class="hljs-string">"category"</span>) || <span class="hljs-string">""</span>;
  <span class="hljs-keyword">const</span> selectedMaterial = searchParams.get(<span class="hljs-string">"material"</span>) || <span class="hljs-string">""</span>;

  <span class="hljs-keyword">const</span> updateFilter = <span class="hljs-function">(<span class="hljs-params">key: <span class="hljs-built_in">string</span>, value: <span class="hljs-built_in">string</span></span>) =&gt;</span> {
    setSearchParams(<span class="hljs-function">(<span class="hljs-params">prev</span>) =&gt;</span> {
      <span class="hljs-keyword">const</span> newParams = <span class="hljs-keyword">new</span> URLSearchParams(prev);
      <span class="hljs-keyword">if</span> (value) {
        newParams.set(key, value);
      } <span class="hljs-keyword">else</span> {
        newParams.delete(key);
      }
      <span class="hljs-keyword">return</span> newParams;
    });
  };

  <span class="hljs-keyword">const</span> clearFilters = <span class="hljs-function">() =&gt;</span> {
    setSearchParams({});
  };

  <span class="hljs-built_in">console</span>.count(<span class="hljs-string">"__ RENDERDED ___"</span>);

  <span class="hljs-keyword">return</span> (
    &lt;div
      style={{
        display: <span class="hljs-string">"flex"</span>,
        alignItems: <span class="hljs-string">"center"</span>,
        justifyContent: <span class="hljs-string">"center"</span>,
        gap: <span class="hljs-string">"1rem"</span>,
      }}
    &gt;
      &lt;input
        value={searchQuery}
        onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> updateFilter(<span class="hljs-string">"search"</span>, e.target.value)}
      /&gt;
      &lt;select
        value={selectedCategory}
        onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> updateFilter(<span class="hljs-string">"category"</span>, e.target.value)}
      &gt;
        &lt;option value=<span class="hljs-string">""</span>&gt;All Categories&lt;/option&gt;
        &lt;option value=<span class="hljs-string">"electronics"</span>&gt;Electronics&lt;/option&gt;
        &lt;option value=<span class="hljs-string">"clothing"</span>&gt;Clothing&lt;/option&gt;
      &lt;/select&gt;
      &lt;select
        value={selectedMaterial}
        onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> updateFilter(<span class="hljs-string">"material"</span>, e.target.value)}
      &gt;
        &lt;option value=<span class="hljs-string">""</span>&gt;All Material&lt;/option&gt;
        &lt;option value=<span class="hljs-string">"leather"</span>&gt;Leather&lt;/option&gt;
        &lt;option value=<span class="hljs-string">"silk"</span>&gt;Silk&lt;/option&gt;
      &lt;/select&gt;
      &lt;button onClick={clearFilters}&gt;Clear Filters&lt;/button&gt;
    &lt;/div&gt;
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Products;
</code></pre>
<p>You now read your values directly from the <code>URL</code>. You also update the <code>URL</code> only whenever any of the values change.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762628297681/03d0eabb-1030-4e70-83a0-11feb7f4e9e9.gif" alt="03d0eabb-1030-4e70-83a0-11feb7f4e9e9" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Notice that you have no re-render when the component mounts, even with predefined filters. The component re-renders once when any filtering key changes.</p>
<p>This approach gives several benefits:</p>
<ul>
<li><p>You don’t need <code>useEffect</code> to keep data in sync.</p>
</li>
<li><p>You only need to maintain a single source of truth, which is the <code>URL</code>.</p>
</li>
<li><p>You have less code, which is easier to maintain.</p>
</li>
</ul>
<h3 id="heading-how-to-derive-state-from-external-data">How to Derive State From External Data</h3>
<p>Data fetching libraries like React Query provide states that can be used as a single source of truth. Saving this data in <code>useState</code> can sometimes be redundant.</p>
<p>Look at this example using React Query. First, create a parent component that will render the user details:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useSearchParams } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-router"</span>;
<span class="hljs-keyword">import</span> UserDetail <span class="hljs-keyword">from</span> <span class="hljs-string">"./user-detail"</span>;

<span class="hljs-keyword">const</span> UserIdInput = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> [search, updateSearch] = useSearchParams();

  <span class="hljs-keyword">return</span> (
    &lt;input
      value={search.get(<span class="hljs-string">"id"</span>) || <span class="hljs-string">""</span>}
      onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> updateSearch({ id: e.target.value })}
    /&gt;
  );
};

<span class="hljs-keyword">const</span> User = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">return</span> (
    &lt;&gt;
      &lt;UserIdInput /&gt;
      &lt;UserDetail /&gt;
    &lt;/&gt;
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> User;
</code></pre>
<p>You use the <code>&lt;UserIdInput /&gt;</code> to update the user details you need to get. Now create the <code>&lt;UserDetail /&gt;</code> component:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useQuery } <span class="hljs-keyword">from</span> <span class="hljs-string">"@tanstack/react-query"</span>;
<span class="hljs-keyword">import</span> { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { useSearchParams } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-router"</span>;

<span class="hljs-keyword">interface</span> IUser {
  id: <span class="hljs-built_in">number</span>;
  name: <span class="hljs-built_in">string</span>;
}

<span class="hljs-keyword">const</span> getUser = <span class="hljs-keyword">async</span> (id: <span class="hljs-built_in">string</span>) =&gt; {
  <span class="hljs-keyword">const</span> req = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">`https://jsonplaceholder.typicode.com/users/<span class="hljs-subst">${id}</span>`</span>);
  <span class="hljs-keyword">await</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">r</span>) =&gt;</span> <span class="hljs-built_in">setTimeout</span>(r, <span class="hljs-number">2</span>_000));
  <span class="hljs-keyword">return</span> req.json();
};

<span class="hljs-keyword">const</span> UserDetail = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> [search] = useSearchParams();
  <span class="hljs-keyword">const</span> userId = search.get(<span class="hljs-string">"id"</span>) || <span class="hljs-string">""</span>;

  <span class="hljs-keyword">const</span> {
    data,
    isFetching,
    error: fError,
  } = useQuery&lt;IUser&gt;({
    queryKey: [<span class="hljs-string">"user"</span>, userId],
    queryFn: <span class="hljs-function">() =&gt;</span> getUser(userId),
    enabled: <span class="hljs-built_in">Boolean</span>(userId),
    refetchOnWindowFocus: <span class="hljs-literal">false</span>,
  });

  <span class="hljs-keyword">const</span> [user, setUser] = useState&lt;IUser&gt;();
  <span class="hljs-keyword">const</span> [error, setError] = useState&lt;<span class="hljs-built_in">string</span>&gt;();
  <span class="hljs-keyword">const</span> [loading, setLoading] = useState(<span class="hljs-literal">true</span>);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    setUser(<span class="hljs-literal">undefined</span>);
    setError(<span class="hljs-literal">undefined</span>);
  }, [userId]);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    setError(fError?.message);
    setLoading(isFetching);
    setUser(data);
  }, [data, fError, isFetching]);

  <span class="hljs-built_in">console</span>.count(<span class="hljs-string">"__ RENDERED __"</span>);

  <span class="hljs-built_in">console</span>.log({ data, user, isFetching, loading, error });

  <span class="hljs-keyword">if</span> (error) <span class="hljs-keyword">return</span> &lt;div&gt;Try again later!&lt;/div&gt;;
  <span class="hljs-keyword">if</span> (loading) <span class="hljs-keyword">return</span> &lt;div&gt;Loading...&lt;/div&gt;;

  <span class="hljs-keyword">if</span> (user) {
    <span class="hljs-keyword">return</span> (
      &lt;div&gt;
        &lt;p style={{ fontSize: <span class="hljs-string">"2rem"</span> }}&gt;
          {user.id}: {user.name}
        &lt;/p&gt;
      &lt;/div&gt;
    );
  }

  <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> UserDetail;
</code></pre>
<p>The <code>&lt;UserDetail /&gt;</code> component renders the details of a user. It gets the user’s ID from the URL.</p>
<p>The component uses the <code>getUser</code> function to get dummy data from an API. It simulates a little network delay by waiting for an extra two seconds with this line of code:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">await</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">r</span>) =&gt;</span> <span class="hljs-built_in">setTimeout</span>(r, <span class="hljs-number">2</span>_000));
</code></pre>
<p>The component uses <code>useQuery</code> to fetch the data. Internally, part of the state React Query keeps track of is the <code>data</code>, <code>isFetching</code>, and <code>error</code>. But the component creates another local state to keep track of these React Query states.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762629782249/ac64794f-9264-4e31-989c-5ee1a7274b0e.gif" alt="ac64794f-9264-4e31-989c-5ee1a7274b0e" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Notice that the component renders a total of four times. The component re-renders when React Query and local states change. Also, notice that both states are out of sync at some point. For example, in the third log, the <code>data</code> key from React Query already had the user details, while the local <code>user</code> state is still undefined. You had to use <code>useEffect</code> to update its state.</p>
<p>It’s more interesting when the component tries to get another user’s details.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762630953734/d1810dfd-82d9-4c6d-9c57-fa473f6438e4.gif" alt="d1810dfd-82d9-4c6d-9c57-fa473f6438e4" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>The component reacts to when the user’s ID changes, when React Query state changes, and when local state changes. Similar to the mount problem, the component re-renders more than it needs to: it re-renders six times. Also, the local state was out of sync at some point because we had to rely on <code>useEffect</code>.</p>
<h4 id="heading-derive-directly-from-react-query">Derive Directly From React Query</h4>
<p>You can correct all these issues by using a derived state like this:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useQuery } <span class="hljs-keyword">from</span> <span class="hljs-string">"@tanstack/react-query"</span>;
<span class="hljs-keyword">import</span> { useSearchParams } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-router"</span>;

<span class="hljs-keyword">interface</span> IUser {
  id: <span class="hljs-built_in">number</span>;
  name: <span class="hljs-built_in">string</span>;
}

<span class="hljs-keyword">const</span> getUser = <span class="hljs-keyword">async</span> (id: <span class="hljs-built_in">string</span>) =&gt; {
  <span class="hljs-keyword">const</span> req = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">`https://jsonplaceholder.typicode.com/users/<span class="hljs-subst">${id}</span>`</span>);
  <span class="hljs-keyword">await</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">r</span>) =&gt;</span> <span class="hljs-built_in">setTimeout</span>(r, <span class="hljs-number">2</span>_000));
  <span class="hljs-keyword">return</span> req.json();
};

<span class="hljs-keyword">const</span> UserDetail = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> [search] = useSearchParams();
  <span class="hljs-keyword">const</span> userId = search.get(<span class="hljs-string">"id"</span>) || <span class="hljs-string">""</span>;

  <span class="hljs-keyword">const</span> {
    data: user,
    isFetching: loading,
    error,
  } = useQuery&lt;IUser&gt;({
    queryKey: [<span class="hljs-string">"user"</span>, userId],
    queryFn: <span class="hljs-function">() =&gt;</span> getUser(userId),
    enabled: <span class="hljs-built_in">Boolean</span>(userId),
  });

  <span class="hljs-built_in">console</span>.count(<span class="hljs-string">"__ RENDERED __"</span>);

  <span class="hljs-keyword">if</span> (error) <span class="hljs-keyword">return</span> &lt;div&gt;Try again later!&lt;/div&gt;;
  <span class="hljs-keyword">if</span> (loading) <span class="hljs-keyword">return</span> &lt;div&gt;Loading...&lt;/div&gt;;

  <span class="hljs-keyword">if</span> (user) {
    <span class="hljs-keyword">return</span> (
      &lt;div&gt;
        &lt;p style={{ fontSize: <span class="hljs-string">"2rem"</span> }}&gt;
          {user.id}: {user.name}
        &lt;/p&gt;
      &lt;/div&gt;
    );
  }

  <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> UserDetail;
</code></pre>
<p>React Query is now the single source of truth. There is no more manual synchronisation or extra <code>useState</code>s.</p>
<p>Also, the first thing you will notice is that you have fewer lines of code. This makes it easier to maintain. Check the GIF below to see how often the component renders when it mounts.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762631232781/d2b03b52-bffe-4226-9535-5b8d5a4eca20.gif" alt="d2b03b52-bffe-4226-9535-5b8d5a4eca20" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>As you can see, the component re-renders only twice. Once, when the component initially mounts, and again when React Query state changes.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762631500289/e87f1c5e-2741-4c2a-bf1d-9406f06812ce.gif" alt="e87f1c5e-2741-4c2a-bf1d-9406f06812ce" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>The same thing happened when you got another user's details. You have fewer re-renders: three times as opposed to six times when we kept track of the state locally.</p>
<h2 id="heading-how-to-prevent-recalculation-of-a-derived-state">How to Prevent Recalculation of a Derived State</h2>
<p>Most calculations are fast enough to run on every render. This becomes a problem when it’s an expensive calculation. In such a case, you an use <code>useMemo</code> and/or <code>memo</code> to avoid unnecessary work.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> Detail = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> product = useQuery(...);
  <span class="hljs-keyword">const</span> users = useQuery(...);

  <span class="hljs-keyword">const</span> result = useMemo(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> expensiveResult = ...;

    <span class="hljs-keyword">return</span> expensiveResult;
  }, [product.data]);

  <span class="hljs-keyword">return</span> (
    &lt;&gt;
      {<span class="hljs-comment">/* some other components */</span>}
      &lt;Result result={result} /&gt;
    &lt;/&gt;
  );
};

<span class="hljs-keyword">const</span> Result = memo(<span class="hljs-function">(<span class="hljs-params">{ result }: { result: <span class="hljs-built_in">number</span> }</span>) =&gt;</span> {
  <span class="hljs-keyword">return</span> &lt;&gt;&lt;/&gt;;
});
</code></pre>
<p>With <code>useMemo</code>, the <code>result</code> variable will be recalculated only when <code>product.data</code> changes. This means that the <code>&lt;Result /&gt;</code> component remains the same until <code>product.data</code> changes.</p>
<p>You can read more here about <a target="_blank" href="https://www.freecodecamp.org/news/how-to-work-with-usememo-in-react/">how to use <code>useMemo</code> in React</a>.</p>
<h2 id="heading-when-to-use-usestate">When to Use <code>useState</code></h2>
<p>While the article has focused on using derived states, there are cases where you must use <code>useState</code>.</p>
<h3 id="heading-controlled-input">Controlled Input</h3>
<p>You need <code>useState</code> to manage the input’s value and keep it in sync with the component’s state when building a controlled input.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useState, <span class="hljs-keyword">type</span> FormEventHandler } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">const</span> ControlledInput = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> [username, setUsername] = useState(<span class="hljs-string">""</span>);

  <span class="hljs-keyword">const</span> handleSubmit: FormEventHandler&lt;HTMLFormElement&gt; = <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
    e.preventDefault();
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Submitted value:"</span>, username);
  };

  <span class="hljs-keyword">return</span> (
    &lt;form
      onSubmit={handleSubmit}
      style={{ display: <span class="hljs-string">"flex"</span>, flexDirection: <span class="hljs-string">"column"</span>, gap: <span class="hljs-string">"1rem"</span> }}
    &gt;
      &lt;div&gt;
        &lt;label htmlFor=<span class="hljs-string">"username"</span>&gt;Username:&lt;/label&gt;
        &lt;input
          id=<span class="hljs-string">"username"</span>
          <span class="hljs-keyword">type</span>=<span class="hljs-string">"text"</span>
          value={username}
          onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> setUsername(e.target.value)}
          style={{
            marginLeft: <span class="hljs-string">"1rem"</span>,
            padding: <span class="hljs-string">"0.5rem 0.4rem"</span>,
            border: <span class="hljs-string">`1px solid <span class="hljs-subst">${username.length &lt; <span class="hljs-number">2</span> ? <span class="hljs-string">"red"</span> : <span class="hljs-string">"green"</span>}</span>`</span>,
          }}
        /&gt;
      &lt;/div&gt;
      &lt;button <span class="hljs-keyword">type</span>=<span class="hljs-string">"submit"</span>&gt;Submit&lt;/button&gt;
    &lt;/form&gt;
  );
};
</code></pre>
<p><code>useState</code> is needed here because:</p>
<ul>
<li><p>you want to track the user’s value as they type</p>
</li>
<li><p>the value comes directly from the user interaction</p>
</li>
</ul>
<p>Because we are tracking the input value with <code>useState</code>, we can easily validate the <code>username</code> and change the border’s color. freeCodeCamp has an <a target="_blank" href="https://www.freecodecamp.org/news/what-are-controlled-and-uncontrolled-components-in-react/">article that explains what controlled inputs are.</a></p>
<h3 id="heading-independent-state-changes">Independent State Changes</h3>
<p>You should use <code>useState</code> when a value can change without depending on other state or props. For example, when toggling a modal, you need <code>useState</code> to keep track of its state.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">const</span> Modal = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> [isModalOpen, setIsModalOpen] = useState(<span class="hljs-literal">false</span>);

  <span class="hljs-keyword">return</span> (
    &lt;div&gt;
      &lt;button onClick={<span class="hljs-function">() =&gt;</span> setIsModalOpen(<span class="hljs-literal">true</span>)}&gt;Open Modal&lt;/button&gt;
      {isModalOpen &amp;&amp; (
        &lt;div&gt;
          &lt;p&gt;Modal Content&lt;/p&gt;
          &lt;button onClick={<span class="hljs-function">() =&gt;</span> setIsModalOpen(<span class="hljs-literal">false</span>)}&gt;Close&lt;/button&gt;
        &lt;/div&gt;
      )}
    &lt;/div&gt;
  );
};
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Managing state is one of the trickiest parts of React. You shouldn’t store what you can calculate, and you should only store what you can’t derive.</p>
<p>By keeping states minimal and deriving when possible, it will be easier to debug and maintain your components.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Fix Memory Leaks in React Applications ]]>
                </title>
                <description>
                    <![CDATA[ Have you ever noticed your React application getting slower the longer you use it? This could be a result of memory leaks. Memory leaks are a common performance issue in React applications. They can slow down your application, crash your browser, and... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/fix-memory-leaks-in-react-apps/</link>
                <guid isPermaLink="false">68d443c623251efd1f372d02</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ optimization ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Olaleye Blessing ]]>
                </dc:creator>
                <pubDate>Wed, 24 Sep 2025 19:17:26 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1758741256644/817dba0f-bf49-424c-9b13-86bf81dc327f.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Have you ever noticed your React application getting slower the longer you use it? This could be a result of memory leaks. Memory leaks are a common performance issue in React applications. They can slow down your application, crash your browser, and frustrate users.</p>
<p>In this tutorial, you’ll learn what causes memory leaks and how to fix them.</p>
<h2 id="heading-table-of-contents">Table Of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-are-memory-leaks-in-react">What Are Memory Leaks in React?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-when-does-a-component-unmount">When Does A Component Unmount?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-common-causes-of-memory-leaks-and-how-to-fix-them">Common Causes Of Memory Leaks And How To Fix Them</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-event-listeners">Event Listeners</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-timers">Timers</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-subscriptions">Subscriptions</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-async-operations">Async Operations</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before you move on, make sure you have:</p>
<ul>
<li><p>Basic knowledge of JavaScript, React, and React hooks</p>
</li>
<li><p>Understanding of event handling, timers, and asynchronous calls</p>
</li>
<li><p>A React development setup.</p>
</li>
</ul>
<p>If you don’t have a React development setup, you can head over to the <a target="_blank" href="https://github.com/Olaleye-Blessing/freecodecamp-fix-memory-leak">memory-leak repo</a>. Run the commands below to set it up:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># clone the repo</span>
git <span class="hljs-built_in">clone</span> &lt;https://github.com/Olaleye-Blessing/freecodecamp-fix-memory-leak.git&gt;

<span class="hljs-comment"># navigate to the folder</span>
<span class="hljs-built_in">cd</span> freecodecamp-fix-memory-leak.git

<span class="hljs-comment"># install the packages</span>
pnpm install

<span class="hljs-comment"># start development</span>
pnpm dev
</code></pre>
<h2 id="heading-what-are-memory-leaks-in-react">What Are Memory Leaks in React?</h2>
<p>In JavaScript, memory leaks happen when an application allocates memory but fails to release it. This occurs even after the memory is no longer needed.</p>
<p>In React, memory leaks happen when a component creates resources but does not remove them when it unmounts. These resources can be event listeners, timers, or subscriptions.</p>
<p>As a user stays longer in the application, these unreleased resources accumulate. This accumulation causes the application to consume more RAM. This will eventually lead to several problems:</p>
<ul>
<li><p>A slow application</p>
</li>
<li><p>The browser crashing</p>
</li>
<li><p>A poor user experience</p>
</li>
</ul>
<p>For example, a component might create a “resize” event listener when it mounts, but forgets to remove it when it unmounts. This builds up memory as the user stays longer in the application and resizes the screen.</p>
<h2 id="heading-when-does-a-component-unmount">When Does A Component Unmount?</h2>
<p>A component unmounts when it no longer exists in the DOM. This can happen if:</p>
<ol>
<li><p>A user navigates away from the page.</p>
<pre><code class="lang-typescript"> &lt;Routes&gt;
   &lt;Route path=<span class="hljs-string">"/posts"</span> element={&lt;Posts /&gt;} /&gt;
   &lt;Route path=<span class="hljs-string">"/dashboard"</span> element={&lt;Dashboard /&gt;} /&gt;
 &lt;/Routes&gt;
</code></pre>
<p> The dashboard component will unmount immediately when a user navigates from <code>/dashboard</code> to any other route in the application.</p>
</li>
<li><p>A component is conditionally rendered.</p>
<pre><code class="lang-typescript"> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
   <span class="hljs-keyword">const</span> [show, setShow] = useState(<span class="hljs-literal">true</span>);

   <span class="hljs-keyword">return</span> &lt;div&gt;{show &amp;&amp; &lt;Component /&gt;}&lt;/div&gt;;
 }
</code></pre>
<p> <code>&lt;Component /&gt;</code> will unmount when <code>show</code> becomes false.</p>
</li>
<li><p>A component key changes.</p>
<pre><code class="lang-typescript"> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
   <span class="hljs-keyword">const</span> [key, setKey] = useState(<span class="hljs-built_in">Date</span>.now());

   <span class="hljs-keyword">return</span> (
     &lt;&gt;
       &lt;button onClick={<span class="hljs-function">() =&gt;</span> setKey(<span class="hljs-built_in">Date</span>.now())}&gt;Change Key&lt;/button&gt;
       &lt;Form key={key} /&gt;
     &lt;/&gt;
   );
 }
</code></pre>
<p> The <code>&lt;Form /&gt;</code> component will unmount every time the key changes. Also note that a new <code>&lt;Form /&gt;</code> component will mount each time the key changes.</p>
</li>
</ol>
<h2 id="heading-common-causes-of-memory-leaks-and-how-to-fix-them">Common Causes Of Memory Leaks And How To Fix Them</h2>
<p>As said earlier, there will be a memory leak when resources are not removed after a component unmounts. React <code>useEffect</code> allows you to return a function that will be called when a component unmounts.</p>
<pre><code class="lang-typescript">useEffect(<span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-comment">// code to remove resources</span>
  };
}, []);
</code></pre>
<p>You can clean any created resources in this returned function. We will go through how to clean up some of these resources.</p>
<h3 id="heading-event-listeners">Event Listeners</h3>
<p>Event listeners persist if they are not removed after a component unmounts. Look at the code below:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">const</span> EventListener = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> [windowWidth, setWindowWidth] = useState(<span class="hljs-number">0</span>);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handleResize</span>(<span class="hljs-params"></span>) </span>{
      <span class="hljs-keyword">const</span> width = <span class="hljs-built_in">window</span>.innerWidth;
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"__ Resizing Event Listerner __"</span>, width);
      setWindowWidth(width);
    }

    <span class="hljs-built_in">window</span>.addEventListener(<span class="hljs-string">"resize"</span>, handleResize);
  }, []);

  <span class="hljs-keyword">return</span> &lt;div&gt;Width is: {windowWidth}&lt;/div&gt;;
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> EventListener;
</code></pre>
<p>We do not remove the resize event listener on unmount, so every mount adds a new listener. This failure to clean up leads to a memory leak.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758149182882/58ddc026-d6b8-4120-9144-53b6d87fb63e.gif" alt="GIF shows multiple 'resize' event listeners being created each time a component mounts." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>As shown in the GIF above, we log the width in the console every time we resize the window’s width. We still log the same information after component unmounts. Also, when we check the “Event Listeners” tab, the number of listeners keeps increasing by 2 instead of being just 1 each time we remount the component.</p>
<p>We see two listeners when the component mounts because React uses StrictMode in development. This helps to see side effects in the development mode. The same reason the listeners increase by 2 any time we mount the component.</p>
<p>To fix this memory leak, we need to remove the event listener in our cleanup function.</p>
<pre><code class="lang-typescript">useEffect(<span class="hljs-function">() =&gt;</span> {
  <span class="hljs-comment">// previous code</span>

  <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-built_in">window</span>.removeEventListener(<span class="hljs-string">"resize"</span>, handleResize);
  };
}, []);
</code></pre>
<p>The cleanup function runs when the component unmounts. This, in turn, removes our event listener and prevents a memory leak.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758149497654/f95d75b4-f41f-4bb0-806b-546555be813a.gif" alt="GIF shows a 'resize' event listener that is removed when the component unmounts." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Notice this time, nothing is shown in the console when we hide the component. Also, the resize event listener was reduced to 0 when we hid (unmounted) the component, and increased to 1 when we showed (mounted) it.</p>
<h3 id="heading-timers">Timers</h3>
<p>Timers like <code>setInterval</code> and <code>setTimeout</code> can also cause memory leaks if they are not cleared after the component unmounts. Look at this:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> Timers = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> [countDown, setCountDown] = useState(<span class="hljs-number">0</span>);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-built_in">setInterval</span>(<span class="hljs-function">() =&gt;</span> {
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"__ Set Interval __"</span>);
      setCountDown(<span class="hljs-function">(<span class="hljs-params">prev</span>) =&gt;</span> prev + <span class="hljs-number">1</span>);
    }, <span class="hljs-number">1000</span>);
  }, []);

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

  <span class="hljs-keyword">return</span> &lt;div&gt;Countdown: {countDown}&lt;/div&gt;;
};
</code></pre>
<p>The interval will continue to run even after React hides or unmounts the component.</p>
<p>Note that, in React 18+, React ignores a state update when a component already unmounts.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758149517577/34868e3e-e8f6-495a-a2f8-ef0e1e745051.gif" alt="GIF shows a countdown timer component that continues to run and update state after it has been unmounted from the DOM." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>In the GIF, we notice that the console stops showing "__ Outside effect ” anytime we hide/unmount the component. But the string, " Interval __”, shows every time.</p>
<p>We can fix this by using the cleanup function. All timers (<code>setInterval</code>, <code>setTimeout</code>) return a unique timer ID that we can use to clear the timer after the component unmounts.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> [countDown, setCountDown] = useState(<span class="hljs-number">0</span>);
useEffect(<span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> timer = <span class="hljs-built_in">setInterval</span>(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-built_in">console</span>.count(<span class="hljs-string">"__ Interval __"</span>);
    setCountDown(<span class="hljs-function">(<span class="hljs-params">prev</span>) =&gt;</span> prev + <span class="hljs-number">1</span>);
  }, <span class="hljs-number">1000</span>);

  <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-built_in">clearInterval</span>(timer);
  };
}, []);
</code></pre>
<p>We now save the ID of the timer and use this ID to clear the interval when the component unmounts. The same method applies to <code>setTimeout</code>; save the ID and clear it with <code>clearTimeout</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758149580900/7c37c824-93db-4b73-8ff4-c45c6404bb65.gif" alt="GIF shows a countdown timer component that stops running and updating state after it unmounts." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h3 id="heading-subscriptions">Subscriptions</h3>
<p>When a component subscribes to external data, it’s always appropriate to unsubscribe after the component unmounts. Most data source returns a callback function to unsubscribe from such data. Take Firebase for an example:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { collection, onSnapshot } <span class="hljs-keyword">from</span> <span class="hljs-string">"firebase/firestore"</span>;
<span class="hljs-keyword">import</span> { useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">const</span> Subscriptions = <span class="hljs-function">() =&gt;</span> {
  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> unsubscribe = onSnapshot(collection(db, <span class="hljs-string">"cities"</span>), <span class="hljs-function">() =&gt;</span> {
        <span class="hljs-comment">// Respond to data</span>
        <span class="hljs-comment">// ...</span>
    });
  }, [])

    <span class="hljs-keyword">return</span> &lt;div&gt;Subscriptions&lt;/div&gt;;
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Subscriptions;
</code></pre>
<p>The <code>onSnapshot</code> function from <code>firebase/firestore</code> gets real-time updates from our database. It returns a callback function that stops listening to the DB updates. If you fail to call this function, our app continues to listen to these updates even when it no longer needs them.</p>
<pre><code class="lang-typescript">useEffect(<span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> unsubscribe = onSnapshot(collection(db, <span class="hljs-string">"cities"</span>), <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-comment">// Respond to data</span>
    <span class="hljs-comment">// ...</span>
  });

  <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> {
    unsubscribe();
  };
}, []);
</code></pre>
<p>Calling <code>unsubscribe()</code> in the returned function means we are no longer interested in listening to the data updates.</p>
<h3 id="heading-async-operations">Async Operations</h3>
<p>One common mistake is not cancelling an API call when it’s no longer needed. It's a waste of resources to allow an API call to keep running when the component unmounts. This is because the browser continues to hold references in memory until the promise resolves. Look at this example:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">interface</span> Post {
  id: <span class="hljs-built_in">string</span>;
  title: <span class="hljs-built_in">string</span>;
  views: <span class="hljs-built_in">number</span>;
}

<span class="hljs-keyword">const</span> ApiCall = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> [loading, setLoading] = useState(<span class="hljs-literal">false</span>);
  <span class="hljs-keyword">const</span> [error, setError] = useState(<span class="hljs-string">""</span>);
  <span class="hljs-keyword">const</span> [data, setData] = useState&lt;Post[] | <span class="hljs-literal">null</span>&gt;(<span class="hljs-literal">null</span>);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> getTodos = <span class="hljs-keyword">async</span> () =&gt; {
      <span class="hljs-keyword">try</span> {
        setLoading(<span class="hljs-literal">true</span>);

        <span class="hljs-built_in">console</span>.time(<span class="hljs-string">"POSTS"</span>);
        <span class="hljs-keyword">const</span> req = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">"&lt;http://localhost:3001/posts&gt;"</span>);
        <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> req.json();
        <span class="hljs-built_in">console</span>.timeLog(<span class="hljs-string">"POSTS"</span>);
        setData(res.posts);
      } <span class="hljs-keyword">catch</span> (error) {
        setError(<span class="hljs-string">"Try again"</span>);
      } <span class="hljs-keyword">finally</span> {
        setLoading(<span class="hljs-literal">false</span>);
      }
    };

    getTodos();
  }, []);

  <span class="hljs-keyword">return</span> (
    &lt;div style={{ marginTop: <span class="hljs-string">"2rem"</span> }}&gt;
      &lt;p&gt;ApiCall Component&lt;/p&gt;
      {loading ? (
        &lt;p&gt;Loading...&lt;/p&gt;
      ) : error ? (
        &lt;p&gt;{error}&lt;/p&gt;
      ) : data ? (
        &lt;p&gt;Views: {data[<span class="hljs-number">0</span>].views}&lt;/p&gt;
      ) : <span class="hljs-literal">null</span>}
    &lt;/div&gt;
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> ApiCall;
</code></pre>
<p>This component fetches a list of posts from our server immediately it mounts. It changes the UI based on the state of the API call:</p>
<ul>
<li><p>It displays a loading text when you click the button.</p>
</li>
<li><p>It shows an error if the API fails.</p>
</li>
<li><p>It shows the data if the API succeeds.</p>
</li>
</ul>
<p>We have a simple server that returns the list of posts. The problem with the server is that it takes three seconds for it to return the list of posts.</p>
<p>What happens when a user comes to this page but decides to leave before three seconds? (We simulate leaving the page by clicking the Hide Component button.)</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758149803784/bf5cf55f-75fe-472f-9a08-653c53bdafaa.gif" alt="GIF shows a component that continues with an API call after it unmounts." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>As you can see, the browser still holds a reference to the request even though it’s no longer needed.</p>
<p>A proper way to fix this is to cancel the request when the component unmounts. We can do this by using the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/AbortController">AbortController</a>. We can use the <code>abort</code> method to cancel the request before it gets completed, thereby releasing memory.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">interface</span> Post {
  id: <span class="hljs-built_in">string</span>;
  title: <span class="hljs-built_in">string</span>;
  views: <span class="hljs-built_in">number</span>;
}

<span class="hljs-keyword">const</span> ApiCall = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-comment">// previous code</span>

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> controller = <span class="hljs-keyword">new</span> AbortController();

    <span class="hljs-keyword">const</span> getTodos = <span class="hljs-keyword">async</span> () =&gt; {
      <span class="hljs-keyword">try</span> {
        <span class="hljs-comment">// previous code</span>

        <span class="hljs-keyword">const</span> req = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">"&lt;http://localhost:3001/posts&gt;"</span>, {
          signal: controller.signal,
        });

        <span class="hljs-comment">// previous code</span>
      } <span class="hljs-keyword">catch</span> (error) {
        <span class="hljs-keyword">if</span> (error <span class="hljs-keyword">instanceof</span> <span class="hljs-built_in">Error</span> &amp;&amp; error.name === <span class="hljs-string">"AbortError"</span>) {
          <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Request was cancelled"</span>);
          <span class="hljs-keyword">return</span>;
        }

        setError(<span class="hljs-string">"Try again"</span>);
      } <span class="hljs-keyword">finally</span> {
        setLoading(<span class="hljs-literal">false</span>);
      }
    };

    getTodos();

    <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> {
      controller.abort();
    };
  }, []);

  <span class="hljs-keyword">return</span> (
    &lt;div style={{ marginTop: <span class="hljs-string">"2rem"</span> }}&gt;
      &lt;p&gt;ApiCall Component&lt;/p&gt;
      {<span class="hljs-comment">/* previous code */</span>}
    &lt;/div&gt;
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> ApiCall;
</code></pre>
<p>We created a controller to track our API request when the component mounts. We then attach the controller to our API request. Our cleanup function cancels the request if the users leave the page within three seconds.</p>
<p>We can see the result of this in the GIF below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758149811531/5497edbd-050c-4059-b088-239e8c5b65ef.gif" alt="GIF shows an API call being cancelled after its component unmounts." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Most production React applications use external libraries to fetch APIs. For example, <a target="_blank" href="https://tanstack.com/query/latest/docs/framework/react/guides/query-cancellation#using-fetch">react query</a> allows us to cancel a processing promise:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> query = useQuery({
  queryKey: [<span class="hljs-string">"todos"</span>],
  queryFn: <span class="hljs-keyword">async</span> ({ signal }) =&gt; {
    <span class="hljs-keyword">const</span> todosResponse = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">"/todos"</span>, { signal });
    <span class="hljs-keyword">const</span> todos = <span class="hljs-keyword">await</span> todosResponse.json();

    <span class="hljs-keyword">return</span> todos;
  },
});
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Memory leaks can significantly impact your React application's performance and user experience. You can prevent these issues by properly cleaning up resources when a component unmounts. In summary, always remember to:</p>
<ul>
<li><p>Remove event listeners with <code>removeEventListener</code>.</p>
</li>
<li><p>Clear timers with <code>clearInterval</code> and <code>clearTimeout</code>.</p>
</li>
<li><p>Unsubscribe from external data sources.</p>
</li>
<li><p>Cancel API requests using <code>AbortController</code>.</p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
