<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Rama Jha on Medium]]></title>
        <description><![CDATA[Stories by Rama Jha on Medium]]></description>
        <link>https://medium.com/@ramajha?source=rss-faab09a15a3d------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*QU_LyitE_kyp_MQtBEuogA.png</url>
            <title>Stories by Rama Jha on Medium</title>
            <link>https://medium.com/@ramajha?source=rss-faab09a15a3d------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Fri, 05 Jun 2026 12:32:51 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@ramajha/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[useEffect Isn’t Magic: Side Effects, Dependencies, and Cleanup Explained]]></title>
            <link>https://ramajha.medium.com/useeffect-isnt-magic-side-effects-dependencies-and-cleanup-explained-915d162acc42?source=rss-faab09a15a3d------2</link>
            <guid isPermaLink="false">https://medium.com/p/915d162acc42</guid>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[useeffect]]></category>
            <category><![CDATA[react]]></category>
            <category><![CDATA[reactjs]]></category>
            <category><![CDATA[frontend]]></category>
            <dc:creator><![CDATA[Rama Jha]]></dc:creator>
            <pubDate>Mon, 15 Dec 2025 12:02:24 GMT</pubDate>
            <atom:updated>2025-12-15T12:02:24.293Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*AUeELLkxlSWs4dNndU3L-A.png" /></figure><p>Last month, I was debugging a partner’s React component and found useEffect firing the same API call three times on mount. Their response? &quot;I don&#39;t know—it just does that.&quot; We spent ten minutes digging, and it turned out React 18&#39;s StrictMode was double-invoking their effect, plus they had no cleanup function. No magic there—just a misunderstanding waiting to happen.</p><p>Here’s the thing: useEffect gets blamed a lot. Developers call it confusing, unpredictable, even broken. But it&#39;s not the hook that&#39;s the problem—it&#39;s usually the mental model. Today, we&#39;re fixing that.</p><h3>What You’ll Learn</h3><p>After reading this post, you’ll be able to:</p><ol><li>Explain what side effects are and recognize when you actually need useEffect (hint: not every time).</li><li>Write a proper effect with the right dependency array pattern and understand <em>why</em> dependencies matter.</li><li>Create and use cleanup functions to prevent memory leaks, infinite loops, and double-fetches.</li><li>Understand React 18’s StrictMode behavior and why your effect might run twice in development (and what to do about it).</li><li>Debug common gotchas like infinite loops, missing dependencies, and race conditions.</li></ol><h3>Prerequisites</h3><p>You should already understand React functional components, basic hooks like useState, and how JavaScript closures work (especially if variables are captured &quot;stale&quot;). If you&#39;re shaky on that, skim the <a href="https://react.dev/learn/state-a-mental-model#how-useState-is-similar-to-a-snapshot">React docs on closures</a> first—it&#39;ll make this much clearer.</p><h3>What’s a Side Effect, Actually?</h3><p>Think about what your component does. Usually, it renders JSX: it takes props and state, runs some logic, and returns UI. That’s <em>pure</em> — same inputs, same output every time. No surprises.</p><p>But real apps need to do messy things: fetch data from an API, listen to window resize events, update the document title, set up a timer. These things happen <em>outside</em> your component’s render function. They’re called side effects.</p><p>Here’s a bad way to handle them:</p><pre>function Counter() {<br>  const [count, setCount] = useState(0);<br>  <br>  // DON&#39;T do this<br>  document.title = `Count: ${count}`;<br>  <br>  return (<br>    &lt;div&gt;<br>      &lt;p&gt;Count: {count}&lt;/p&gt;<br>      &lt;button onClick={() =&gt; setCount(count + 1)}&gt;Increment&lt;/button&gt;<br>    &lt;/div&gt;<br>  );<br>}</pre><p>Why is this bad? Because you’re updating the document title every single time the component renders. React can re-render for all kinds of reasons — a parent prop changed, another state variable shifted, whatever. Each render updates the title, which is wasteful and weird.</p><p>useEffect is React&#39;s way of saying: &quot;Hey, do this side effect <em>after</em> rendering, and only when I tell you to.&quot;</p><h3>The Syntax (It’s Simpler Than You Think)</h3><pre>useEffect(() =&gt; {<br>  // This code runs AFTER the component renders<br>  // Put your side effect here<br>  console.log(&#39;Component rendered!&#39;);<br>}, [/* dependency array */]);</pre><p>That’s it. Two arguments: a function and an array.</p><h3>The Three Patterns: When Does Your Effect Run?</h3><p>This is where beginners get lost. Let’s be concrete.</p><h3>Pattern 1: Empty Dependency Array []</h3><pre>useEffect(() =&gt; {<br>  console.log(&#39;This runs ONCE, after the first render&#39;);<br>  document.title = &#39;Welcome!&#39;;<br>}, []);</pre><p>When it runs: Once, right after your component mounts. Never again, even if state or props change.</p><p>Real use: Fetch initial data, set up an event listener, initialize a timer that shouldn’t restart.</p><pre>function UserProfile({ userId }) {<br>  const [user, setUser] = useState(null);<br>  <br>  useEffect(() =&gt; {<br>    // Fetch the user once, on mount<br>    fetch(`/api/users/${userId}`)<br>      .then(res =&gt; res.json())<br>      .then(data =&gt; setUser(data));<br>  }, []); // Only run on mount<br>  <br>  return user ? &lt;h1&gt;{user.name}&lt;/h1&gt; : &lt;p&gt;Loading...&lt;/p&gt;;<br>}</pre><p>The gotcha: If userId changes later, this effect <em>won&#39;t</em> re-run. Your component will keep showing the old user. This is actually a bug in the example above. We&#39;ll fix it in a moment.</p><h3>Pattern 2: Dependency Array With Values [value]</h3><pre>useEffect(() =&gt; {<br>  console.log(`Dependency changed to: ${value}`);<br>}, [value]);</pre><p>When it runs: After mount, and <em>again</em> whenever value changes.</p><p>Real use: When a prop or state change should trigger a side effect, like fetching new data.</p><pre>function UserProfile({ userId }) {<br>  const [user, setUser] = useState(null);<br>  <br>  useEffect(() =&gt; {<br>    fetch(`/api/users/${userId}`)<br>      .then(res =&gt; res.json())<br>      .then(data =&gt; setUser(data));<br>  }, [userId]); // Re-run when userId changes<br>  <br>  return user ? &lt;h1&gt;{user.name}&lt;/h1&gt; : &lt;p&gt;Loading...&lt;/p&gt;;<br>}</pre><p>Now if userId changes, the effect re-runs and fetches the new user. Problem solved.</p><p>You can include multiple dependencies:</p><pre>useEffect(() =&gt; {<br>  console.log(`Search: ${query}, Page: ${page}`);<br>  // Fetch results based on query AND page<br>}, [query, page]); // Both are dependencies</pre><p>Real talk: Missing a dependency is one of the top mistakes. The ESLint rule eslint-plugin-react-hooks will yell at you if you forget one. Listen to it.</p><h3>Pattern 3: No Dependency Array (Don’t Do This)</h3><pre>useEffect(() =&gt; {<br>  console.log(&#39;This runs after EVERY render&#39;);<br>});</pre><p>When it runs: After <em>every</em> render.</p><p>When you’d use it: Almost never. This is usually a mistake. It defeats the whole purpose of useEffect and can tank your app&#39;s performance.</p><h3>Cleanup Functions: The Secret Weapon</h3><p>Here’s where React 18 changes everything. In development (with StrictMode on), React deliberately unmounts and remounts your component to catch bugs. If your effect doesn’t clean up after itself, you’ll get problems: duplicate API calls, memory leaks, event listeners that never get removed.</p><p>A cleanup function is just a function you return from your effect:</p><pre>useEffect(() =&gt; {<br>  console.log(&#39;Effect running&#39;);<br>  <br>  // Return a cleanup function<br>  return () =&gt; {<br>    console.log(&#39;Cleaning up!&#39;);<br>  };<br>}, []);</pre><p>The cleanup function runs right before:</p><ol><li>The component unmounts, <em>or</em></li><li>The effect re-runs (on dependency changes)</li></ol><p>Real example: Event listener</p><pre>function WindowSize() {<br>  const [width, setWidth] = useState(window.innerWidth);<br>  <br>  useEffect(() =&gt; {<br>    const handleResize = () =&gt; setWidth(window.innerWidth);<br>    window.addEventListener(&#39;resize&#39;, handleResize);<br>    <br>    // Cleanup: remove the listener before the next effect or unmount<br>    return () =&gt; {<br>      window.removeEventListener(&#39;resize&#39;, handleResize);<br>    };<br>  }, []);<br>  <br>  return &lt;p&gt;Window width: {width}px&lt;/p&gt;;<br>}</pre><p>If you don’t clean up, every time the component re-mounts (happens a lot in StrictMode), you add <em>another</em> event listener. Soon you’ve got 10 listeners firing on every resize. Ouch.</p><p>Real example: Timer</p><pre>function Countdown() {<br>  const [seconds, setSeconds] = useState(10);<br>  <br>  useEffect(() =&gt; {<br>    const interval = setInterval(() =&gt; {<br>      setSeconds(s =&gt; s - 1);<br>    }, 1000);<br>    <br>    // Clean up the interval<br>    return () =&gt; clearInterval(interval);<br>  }, []);<br>  <br>  return &lt;p&gt;{seconds}s left&lt;/p&gt;;<br>}</pre><p>Without the cleanup, old intervals keep running even after unmount, and new ones pile up.</p><h3>The Hands-On Task: Update the Page Title</h3><p>Let’s tie this together with the task from the schedule. Create a component that updates document.title based on a counter:</p><pre>import { useState, useEffect } from &#39;react&#39;;<br><br>function PageTitle() {<br>  const [count, setCount] = useState(0);<br>  <br>  useEffect(() =&gt; {<br>    // This side effect runs whenever count changes<br>    document.title = `Count: ${count}`;<br>  }, [count]); // Dependency: count<br>  <br>  return (<br>    &lt;div&gt;<br>      &lt;p&gt;Current count: {count}&lt;/p&gt;<br>      &lt;button onClick={() =&gt; setCount(count + 1)}&gt;<br>        Increment<br>      &lt;/button&gt;<br>    &lt;/div&gt;<br>  );<br>}<br>export default PageTitle;</pre><p>Try it:</p><ol><li>Click the button a few times.</li><li>Watch the browser tab title change.</li><li>Open React DevTools, go to the Profiler, and trigger a re-render. See how document.title updates without extra renders.</li></ol><p>Gotcha moment: What if you accidentally leave out the dependency array?</p><pre>useEffect(() =&gt; {<br>  document.title = `Count: ${count}`;<br>}); // No dependency array</pre><p>Now the effect runs <em>every</em> render. Click once, title updates. Great. Click again, renders again, title updates again. Seems fine until you realize React might trigger re-renders for other reasons (parent changes, other state shifts), and every single time, you’re re-setting the title. Wasteful. Include the dependency array.</p><h3>React 18 StrictMode: Why Your Effect Runs Twice (And It’s Okay)</h3><p>In development, if you wrap your app with &lt;StrictMode&gt;, React intentionally mounts, unmounts, and remounts your component. It looks like this:</p><ol><li>Component mounts → effect runs</li><li>Effect cleanup runs</li><li>Component unmounts</li><li>Component remounts → effect runs <em>again</em></li><li>Effect cleanup runs</li></ol><p>This happens <em>only in development</em> and only in StrictMode. It’s React’s way of forcing you to write proper cleanup functions. If your app breaks during this double-invoke, you’ve got a bug.</p><p>Example of a broken effect in StrictMode:</p><pre>useEffect(() =&gt; {<br>  let count = 0;<br>  const interval = setInterval(() =&gt; {<br>    count++;<br>    console.log(count); // Logs 1, then on the next cycle, logs 1 again<br>  }, 1000);<br>  <br>  // Oops, no cleanup!<br>}, []);</pre><p>In StrictMode:</p><ol><li>First mount: interval starts, logs 1, 2, 3…</li><li>Unmount (cleanup didn’t run, so interval still exists)</li><li>Remount: <em>another</em> interval starts</li><li>Now two intervals are running, and logs get weird</li></ol><p>Fix: Add the cleanup function.</p><pre>useEffect(() =&gt; {<br>  let count = 0;<br>  const interval = setInterval(() =&gt; {<br>    count++;<br>    console.log(count);<br>  }, 1000);<br>  <br>  return () =&gt; clearInterval(interval); // Cleanup!<br>}, []);</pre><p>Now:</p><ol><li>Mount: interval starts</li><li>Unmount: cleanup clears it</li><li>Remount: fresh interval starts</li><li>Clean, predictable behavior</li></ol><p>Here’s my honest take: StrictMode feels annoying at first. “Why is my effect running twice?” But it catches real bugs that would otherwise only show up after deploy. Embrace it.</p><h3>Common Gotcha: The Infinite Loop</h3><p>One of the scariest bugs with useEffect is the infinite re-render loop. Here&#39;s how it happens:</p><pre>function BadExample() {<br>  const [data, setData] = useState([]);<br>  <br>  useEffect(() =&gt; {<br>    setData([1, 2, 3]); // Sets state<br>    // But data is a dependency...?<br>  }, [data]); // When state updates, effect re-runs, calls setData again, ...<br>}</pre><p>Effect runs → sets data → data changes → effect runs again → infinite loop → browser slows to a crawl.</p><p>The fix: Don’t include state variables in the dependency array if you’re updating them in the effect.</p><pre>useEffect(() =&gt; {<br>  setData([1, 2, 3]);<br>}, []); // Empty array: run once, that&#39;s it</pre><p>Another common cause:</p><pre>function DetailsPage({ user }) {<br>  useEffect(() =&gt; {<br>    fetch(`/api/users/${user}`);<br>  }, [user]); // Fine so far<br>}<br><br>// Called from parent like this:<br>&lt;DetailsPage user={{ id: 123 }} /&gt; // DANGER: new object every render!</pre><p>Each render, a new user object gets created, React sees it as &quot;changed&quot; (different reference), and the effect re-runs. If the parent re-renders often, the effect runs constantly.</p><p>Fix: Memoize the object or pass just the ID.</p><pre>// Better: pass just the ID<br>&lt;DetailsPage userId={123} /&gt;<br><br>// Or memoize the object<br>const user = useMemo(() =&gt; ({ id: 123 }), []);<br>&lt;DetailsPage user={user} /&gt;</pre><h3>Fetching Data: The Right Way</h3><p>Data fetching is where useEffect really shines—and where it breaks if you&#39;re not careful.</p><p>Here’s a solid pattern using AbortController for cleanup:</p><pre>function UserList() {<br>  const [users, setUsers] = useState([]);<br>  const [loading, setLoading] = useState(true);<br>  const [error, setError] = useState(null);<br>  <br>  useEffect(() =&gt; {<br>    // Create a controller to cancel the fetch if needed<br>    const controller = new AbortController();<br>    <br>    (async () =&gt; {<br>      try {<br>        const response = await fetch(&#39;/api/users&#39;, {<br>          signal: controller.signal, // Attach the signal<br>        });<br>        if (!response.ok) throw new Error(&#39;Failed to fetch&#39;);<br>        const data = await response.json();<br>        setUsers(data);<br>      } catch (err) {<br>        // Only update state if it wasn&#39;t aborted<br>        if (err.name !== &#39;AbortError&#39;) {<br>          setError(err.message);<br>        }<br>      } finally {<br>        setLoading(false);<br>      }<br>    })();<br>    <br>    // Cleanup: abort the fetch if component unmounts<br>    return () =&gt; controller.abort();<br>  }, []);<br>  <br>  if (loading) return &lt;p&gt;Loading...&lt;/p&gt;;<br>  if (error) return &lt;p&gt;Error: {error}&lt;/p&gt;;<br>  return (<br>    &lt;ul&gt;<br>      {users.map(user =&gt; (<br>        &lt;li key={user.id}&gt;{user.name}&lt;/li&gt;<br>      ))}<br>    &lt;/ul&gt;<br>  );<br>}</pre><p>Why AbortController? If the user navigates away before the fetch completes, you want to cancel the request and avoid updating unmounted state (which causes a memory leak warning).</p><p>Why check for AbortError? When you abort, the fetch throws an error. We catch it but don&#39;t show an error message to the user—they left the page anyway.</p><h3>One More Thing: When NOT to Use useEffect</h3><p>React’s official docs have a section called “You Might Not Need an Effect,” and it’s worth reading. Here are some cases where beginners use useEffect but shouldn&#39;t:</p><p>1. Deriving state from props</p><pre>// Don&#39;t do this<br>function User({ firstName, lastName }) {<br>  const [fullName, setFullName] = useState(&#39;&#39;);<br>  <br>  useEffect(() =&gt; {<br>    setFullName(`${firstName} ${lastName}`);<br>  }, [firstName, lastName]);<br>  <br>  return &lt;p&gt;{fullName}&lt;/p&gt;;<br>}</pre><pre>// Do this instead<br>function User({ firstName, lastName }) {<br>  const fullName = `${firstName} ${lastName}`;<br>  return &lt;p&gt;{fullName}&lt;/p&gt;;<br>}</pre><p>If you can calculate a value from props or state, just calculate it during render. No effect needed.</p><p>2. Transforming props for event handlers</p><pre>// Don&#39;t do this<br>function SearchBox({ onSearch }) {<br>  const [handler, setHandler] = useState(null);<br>  <br>  useEffect(() =&gt; {<br>    setHandler(() =&gt; (query) =&gt; onSearch(query.trim()));<br>  }, [onSearch]);<br>  <br>  return &lt;input onChange={handler} /&gt;;<br>}</pre><pre>// Do this instead<br>function SearchBox({ onSearch }) {<br>  return (<br>    &lt;input onChange={(e) =&gt; onSearch(e.target.value.trim())} /&gt;<br>  );<br>}</pre><p>No effect needed. Just handle it in the event handler.</p><p>The rule: If you can do it during render or in an event handler, do that instead of an effect. Effects are for syncing with external systems (APIs, timers, DOM listeners), not for transforming data.</p><h3>Next Steps: What You Can Build</h3><p>Spend the next hour building a weather widget that ties everything together:</p><ol><li>Create a component that fetches weather data based on a city name.</li><li>Let the user type a city name in an input field.</li><li>On submit, update the dependency array and fetch new data.</li><li>Show loading and error states.</li><li>Add a cleanup function with AbortController.</li><li>Wrap your app in &lt;StrictMode&gt; and verify the effect runs twice in development (it should, and everything should still work).</li></ol><p>This will give you real practice with dependencies, cleanup, state updates, and debugging.</p><h3>Wrapping Up</h3><p>useEffect isn&#39;t magic—it&#39;s a tool for syncing your component with the outside world. The three key things to remember:</p><ol><li>Dependency array matters. [] means &quot;run once.&quot; [value] means &quot;run when value changes.&quot; No array means &quot;run every render&quot; (almost always wrong).</li><li>Always clean up. Return a function that cancels timers, removes listeners, or aborts requests. React 18 will call it, and your app will thank you.</li><li>React 18’s double-invoke isn’t a bug; it’s a feature. It forces you to write correct code.</li></ol><p>If you enjoyed this, check out my previous post on <a href="https://medium.com/@ramajha/moving-state-up-why-your-components-need-a-common-parent-d14098928120"><strong>Moving State Up: Why Your Components Need a Common Parent</strong></a><strong> </strong>— it connects directly to today’s lesson, because managing side effects often means thinking about where your state lives.</p><h3>About the Author</h3><p>I’m a React developer who spends way too much time thinking about why things work the way they do. I’ve written this handbook because I remember when ES6 syntax felt like a wall, and I want to save you that confusion. You can find me on <a href="https://x.com/rama_vats">X/Twitter</a>, or <a href="https://github.com/ramavats">GitHub</a>. Drop a question anytime; I read every reply.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=915d162acc42" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Mutating Arrays in React State Breaks Your UI (Here’s the Fix)]]></title>
            <link>https://ramajha.medium.com/mutating-arrays-in-react-state-breaks-your-ui-heres-the-fix-e3939a1cef8d?source=rss-faab09a15a3d------2</link>
            <guid isPermaLink="false">https://medium.com/p/e3939a1cef8d</guid>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[react-state]]></category>
            <category><![CDATA[react]]></category>
            <category><![CDATA[reactjs]]></category>
            <category><![CDATA[frontend]]></category>
            <dc:creator><![CDATA[Rama Jha]]></dc:creator>
            <pubDate>Thu, 11 Dec 2025 12:02:26 GMT</pubDate>
            <atom:updated>2025-12-11T12:02:26.975Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1C-oZc3XA8yAiLnob_5HmA.png" /></figure><p>Last month, I watched a junior developer debug a todo app for 20 minutes. Items added just fine, but when they edited an existing todo, the UI didn’t refresh. They’d updated the object in the array, called setState, and waited for React to re-render. Nothing happened. The culprit? A shallow mutation hiding in plain sight.​</p><p>This is the exact moment immutability clicks for most React developers. Let’s dig into why arrays in state need special handling and how to avoid the bugs that come from mutating them.</p><h3>Why React Doesn’t See Your Array Changes</h3><p>React compares the old state object to the new one to decide whether to re-render. It does this efficiently with a shallow check: if the reference to the state variable didn’t change, React assumes nothing changed inside it.​</p><p>Here’s the trap. JavaScript arrays are objects, and objects are mutable. You can do this:</p><pre>const myArray = [1, 2, 3];<br><br>myArray[0] = 99;  // Changed the array, but didn&#39;t change the reference</pre><p>The array still exists at the same memory address. React sees setState(myArray) and thinks, &quot;That&#39;s the same array I already have. Nothing to re-render.&quot; But you changed the contents, so your UI lies to the user.​</p><p>Let me show you the broken version:</p><pre>import { useState } from &#39;react&#39;;<br><br>export default function TodoList() {<br>  const [todos, setTodos] = useState([]);<br>  function handleAddTodo(text) {<br>    // ❌ MUTATION: This modifies the existing array<br>    todos.push({ id: Date.now(), text, done: false });<br>    setTodos(todos);  // React doesn&#39;t notice-same reference<br>  }<br>  function handleToggleTodo(id) {<br>    // ❌ MUTATION: This modifies an object inside the array<br>    const todo = todos.find(t =&gt; t.id === id);<br>    todo.done = !todo.done;<br>    setTodos(todos);  // React still doesn&#39;t notice<br>  }<br>  return (<br>    &lt;div&gt;<br>      &lt;button onClick={() =&gt; handleAddTodo(&#39;New task&#39;)}&gt;Add Todo&lt;/button&gt;<br>      &lt;ul&gt;<br>        {todos.map(todo =&gt; (<br>          &lt;li key={todo.id}&gt;<br>            &lt;input type=&quot;checkbox&quot; checked={todo.done} onChange={() =&gt; handleToggleTodo(todo.id)} /&gt;<br>            {todo.text}<br>          &lt;/li&gt;<br>        ))}<br>      &lt;/ul&gt;<br>    &lt;/div&gt;<br>  );<br>}</pre><p>Run this and you’ll experience the bug: the UI won’t update even though the data changed. Sometimes it’ll update after a second action (the visual lag feels broken), or it won’t update at all. The state changed, but React never knew to look.</p><h3>The Fix: Create a New Array, Every Time</h3><p>The solution is simple in principle but easy to mess up in practice: always pass a new array reference to setState. React uses that new reference to know something changed and runs a re-render.​</p><p>For adding items, use the spread operator:</p><pre>function handleAddTodo(text) {<br>  // ✅ CORRECT: Create a new array with spread operator<br>  setTodos([<br>    ...todos,<br>    { id: Date.now(), text, done: false }<br>  ]);<br>}</pre><p>This creates a brand-new array in memory with the old items plus the new one. React sees a different reference and knows to re-render.​</p><p>For removing items, use filter():</p><pre>function handleDeleteTodo(id) {<br>  // ✅ CORRECT: filter() returns a new array<br>  setTodos(todos.filter(t =&gt; t.id !== id));<br>}</pre><p>filter() doesn&#39;t mutate the original—it returns a fresh array containing only the items that pass the test.​</p><p>For updating an item in the array, use map():</p><pre>function handleToggleTodo(id) {<br>  // ✅ CORRECT: map() returns a new array with transformed items<br>  setTodos(todos.map(todo =&gt;<br>    todo.id === id<br>      ? { ...todo, done: !todo.done }  // Create a new object for this item<br>      : todo  // Keep other items unchanged<br>  ));<br>}</pre><p>Here’s the gotcha: map() gives you a new array, but if you mutate the objects <em>inside</em> it, you still break state. That&#39;s why we use the object spread operator { ...todo, done: ... } to create a fresh object, not mutate the original.​</p><h3>The Complete, Fixed Example</h3><pre>import { useState } from &#39;react&#39;;<br><br>export default function TodoList() {<br>  const [todos, setTodos] = useState([]);<br>  function handleAddTodo(text) {<br>    if (text.trim()) {<br>      setTodos([<br>        ...todos,<br>        { id: Date.now(), text, done: false }<br>      ]);<br>    }<br>  }<br>  function handleToggleTodo(id) {<br>    setTodos(todos.map(todo =&gt;<br>      todo.id === id<br>        ? { ...todo, done: !todo.done }<br>        : todo<br>    ));<br>  }<br>  function handleDeleteTodo(id) {<br>    setTodos(todos.filter(t =&gt; t.id !== id));<br>  }<br>  return (<br>    &lt;div&gt;<br>      &lt;input onKeyDown={e =&gt; {<br>        if (e.key === &#39;Enter&#39;) {<br>          handleAddTodo(e.target.value);<br>          e.target.value = &#39;&#39;;<br>        }<br>      }} placeholder=&quot;Add a todo...&quot; /&gt;<br>      &lt;ul&gt;<br>        {todos.map(todo =&gt; (<br>          &lt;li key={todo.id} style={{ textDecoration: todo.done ? &#39;line-through&#39; : &#39;none&#39; }}&gt;<br>            &lt;input<br>              type=&quot;checkbox&quot;<br>              checked={todo.done}<br>              onChange={() =&gt; handleToggleTodo(todo.id)}<br>            /&gt;<br>            {todo.text}<br>            &lt;button onClick={() =&gt; handleDeleteTodo(todo.id)}&gt;Delete&lt;/button&gt;<br>          &lt;/li&gt;<br>        ))}<br>      &lt;/ul&gt;<br>    &lt;/div&gt;<br>  );<br>}</pre><p>Run this version and you’ll see updates happen instantly. Every action — add, toggle, delete — creates a new array, React catches it, and the UI reflects the truth.</p><h3>A Common Trap: Shallow Copy Isn’t Always Enough</h3><p>Here’s where people stumble next. You might copy an array but mutate its contents:</p><pre>function handleBadUpdate(id) {<br>  // ❌ NEW ARRAY, but same objects inside<br>  const newTodos = [...todos];<br>  const todo = newTodos.find(t =&gt; t.id === id);<br>  todo.done = !todo.done;  // Mutating an object that&#39;s still in the original state<br>  setTodos(newTodos);<br>}</pre><p>The spread operator is <em>shallow</em> — it copies the array structure, not the objects it contains. Both the old todos and the new newTodos point to the same todo objects in memory. You&#39;ve created a new array, but you&#39;re mutating shared objects. React will re-render (because the array reference changed), but you&#39;ve polluted the old state, which breaks features like undo/redo and can cause nasty bugs in concurrent rendering.​</p><p>That’s why we use map() with object spread for updates: it ensures a fresh object exists in the new array.</p><h3>When Arrays Get Complex: Meet Immer</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*haZlU25SKXVnB-LJXceSxA.png" /></figure><p>If your arrays contain deeply nested objects or you’re doing a lot of updates, the spread syntax can get repetitive:</p><pre>function handleUpdateNested(id, newArtwork) {<br>  setTodos(todos.map(todo =&gt;<br>    todo.id === id<br>      ? { ...todo, details: { ...todo.details, artwork: { ...newArtwork } } }<br>      : todo<br>  ));<br>}</pre><p>This is verbose and error-prone. The <a href="https://www.npmjs.com/package/immer">Immer library</a> lets you write mutating code, and it figures out the immutable updates for you:​</p><pre>import { useImmer } from &#39;use-immer&#39;;<br><br>function handleUpdateNested(id, newArtwork) {<br>  updateTodos(draft =&gt; {<br>    const todo = draft.find(t =&gt; t.id === id);<br>    if (todo) {<br>      todo.details.artwork = newArtwork;  // Looks like mutation, but Immer handles it<br>    }<br>  });<br>}</pre><p>With Immer, you write the convenient syntax, and it produces the immutable updates behind the scenes. For beginners, I’d stick with map() and spread—it teaches you the mental model. But Immer&#39;s worth knowing for larger projects.</p><h3>Try This Now: Build a Mini Todo App</h3><p>Open your React sandbox and create this challenge in under 5 minutes:</p><ol><li>Build a list that displays three hardcoded todos.</li><li>Add a button for each that toggles done on click—watch the UI update.</li><li>Add a button that removes a todo from the list.</li></ol><p>Use only map() and filter(). No Immer. This forces you to feel the pattern in your hands, not just read about it.</p><h3>Learning Objectives</h3><p>After reading this, you should be able to:</p><ol><li>Explain why mutating array state breaks React re-renders and how spread/map/filter create new references.</li><li>Write add, remove, and update operations on arrays in state without mutations.</li><li>Identify shallow copy bugs and fix them by creating new objects within new arrays.</li><li>Recognize when Immer makes sense and know how to install it.</li><li>Debug “update doesn’t show” problems by checking whether you’re mutating vs. creating new arrays.</li></ol><h3>Prerequisites</h3><p>You should have React installed (via create-react-app or Vite) and be comfortable with useState basics. If you need a refresher on why state exists, check <a href="https://medium.com/@ramajha/the-usestate-rabbit-hole-functional-updates-stale-closures-and-your-first-counter-481d65d6241f">The useState Rabbit Hole</a>.</p><h3>What’s Next</h3><p>Build a form-based todo app that lets users edit todo text. You’ll need to update an object inside an array — this teaches you map() with object spread in one shot. It should take under an hour and cement the pattern.</p><p>If you enjoyed this, check out my previous post on <a href="https://medium.com/@ramajha/moving-state-up-why-your-components-need-a-common-parent-d14098928120">Moving State Up: Why Your Components Need a Common Parent</a> — it connects directly to today’s lesson and shows you the foundation that makes immutability necessary.</p><h3>About Me</h3><p>I’m a React developer who spends way too much time thinking about why things work the way they do. I’ve written this handbook because I remember when ES6 syntax felt like a wall, and I want to save you that confusion. You can find me on <a href="https://x.com/rama_vats">X/Twitter</a>, or <a href="https://github.com/ramavats">GitHub</a>. Drop a question anytime; I read every reply.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e3939a1cef8d" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Moving State Up: Why Your Components Need a Common Parent]]></title>
            <link>https://ramajha.medium.com/moving-state-up-why-your-components-need-a-common-parent-d14098928120?source=rss-faab09a15a3d------2</link>
            <guid isPermaLink="false">https://medium.com/p/d14098928120</guid>
            <category><![CDATA[reactjs]]></category>
            <category><![CDATA[webdev]]></category>
            <category><![CDATA[front-end-development]]></category>
            <category><![CDATA[react]]></category>
            <category><![CDATA[javascript]]></category>
            <dc:creator><![CDATA[Rama Jha]]></dc:creator>
            <pubDate>Sun, 07 Dec 2025 12:02:13 GMT</pubDate>
            <atom:updated>2025-12-07T12:02:13.725Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*FFoiQP3cGb30jbv-79e9Bg.png" /></figure><p>I remember the exact moment my temperature converter broke. Two inputs, one supposed to update the other — Celsius to Fahrenheit and back. But they’d both change, they’d both have different values, and I had no idea why. Turns out I’d built them as independent components with separate state. They were like two people trying to dance to the same song from different rooms, never quite in sync.</p><p>That’s the moment I learned about lifting state.</p><p>In React, lifting state up is one of those patterns that doesn’t sound like much until you realize how often you need it. It’s the antidote to components talking past each other, and it’s the bridge between scattered local state and needing a full state management library. Let’s dig into why your components sometimes need a common parent, and how to build that relationship without making things more complicated than they need to be.</p><h3>Learning Objectives</h3><p>After reading this post, you’ll be able to:</p><ol><li>Identify when two or more components need to share state and recognize when lifting is the right pattern</li><li>Move state from child components to a common parent and wire up props correctly</li><li>Write handler functions that encapsulate state updates, rather than passing setState directly</li><li>Understand the difference between controlled components and independent state</li><li>Recognize the limits of lifting and know when to reach for Context API instead</li></ol><h3>Prerequisites</h3><p>You should already be comfortable with:</p><ul><li>The basics of useState in functional components</li><li>Passing and receiving props</li><li>How React re-renders when state changes</li></ul><p>No external libraries needed for this pattern — just React’s built-in useState and basic JavaScript.</p><h3>What’s Actually Happening When You Lift State?</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yDPJLyhaxberA7tLUgazgA.png" /></figure><p>Here’s the honest truth: lifting state is just moving where your state lives. That’s it. Instead of each component managing its own piece of truth, you push that state up to a parent component, and the children read from and write to that shared parent state through props.</p><p>The pattern solves a real problem. When two or more components need to stay in sync — like those temperature inputs, or a todo list where the counter and the task display both need the same data — keeping state in separate places means they’ll inevitably drift out of sync. The parent becomes the single source of truth. Child components don’t store their own copy; they just reflect what the parent tells them to show.​</p><p>React enforces unidirectional data flow. Data flows down as props; updates flow up through callback functions. This isn’t a limitation — it’s the whole point. It makes your app predictable. You always know where state lives, and you always know how to change it.</p><h3>The Pattern: State, Props, and Handlers</h3><p>Let’s build the temperature converter and actually see this work. I’ll show you the right way to pass update functions, not the anti-pattern you’ll sometimes see online.</p><p>Parent Component (TemperatureConverter.jsx):</p><pre>import { useState } from &#39;react&#39;;<br>import CelsiusInput from &#39;./CelsiusInput&#39;;<br>import FahrenheitInput from &#39;./FahrenheitInput&#39;;<br><br>export default function TemperatureConverter() {<br>  const [celsius, setCelsius] = useState(&#39;&#39;);<br>  const fahrenheit = celsius ? (celsius * 9) / 5 + 32 : &#39;&#39;;<br>  const handleCelsiusChange = (e) =&gt; {<br>    setCelsius(e.target.value);<br>  };<br>  const handleFahrenheitChange = (e) =&gt; {<br>    const f = parseFloat(e.target.value);<br>    setCelsius((f - 32) * 5) / 9);<br>  };<br>  return (<br>    &lt;div&gt;<br>      &lt;h2&gt;Temperature Converter&lt;/h2&gt;<br>      &lt;CelsiusInput value={celsius} onChange={handleCelsiusChange} /&gt;<br>      &lt;FahrenheitInput value={fahrenheit} onChange={handleFahrenheitChange} /&gt;<br>    &lt;/div&gt;<br>  );<br>}</pre><p>Child Component (CelsiusInput.jsx):</p><pre>export default function CelsiusInput({ value, onChange }) {<br>  return (<br>    &lt;div&gt;<br>      &lt;label&gt;Celsius&lt;/label&gt;<br>      &lt;input <br>        type=&quot;number&quot; <br>        value={value} <br>        onChange={onChange} <br>        placeholder=&quot;Enter temperature&quot;<br>      /&gt;<br>    &lt;/div&gt;<br>  );<br>}</pre><p>Child Component (FahrenheitInput.jsx):</p><pre>export default function FahrenheitInput({ value, onChange }) {<br>  return (<br>    &lt;div&gt;<br>      &lt;label&gt;Fahrenheit&lt;/label&gt;<br>      &lt;input <br>        type=&quot;number&quot; <br>        value={value} <br>        onChange={onChange} <br>        placeholder=&quot;Enter temperature&quot;<br>      /&gt;<br>    &lt;/div&gt;<br>  );<br>}</pre><p>See what’s happening here? The parent owns the state. The children receive value and onChange as props. When a child&#39;s input changes, it calls the onChange handler passed from the parent. The parent updates its state, recalculates the other temperature, and both children re-render with fresh values. They&#39;re always in sync because there&#39;s only one source of truth.​</p><p>The children don’t care <em>how</em> the state updates. They just call the callback. This is crucial — it decouples the child from the parent’s internal logic.</p><h3>The Gotcha: Don’t Pass setState Directly</h3><p>Here’s where people stumble. You might think: “Why not just pass setCelsius directly to the child?&quot; Technically, you can, but you shouldn&#39;t.​</p><pre>// ❌ Don&#39;t do this<br>&lt;CelsiusInput setTemperature={setCelsius} /&gt;</pre><p>The problem? Your child component now knows it’s writing to the parent’s state. It’s tightly coupled. More importantly, setState is asynchronous and batched. If you pass it directly, your child loses the ability to wrap the update in any logic—error checking, validation, side effects, or coordinating multiple state updates. You&#39;ve handed all control to the child.​</p><p>Instead, pass a handler function that you’ve written:</p><pre>// ✅ Do this<br>const handleCelsiusChange = (e) =&gt; {<br>  const value = e.target.value;<br>  if (value === &#39;&#39; || !isNaN(value)) {<br>    setCelsius(value);<br>  }<br>};<br><br>&lt;CelsiusInput value={celsius} onChange={handleCelsiusChange} /&gt;</pre><p>Now validation lives in the parent, where all your temperature logic lives. The child is a dumb input — it just renders and calls what you gave it. This separation makes your code easier to test, debug, and extend.</p><h3>When Lifting State Gets Messy (and What to Do About It)</h3><p>Here’s my honest opinion: lifting state is great until you’re passing props through five levels of components to reach the one that actually needs them. This is called prop drilling, and it’s the moment you’re supposed to reach for Context API or a state management library like Zustand.​</p><p>But here’s the catch — you’ll reach for those tools too early if you don’t understand lifting state first. Why? Because those solutions solve a different problem than just two or three components sharing data. If you have a parent and two children that need to sync, lifting state is the right answer. It’s simple, explicit, and you don’t need any extra dependencies.</p><p>Try this now: Open your code editor and build a simple form with two related inputs — maybe a password field and a “confirm password” field. Put them in separate child components, lift the state to a parent, and sync them with a handler function. See how they stay in lockstep? That’s the pattern working.</p><h3>One More Thing: Performance and useCallback</h3><p>If your parent component is re-rendering frequently and you’re passing handler functions to memoized children, those functions get recreated on every render. It’s not a disaster for simple apps, but if you want to optimize, wrap your handlers in useCallback:​</p><pre>const handleCelsiusChange = useCallback((e) =&gt; {<br>  setCelsius(e.target.value);<br>}, []);<br><br>const handleFahrenheitChange = useCallback((e) =&gt; {<br>  const f = parseFloat(e.target.value);<br>  setCelsius((f - 32) * 5) / 9);<br>}, []);</pre><p>Now the handler reference stays the same across renders, so a memoized child won’t re-render unnecessarily. But honestly? Don’t over-optimize here. If you’re building a form with a few inputs, this level of tuning is premature. Get it working first, then profile if performance is actually slow.</p><h3>What You Can Do Now</h3><p>After reading this, you’re ready to:</p><ol><li>Identify shared state between sibling components and move it to their nearest common parent</li><li>Write handler functions in the parent and pass them down as props (not setState directly)</li><li>Build controlled components where the parent owns the value and the child just renders and calls callbacks</li><li>Recognize when lifting stops making sense — when you’re passing props through too many layers, it’s time for Context API</li><li>Debug synchronization issues by tracing state to its owner — if two components are out of sync, their shared state is probably still scattered</li></ol><h3>Next Steps</h3><p>Pick a feature you’ve built where multiple components show or edit the same data. Refactor it to lift state to their common parent, moving all the logic and update handlers up there. You’ll probably finish this in under an hour, and you’ll feel the simplicity click. Once you’ve done it once, the pattern becomes automatic.</p><p>If you enjoyed this, check out my previous post on <a href="https://medium.com/@ramajha/stop-overthinking-props-vs-state-3-tiny-rules-that-work-in-every-project-1b6c711d8d2d">Stop Overthinking Props vs. State: 3 Tiny Rules That Work in Every Project</a> — it connects directly to today’s lesson and lays the groundwork for understanding why lifting state even matters.</p><h3>About the Author</h3><p>I’m a React developer who spends way too much time thinking about why things work the way they do. I’ve written this handbook because I remember when ES6 syntax felt like a wall, and I want to save you that confusion. You can find me on <a href="https://x.com/rama_vats">X/Twitter</a>, or <a href="https://github.com/ramavats">GitHub</a>. Drop a question anytime; I read every reply.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d14098928120" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Controlled Inputs and Boolean Toggles: The 80% of React State You Need to Know]]></title>
            <link>https://ramajha.medium.com/controlled-inputs-and-boolean-toggles-the-80-of-react-state-you-need-to-know-1f99512cbae3?source=rss-faab09a15a3d------2</link>
            <guid isPermaLink="false">https://medium.com/p/1f99512cbae3</guid>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[react]]></category>
            <category><![CDATA[front-end-development]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[reactjs]]></category>
            <dc:creator><![CDATA[Rama Jha]]></dc:creator>
            <pubDate>Tue, 25 Nov 2025 12:02:13 GMT</pubDate>
            <atom:updated>2025-11-25T12:03:08.094Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*E0fedAa1qgpO67w8TH2CRQ.png" /></figure><p>Last Tuesday, I watched a junior dev spend twenty minutes debugging why their shopping cart’s “Remove Item” button was removing two items instead of one. The problem? They’d chained two state updates in a click handler without understanding how React 18’s batching actually works. By lunchtime, one tiny prev arrow function fixed it. That moment stuck with me because it&#39;s where theory collides with practice—and it&#39;s exactly what today&#39;s about.</p><p>Most React apps live in two simple state patterns: booleans for toggling visibility or features, and strings for capturing what users type. Master these, understand how functional updates work, and you’ve solved probably 80% of the state problems you’ll actually hit in production. Let’s go there.</p><h3>Learning Objectives</h3><p>After reading this post, you’ll be able to:</p><ul><li>Build toggle components that switch visibility or features on and off using boolean state</li><li>Create controlled inputs that sync their value with React state and display live updates</li><li>Understand the difference between setValue(newValue) and setValue(prev =&gt; ...), and know when each matters</li><li>Recognize and fix the batching gotchas that trip up beginners in React 18</li><li>Handle multiple rapid state updates without losing information to stale closures</li></ul><h3>Prerequisites</h3><p>You should have worked through <a href="https://medium.com/@ramajha/the-usestate-rabbit-hole-functional-updates-stale-closures-and-your-first-counter-481d65d6241f"><strong>Day 15’s intro to useState</strong></a> and understand that state variables trigger re-renders when you call their setter function. If useState syntax feels new, spend 10 minutes reviewing the basics before diving in.</p><h3>The Boolean Toggle: Your First State Pattern</h3><p>Let’s start with something you’ve probably seen a hundred times: a button that shows or hides content.</p><pre>import { useState } from &#39;react&#39;;<br><br>export default function ToggleVisibility() {<br>  const [isVisible, setIsVisible] = useState(false);<br>  return (<br>    &lt;div&gt;<br>      &lt;button onClick={() =&gt; setIsVisible(!isVisible)}&gt;<br>        {isVisible ? &#39;Hide Details&#39; : &#39;Show Details&#39;}<br>      &lt;/button&gt;<br>      {isVisible &amp;&amp; (<br>        &lt;p&gt;Here&#39;s something you can toggle on and off.&lt;/p&gt;<br>      )}<br>    &lt;/div&gt;<br>  );<br>}</pre><p>This is straightforward: isVisible is false initially, we flip it with !isVisible in the click handler, and conditional rendering shows or hides the paragraph. React re-renders when state changes, so the button text and the paragraph update instantly.</p><p>The gotcha most people miss? Try clicking that button rapidly. You’re fine. But if you had called setIsVisible(!isVisible) twice inside the same handler before batching was introduced, you&#39;d see unexpected behavior. We&#39;ll circle back to why—React 18 fixed it—but for now, the mental model is simple: each toggle call inverts the state.</p><h3>Here’s Where Beginners Stumble: The Batching Problem</h3><p>Before React 18, if you did this:</p><pre>function handleClick() {<br>  setIsVisible(!isVisible);<br>  setIsVisible(!isVisible);<br>  // After the click, isVisible is still the same—both calls saw the old value<br>}</pre><p>Both calls would read isVisible as, say, false, and both would set it to true. You&#39;d only get one update instead of two flips. The state value didn&#39;t change mid-handler because React batches state updates.</p><p>React 18 made this <em>more</em> predictable by batching updates everywhere (not just in event handlers), but it also means you can’t rely on the latest state mid-execution. This is where the updater function pattern saves you.</p><h3>The Updater Function Pattern: prev =&gt; ...</h3><p>Instead of reading the state directly, you pass a function that receives the current pending state:</p><pre>export default function Counter() {<br>  const [count, setCount] = useState(0);<br><br>function handleTripleClick() {<br>    setCount(c =&gt; c + 1); // Receives 0, returns 1<br>    setCount(c =&gt; c + 1); // Receives 1, returns 2<br>    setCount(c =&gt; c + 1); // Receives 2, returns 3<br>  }<br>  return (<br>    &lt;div&gt;<br>      &lt;p&gt;Count: {count}&lt;/p&gt;<br>      &lt;button onClick={handleTripleClick}&gt;+3&lt;/button&gt;<br>    &lt;/div&gt;<br>  );<br>}</pre><p>React queues each updater function and runs them in order. The first call sees 0 and returns 1. The second sees 1 and returns 2. The third sees 2 and returns 3. No stale closures, no surprise duplicates.</p><p>When should you use this pattern? Any time you’re calculating the next state <em>from the previous state</em>. If you’re just setting a new fixed value — like setName(&#39;Alice&#39;)—the direct approach is fine. But the moment you&#39;re incrementing, toggling, or transforming based on what came before, use the updater function.</p><h3>Controlled Inputs: Strings and Live Updates</h3><p>Now let’s talk about capturing user input. A controlled input is one where React “owns” its value through state.</p><pre>import { useState } from &#39;react&#39;;<br><br>export default function NameInput() {<br>  const [name, setName] = useState(&#39;&#39;);<br>  return (<br>    &lt;div&gt;<br>      &lt;input<br>        type=&quot;text&quot;<br>        value={name}<br>        onChange={(e) =&gt; setName(e.target.value)}<br>        placeholder=&quot;Type your name&quot;<br>      /&gt;<br>      &lt;p&gt;Hello, {name || &#39;stranger&#39;}!&lt;/p&gt;<br>    &lt;/div&gt;<br>  );<br>}</pre><p>Here’s the flow: the input’s value prop is tied to the name state. When the user types, the onChange handler fires, reads the new value from the event object (e.target.value), and calls setName to update state. React re-renders, the input&#39;s value updates, and your greeting updates in sync. It feels instant to the user.</p><p>The key insight: the input value is never your source of truth — state is. If you tried to set the input’s value directly with JavaScript (inputElement.value = &#39;...&#39;), React would get out of sync and weird bugs would surface. Always funnel through state.</p><h3>A Real-World Application: A Live-Updating Form</h3><p>Let’s combine what we’ve learned. Here’s a component that demonstrates both toggles and controlled inputs working together:</p><pre>import { useState } from &#39;react&#39;;<br>import styles from &#39;./Form.module.css&#39;;<br><br>export default function SignupForm() {<br>  const [email, setEmail] = useState(&#39;&#39;);<br>  const [password, setPassword] = useState(&#39;&#39;);<br>  const [showPassword, setShowPassword] = useState(false);<br>  function handleSubmit(e) {<br>    e.preventDefault();<br>    if (!email || !password) {<br>      alert(&#39;Please fill in all fields&#39;);<br>      return;<br>    }<br>    console.log(&#39;Submitting:&#39;, { email, password });<br>  }<br>  return (<br>    &lt;form onSubmit={handleSubmit} className={styles.form}&gt;<br>      &lt;label&gt;<br>        Email:<br>        &lt;input<br>          type=&quot;email&quot;<br>          value={email}<br>          onChange={(e) =&gt; setEmail(e.target.value)}<br>          placeholder=&quot;you@example.com&quot;<br>        /&gt;<br>      &lt;/label&gt;<br>      &lt;label&gt;<br>        Password:<br>        &lt;input<br>          type={showPassword ? &#39;text&#39; : &#39;password&#39;}<br>          value={password}<br>          onChange={(e) =&gt; setPassword(e.target.value)}<br>          placeholder=&quot;••••••&quot;<br>        /&gt;<br>      &lt;/label&gt;<br>      &lt;button<br>        type=&quot;button&quot;<br>        onClick={() =&gt; setShowPassword(!showPassword)}<br>        className={styles.toggleBtn}<br>      &gt;<br>        {showPassword ? &#39;👁️ Hide&#39; : &#39;👁️ Show&#39;}<br>      &lt;/button&gt;<br>      &lt;button type=&quot;submit&quot;&gt;Sign Up&lt;/button&gt;<br>      &lt;p className={styles.preview}&gt;<br>        Email: &lt;strong&gt;{email || &#39;(empty)&#39;}&lt;/strong&gt;<br>      &lt;/p&gt;<br>    &lt;/form&gt;<br>  );<br>}</pre><p>Notice a few things:</p><ul><li>Email and password are controlled strings — they update as the user types.</li><li>showPassword is a boolean toggle that changes the input type between &#39;password&#39; and &#39;text&#39;.</li><li>The form’s onSubmit handler runs validation before logging data (in a real app, you&#39;d send this to a server).</li><li>The preview paragraph shows the current email value live — this is React’s reactivity in action.</li></ul><p>Try this now: Copy this component and add another toggle for “Remember me” that stores a boolean in state. Use the updater function pattern to add a button that clears both the email and password at once (with two updates in one handler).</p><h3>Understanding React 18’s Batching and State Updates</h3><p>React 18 introduced automatic batching outside event handlers too. This means if you call state setters inside an async callback or a Promise, they’ll still batch together.</p><pre>export default function AsyncExample() {<br>  const [count, setCount] = useState(0);<br>  const [loading, setLoading] = useState(false);<br><br>function handleFetch() {<br>    setLoading(true); // Batched with the next update<br>    fetch(&#39;/api/data&#39;).then(() =&gt; {<br>      setCount(c =&gt; c + 1);<br>      setLoading(false); // Both of these batch together<br>    });<br>  }<br>  return (<br>    &lt;div&gt;<br>      &lt;button onClick={handleFetch}&gt;Fetch &amp; Increment&lt;/button&gt;<br>      &lt;p&gt;Count: {count}, Loading: {String(loading)}&lt;/p&gt;<br>    &lt;/div&gt;<br>  );<br>}</pre><p>In older React versions, those two then callbacks would cause separate re-renders. Now they batch—only one re-render happens after both updates. This is faster and more predictable.</p><p>The trade-off: Sometimes you genuinely need to force a re-render between updates, like if you’re accessing the DOM for measurements. React exports a flushSync function for rare cases, but you&#39;ll rarely need it. Batch updates are good. Trust the batching.</p><h3>One More Gotcha: Functional vs. Direct Updates</h3><p>Here’s a question that confuses lots of developers: should I always use the updater function? The answer is <em>no, not always</em> — but it’s worth understanding the trade-off.</p><pre>// Direct update—simple, clear when state is independent<br>setName(&#39;Alice&#39;);<br><br>// Updater function-safer when the new state depends on the old one<br>setCount(c =&gt; c + 1);<br><br><br>// You could technically use updater for everything, but it&#39;s overkill<br>setName(prev =&gt; &#39;Alice&#39;); // ← Unnecessary, because &#39;Alice&#39; doesn&#39;t depend on prev</pre><p>A practical heuristic: If you’re reading the current state value to calculate the next value, use the updater function. Otherwise, set the value directly. The updater function is insurance against batching bugs and stale closures — it’s worth the extra parentheses.</p><h3>Debugging Moment: Why Isn’t My Input Updating?</h3><p>You’ll inevitably hit this: you type in an input and nothing happens.</p><pre>// 🚩 Common mistake: forgetting the value prop<br>&lt;input onChange={(e) =&gt; setEmail(e.target.value)} /&gt;<br><br><br><br>// ✅ The input must be controlled (value prop tied to state)<br>&lt;input value={email} onChange={(e) =&gt; setEmail(e.target.value)} /&gt;</pre><p>If your input is missing the value prop, React can&#39;t control it. It becomes &quot;uncontrolled&quot;—the browser manages its own value, and your state sits unused. The fix is always to add value={stateVariable}.</p><p>Another common one: the handler runs, but state doesn’t update.</p><pre>// 🚩 Mistake: setState is called with a function, but React treats it as an initializer<br>const [fn, setFn] = useState(someFunction);<br><br><br>// ✅ Wrap it to store a function as state<br>const [fn, setFn] = useState(() =&gt; someFunction);</pre><p>This only comes up if you’re storing functions in state (rare), but it’s worth knowing because the error message is cryptic.</p><h3>What’s Next?</h3><p>Pick one piece of functionality you’ve sketched but haven’t built: a password visibility toggle, a search box that filters a list, a form with multiple fields. Build it with these patterns — boolean state for toggles, string state for inputs, and updater functions for multi-step updates. Spend an hour on it. You’ll own this stuff by muscle memory.</p><h3>Looking Further</h3><p>We’ve covered the most common state patterns. Tomorrow (Day 17), we’ll step into arrays and objects in state — where immutability becomes critical. The principles you’re learning now scale directly to more complex state shapes, so cement these ideas first.</p><p>If you enjoyed this, check out my previous post on <a href="https://medium.com/@ramajha/stop-overthinking-props-vs-state-3-tiny-rules-that-work-in-every-project-1b6c711d8d2d"><strong>Stop Overthinking Props vs. State: 3 Tiny Rules That Work in Every Project</strong></a> — it connects directly to today’s lesson and covers the fundamentals that make everything here click.</p><h3>About the Author</h3><p>I’m a React developer documenting my 30-day React journey on Medium, sharing the lessons, gotchas, and “oh no” moments that beginners actually face. Follow along for practical tutorials, real code, and no buzzword fluff. Let’s connect on <a href="https://x.com/rama_vats/">[X/Twitter]</a> and <a href="https://github.com/ramavats">[GitHub]</a> — I’d love to hear about your own list-rendering disasters (we’ve all been there). Got questions? Drop a comment or message me anytime.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1f99512cbae3" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The useState Rabbit Hole: Functional Updates, Stale Closures, and Your First Counter]]></title>
            <link>https://ramajha.medium.com/the-usestate-rabbit-hole-functional-updates-stale-closures-and-your-first-counter-481d65d6241f?source=rss-faab09a15a3d------2</link>
            <guid isPermaLink="false">https://medium.com/p/481d65d6241f</guid>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[front-end-development]]></category>
            <category><![CDATA[reactjs]]></category>
            <category><![CDATA[react]]></category>
            <dc:creator><![CDATA[Rama Jha]]></dc:creator>
            <pubDate>Sat, 22 Nov 2025 12:02:06 GMT</pubDate>
            <atom:updated>2025-11-22T12:02:06.036Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*uSU2FgqeDaPUtCkWE08JyQ.png" /></figure><h3>Learning Objectives</h3><p>After reading this post, you’ll be able to:</p><ol><li>Declare and use useState with correct array destructuring syntax</li><li>Understand state as a snapshot and why calling setState doesn&#39;t change the value immediately</li><li>Implement functional (updater) patterns to reliably handle multiple state updates</li><li>Recognize the stale closure gotcha when state updates depend on previous values</li><li>Debug React 18 StrictMode double-invocations and understand why they’re actually your friend</li></ol><h3>Prerequisites</h3><p>You should have a basic understanding of React functional components and know how to create a simple component. Familiarity with JavaScript array destructuring (const [a, b] = arr) is helpful. All examples use React 18+; if you&#39;re on an older version, some behavior (especially StrictMode) may differ.</p><h3>Why useState Exists (And Why You Probably Got It “Wrong” at First)</h3><p>I still remember the first time I tried to build a counter without looking at docs. I did something like this:</p><pre>let count = 0;<br><br>function Counter() {<br>  function handleClick() {<br>    count += 1;<br>    console.log(count); // Works!<br>  }<br>  <br>  return &lt;button onClick={handleClick}&gt;{count}&lt;/button&gt;;<br>}</pre><p>It printed the right number. The button just never updated on screen. I spent twenty minutes confused before I realized: React didn’t <em>know</em> the value changed. There’s no magic observer watching my count variable.</p><p>That’s what useState solves. It tells React: &quot;Hey, this value can change, and when it does, please re-render my component.&quot;</p><h3>The Basics: useState Syntax and Array Destructuring</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*RSJxXTgFwLxIOB8fC3jBmQ.png" /></figure><p>Here’s how you declare state in a functional component:</p><pre>import { useState } from &#39;react&#39;;<br><br>export default function Counter() {<br>  const [count, setCount] = useState(0);<br>  return (<br>    &lt;div&gt;<br>      &lt;p&gt;Count: {count}&lt;/p&gt;<br>      &lt;button onClick={() =&gt; setCount(count + 1)}&gt;Increment&lt;/button&gt;<br>    &lt;/div&gt;<br>  );<br>}</pre><p>Let’s break this down:</p><ul><li>useState(0): You pass the <em>initial</em> value. React stores this and ignores it on every render after the first one.</li><li>const [count, setCount]: useState returns an array with two items. We use destructuring to unpack them. The naming convention is [value, setValue].</li><li>count: The current state value.</li><li>setCount: The function you call to update state and trigger a re-render.</li></ul><p>React 18+ has been around long enough that this pattern is rock-solid. You’ll see it everywhere.</p><h3>The Gotcha: State Updates Don’t Change the Current Value</h3><p>Here’s where things get weird. Try this:</p><pre>function Counter() {<br>  const [count, setCount] = useState(0);<br><br>function handleClick() {<br>    setCount(count + 1);<br>    console.log(count); // What will this print?<br>  }<br>  return &lt;button onClick={handleClick}&gt;{count}&lt;/button&gt;;<br>}</pre><p>You’d think console.log(count) prints the <em>new</em> value, right? Nope. It still prints the <em>old</em> value. If count was 5 and you clicked the button, the console logs 5, not 6.</p><p>This trips up beginners all the time. Here’s why it happens:</p><p>State is a snapshot. When your component renders, count is frozen at a particular value for that render. Calling setCount() doesn&#39;t mutate that frozen value. It <em>requests a new render</em> with a new frozen value. The old value stays frozen in the current function execution.</p><pre>// Think of it like this mentally:<br>function handleClick() {<br>  setCount(count + 1); // Request a re-render with count = 6<br>  console.log(count); // But *this* render still sees count = 5<br>}</pre><p>This is actually a feature, not a bug. It prevents race conditions and makes your code more predictable. But it catches everyone off guard.</p><h3>Re-renders and Why They Matter</h3><p>When you call setCount(), React doesn&#39;t update the button instantly. Here&#39;s what actually happens:</p><ol><li>You call setCount(count + 1)</li><li>React queues up this state update</li><li>React re-renders the Counter component with the new state</li><li>Your function runs again from the top with count now equal to the new value</li><li>The button JSX re-evaluates and React updates the DOM</li></ol><p>This is why the mental model of “state is a snapshot” matters. Each render gets its own frozen version of state. The next render gets a different frozen version.</p><h3>The Problem with Multiple Updates: Meet the Stale Closure</h3><p>Let’s say you want a button that increments the counter by 3:</p><pre>function tripleIncrement() {<br>  setCount(count + 1);<br>  setCount(count + 1);<br>  setCount(count + 1);<br>}</pre><p>What do you think happens? If count starts at 0, you&#39;d expect it to end at 3.</p><p>It ends at 1.</p><p>All three calls happen within the same render. They all see the <em>same frozen value</em> of count (0 in this case). So you end up with:</p><ul><li>setCount(0 + 1) → sets to 1</li><li>setCount(0 + 1) → sets to 1 (overwrites the previous one)</li><li>setCount(0 + 1) → sets to 1 (overwrites again)</li></ul><p>Result: count is 1, not 3. This is called a stale closure — your function is “closed over” an old value of state.</p><p>The fix? Use a functional update (also called an updater function):</p><pre>function tripleIncrement() {<br>  setCount(c =&gt; c + 1); // Updater receives the pending state<br>  setCount(c =&gt; c + 1); // Updater receives the result of the previous update<br>  setCount(c =&gt; c + 1); // Updater receives the result of that update<br>}</pre><p>Now React queues these updates <em>and applies them in order</em>, each one receiving the result of the last:</p><ol><li>setCount(c =&gt; c + 1) → 0 becomes 1</li><li>setCount(c =&gt; c + 1) → 1 becomes 2</li><li>setCount(c =&gt; c + 1) → 2 becomes 3</li></ol><p>Result: count is 3. Correct.</p><p>The updater function gives you access to the pending state — the result of all previously queued updates in this batch. That’s the key difference.</p><h3>When to Use Direct Updates vs. Updaters</h3><p>Direct update (just pass the new value):</p><pre>setCount(count + 1);</pre><p>Use this when you’re updating based on <em>user interaction in a single moment</em>. React guarantees that the latest value is available in event handlers. One click = one update, no stale closure risk.</p><p>Functional update (pass a function):</p><pre>setCount(c =&gt; c + 1);</pre><p>Use this when:</p><ul><li>You’re calling setState multiple times in the same handler</li><li>You’re unsure whether the state you’re reading is fresh</li><li>You want to update based on the <em>previous state</em>, not the state you’re holding</li></ul><p>Here’s where people stumble: functional updates feel verbose, so beginners avoid them. Then their counter mysteriously skips numbers or their form data doesn’t save correctly. They debug for an hour before realizing they needed the updater pattern all along.</p><p>A good rule of thumb: if your update calculation depends on the current state, use the updater. If not, the direct approach is fine.</p><h3>Object State and Immutability</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*LyuwTffs9bu7IcUUewL9sA.png" /></figure><p>State can hold objects, too:</p><pre>const [form, setForm] = useState({ name: &#39;&#39;, age: &#39;&#39; });</pre><p>Here’s a critical rule: treat state as read-only. Don’t do this:</p><pre>// 🚩 Don&#39;t mutate:<br>form.name = &#39;Alice&#39;;<br>setForm(form); // React won&#39;t know anything changed</pre><p>React compares the old and new state objects. If you mutate the object in place, the object reference is the same, so React thinks nothing changed. No re-render.</p><p>Instead, create a new object:</p><pre>// ✅ Correct:<br>setForm({<br>  ...form, // Spread the old values<br>  name: &#39;Alice&#39; // Override just this field<br>});</pre><p>The spread operator (...) creates a shallow copy. React sees a <em>new</em> object, so it knows to re-render.</p><h3>React 18 StrictMode: Why Your Component Renders Twice (And It’s Okay)</h3><p>If you’ve ever opened the console and seen your component render twice, you’ve met StrictMode.</p><p>In development, React 18 wraps your app in &lt;StrictMode&gt; by default:</p><pre>root.render(<br>  &lt;StrictMode&gt;<br>    &lt;App /&gt;<br>  &lt;/StrictMode&gt;<br>);</pre><p>StrictMode intentionally calls your component function <em>twice</em> during render. And it calls your initializer and updater functions <em>twice</em>. It looks buggy in the console, but it’s actually genius.</p><p>Why? React is checking: “Is my component pure?” A pure function returns the same output given the same input. If you’re doing side effects (like Math.random() or fetching data inside the component body), StrictMode will expose it by calling things twice.</p><p>Here’s the thing: this only happens in development. Your production build runs normally. It’s a safety net.</p><p>If you’re using correct patterns — pure functions, proper immutability — StrictMode won’t break anything. Those double calls will produce identical results, and React discards one of them.</p><p>If you <em>are</em> doing something impure and one of the calls behaves differently, that’s the bug StrictMode is catching <em>for you</em>. You fix it now instead of shipping a production bug.</p><pre>// 🚩 Impure—StrictMode will catch this:<br>function Counter() {<br>  const [items, setItems] = useState([]);<br>  <br>  items.push(&#39;new item&#39;); // Mutating! This will happen twice in StrictMode.<br>  <br>  return &lt;div&gt;{items.length}&lt;/div&gt;;<br>}<br><br>// ✅ Pure-StrictMode won&#39;t complain:<br>function Counter() {<br>  const [items, setItems] = useState([]);<br>  <br>  const withNewItem = [...items, &#39;new item&#39;]; // New array, never mutating<br>  <br>  return &lt;div&gt;{withNewItem.length}&lt;/div&gt;;<br>}</pre><p>I used to find the double-render confusing. Now I see it as React saying, “I’m watching to make sure you’re keeping things pure.”</p><h3>Your First Counter: Putting It All Together</h3><p>Let’s build a proper counter component that handles all these concepts:</p><pre>import { useState } from &#39;react&#39;;<br><br>export default function Counter() {<br>  const [count, setCount] = useState(0);<br>  const handleIncrement = () =&gt; setCount(c =&gt; c + 1);<br>  const handleDecrement = () =&gt; setCount(c =&gt; c - 1);<br>  const handleReset = () =&gt; setCount(0);<br>  return (<br>    &lt;div style={{ textAlign: &#39;center&#39;, padding: &#39;20px&#39; }}&gt;<br>      &lt;h1&gt;Count: {count}&lt;/h1&gt;<br>      &lt;div style={{ display: &#39;flex&#39;, gap: &#39;10px&#39;, justifyContent: &#39;center&#39; }}&gt;<br>        &lt;button onClick={handleDecrement}&gt;−&lt;/button&gt;<br>        &lt;button onClick={handleReset}&gt;Reset&lt;/button&gt;<br>        &lt;button onClick={handleIncrement}&gt;+&lt;/button&gt;<br>      &lt;/div&gt;<br>    &lt;/div&gt;<br>  );<br>}</pre><p>Notice:</p><ul><li>Increment and decrement use the updater pattern (c =&gt; c + 1) because they depend on the previous state</li><li>Reset uses a direct update (setCount(0)) because it doesn&#39;t depend on the current value</li><li>Each button has a clear, named handler function</li></ul><p>This is production-ready. It handles edge cases, works with StrictMode, and scales if you add more features.</p><h3>Try This Now: Multi-Click Challenge</h3><p>Create a button that increments the counter by 2 every click. Use both approaches — first try the naive way with direct updates, see what happens. Then refactor to use the updater pattern. Notice the difference?</p><pre>// Try this:<br>function doubleIncrement() {<br>  setCount(count + 1);<br>  setCount(count + 1);<br>}<br><br><br>// Then try this:<br>function doubleIncrement() {<br>  setCount(c =&gt; c + 1);<br>  setCount(c =&gt; c + 1);<br>}</pre><p>The second one should give you the behavior you expect. This tiny experiment teaches the stale closure concept better than any explanation.</p><h3>What’s Next?</h3><p>The natural next step is to extract this counter into a custom Hook. If you find yourself managing the same state in multiple components, wrapping the logic in a custom Hook keeps things DRY. Build a useCounter hook that encapsulates the increment, decrement, and reset logic—then use it in different components. You can finish this in under an hour and it&#39;ll make the real power of Hooks click.</p><p>If you enjoyed this, check out my previous post on <a href="https://medium.com/@ramajha/building-a-product-catalog-the-real-test-of-your-week-2-react-skills-3af1af696b35">Building a Product Catalog: The Real Test of Your Week 2 React Skills</a> — it connects directly to today’s lesson.</p><h3>About the Author</h3><p>I’m a React developer who spends way too much time thinking about why things work the way they do. I’ve written this handbook because I remember when ES6 syntax felt like a wall, and I want to save you that confusion. You can find me on <a href="https://x.com/rama_vats">X/Twitter</a>, or <a href="https://github.com/ramavats">GitHub</a>. Drop a question anytime; I read every reply.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=481d65d6241f" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Building a Product Catalog: The Real Test of Your Week 2 React Skills]]></title>
            <link>https://ramajha.medium.com/building-a-product-catalog-the-real-test-of-your-week-2-react-skills-3af1af696b35?source=rss-faab09a15a3d------2</link>
            <guid isPermaLink="false">https://medium.com/p/3af1af696b35</guid>
            <category><![CDATA[front-end-development]]></category>
            <category><![CDATA[reactjs]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[react]]></category>
            <category><![CDATA[web-development]]></category>
            <dc:creator><![CDATA[Rama Jha]]></dc:creator>
            <pubDate>Thu, 20 Nov 2025 12:02:04 GMT</pubDate>
            <atom:updated>2025-11-20T12:03:01.356Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QUeJmZix1iSeeLT2ZmE49Q.png" /></figure><p>I remember the moment I first tried to build a product list. I had learned props, mapping, and conditional rendering separately, and I felt confident about each one. Then I sat down to combine them, and nothing worked the way I expected. My component rendered correctly, then broke when I added the key prop. I’d pass a function as a prop and wonder why my event handler didn’t fire. That’s when I realized: Week 2 concepts aren’t hard individually — they’re hard together. This project will show you exactly what I mean.</p><h3>What You’ll Build</h3><p>Over the next two weeks, you’ve absorbed a lot: passing data with props, composing components, rendering lists with .map(), showing and hiding content with conditional logic, and handling user events. A static product catalog pulls all of these together in a real e-commerce scenario. You&#39;ll build a component tree that mirrors how actual apps work. By the end, you&#39;ll have a working product list with inventory badges, and you&#39;ll understand <em>why</em> each piece matters.</p><h3>Learning Objectives</h3><p>By the time you finish this post and build the project, you’ll be able to:</p><ol><li>Plan a component architecture by breaking a UI into reusable pieces and deciding what data each needs</li><li>Structure product data as an array of objects and pass it through your component tree</li><li>Render lists efficiently with .map() and proper keys, avoiding reconciliation bugs</li><li>Show conditional content like “Out of Stock” badges based on product state</li><li>Handle click events on list items and buttons in preparation for next week’s state management</li></ol><h3>Prerequisites</h3><p>You should have completed Days 1–13 of the 30-day schedule. Specifically:</p><ul><li>You’re comfortable writing functional components with props</li><li>You’ve used .map() to render arrays in JSX</li><li>You understand ternary operators and logical AND for conditional rendering</li><li>You have a Vite React project running locally (<a href="https://medium.com/@ramajha/the-5-minute-guide-to-starting-a-react-project-fa68b4e79126">from Day 2 setup</a>)</li></ul><p>If you need a quick refresher, revisit the <a href="https://react.dev/learn/passing-props-to-a-component">Passing Props guide</a> or <a href="https://react.dev/learn/rendering-lists">Rendering Lists</a>.</p><h3>Where People Stumble: The Data Flow Gap</h3><p>Here’s what I see beginners struggle with most: they understand props individually but freeze when structuring data for a real feature. They’ll pass a single prop perfectly, then panic when they need to pass an entire object, an array, or a function. The mental jump from “I pass name=&quot;John&quot;&quot; to &quot;I pass an array of products, each with 10 properties&quot; feels bigger than it should.</p><p>The other gotcha? Keys. Beginners often add key={index} and everything <em>looks</em> fine until they add sorting or filtering. Then React&#39;s virtual DOM reconciliation gets confused, and you end up with weird bugs where animations fire twice or input focus gets lost. The fix is one line—use a stable, unique identifier—but the <em>why</em> takes explaining.</p><h3>Step 1: Plan Your Component Architecture</h3><p>Before you write any code, think about your UI. A product catalog typically has:</p><ul><li>A header showing the catalog title</li><li>A product list container that holds all items</li><li>Individual product card components for each item</li><li>An optional filter or sort area (we’ll keep it simple this week)</li></ul><p>Here’s a tiny sketch in React thinking:</p><pre>&lt;App&gt;<br>  └── &lt;ProductCatalog&gt;<br>      ├── &lt;CatalogHeader /&gt;<br>      ├── &lt;ProductList products={productData}&gt;<br>      │   ├── &lt;ProductCard product={item1} /&gt;<br>      │   ├── &lt;ProductCard product={item2} /&gt;<br>      │   └── &lt;ProductCard product={item3} /&gt;<br>      └── &lt;FilterBar /&gt; (optional)</pre><p>Notice the data flow: &lt;App&gt; holds the product array, passes it to &lt;ProductCatalog&gt;, which passes it to &lt;ProductList&gt;, which loops and passes individual items to &lt;ProductCard&gt;. This is a real-world data flow—not trivial, not overly complex.</p><p>Why structure it this way? Because later, when you add state management (Week 3), you’ll need to modify that product array. Having it defined in one place makes that change straightforward.</p><h3>Step 2: Design Your Data Structure</h3><p>Your product data matters. Here’s what a real product looks like in an e-commerce system:</p><pre>const products = [<br>  {<br>    id: 1,<br>    name: &quot;Wireless Headphones&quot;,<br>    price: 79.99,<br>    category: &quot;Audio&quot;,<br>    inStock: true,<br>    image: &quot;headphones.jpg&quot;,<br>  },<br>  {<br>    id: 2,<br>    name: &quot;USB-C Cable&quot;,<br>    price: 12.99,<br>    category: &quot;Cables&quot;,<br>    inStock: false,<br>    image: &quot;cable.jpg&quot;,<br>  },<br>  {<br>    id: 3,<br>    name: &quot;Phone Stand&quot;,<br>    price: 25.00,<br>    category: &quot;Accessories&quot;,<br>    inStock: true,<br>    image: &quot;stand.jpg&quot;,<br>  },<br>];</pre><p>Each product has:</p><ul><li>id: A stable, unique value (use this for keys, not array index)</li><li>name and price: Display content</li><li>inStock: Controls conditional rendering of the “Out of Stock” badge</li><li>category and image: Future-proofing for filtering or image display</li></ul><p>This structure mirrors real APIs you’ll fetch from next week.</p><h3>Step 3: Build the ProductCard Component</h3><p>Let’s start small. A single product card should display the product name, price, and a conditional badge if it’s out of stock.</p><pre>// ProductCard.jsx<br>export default function ProductCard({ product }) {<br>  return (<br>    &lt;div className=&quot;product-card&quot;&gt;<br>      &lt;h3&gt;{product.name}&lt;/h3&gt;<br>      &lt;p className=&quot;price&quot;&gt;${product.price.toFixed(2)}&lt;/p&gt;<br>      {!product.inStock &amp;&amp; &lt;span className=&quot;badge&quot;&gt;Out of Stock&lt;/span&gt;}<br>    &lt;/div&gt;<br>  );<br>}</pre><p>Breaking this down:</p><ul><li>{ product } destructures the prop so you access product.name instead of props.product.name</li><li>${product.price.toFixed(2)} ensures prices always show two decimal places</li><li>{!product.inStock &amp;&amp; &lt;span className=&quot;badge&quot;&gt;Out of Stock&lt;/span&gt;} is the logical AND pattern for conditional rendering—render the badge only if inStock is false</li></ul><p>Gotcha: Don’t write {product.inStock ? null : &lt;span&gt;...}. It works, but the logical AND is cleaner for simple &quot;show or hide&quot; logic.</p><h3>Step 4: Build the ProductList Component</h3><p>Now render multiple products. This is where .map() and keys come into play.</p><pre>// ProductList.jsx<br>export default function ProductList({ products }) {<br>  return (<br>    &lt;div className=&quot;product-list&quot;&gt;<br>      {products.map((product) =&gt; (<br>        &lt;ProductCard key={product.id} product={product} /&gt;<br>      ))}<br>    &lt;/div&gt;<br>  );<br>}</pre><p>What’s happening:</p><ul><li>products.map() loops through each item and returns JSX for each one</li><li>key={product.id} tells React this item is uniquely identified by its ID</li><li>product={product} passes the entire product object as a prop to ProductCard</li></ul><p>Why id not index? If you reorder your products array later, or delete an item, array indexes shift. React uses keys to track which item is which. A stable ID (like a database ID) survives reordering. If you use key={index} and sort your products, React gets confused about which component instance belongs to which data—animations glitch, form inputs lose focus, and performance tanks.</p><p>Try this now: Replace key={product.id} with key={products.indexOf(product)} (which is basically the array index). Your catalog still renders. Then add a sort button that reverses the products array. With index keys, things break subtly. With stable IDs, they work fine. That&#39;s the difference.</p><h3>Step 5: Assemble Everything in App</h3><p>Pull it together at the top level:</p><pre>// App.jsx<br>import ProductList from &#39;./ProductList&#39;;<br><br>const products = [<br>  {<br>    id: 1,<br>    name: &quot;Wireless Headphones&quot;,<br>    price: 79.99,<br>    category: &quot;Audio&quot;,<br>    inStock: true,<br>    image: &quot;headphones.jpg&quot;,<br>  },<br>  {<br>    id: 2,<br>    name: &quot;USB-C Cable&quot;,<br>    price: 12.99,<br>    category: &quot;Cables&quot;,<br>    inStock: false,<br>    image: &quot;cable.jpg&quot;,<br>  },<br>  {<br>    id: 3,<br>    name: &quot;Phone Stand&quot;,<br>    price: 25.00,<br>    category: &quot;Accessories&quot;,<br>    inStock: true,<br>    image: &quot;stand.jpg&quot;,<br>  },<br>];<br>export default function App() {<br>  return (<br>    &lt;div className=&quot;app&quot;&gt;<br>      &lt;h1&gt;Our Store&lt;/h1&gt;<br>      &lt;ProductList products={products} /&gt;<br>    &lt;/div&gt;<br>  );</pre><p>This is your first real multi-level data flow. App owns the products data, passes it to ProductList, which maps over it and passes each item to ProductCard. If you later move this data to state (which you&#39;ll do next week), you only change it in one place.</p><h3>Add a Little Polish: Click Handlers Ready</h3><p>Here’s where we prep for next week. Add a click handler that doesn’t do much yet, but shows you the pattern:</p><pre>// ProductCard.jsx (updated)<br>export default function ProductCard({ product, onSelectProduct }) {<br>  return (<br>    &lt;div className=&quot;product-card&quot; onClick={() =&gt; onSelectProduct(product)}&gt;<br>      &lt;h3&gt;{product.name}&lt;/h3&gt;<br>      &lt;p className=&quot;price&quot;&gt;${product.price.toFixed(2)}&lt;/p&gt;<br>      {!product.inStock &amp;&amp; &lt;span className=&quot;badge&quot;&gt;Out of Stock&lt;/span&gt;}<br>    &lt;/div&gt;<br>  );<br>}</pre><p>And in your ProductList:</p><pre>// ProductList.jsx (updated)<br>export default function ProductList({ products }) {<br>  const handleSelectProduct = (product) =&gt; {<br>    console.log(&#39;Selected:&#39;, product.name);<br>  };<br><br>return (<br>    &lt;div className=&quot;product-list&quot;&gt;<br>      {products.map((product) =&gt; (<br>        &lt;ProductCard <br>          key={product.id} <br>          product={product} <br>          onSelectProduct={handleSelectProduct}<br>        /&gt;<br>      ))}<br>    &lt;/div&gt;<br>  );<br>}</pre><p>You’re passing a function onSelectProduct as a prop. When you click a card, it logs the product. Next week, you&#39;ll swap console.log for something that updates state—maybe storing the selected product or adding it to a cart. The structure is already there.</p><h3>Basic Styling to Make It Look Real</h3><p>Add this to your index.css or a module to give your catalog some shape:</p><pre>.product-card {<br>  border: 1px solid #e0e0e0;<br>  border-radius: 8px;<br>  padding: 16px;<br>  margin: 8px;<br>  min-width: 200px;<br>  cursor: pointer;<br>  transition: transform 0.2s, box-shadow 0.2s;<br>}<br><br>.product-card:hover {<br>  transform: translateY(-4px);<br>  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);<br>}<br>.price {<br>  font-size: 18px;<br>  font-weight: bold;<br>  color: #2c5282;<br>  margin: 8px 0;<br>}<br>.badge {<br>  display: inline-block;<br>  background-color: #fc8181;<br>  color: white;<br>  padding: 4px 8px;<br>  border-radius: 4px;<br>  font-size: 12px;<br>  font-weight: bold;<br>  margin-top: 8px;<br>}<br>.product-list {<br>  display: flex;<br>  flex-wrap: wrap;<br>  justify-content: flex-start;<br>}</pre><p>Nothing fancy, but it’s enough to see your components layout correctly.</p><h3>One More Debugging Moment</h3><p>Here’s a real mistake I made repeatedly: forgetting to pass a required prop and watching React silently fail to render anything. In ProductCard, if I wrote {product.price} but never received the product prop, the component crashes silently in development (you&#39;ll see an error in the console). The fix is always the same: check your prop names match between parent and child.</p><p>Quick checklist:</p><ul><li>Parent passes: &lt;ProductCard product={item} /&gt;</li><li>Child receives: function ProductCard({ product })</li><li>Child uses: {product.name}</li></ul><p>If any of those three don’t line up, nothing works.</p><h3>Your Next Mini-Challenge</h3><p>Take the code from this post and do this: add a fourth product to the array where inStock is false. Make sure the &quot;Out of Stock&quot; badge shows. Then, in your ProductList, add a console.log that prints the product ID when you click a card. Don&#39;t overthink it—if you get the click firing and the ID logging, you&#39;ve understood the data flow and events are ready for state management.</p><h3>What You’ve Accomplished</h3><p>You now have a product catalog that:</p><ul><li>Renders a list of products from an array</li><li>Uses stable keys to track each item properly</li><li>Shows conditional content (the “Out of Stock” badge)</li><li>Responds to user clicks</li><li>Has a clear component hierarchy and data flow</li></ul><p>This is the foundation. Next week, you’ll make it interactive: add items to a cart, toggle stock status, filter by category — all with state. But the skeleton is perfect.</p><h3>Next Steps</h3><p>Build a simple product filter: create an array of categories and add buttons that filter the product list by selected category. No state needed yet — just use Array.filter() to create a new filtered array and pass it to ProductList. This reinforces the data flow pattern and prepares you for dynamic state updates next week.</p><p>If you enjoyed this, check out my previous post on <a href="https://medium.com/@ramajha/stop-overthinking-props-vs-state-3-tiny-rules-that-work-in-every-project-1b6c711d8d2d"><strong>Stop Overthinking Props vs. State: 3 Tiny Rules That Work in Every Project</strong></a> — it connects directly to today’s lesson by showing you exactly which data should live where before we introduce state management.</p><h3>About the Author</h3><p>I’m a React developer who spends way too much time thinking about why things work the way they do. I’ve written this handbook because I remember when ES6 syntax felt like a wall, and I want to save you that confusion. You can find me on <a href="https://x.com/rama_vats">X/Twitter</a>, or <a href="https://github.com/ramavats">GitHub</a>. Drop a question anytime; I read every reply.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3af1af696b35" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Stop Overthinking Props vs. State: 3 Tiny Rules That Work in Every Project]]></title>
            <link>https://ramajha.medium.com/stop-overthinking-props-vs-state-3-tiny-rules-that-work-in-every-project-1b6c711d8d2d?source=rss-faab09a15a3d------2</link>
            <guid isPermaLink="false">https://medium.com/p/1b6c711d8d2d</guid>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[react]]></category>
            <category><![CDATA[reactjs]]></category>
            <category><![CDATA[frontend]]></category>
            <category><![CDATA[web-development]]></category>
            <dc:creator><![CDATA[Rama Jha]]></dc:creator>
            <pubDate>Mon, 17 Nov 2025 10:28:00 GMT</pubDate>
            <atom:updated>2025-11-17T10:28:00.386Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5KVCWZnK1OzKesdCtywGXg.png" /></figure><p>Last week, I spent two hours debugging why a form wouldn’t update properly. Turns out, I’d accidentally “mirrored” the user’s email from a prop into component state — so when the parent updated, my local state didn’t get the memo. The input showed stale data. Classic beginner trap.</p><p>The truth? Props vs. state isn’t complicated. There’s just a pattern most tutorials skip: a simple mental test that tells you which one to reach for before you write a single line of code.</p><p>Let me walk you through it.</p><h3>What Actually Are Props and State?</h3><p>Props are data <em>from the outside</em> — passed down from a parent component. Think of them like function parameters. A parent says, “Hey, here’s a user’s name,” and the child receives it. The child can’t change it; it can only read and use it.</p><p>State is data a component owns and controls. It’s internal. When state changes, the component notices and updates the UI to match. Only that component can modify its own state.</p><p>Here’s a concrete example. Imagine a UserCard component:</p><pre>function UserCard(props) {<br>  // props.name comes from the parent—immutable here<br>  return &lt;h1&gt;{props.name}&lt;/h1&gt;;<br>}<br><br><br>// Parent passes it:<br>&lt;UserCard name=&quot;Alice&quot; /&gt;</pre><p>If Alice’s name needs to change later, the <em>parent</em> updates it and passes a new prop. The child doesn’t decide to change it on its own.</p><p>Now, what if we have a toggle button that switches between “view” and “edit” mode?</p><pre>function UserCard(props) {<br>  const [isEditing, setIsEditing] = useState(false);<br>  <br>  return (<br>    &lt;div&gt;<br>      &lt;h1&gt;{props.name}&lt;/h1&gt;<br>      &lt;button onClick={() =&gt; setIsEditing(!isEditing)}&gt;<br>        {isEditing ? &quot;Save&quot; : &quot;Edit&quot;}<br>      &lt;/button&gt;<br>    &lt;/div&gt;<br>  );<br>}</pre><p>Here, isEditing is state because <em>this component</em> manages it. Nobody outside cares if we&#39;re in edit mode—it&#39;s internal UI logic. The parent never passes it; the component decides and updates it.</p><h3>Rule 1: “Is It Information Only This Component Needs?”</h3><p>If data is private to one component — like a modal’s “open/closed” state or the current value in a text input before submission — make it state.</p><p>I’ve seen developers pass UI toggles down as props from a parent. “Does your modal show?” becomes a prop. Then another sibling component wants to toggle it too, so now the parent manages it, and suddenly you have props drilling through five layers. Stop there.</p><p>Quick test: Could this component do its job if nobody else cared about this data? If yes, it’s state.</p><p>Example: A filter dropdown that expands and collapses. The parent doesn’t care whether it’s open or closed — only this dropdown cares.</p><pre>function FilterDropdown() {<br>  const [isOpen, setIsOpen] = useState(false);<br>  <br>  return (<br>    &lt;div&gt;<br>      &lt;button onClick={() =&gt; setIsOpen(!isOpen)}&gt;Filters&lt;/button&gt;<br>      {isOpen &amp;&amp; &lt;div&gt;Filter options...&lt;/div&gt;}<br>    &lt;/div&gt;<br>  );<br>}</pre><h3>Rule 2: “Can This Data Change? And Who Decides?”</h3><p>This is where things get weird for beginners. The mistake I made (mirroring props in state) happens here.</p><p>Here’s the anti-pattern:</p><pre>function UserProfile(props) {<br>  // ❌ Don&#39;t do this<br>  const [name, setName] = useState(props.name);<br>  <br>  return &lt;h1&gt;{name}&lt;/h1&gt;;<br>}</pre><p>Why’s this bad? Because if the parent updates props.name later, name in state won&#39;t notice. Your component&#39;s now &quot;out of sync&quot;—React calls this &quot;derived state,&quot; and it&#39;s a footgun.</p><p>If data comes from the parent and you’re not modifying it locally, just use the prop directly. Seriously. No state wrapper needed.​</p><pre>function UserProfile(props) {<br>  // ✅ Do this instead<br>  return &lt;h1&gt;{props.name}&lt;/h1&gt;;<br>}</pre><p>The exception? If you need to modify it locally <em>before</em> sending it back up. Example: a controlled form input.​</p><pre>function EditUserForm(props) {<br>  const [name, setName] = useState(props.name);<br>  <br>  const handleSave = () =&gt; {<br>    props.onSave(name); // Send updated value to parent<br>  };<br>  <br>  return (<br>    &lt;&gt;<br>      &lt;input value={name} onChange={(e) =&gt; setName(e.target.value)} /&gt;<br>      &lt;button onClick={handleSave}&gt;Save&lt;/button&gt;<br>    &lt;/&gt;<br>  );<br>}</pre><p>Notice: you initialize state from the prop (for the initial value), but you <em>also</em> call a callback (props.onSave) to tell the parent what changed. The parent then decides whether to update and re-render. Unidirectional data flow.​</p><p>Quick test: If the parent changes this data, should <em>my</em> copy update automatically? If yes, use a prop. If no — only if the user modifies it locally — use state and a callback.</p><h3>Rule 3: “Does More Than One Component Care About This?”</h3><p>This one’s about sharing state between siblings or distant cousins.</p><p>Imagine a todo app. You’ve got a form that adds todos and a list that shows them.</p><pre>function App() {<br>  const [todos, setTodos] = useState([]);<br>  <br>  const addTodo = (newTodo) =&gt; {<br>    setTodos([...todos, newTodo]);<br>  };<br>  <br>  return (<br>    &lt;&gt;<br>      &lt;TodoForm onAdd={addTodo} /&gt;<br>      &lt;TodoList todos={todos} /&gt;<br>    &lt;/&gt;<br>  );<br>}</pre><p>The todos state lives in the parent (App) because <em>both</em> children need it. The form updates it (via callback), the list displays it (via prop). This is called &quot;lifting state up,&quot; and it&#39;s the backbone of React data flow.​</p><p>But here’s where people stumble: they put state in the wrong place. A junior dev once put the todos array in the form component, then tried to access it from the list. Doesn&#39;t work—sibling components can&#39;t see each other&#39;s state. You have to lift it to their common ancestor.​</p><p>Quick test: Could two or more components need this data? Move it to the lowest common parent. Pass it down as props. Pass callbacks back up for updates.</p><p>The catch? If you go deep — like 5+ layers of prop drilling — you hit “prop drilling hell.” That’s when Context API or a small state library like Zustand starts looking good. But for your first 30 days of React, master lifting state first. That’s the foundation.​</p><h3>Where People Actually Stumble</h3><p>Here’s the gotcha nobody mentions in tutorials: defaulting the wrong way.</p><p>Beginners tend to make everything state “just in case.” “What if I need to change this?” Then they inherit a prop, mirror it into state, and the component breaks when the parent updates. Or they lift state way too high, and now pass a prop through 5 components that don’t even use it.</p><p>The better instinct? Start by asking: “Does this come from outside?” → Use props. “Does this component decide and change it?” → Use state. Don’t pre-optimize. Don’t “just in case.” Answer honestly, write the code, and refactor when you actually need to.</p><p>I learned this the hard way. My first projects had state <em>everywhere</em>. Data wasn’t syncing. Callbacks got lost. Now? I default to props, use state only when the component truly owns something, and I avoid derived state like it owes me money.​</p><h3>Let’s Apply the Rules: A Real Scenario</h3><p>Say you’re building a product card for an e-commerce site.</p><pre>function ProductCard(props) {<br>  // props.product comes from parent (the list)<br>  // props.onAddToCart is a callback from parent<br>  <br>  const [quantity, setQuantity] = useState(1);<br>  const [showDetails, setShowDetails] = useState(false);<br>  <br>  return (<br>    &lt;div&gt;<br>      &lt;h3&gt;{props.product.name}&lt;/h3&gt;<br>      &lt;p&gt;${props.product.price}&lt;/p&gt;<br>      <br>      &lt;button onClick={() =&gt; setShowDetails(!showDetails)}&gt;<br>        {showDetails ? &quot;Hide&quot; : &quot;Show&quot;} Details<br>      &lt;/button&gt;<br>      {showDetails &amp;&amp; &lt;p&gt;{props.product.description}&lt;/p&gt;}<br>      <br>      &lt;input<br>        type=&quot;number&quot;<br>        value={quantity}<br>        onChange={(e) =&gt; setQuantity(Number(e.target.value))}<br>      /&gt;<br>      <br>      &lt;button onClick={() =&gt; props.onAddToCart(props.product.id, quantity)}&gt;<br>        Add to Cart<br>      &lt;/button&gt;<br>    &lt;/div&gt;<br>  );<br>}</pre><p>Let’s break it down:</p><ul><li>props.product and props.onAddToCart? Props. The parent (the product list) owns this data and decides when to update it.</li><li>quantity and showDetails? State. Only this card cares. The parent never needs to know; it&#39;s just UI logic.</li></ul><p>This keeps things simple and composable. Want to reuse ProductCard elsewhere? It works. It doesn&#39;t depend on specific parent logic—it just takes a product and a callback.</p><h3>One Mini-Challenge for Right Now</h3><p>Grab a component you’ve written recently (or write a quick one). List every piece of data it uses. For each one, ask yourself: “Who owns this? Can it change? Who decides?”</p><ul><li>Data from parent → Prop.</li><li>Data this component controls → State.</li><li>Data two siblings need → Lift it up to the parent.</li></ul><p>Rewrite using only those rules. Notice how much clearer the component gets. That clarity? That’s when you know you’re thinking like a React developer.</p><h3>Your Next Move</h3><p>Now that you’ve got these three rules, the next big step is learning useState and how to update state correctly without accidentally mutating it. That&#39;s Day 15 in your schedule—and it builds directly on everything you&#39;ve just learned. You&#39;re not starting from scratch; you&#39;re ready.</p><p>Try this: build a simple form with a name input, an email input, and a submit button. Use props to pass the form configuration (labels, placeholders) and state to manage what the user types. When the user submits, call a callback function that the parent passes. Get comfortable with that loop: parent passes props and callbacks, component manages local state, component calls callbacks to tell the parent about changes.</p><p>If you enjoyed this, check out my previous post on <a href="https://medium.com/@ramajha/es6-essentials-for-react-a-developers-handbook-not-a-tutorial-e0e46bc31469"><strong>ES6 Essentials for React: A Developer’s Handbook (Not a Tutorial)</strong></a> — it covers destructuring and the spread operator, which you’ll lean on every single day when working with props and state.</p><h3>About the Author</h3><p>I’m a React developer who spends way too much time thinking about why things work the way they do. I’ve written this handbook because I remember when ES6 syntax felt like a wall, and I want to save you that confusion. You can find me on <a href="https://x.com/rama_vats">X/Twitter</a>, or <a href="https://github.com/ramavats">GitHub</a>. Drop a question anytime; I read every reply.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1b6c711d8d2d" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[onClick and onChange: The Event Handler Patterns Beginners Get Wrong]]></title>
            <link>https://ramajha.medium.com/onclick-and-onchange-the-event-handler-patterns-beginners-get-wrong-d1da59b74fad?source=rss-faab09a15a3d------2</link>
            <guid isPermaLink="false">https://medium.com/p/d1da59b74fad</guid>
            <category><![CDATA[front-end-development]]></category>
            <category><![CDATA[reactjs]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[react]]></category>
            <dc:creator><![CDATA[Rama Jha]]></dc:creator>
            <pubDate>Mon, 10 Nov 2025 09:58:16 GMT</pubDate>
            <atom:updated>2025-11-10T10:06:05.052Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*YNcEpWQmAJtr5M28padDnQ.png" /></figure><p>Last week, I was pair-programming with a dev who’d been building React for about three weeks. He had a form with an input field, and when the user typed, nothing happened. He’d written the onChange handler, passed the event object correctly, but he was trying to access event.target.value <em>after</em> the state update, in the same function. His code looked clean—no errors in the console. But nothing worked.</p><p>After five minutes, he said: “Wait, is the event object gone?” Bingo.</p><p>This is one of the <em>biggest</em> gotchas when you’re learning event handling in React, and it’s not your fault if it catches you. The docs don’t emphasize it enough, and beginners usually figure it out through frustration. Today, we’re going to walk through the actual patterns that work, the ones that break, and the weird edge case that’ll save you hours of debugging.</p><h3>Why This Matters</h3><p>React doesn’t use browser events directly. It uses something called synthetic events — a cross-browser wrapper around the native event system. That’s great for consistency, but it also means some of the old tricks you might know from vanilla JavaScript don’t work the same way. And the patterns you choose for handling onClick and onChange shape how you think about state, forms, and interactivity for the next three weeks of learning.</p><p>Get this right now, and everything that comes next feels natural. Get it wrong, and you’ll spend next week fighting with form inputs that won’t update or buttons that don’t respond.</p><h3>The Big Pattern: Controlled Components</h3><p>Here’s the core idea: in React, form inputs are usually “controlled,” meaning React owns their value. You don’t let the browser manage the input’s state. You manage it with React state, and the input renders whatever value you tell it to render.</p><p>This sounds like extra work — and it is, a little — but it gives you total control over what the user sees, when it updates, and how you validate it.</p><p>Here’s the minimal version:</p><pre>import { useState } from &#39;react&#39;;<br>export default function LoginForm() {<br>  const [email, setEmail] = useState(&#39;&#39;);<br>  const handleEmailChange = (event) =&gt; {<br>    setEmail(event.target.value);<br>  };<br>  return (<br>    &lt;form&gt;<br>      &lt;label htmlFor=&quot;email&quot;&gt;Email:&lt;/label&gt;<br>      &lt;input<br>        id=&quot;email&quot;<br>        type=&quot;email&quot;<br>        value={email}<br>        onChange={handleEmailChange}<br>      /&gt;<br>      &lt;p&gt;You typed: {email}&lt;/p&gt;<br>    &lt;/form&gt;<br>  );<br>}</pre><p>Three things are happening:</p><ol><li>The input has a value prop set to email state. This means React controls what appears in the field.</li><li>The onChange handler fires whenever the user types. It receives an event object, extracts the new value from event.target.value, and updates state.</li><li>React re-renders, the input gets the new email value, and the &lt;p&gt; tag shows what you typed in real time.</li></ol><p>Try this now: create a new component, paste this code, and type in the input. You’ll see the live update. That’s the pattern working.</p><h3>Where Beginners Stumble: The Event Object Disappor</h3><p>Here’s the gotcha I mentioned. When you need the event value <em>and</em> want to do async work (like calling an API), people write this:</p><pre>const handleChange = (event) =&gt; {<br>  const value = event.target.value;<br>  setEmail(value);<br>  <br>  // ❌ This doesn&#39;t work as expected<br>  setTimeout(() =&gt; {<br>    console.log(event.target.value); // undefined or stale value<br>  }, 0);<br>};</pre><p>If you run this, the setTimeout will probably log an empty string or the old value, not the new one. Why? In React 16 and earlier, synthetic events were <em>pooled</em> for performance—React reused event objects. Once the handler finished, React cleared the event object&#39;s properties. Since React 17, that pooling is gone in modern browsers, <em>but</em> the pattern of not relying on the event object after state updates is still safer and clearer.</p><p><strong>Here’s the fix:</strong> grab the value you need right away, then use it:</p><pre>const handleChange = (event) =&gt; {<br>  const value = event.target.value;<br>  setEmail(value);<br>  <br>  // ✅ Correct: use the variable, not the event<br>  setTimeout(() =&gt; {<br>    console.log(value); // works as expected<br>  }, 0);<br>};</pre><p>Or, <strong>even cleaner: </strong>extract the value first, then update state with it:</p><pre>const handleChange = (event) =&gt; {<br>  const newEmail = event.target.value;<br>  setEmail(newEmail);<br>  <br>  // If you need to react to the state change, use useEffect (more on that later)<br>};</pre><p>This pattern isn’t just about avoiding a bug — it’s about thinking clearly. The event is a <em>snapshot in time</em>. The value is what you care about. Treat them as separate things, and your code stays understandable.</p><h3>onClick: Simpler, But Patterns Still Matter</h3><p>Buttons are straightforward, but there’s an easy wrong turn. Here’s a button that deletes a task when clicked:</p><pre>export default function TaskItem({ taskId, onDelete }) {<br>  const handleDelete = () =&gt; {<br>    onDelete(taskId);<br>  };<br><br>return &lt;button onClick={handleDelete}&gt;Delete&lt;/button&gt;;<br>}</pre><p>This works. The function is defined once, and React calls it whenever someone clicks. No event object complications (you don’t need it, most of the time).</p><p>But here’s where beginners go wrong: they try to pass parameters inline:</p><pre>// ❌ This re-creates the function on every render<br>&lt;button onClick={() =&gt; onDelete(taskId)}&gt;Delete&lt;/button&gt;<br><br>// ✅ This is fine too, but less obvious why<br>&lt;button onClick={handleDelete}&gt;Delete&lt;/button&gt;</pre><p>Neither is <em>wrong</em>, actually. The inline arrow function is recreated every render, but for a single button, React’s so fast it doesn’t matter in practice. However, if you’re rendering a <em>list</em> of items with inline functions, React has to re-create hundreds of functions every render. That’s wasteful.</p><p>Here’s my opinion: define the handler outside the JSX, and pass parameters through a wrapper function or closure. It’s clearer, and it scales better:</p><pre>export default function TaskList({ tasks, onDelete }) {<br>  const handleDeleteClick = (taskId) =&gt; {<br>    onDelete(taskId);<br>  };<br><br>return (<br>    &lt;ul&gt;<br>      {tasks.map((task) =&gt; (<br>        &lt;li key={task.id}&gt;<br>          {task.name}<br>          &lt;button onClick={() =&gt; handleDeleteClick(task.id)}&gt;Delete&lt;/button&gt;<br>        &lt;/li&gt;<br>      ))}<br>    &lt;/ul&gt;<br>  );<br>}</pre><p>This way, handleDeleteClick is defined once. The inline arrow function passes the specific taskId. It&#39;s a small thing, but it becomes habit, and habits matter when you write bigger apps.</p><h3>onChange vs. onInput: Subtle But Real</h3><p>I’d be remiss not to mention this: React normalizes onChange so it fires on every keystroke, just like onInput does in vanilla JavaScript. Technically, they&#39;re the same in React.</p><p>But here’s the catch: if you reach for vanilla JavaScript or third-party libraries, onChange on native inputs <em>doesn&#39;t</em> fire on every keystroke—it only fires when focus leaves the input. onInput does fire on every keystroke.</p><p>In React, use onChange on inputs. React handles the normalization. You don&#39;t need onInput. But if you&#39;re reading code from 2015, or if you&#39;re mixing React with non-React parts, this might bite you. Just be aware.</p><h3>Preventing Default: The Small But Important Piece</h3><p>When you submit a form or click a link, the browser has a default behavior: it reloads the page or navigates away. In React, you almost always want to <em>prevent</em> that and handle it in JavaScript instead.</p><pre>export default function LoginForm() {<br>  const [email, setEmail] = useState(&#39;&#39;);<br>  const handleSubmit = (event) =&gt; {<br>    event.preventDefault(); // Stops the page reload<br>    console.log(&#39;Submitting email:&#39;, email);<br>    // Now send it to your backend<br>  };<br>  return (<br>    &lt;form onSubmit={handleSubmit}&gt;<br>      &lt;input<br>        value={email}<br>        onChange={(event) =&gt; setEmail(event.target.value)}<br>      /&gt;<br>      &lt;button type=&quot;submit&quot;&gt;Log In&lt;/button&gt;<br>    &lt;/form&gt;<br>  );<br>}</pre><p>The event.preventDefault() is <em>essential</em>. Without it, clicking &quot;Log In&quot; refreshes the page, and your React state gets wiped. Very annoying. Very easy to forget. I still check for it when something &quot;isn&#39;t working.&quot;</p><h3>Try This Now: Build a Comment Counter</h3><p>Here’s a real challenge to cement this stuff:</p><ol><li>Create a component with a text input (controlled).</li><li>Add a button below it labeled “Add Comment.”</li><li>When you click the button, display the comment in a list below and clear the input.</li><li>Add a “Delete” button next to each comment.</li></ol><p>You’ll use onChange to manage the input, onClick on &quot;Add Comment&quot; to read the current input value, onClick on delete to remove items, and preventDefault logic if you put it in a form. This is approximately what a real app does, just smaller.</p><p>Here’s a scaffold to get you started:</p><pre>import { useState } from &#39;react&#39;;<br>export default function CommentBoard() {<br>  const [comments, setComments] = useState([]);<br>  const [input, setInput] = useState(&#39;&#39;);<br>  // You fill in the handlers below<br>  const handleAddComment = () =&gt; {<br>    // Extract input, add to comments, clear input<br>  };<br>  const handleDelete = (index) =&gt; {<br>    // Remove comment at index<br>  };<br>  return (<br>    &lt;div&gt;<br>      &lt;input<br>        value={input}<br>        onChange={(event) =&gt; setInput(event.target.value)}<br>        placeholder=&quot;Write a comment...&quot;<br>      /&gt;<br>      &lt;button onClick={handleAddComment}&gt;Add Comment&lt;/button&gt;<br>      &lt;ul&gt;<br>        {comments.map((comment, index) =&gt; (<br>          &lt;li key={index}&gt;<br>            {comment}<br>            &lt;button onClick={() =&gt; handleDelete(index)}&gt;Delete&lt;/button&gt;<br>          &lt;/li&gt;<br>        ))}<br>      &lt;/ul&gt;<br>    &lt;/div&gt;<br>  );<br>}</pre><p>(Yes, using array index as a key is fine here for a simple list. We’ll talk about better keys later.)</p><h3>What You Can Do Now</h3><p>After reading this, you should be able to:</p><ol><li>Build a controlled input with onChange that updates state in real time.</li><li>Handle button clicks with onClick and pass parameters without recreating functions unnecessarily.</li><li>Prevent default form behavior and manage submission in React instead of letting the browser reload.</li><li>Spot the gotcha when the event object isn’t available and know to extract values upfront.</li><li>Choose between inline and separate handler functions based on what makes sense for your use case.</li></ol><h3>What’s Next</h3><p>Next, you’ll use these event handlers to build actual forms, and you’ll learn how React state flows down and events flow up. If you want to level up right now, try the comment board challenge above. It’ll take you 20 minutes, and you’ll have hit all five of those objectives.</p><p>But here’s a heads-up: Next time, we’ll talk about props vs. state — the mental model that makes all of this click. Because events are just half the story. The other half is knowing <em>where</em> to put your state so that events bubble up the way you expect.</p><p>If you enjoyed this, check out my previous post on <a href="https://ramajha.medium.com/the-one-conditional-rendering-mistake-thatll-crash-your-react-app-735452e09a4d">Conditional Rendering: Showing and Hiding Components</a> — it connects directly to today’s lesson, since you’ll often use events to toggle visibility or change what’s displayed based on user interaction.</p><h3>About the Author</h3><p>I’m a React developer documenting my 30-day React journey on Medium, sharing the lessons, gotchas, and “oh no” moments that beginners actually face. Follow along for practical tutorials, real code, and no buzzword fluff. Let’s connect on <a href="https://x.com/rama_vats/">[X/Twitter]</a> and <a href="https://github.com/ramavats">[GitHub]</a> — I’d love to hear about your own list-rendering disasters (we’ve all been there). Got questions? Drop a comment or message me anytime.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d1da59b74fad" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The One Conditional Rendering Mistake That’ll Crash Your React App]]></title>
            <link>https://ramajha.medium.com/the-one-conditional-rendering-mistake-thatll-crash-your-react-app-735452e09a4d?source=rss-faab09a15a3d------2</link>
            <guid isPermaLink="false">https://medium.com/p/735452e09a4d</guid>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[reactjs]]></category>
            <category><![CDATA[react]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[front-end-development]]></category>
            <dc:creator><![CDATA[Rama Jha]]></dc:creator>
            <pubDate>Mon, 03 Nov 2025 09:42:06 GMT</pubDate>
            <atom:updated>2025-11-03T09:42:06.093Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*xuKw5hFMHf7MiOC4" /><figcaption>Photo by <a href="https://unsplash.com/@lautaroandreani?utm_source=medium&amp;utm_medium=referral">Lautaro Andreani</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>Last week, a developer in a code review pointed out something that made me pause: they’d written {items.length &amp;&amp; &lt;ItemList items={items} /&gt;} in a component. At first glance, looks fine. Then their first test run hit an edge case—empty array—and suddenly the page rendered the number 0 where nothing should appear. Not catastrophic, but sloppy. And it gets worse in production codebases where state changes are unpredictable.</p><p>That 0 appearing on screen is the classic gotcha with conditional rendering in React. It&#39;s the mistake that teaches you something fundamental about how React actually works—not what you assume it does. Let me show you exactly why this happens, how to fix it, and more importantly, how to think about conditional rendering so you never second-guess yourself.</p><h3>What You’ll Learn</h3><p>After reading this, you’ll be able to:</p><ul><li>Spot the 0 rendering bug before it ships</li><li>Choose the right conditional rendering pattern for your use case</li><li>Understand why React renders numbers but not booleans</li><li>Handle complex UI logic without writing messy nested ternaries</li><li>Debug conditional rendering issues in production</li></ul><p><strong>Prerequisites</strong>: Familiarity with React hooks (useState, basic props), JSX syntax, and how JavaScript falsy/truthy values work.</p><h3>The Problem: Why That Sneaky Zero Shows Up</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*wh3dWITMnNKN1ak2nK_nHw.png" /></figure><p>Here’s the scenario. You want to show a list of notifications only if there are notifications to display. So you write:</p><pre>function NotificationCenter({ notifications }) {<br>  return (<br>    &lt;div&gt;<br>      {notifications.length &amp;&amp; &lt;NotificationList items={notifications} /&gt;}<br>    &lt;/div&gt;<br>  );<br>}</pre><p>Seems right. If notifications.length is truthy (like 5), render the component. If it&#39;s falsy (like 0), render nothing. That&#39;s the expectation.</p><p>But this is where most people trip up: <strong>React doesn’t render booleans, but it <em>does</em> render numbers.</strong></p><p>When notifications.length equals 0, JavaScript evaluates 0 &amp;&amp; &lt;NotificationList /&gt; and returns 0 (the left operand, since it&#39;s falsy). React then goes: &quot;Okay, I got a number. I&#39;ll render that on the DOM.&quot; Result? The digit 0 appears on screen.</p><p>Here’s what actually happens under the hood:</p><pre>// ❌ When notifications is empty:<br>0 &amp;&amp; &lt;NotificationList items={[]} /&gt;<br>// JavaScript returns: 0 (left side, because it&#39;s falsy)<br>// React renders: 0<br>// What you see: The number 0 on the page</pre><pre>// ❌ For comparison, with booleans:<br>false &amp;&amp; &lt;NotificationList items={[]} /&gt;<br>// JavaScript returns: false<br>// React renders: nothing (booleans are ignored)<br>// What you see: Empty DOM</pre><p>The difference feels academic until it breaks your UI at 2 AM on a Friday.</p><h3>Why React Designed It This Way</h3><p>Here’s where understanding the design decision actually helps: if React ignored <em>all</em> falsy values including 0, you couldn&#39;t render numbers in your JSX at all. Imagine an email app:</p><pre>&lt;div&gt;<br>  You have {emailCount} unread messages<br>&lt;/div&gt;</pre><p>If emailCount is 0 and React refuses to render 0, that person with an empty inbox would see blank text. That&#39;s broken. So React made a choice: render numbers (including 0), ignore booleans and null. It makes sense in isolation, but creates this trap.</p><h3>The Fix: Four Patterns That Actually Work</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*VxpaWNmQAYQzw6vlc_Qceg.png" /></figure><h3>Pattern 1: Use the Ternary Operator (Safest for Most Cases)</h3><p>The most straightforward fix is the ternary operator. Show one thing if true, something else if false:</p><pre>function NotificationCenter({ notifications }) {<br>  return (<br>    &lt;div&gt;<br>      {notifications.length &gt; 0 ? (<br>        &lt;NotificationList items={notifications} /&gt;<br>      ) : null}<br>    &lt;/div&gt;<br>  );<br>}</pre><p>This is explicit and clear. notifications.length &gt; 0 is <em>always</em> a boolean. No numbers, no ambiguity. React knows: true = render, false = render nothing.</p><p><strong>When to use this</strong>: Simple binary decisions. One thing or nothing. You’ll use this constantly, and it’s fine to use it even in small cases.</p><h3>Pattern 2: Coerce the Condition to Boolean (Quick Fix)</h3><p>If you love the brevity of &amp;&amp;, you can convert the left side to a boolean using the !! operator:</p><pre>function NotificationCenter({ notifications }) {<br>  return (<br>    &lt;div&gt;<br>      {!!notifications.length &amp;&amp; &lt;NotificationList items={notifications} /&gt;}<br>    &lt;/div&gt;<br>  );<br>}</pre><p>The !! converts any truthy/falsy value to explicit true or false. !!0 becomes false. !!5 becomes true. It&#39;s a bit terse, though—your future self reading this code might not remember why you added the extra exclamation marks.</p><p><strong>When to use this</strong>: When you’re confident in the code’s intent and want the &amp;&amp; shorthand. Fair warning: it looks quirky to newcomers on your team.</p><h3>Pattern 3: Explicit Boolean Check (Clearest Intent)</h3><pre>function NotificationCenter({ notifications }) {<br>  const hasNotifications = notifications.length &gt; 0;<br>  <br>  return (<br>    &lt;div&gt;<br>      {hasNotifications &amp;&amp; &lt;NotificationList items={notifications} /&gt;}<br>    &lt;/div&gt;<br>  );<br>}</pre><p>By naming the variable hasNotifications, the code says what it means. Someone reading this six months from now (maybe not you) understands immediately what&#39;s being checked. This scales well to complex logic too.</p><p><strong>When to use this</strong>: Always, if performance isn’t a barrier (and for UI logic, it rarely is). This is professional, maintainable, and kind to your team.</p><h3>Pattern 4: Returning null from the Component (Hide Don’t Show)</h3><p>Sometimes, instead of conditionally rendering at the parent level, you flip it: the component itself decides whether to render anything:</p><pre>function NotificationList({ items }) {<br>  if (items.length === 0) {<br>    return null;<br>  }<br>  <br>  return &lt;ul&gt;{/* render items */}&lt;/ul&gt;;<br>}</pre><pre>// Parent component:<br>function NotificationCenter({ notifications }) {<br>  return (<br>    &lt;div&gt;<br>      &lt;NotificationList items={notifications} /&gt;<br>    &lt;/div&gt;<br>  );<br>}</pre><p>This encapsulates the logic inside the component that understands its own data best. The parent doesn’t need to know or care about the empty state.</p><p><strong>When to use this</strong>: When the component has full control over its visibility. Common with feature toggles, auth checks, or modals.</p><h3>A Real-World Scenario: The Tricky Bits</h3><p>Here’s where things get complicated in practice. Say you’re building a dashboard with different sections that can be loading, empty, or populated:</p><pre>function UserDashboard({ users, isLoading, error }) {<br>  return (<br>    &lt;div&gt;<br>      {/* Here&#39;s where people stumble: */}<br>      {users.length &amp;&amp; &lt;UserGrid users={users} /&gt;}<br>      {error &amp;&amp; &lt;ErrorBanner message={error} /&gt;}<br>      {isLoading &amp;&amp; &lt;Spinner /&gt;}<br>    &lt;/div&gt;<br>  );<br>}</pre><p>This looks clean until:</p><ul><li>users.length is 0 → renders 0</li><li>error is an empty string &quot;&quot; → renders nothing (strings are rendered, but empty ones look empty, so... accidentally works)</li><li>isLoading is false → correctly renders nothing</li></ul><p>The inconsistency is the real problem. Your team has to remember which pattern prevents the bug and which doesn’t.</p><p><strong>Better approach:</strong></p><pre>function UserDashboard({ users, isLoading, error }) {<br>  return (<br>    &lt;div&gt;<br>      {users.length &gt; 0 &amp;&amp; &lt;UserGrid users={users} /&gt;}<br>      {error &amp;&amp; &lt;ErrorBanner message={error} /&gt;}<br>      {isLoading &amp;&amp; &lt;Spinner /&gt;}<br>    &lt;/div&gt;<br>  );<br>}</pre><p>Now every conditional is consistent: an explicit boolean check on the left, component on the right.</p><h3>When Conditional Rendering Gets Complex</h3><p>If you find yourself nesting ternaries or chaining multiple &amp;&amp; operators, that&#39;s a sign to refactor. Here&#39;s when things get ugly:</p><pre>// ❌ Nested ternaries are hard to follow:<br>{isLoggedIn ? (<br>  isPremium ? (<br>    &lt;PremiumDashboard /&gt;<br>  ) : (<br>    &lt;FreeDashboard /&gt;<br>  )<br>) : (<br>  &lt;LoginPrompt /&gt;<br>)}</pre><p>Introduce a separate state management hook or derive a single variable:</p><pre>// ✅ Extract the logic:<br>const dashboardContent = getDashboardContent(isLoggedIn, isPremium);</pre><pre>return &lt;div&gt;{dashboardContent}&lt;/div&gt;;</pre><pre>// Elsewhere:<br>function getDashboardContent(isLoggedIn, isPremium) {<br>  if (!isLoggedIn) return &lt;LoginPrompt /&gt;;<br>  if (isPremium) return &lt;PremiumDashboard /&gt;;<br>  return &lt;FreeDashboard /&gt;;<br>}</pre><p>This reads like prose and scales when you have five conditions instead of three.</p><h3>The Debugging Moment: Your Checklist</h3><p>When conditional rendering misbehaves, run through this:</p><ol><li><strong>Does a number appear where nothing should?</strong> → Check if you used count &amp;&amp; &lt;Component /&gt; pattern. Use count &gt; 0 &amp;&amp; &lt;Component /&gt; instead.</li><li><strong>Is the component always rendering?</strong> → Check if your condition is actually falsy. Log it: console.log(condition) in the JSX before your &amp;&amp;.</li><li><strong>Is the component never rendering when it should?</strong> → Flip your logic. If !isOpen &amp;&amp; &lt;Modal /&gt; looks wrong, it probably is. Use a ternary for clarity.</li><li><strong>Multiple conditions not working as expected?</strong> → Build them separately. Don’t chain three &amp;&amp; in a row; assign each to a variable first.</li></ol><h3>Try This Now: Your 5-Minute Challenge</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*CoJcequIiiohOSfirp0iKg.png" /></figure><p>Create a TaskList component that:</p><ul><li>Shows a list of tasks if tasks exist</li><li>Shows “No tasks yet!” if the array is empty</li><li>Shows a loading spinner while fetching</li><li>Shows an error message if something broke</li></ul><p>Here’s the skeleton:</p><pre>function TaskList({ tasks = [], isLoading, error }) {<br>  // Your job: fill in the returns<br>  <br>  if (isLoading) {<br>    return &lt;Spinner /&gt;;<br>  }<br>  <br>  if (error) {<br>    return &lt;ErrorMessage text={error} /&gt;;<br>  }<br>  <br>  // The tricky part:<br>  if (tasks.length &gt; 0) {<br>    return &lt;ul&gt;{tasks.map(t =&gt; &lt;li key={t.id}&gt;{t.name}&lt;/li&gt;)}&lt;/ul&gt;;<br>  }<br>  <br>  return &lt;p&gt;No tasks yet!&lt;/p&gt;;<br>}</pre><p>This covers every state your app can enter. Notice how we handle each case <em>before</em> rendering, rather than wrapping JSX in conditions. This reads like a decision tree, which is exactly what it is.</p><h3>What’s Next</h3><p>Build a small auth-gated component this week: a Dashboard that shows different content based on isLoggedIn and userRole (like admin, user, guest). Practice writing the conditional logic in different ways—ternary, &amp;&amp;, and extracted functions—and see which feels most natural for your brain and your team&#39;s style.</p><p>If you enjoyed this, check out my previous post on <a href="https://medium.com/@ramajha/keys-chaos-and-the-map-method-handling-dynamic-lists-in-2025-react-af4397bc0238">Keys, Chaos, and the .map() Method: Handling Dynamic Lists in 2025 React</a> — it connects directly to today’s lesson, especially how useState updates trigger re-renders that re-evaluate your conditionals.</p><h3>About the Author</h3><p>I’m a React developer documenting my 30-day React journey on Medium, sharing the lessons, gotchas, and “oh no” moments that beginners actually face. Follow along for practical tutorials, real code, and no buzzword fluff. Let’s connect on <a href="https://x.com/rama_vats/">[X/Twitter]</a> and <a href="https://github.com/ramavats">[GitHub]</a> — I’d love to hear about your own list-rendering disasters (we’ve all been there). Got questions? Drop a comment or message me anytime.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=735452e09a4d" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Keys, Chaos, and the .map() Method: Handling Dynamic Lists in 2025 React]]></title>
            <link>https://ramajha.medium.com/keys-chaos-and-the-map-method-handling-dynamic-lists-in-2025-react-af4397bc0238?source=rss-faab09a15a3d------2</link>
            <guid isPermaLink="false">https://medium.com/p/af4397bc0238</guid>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[front-end-development]]></category>
            <category><![CDATA[react]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[reactjs]]></category>
            <dc:creator><![CDATA[Rama Jha]]></dc:creator>
            <pubDate>Thu, 30 Oct 2025 10:01:54 GMT</pubDate>
            <atom:updated>2025-10-30T10:02:53.685Z</atom:updated>
            <content:encoded><![CDATA[<h3>Keys, Chaos, and the .map() Method: Handling Dynamic Lists in 2025 React</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*FQGt-edcSmZGMJiNrxHLig.png" /></figure><p>Last week I spent three hours debugging a form where checkbox values kept disappearing after users sorted the list. The weird part? Everything worked fine until someone clicked “Sort by Name.” Then boom — checked boxes randomly jumped to different items, and the one they’d just selected was blank. Turns out, I’d been using array indexes as keys. That single mistake broke React’s reconciliation, and suddenly my “simple” feature list looked like a crime scene.</p><p>If you’ve ever rendered a list in React, you’ve probably used .map() and slapped on a key prop without thinking twice. But there&#39;s a universe of chaos hiding behind that innocent little attribute, and in 2025, with React 19&#39;s smarter reconciliation, understanding keys isn&#39;t optional—it&#39;s what separates working code from UI nightmares.​</p><h3>What You’ll Learn</h3><p>After reading this, you’ll be able to:</p><ul><li>Use .map() to transform arrays into JSX components cleanly and correctly.</li><li>Understand why the key prop matters for React&#39;s reconciliation algorithm and what breaks when you get it wrong.</li><li>Avoid the array-index-as-key trap and recognize when it’s actually safe (spoiler: almost never for dynamic lists).</li><li>Generate stable, unique keys using IDs, UUIDs, or crypto APIs depending on your data source.​</li><li>Filter and sort arrays before rendering them without mutating state or breaking your UI.​​</li></ul><h3>Prerequisites</h3><p>You’ll need a <a href="https://medium.com/@ramajha/the-5-minute-guide-to-starting-a-react-project-fa68b4e79126">basic React setup</a> (Vite is the 2025 standard), comfort with JSX syntax, and a working knowledge of <a href="https://medium.com/@ramajha/es6-essentials-for-react-a-developers-handbook-not-a-tutorial-e0e46bc31469">JavaScript array methods</a> like .map(), .filter(), and .sort(). I&#39;ll provide runnable snippets, but you can also spin up a quick project with npm create vite@latest if you want to follow along in code.</p><h3>Why .map() is Your Best Friend (and Worst Enemy)</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JFmNwpM09h4m-jOs8ogBNA.png" /></figure><p>In React, you can’t just loop over an array with a for loop and push JSX into a variable—well, you can, but it&#39;s ugly and un-React-like. Instead, you use .map() to transform data arrays into component arrays. Here&#39;s the simplest version:</p><pre>const users = [&#39;Alice&#39;, &#39;Bob&#39;, &#39;Charlie&#39;];<br><br>function UserList() {<br>  return (<br>    &lt;ul&gt;<br>      {users.map((name) =&gt; (<br>        &lt;li&gt;{name}&lt;/li&gt;<br>      ))}<br>    &lt;/ul&gt;<br>  );<br>}</pre><p>This works, but open your console and you’ll see React screaming at you: “Warning: Each child in a list should have a unique ‘key’ prop.” React needs keys to track which items are which during re-renders. Without them, it falls back to positional matching, which is basically guessing.​</p><h3>The key Prop: React&#39;s Memory System</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ARIXQY7hQFItPijcs9-32Q.png" /></figure><p>Think of keys like nametags at a conference. If everyone’s wearing one, React knows exactly who moved, who left, and who’s new. If people just line up by position, React has no idea if the person in slot 2 is the same person or a replacement, so it re-renders everything just to be safe.​</p><p>Here’s what happens under the hood. React builds a virtual DOM tree and compares it to the previous one. For each element in an array, it checks: “Does this key match something I saw before?” If yes, it updates that component. If no, it mounts a new one or unmounts the missing one. When you use indexes as keys, React thinks the key didn’t change — even though the data did — so it updates the wrong components.​</p><h3>The Classic Gotcha: Index Keys</h3><p>Let me show you the bug that cost me three hours. Suppose you have a list of users with input fields:</p><pre>const [users, setUsers] = useState([<br>  { name: &#39;Alice&#39;, email: &#39;alice@example.com&#39; },<br>  { name: &#39;Bob&#39;, email: &#39;bob@example.com&#39; },<br>  { name: &#39;Charlie&#39;, email: &#39;charlie@example.com&#39; }<br>]);<br><br>return (<br>  &lt;ul&gt;<br>    {users.map((user, index) =&gt; (<br>      &lt;li key={index}&gt;<br>        &lt;input defaultValue={user.email} /&gt;<br>      &lt;/li&gt;<br>    ))}<br>  &lt;/ul&gt;<br>);</pre><p>Looks fine, right? But when you delete the first item (Alice), here’s what React sees:​</p><ul><li>Key 0: Was Alice, now Bob → Update the component (DOM stays, but data swaps).</li><li>Key 1: Was Bob, now Charlie → Update the component.</li><li>Key 2: Missing → Delete the last component.</li></ul><p>So instead of removing Alice’s input, React keeps all the DOM nodes, updates their data, and deletes Charlie’s. If those inputs had state (like a typed value or a checkbox), that state sticks to the DOM node, not the data. Bob’s input now shows Alice’s old value. Users see their data jump around like a shell game.​</p><p>Here’s the proof in plain JavaScript (simulating React’s reconciliation):</p><pre>=== Using Array Indexes as Keys (PROBLEMATIC) ===<br><br>Initial state:<br>  Key: 0 -&gt; Alice (alice@email.com)<br>  Key: 1 -&gt; Bob (bob@email.com)<br>  Key: 2 -&gt; Charlie (charlie@email.com)<br><br>After deleting first item (Alice):<br>  Key: 0 -&gt; Bob (bob@email.com)<br>  Key: 1 -&gt; Charlie (charlie@email.com)<br><br>⚠️  React sees:<br>  - Key 0: Was &#39;Alice&#39;, now &#39;Bob&#39; → UPDATE component<br>  - Key 1: Was &#39;Bob&#39;, now &#39;Charlie&#39; → UPDATE component<br>  - Key 2: Missing → DELETE component<br><br>Result: React updates Bob and Charlie instead of just removing Alice!</pre><h3>The Fix: Stable, Unique IDs</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*eH3r3JWEwPE1oZGojm2Upw.png" /></figure><p>Always use an ID that travels with the data. If your data comes from a database, use the row ID. If you’re generating it locally (like a to-do list), use crypto.randomUUID() or the uuid npm package:​</p><pre>import { v4 as uuidv4 } from &#39;uuid&#39;;<br><br>const [users, setUsers] = useState([<br>  { id: uuidv4(), name: &#39;Alice&#39;, email: &#39;alice@example.com&#39; },<br>  { id: uuidv4(), name: &#39;Bob&#39;, email: &#39;bob@example.com&#39; },<br>  { id: uuidv4(), name: &#39;Charlie&#39;, email: &#39;charlie@example.com&#39; }<br>]);<br>return (<br>  &lt;ul&gt;<br>    {users.map((user) =&gt; (<br>      &lt;li key={user.id}&gt;<br>        &lt;input defaultValue={user.email} /&gt;<br>      &lt;/li&gt;<br>    ))}<br>  &lt;/ul&gt;<br>);</pre><p>Now when you delete Alice, React sees:</p><pre>✅ React sees:<br>  - Key usr_001: Missing → DELETE component (Alice)<br>  - Key usr_002: No change → KEEP component (Bob)<br>  - Key usr_003: No change → KEEP component (Charlie)</pre><pre>Result: React only removes Alice&#39;s component. Bob and Charlie stay intact!</pre><p>Perfect. No shuffling, no lost state.​</p><p>Important: Don’t call uuidv4() inside the .map() itself—that regenerates the key on every render, which defeats the whole point. Generate the ID once when you create the data, then reuse it.​</p><h3>Filtering and Sorting Before You Map</h3><p>Real apps don’t just render static lists. You filter search results, sort by date, toggle categories. The golden rule: manipulate the array first, then map over the result. React doesn’t care what you do to the data before JSX; it only sees the final array.​​</p><h3>Filtering Example</h3><p>Show only users whose email domain is example.com:</p><pre>function FilteredUserList({ users }) {<br>  const exampleUsers = users.filter(user =&gt; user.email.endsWith(&#39;@example.com&#39;));<br><br>return (<br>    &lt;ul&gt;<br>      {exampleUsers.map((user) =&gt; (<br>        &lt;li key={user.id}&gt;{user.name}&lt;/li&gt;<br>      ))}<br>    &lt;/ul&gt;<br>  );<br>}</pre><h3>Sorting Example</h3><p>Sort users alphabetically by name. Warning: .sort() mutates the original array, which is a no-no in React because state should be immutable. Clone first:​</p><pre>function SortedUserList({ users }) {<br>  const sortedUsers = [...users].sort((a, b) =&gt; a.name.localeCompare(b.name));<br><br>return (<br>    &lt;ul&gt;<br>      {sortedUsers.map((user) =&gt; (<br>        &lt;li key={user.id}&gt;{user.name}&lt;/li&gt;<br>      ))}<br>    &lt;/ul&gt;<br>  );<br>}</pre><p>Alternatively, use .toSorted() (a newer method that returns a sorted copy without mutating). Either way, clone before you sort, or you&#39;ll get weird side effects.​</p><h3>Chaining Filter, Sort, and Map</h3><p>You can chain them in one expression, but keep it readable:</p><pre>const activeUsers = users<br>  .filter(user =&gt; user.active)<br>  .sort((a, b) =&gt; a.name.localeCompare(b.name))<br>  .map(user =&gt; &lt;UserCard key={user.id} user={user} /&gt;);</pre><p>This is clean, but if the logic gets complex, break it into variables. Debugging a five-method chain is no fun.​</p><h3>Handling Edge Cases and Performance</h3><h3>Duplicate Keys</h3><p>If your data has duplicate IDs (or you accidentally use the same key twice), React will throw a warning: “Encountered two children with the same key”. This usually means bad data. Track down the source, because duplicate keys cause orphaned DOM nodes and phantom components that refuse to unmount.​</p><h3>Large Lists and Memoization</h3><p>In React 19, automatic memoization handles many cases where you’d previously reach for React.memo or useMemo. But if you&#39;re rendering thousands of items, you might still need to memoize the list itself or use windowing libraries like react-window. Don&#39;t prematurely optimize—profile first, then fix bottlenecks.​</p><h3>When Index Keys Are Actually Okay</h3><p>There’s one exception: static lists that never reorder, filter, or change. If you’re rendering a hardcoded array of strings that’ll never update, using indexes won’t hurt you. But honestly, why risk it? Just add an ID to your data and sleep better at night.​</p><h3>A Quick Debugging Story</h3><p>Here’s where people stumble: You build a todo list. You map over todos with indexes as keys. Everything works. Then you add a “Mark Complete” button that reorders completed items to the bottom. Suddenly, clicking “Complete” on the second todo makes the third one vanish, and the checkboxes jump around like they’re haunted.​</p><p>What happened? React compared the old and new arrays by index. When you moved item 2 to the end, React saw:</p><ul><li>Index 0: Same</li><li>Index 1: Different data → update</li><li>Index 2: Different data → update</li><li>Index 3: New → mount</li></ul><p>So it updated items 1 and 2 in place (keeping their DOM state), then mounted a new component for item 3. If item 1 had a checked checkbox, that state stuck to the DOM node, which now shows item 2’s data. Chaos.​</p><p>The fix: Use stable IDs. React sees item 2 moved, so it moves the actual component (with its state intact) instead of updating a different component’s data.</p><h3>Try This Now: The Mini-Challenge</h3><p>Create a component that renders an array of user objects (at least three users, each with an id, name, and email). Add two buttons: one to sort by name, one to delete the first user. Use proper keys. Then intentionally break it—switch to index keys and watch the chaos unfold. Swap back to unique IDs and confirm it works. This hands-on moment will cement the lesson way better than reading.​</p><p>Here’s starter code:</p><pre>import { useState } from &#39;react&#39;;<br>import { v4 as uuidv4 } from &#39;uuid&#39;;<br><br>function UserManager() {<br>  const [users, setUsers] = useState([<br>    { id: uuidv4(), name: &#39;Charlie&#39;, email: &#39;charlie@test.com&#39; },<br>    { id: uuidv4(), name: &#39;Alice&#39;, email: &#39;alice@test.com&#39; },<br>    { id: uuidv4(), name: &#39;Bob&#39;, email: &#39;bob@test.com&#39; }<br>  ]);<br>  const sortByName = () =&gt; {<br>    setUsers([...users].sort((a, b) =&gt; a.name.localeCompare(b.name)));<br>  };<br>  const deleteFirst = () =&gt; {<br>    setUsers(users.slice(1));<br>  };<br>  return (<br>    &lt;div&gt;<br>      &lt;button onClick={sortByName}&gt;Sort by Name&lt;/button&gt;<br>      &lt;button onClick={deleteFirst}&gt;Delete First&lt;/button&gt;<br>      &lt;ul&gt;<br>        {users.map((user) =&gt; (<br>          &lt;li key={user.id}&gt;<br>            {user.name} - {user.email}<br>          &lt;/li&gt;<br>        ))}<br>      &lt;/ul&gt;<br>    &lt;/div&gt;<br>  );<br>}</pre><h3>What’s Next: Build a Searchable Product List</h3><p>Take what you learned and build a mini product catalog. Create an array of at least five products (each with id, name, price, category). Add:</p><ul><li>A search input that filters by name.</li><li>Buttons to filter by category (e.g., “Electronics,” “Clothing”).</li><li>A toggle to sort by price (low to high, high to low).</li></ul><p>Use .filter(), .sort(), and .map() with proper keys. Bonus: Add a &quot;Delete&quot; button for each product and confirm the right item gets removed. You should finish in under an hour, and it&#39;ll reinforce filtering, sorting, and reconciliation in one project.​</p><h3>Wrapping Up</h3><p>The .map() method is how you bring data to life in React, but without the right keys, your lists will betray you at the worst possible moment. Indexes work until they don&#39;t, and by then you&#39;re debugging phantom state and disappearing inputs at 2 a.m. In 2025, with React 19&#39;s improved reconciliation and automatic memoization, getting keys right is even more important—because React&#39;s optimizations assume you&#39;re playing by the rules.​</p><p>Use stable, unique IDs. Filter and sort before you map. Clone arrays before mutating. And when something breaks, check your keys first. Nine times out of ten, that’s the culprit.</p><p>If you enjoyed this, check out my previous post on <a href="https://medium.com/@ramajha/the-children-prop-reacts-secret-to-reusable-wrapper-components-30fe998fd90b">The Children Prop: React’s Secret to Reusable Wrapper Components</a> — it connects directly to today’s lesson and will help you build on what you’ve learned here.</p><p>About the Author</p><p>I’m a React developer documenting my 30-day React journey on Medium, sharing the lessons, gotchas, and “oh no” moments that beginners actually face. Follow along for practical tutorials, real code, and no buzzword fluff. Let’s connect on <a href="https://x.com/rama_vats/">[X/Twitter]</a> and <a href="https://github.com/ramavats">[GitHub]</a> — I’d love to hear about your own list-rendering disasters (we’ve all been there). Got questions? Drop a comment or message me anytime.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=af4397bc0238" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>