<?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[ Kelechi Apugo - 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[ Kelechi Apugo - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Tue, 05 May 2026 11:02:43 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/author/Laviedegeorge/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Debug React State Updates Like a Pro (Without Polluting Production) ]]>
                </title>
                <description>
                    <![CDATA[ When you’re debugging a large React codebase, you might start to feel like a detective. Especially when you are looking for unexpected state changes, components that re-render when they like, or Context values that disappear into thin air without any... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-debug-react-state-updates-like-a-pro-without-polluting-production/</link>
                <guid isPermaLink="false">698115a622cd39b64c5fac49</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ debugging ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Frontend Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React state management ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Kelechi Apugo ]]>
                </dc:creator>
                <pubDate>Mon, 02 Feb 2026 21:22:46 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1770067225475/d0910306-5756-465a-8b6f-adf839fe004a.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>When you’re debugging a large React codebase, you might start to feel like a detective. Especially when you are looking for unexpected state changes, components that re-render when they like, or Context values that disappear into thin air without any prior warning sign.</p>
<p>And the main difficulty isn’t necessarily what went wrong – it’s pinpointing where it went wrong.</p>
<p>React offers powerful ways to change state, but it doesn’t specify who or what caused those changes. In large apps with many layers of components, hooks, and contexts, this lack of insight can turn simple bugs into frustrating, time-consuming puzzles.</p>
<p>This is where more innovative debugging methods become crucial. Before now, the go-to solution was to sprinkle <code>console.log</code> calls at key points or to fall back to DevTools.</p>
<p>But these days, you can write a small but powerful utility function that can catch the criminal involved in the crimes against your codebase. This utility function can log changes, display meaningful stack traces, and work smoothly with <code>useState</code>, <code>useReducer</code>, Context providers, and custom hooks. And all of the above can occur while remaining invisible in production.</p>
<p>This article guides you through how to use this helper function to improve clarity, minimise guesswork, and debug efficiently without affecting performance or code cleanliness in your live environment.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-the-problem">The Problem</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-why-this-problem-exists">Why This Problem Exists</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-my-solution-createdebugsetter">My Solution: <code>createDebugSetter</code></a></p>
<ul>
<li><a class="post-section-overview" href="#heading-practical-examples-of-createdebugsetter">Practical Examples of <code>createDebugSetter</code></a></li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-best-practices-for-using-createdebugsetter">Best Practices for using <code>createDebugSetter</code></a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-things-to-avoid">Things to Avoid</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-convert-createdebugsetter-to-a-hook">Bonus: How to Convert <code>createDebugSetter</code> to a Hook</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-the-problem">The Problem</h2>
<p>React’s state system is powerful, but it hides too much information when something goes wrong – for example, when an unexpected update happens or a component re-renders endlessly. React doesn’t tell you <em>what</em> triggered the update, <em>what</em> changed, or <em>why</em> it happened. This lack of visibility creates several challenges.</p>
<p>The first is that you can’t easily see which component, function, or effect initiated a state update. In large applications, where the same state may be modified from multiple places, this quickly turns debugging into guesswork. Without clear traces, developers often sprinkle <code>console.log</code> throughout their code to find the source of a single update.</p>
<p>Secondly, React lacks a built-in method for directly comparing previous and current values. This complicates diagnosing whether a bug stems from an incorrect calculation, a faulty API response, or erroneous business logic. The challenge increases with nested objects, arrays, or shared context.</p>
<p>Thirdly, Context updates can trigger re-renders across the entire tree, even for components wrapped in memoisation. But React doesn’t explain <em>why</em> a particular provider changed, leaving teams to wonder what triggered the cascade.</p>
<p>Finally, infinite loops caused by effects, unstable dependencies, or repeated setState calls provide no clues in the console. You only see symptoms like “loading…” repeating endlessly, with no indication of the source.</p>
<p>All of this makes debugging complex React apps frustrating, slow, and often misleading without additional tools or structured techniques.</p>
<h2 id="heading-why-this-problem-exists">Why This Problem Exists</h2>
<p>React intentionally conceals its internal update process to keep the framework fast and predictable.</p>
<p>Because of this:</p>
<ul>
<li><p><code>setState()</code> doesn’t report where it was called from</p>
</li>
<li><p>Context re-renders can originate from anywhere.</p>
</li>
<li><p>State overrides can happen silently.</p>
</li>
<li><p>Debugging often relies on manually adding console logs.</p>
</li>
</ul>
<p>In large applications, this lack of visibility makes it nearly impossible to trace unexpected state changes.</p>
<h2 id="heading-my-solution-createdebugsetter">My Solution: <code>createDebugSetter</code></h2>
<p>A small helper function, <code>createDebugSetter</code>, wraps your state setter and logs:</p>
<ul>
<li><p>The label of the state</p>
</li>
<li><p>The new value</p>
</li>
<li><p>A complete stack trace showing exactly where the update originated</p>
</li>
</ul>
<p>And best of all, it automatically disables itself in production using <code>NODE_ENV</code> so there’s no impact on your live app.</p>
<h3 id="heading-createdebugsetter">createDebugSetter</h3>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">createDebugSetter</span>(<span class="hljs-params">
  label: <span class="hljs-built_in">string</span>,
  setter:
    | React.Dispatch&lt;React.SetStateAction&lt;unknown&gt;&gt;
    | React.Dispatch&lt;unknown&gt;
</span>): <span class="hljs-title">React</span>.<span class="hljs-title">Dispatch</span>&lt;<span class="hljs-title">React</span>.<span class="hljs-title">SetStateAction</span>&lt;<span class="hljs-title">unknown</span>&gt;&gt; | <span class="hljs-title">React</span>.<span class="hljs-title">Dispatch</span>&lt;<span class="hljs-title">unknown</span>&gt; </span>{
  <span class="hljs-comment">// In production, return the original setter unchanged</span>
  <span class="hljs-keyword">if</span> (<span class="hljs-keyword">import</span>.meta.env.PROD <span class="hljs-comment">/* vite-react */</span>) {
    <span class="hljs-keyword">return</span> setter;
  }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  <span class="hljs-comment">// In production, return the original setter (no wrapping overhead)</span>
  <span class="hljs-comment">// In development, return the debug wrapper</span>
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">import</span>.meta.env.PROD ? setState : debugSetter;
}
</code></pre>
<h3 id="heading-how-the-hook-version-works">How the Hook Version Works</h3>
<p>The core difference between the <code>useDebugSetter</code> hook and <code>createDebugSetter</code> is that the function is wrapped in a <code>useCallback</code> that logs debug information before calling the original setter. Apart from this, all other components of the functions remain the same.</p>
<h3 id="heading-why-the-hook-version-is-better">Why the Hook Version is Better</h3>
<p>The hook version is superior for component usage because it leverages <code>useCallback</code> memoisation of the debug wrapper. This means the function reference stays the same across renders, avoiding potential re-render cascades when the setter is passed to child components or used in <code>useEffect</code> dependencies.</p>
<p>The plain function, by contrast, generates a brand new wrapper on every render, which can break React's optimisation strategies and cause subtle bugs. In production, both versions simply return the original setter, so there's no performance difference there – but in development, the hook prevents unnecessary work.</p>
<h3 id="heading-when-to-use-the-hook">When to Use the Hook</h3>
<p>Use <code>useDebugSetter</code> whenever you're inside a React component and need to debug state updates. This covers the vast majority of cases: wrapping <code>useState</code> setters, passing debug setters to child components, or including them in effect dependencies.</p>
<p>Only reach for the plain <code>createDebugSetter</code> function when you're working outside React components entirely, such as in utility modules, global stores, or configuration files where hooks can't be used. For day-to-day component debugging, the hook is the right choice.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Debugging React state doesn’t have to be guesswork. With a simple helper, you can instantly see what changed, who changed it, where the change originated, and how your app reached that state – all without touching your production environment.</p>
<p>This small utility function can save hours spent searching through your codebase, making you faster, more precise, and more confident in your React application’s behaviour.</p>
<p>Once you adopt this approach, you’ll never debug state the old way again. 🚀</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
