{"id":"l-rn-3-3","courseId":"react-native-apps","lang":"en","kind":"lesson","summary":"Share Trailhead’s hike list across tabs and screens with a reducer and context provided from the root layout, derive totals instead of storing them, and know when a state library earns its place.","html":"<p>Four parts of Trailhead now care about the same data. The Log tab lists hikes, the Stats tab totals them, the detail screen shows one, and the add-hike form (next lesson) creates them. If each screen loads and keeps its own copy, they drift apart: you delete a hike on the detail screen, go back, and the Stats tab still counts it, because stack screens and visited tabs stay mounted and don’t reload.</p>\n<p>The fix is the React principle you already know, applied to a navigation tree: one source of truth, held above everything that needs it.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">Where shared state lives in Expo Router</h2>\n<p>In a web app you’d put the provider near the root component. In Expo Router, the root component is <code dir=\"ltr\">src/app/_layout.tsx</code>: it renders the Stack, which renders the tab group and every pushed screen. A provider placed around that Stack covers the whole app.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 680 260\" role=\"img\" aria-labelledby=\"l-rn-3-3-t1\"><defs><marker id=\"arrow-l-rn-3-3-1\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0 L10 5 L0 10 z\" class=\"d-arrowhead\"/></marker></defs>\n  <title id=\"l-rn-3-3-t1\">HikesProvider wraps the root Stack in src/app/_layout.tsx; the Log tab, the Stats tab and the hike detail screen all read the same hikes and dispatch actions to the same reducer.</title>\n  <rect class=\"d-box-primary\" x=\"200\" y=\"16\" width=\"280\" height=\"64\" rx=\"12\"/>\n  <text class=\"d-label-strong\" x=\"340\" y=\"44\" text-anchor=\"middle\">HikesProvider</text>\n  <text class=\"d-code\" x=\"340\" y=\"66\" text-anchor=\"middle\">src/app/_layout.tsx</text>\n  <rect class=\"d-box\" x=\"240\" y=\"104\" width=\"200\" height=\"40\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"340\" y=\"129\" text-anchor=\"middle\">root Stack</text>\n  <path class=\"d-line\" d=\"M340 80 L340 104\"/>\n  <rect class=\"d-box-accent\" x=\"40\" y=\"186\" width=\"170\" height=\"50\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"125\" y=\"216\" text-anchor=\"middle\">Log tab</text>\n  <rect class=\"d-box-accent\" x=\"255\" y=\"186\" width=\"170\" height=\"50\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"340\" y=\"216\" text-anchor=\"middle\">Stats tab</text>\n  <rect class=\"d-box-accent\" x=\"470\" y=\"186\" width=\"170\" height=\"50\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"555\" y=\"216\" text-anchor=\"middle\">/hikes/[id]</text>\n  <path class=\"d-line\" d=\"M300 144 L125 186\"/>\n  <path class=\"d-line\" d=\"M340 144 L340 186\"/>\n  <path class=\"d-line\" d=\"M380 144 L555 186\"/>\n  <path class=\"d-arrow d-dashed\" d=\"M600 186 C640 120 560 60 482 52\" marker-end=\"url(#arrow-l-rn-3-3-1)\"/>\n  <text class=\"d-label-muted\" x=\"612\" y=\"110\" text-anchor=\"middle\">dispatch</text>\n</svg><figcaption>One provider in the root layout feeds every screen</figcaption></figure>\n<h2 id=\"sec-2\" tabindex=\"-1\">A reducer for every change</h2>\n<p>Hikes get added, edited, deleted and favorited. Rather than scattering <code dir=\"ltr\">setHikes</code> calls across screens, describe each change as an <strong>action</strong> and handle them all in one pure function:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">src/state/hikesReducer.ts</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">export</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Hike</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#953800;--shiki-dark:#FFA657\">id</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; </span><span style=\"color:#953800;--shiki-dark:#FFA657\">name</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; </span><span style=\"color:#953800;--shiki-dark:#FFA657\">date</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; </span><span style=\"color:#953800;--shiki-dark:#FFA657\">distanceKm</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> number</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; </span><span style=\"color:#953800;--shiki-dark:#FFA657\">favorite</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> boolean</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> };</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">export</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> HikesAction</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  |</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#953800;--shiki-dark:#FFA657\">type</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'added'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; </span><span style=\"color:#953800;--shiki-dark:#FFA657\">hike</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Hike</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  |</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#953800;--shiki-dark:#FFA657\">type</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'updated'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; </span><span style=\"color:#953800;--shiki-dark:#FFA657\">id</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; </span><span style=\"color:#953800;--shiki-dark:#FFA657\">changes</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Partial</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">Omit</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">Hike</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'id'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>> }</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  |</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#953800;--shiki-dark:#FFA657\">type</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'deleted'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; </span><span style=\"color:#953800;--shiki-dark:#FFA657\">id</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  |</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#953800;--shiki-dark:#FFA657\">type</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'favoriteToggled'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; </span><span style=\"color:#953800;--shiki-dark:#FFA657\">id</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> };</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">export</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> hikesReducer</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">state</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Hike</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[], </span><span style=\"color:#953800;--shiki-dark:#FFA657\">action</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> HikesAction</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Hike</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[] {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  switch</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (action.type) {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    case</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'added'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">      return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [action.hike, </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">...</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">state];</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    case</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'updated'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">      return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> state.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">map</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">h</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (h.id </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">===</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> action.id </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">?</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">...</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">h, </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">...</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">action.changes } </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> h));</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    case</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'deleted'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">      return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> state.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">filter</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">h</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> h.id </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">!==</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> action.id);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    case</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'favoriteToggled'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">      return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> state.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">map</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">h</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (h.id </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">===</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> action.id </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">?</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">...</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">h, favorite: </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">!</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">h.favorite } </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> h));</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>Every case returns a <strong>new</strong> array, and a new object for the hike that changed. That’s how React knows something changed, and it’s what lets a memoized <code dir=\"ltr\">HikeRow</code> skip re-rendering the 599 rows that didn’t.</p>\n<p>Notice the action names: <code dir=\"ltr\">added</code>, <code dir=\"ltr\">deleted</code>, <code dir=\"ltr\">favoriteToggled</code>. They describe <strong>what happened</strong>, not how to update state. That keeps screens simple (the detail screen reports that the user toggled a favorite and doesn’t care how the list is stored) and gives you one place to change when the rules change, for example when deleting a hike should also remove its photos.</p>\n<p>Because the reducer has no React Native imports, it runs anywhere. You’ll test reducers like this one with plain Jest in section 5, and you’ll write one in this lesson’s exercise.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">The provider and its hooks</h2>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"tsx\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">src/state/hikes.tsx</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { createContext, use, useMemo, useReducer, </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">type</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> Dispatch, </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">type</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> ReactNode } </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'react'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { hikesReducer, </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">type</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> Hike, </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">type</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> HikesAction } </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> './hikesReducer'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> HikesContext</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> createContext</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">Hike</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">|</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> null</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">null</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> DispatchContext</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> createContext</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">Dispatch</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">HikesAction</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">> </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">|</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> null</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">null</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">export</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> HikesProvider</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">({ </span><span style=\"color:#953800;--shiki-dark:#FFA657\">initial</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">children</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#953800;--shiki-dark:#FFA657\">initial</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Hike</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[]; </span><span style=\"color:#953800;--shiki-dark:#FFA657\">children</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> ReactNode</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }) {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  const</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">hikes</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">dispatch</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> useReducer</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(hikesReducer, initial);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">DispatchContext</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> value</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">dispatch</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">HikesContext</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> value</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">hikes</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">{</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">children</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">HikesContext</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    &#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">DispatchContext</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  );</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">export</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> useHikes</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">() {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> hikes</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> use</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(HikesContext);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  if</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">!</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">hikes) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">throw</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> new</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> Error</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'useHikes must be used inside HikesProvider'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> hikes;</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">export</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> useHike</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">id</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> undefined</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> hikes</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> useHikes</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">();</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> useMemo</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(() </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> hikes.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">find</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">h</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> h.id </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">===</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> id), [hikes, id]);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">export</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> useHikesDispatch</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">() {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> dispatch</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> use</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(DispatchContext);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  if</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">!</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">dispatch) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">throw</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> new</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> Error</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'useHikesDispatch must be used inside HikesProvider'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> dispatch;</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>Then wrap the Stack in the root layout: <code dir=\"ltr\">&lt;HikesProvider initial={sampleHikes}&gt;&lt;Stack&gt;…&lt;/Stack&gt;&lt;/HikesProvider&gt;</code>. In section 4 the initial data will come from the device’s database instead.</p>\n<p>A few details worth copying. React 19 lets you render a context directly as its provider (<code dir=\"ltr\">&lt;HikesContext value={…}&gt;</code>) and read it with <code dir=\"ltr\">use</code>. Splitting state and dispatch into <strong>two contexts</strong> means components that only dispatch, like a delete button, don’t re-render when the list changes. And the hooks throw a clear error if someone forgets the provider, instead of failing later with “cannot read properties of null”.</p>\n<p>A screen now reads like this:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"tsx\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">TSX</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> hike</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> useHike</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(id);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> dispatch</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> useHikesDispatch</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">();</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// …</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Pressable</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> onPress</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">() </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> dispatch</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">({ type: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'favoriteToggled'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, id: hike.id })</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span></code></pre></div>\n<h2 id=\"sec-4\" tabindex=\"-1\">Derive, don’t duplicate</h2>\n<p>The Stats tab needs total distance, hike count and the longest hike. None of those are state. They’re <strong>derived</strong> from the hike list, so compute them:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"tsx\" data-index=\"3\"><div class=\"code-head\"><span class=\"code-title\">TSX</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> hikes</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> useHikes</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">();</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> stats</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> useMemo</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(() </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> totalKm</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> hikes.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">reduce</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">sum</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">h</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sum </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">+</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> h.distanceKm, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> longest</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> hikes.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">reduce</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">best</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">h</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">!</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">best </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">||</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> h.distanceKm </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> best.distanceKm </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">?</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> h </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> best), </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">undefined</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> as</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Hike</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> undefined</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { count: hikes.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">length</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, totalKm, longest };</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}, [hikes]);</span></span></code></pre></div>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">Common mistake</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Copying shared data into local state</span></p>\n<p><code dir=\"ltr\">const [hike, setHike] = useState(useHike(id))</code> snapshots the hike when the screen mounts. Edit it from elsewhere and this screen keeps showing the old copy, because <code dir=\"ltr\">useState</code> only uses its argument on the first render. Read from the shared hook on every render; copy into local state only for a draft the user is editing, such as a form, and dispatch when they save.</p>\n</aside>\n<h2 id=\"sec-5\" tabindex=\"-1\">When context isn’t enough</h2>\n<p>Context re-renders every consumer when its value changes. For Trailhead’s hike list that’s fine. It becomes a problem when a value changes very often (a live GPS position updating every second) or when a large app has many unrelated consumers. Teams then reach for <strong>Zustand</strong>, a small store with selectors so components re-render only for the slice they read. Data that lives on a server has different problems again (caching, refetching, staleness), which is the job of TanStack Query; you’ll meet that in section 4. Start with reducer plus context, and move when you’ve measured a reason.</p>\n<p>Next, the screen that creates hikes: a form that works with a phone keyboard.</p>\n","toc":[{"id":"sec-1","text":"Where shared state lives in Expo Router","level":2},{"id":"sec-2","text":"A reducer for every change","level":2},{"id":"sec-3","text":"The provider and its hooks","level":2},{"id":"sec-4","text":"Derive, don’t duplicate","level":2},{"id":"sec-5","text":"When context isn’t enough","level":2}],"takeaways":["State used by several routes belongs above all of them, which in Expo Router means a provider in the root <code dir=\"ltr\">_layout.tsx</code>.","A reducer keeps every change to the hike list in one pure, testable function that returns new arrays and objects.","Wrap context in small hooks such as <code dir=\"ltr\">useHikes()</code> and <code dir=\"ltr\">useHike(id)</code>, so screens never touch the context object directly.","Derive totals and filtered lists from the source state with <code dir=\"ltr\">useMemo</code>; don’t store them separately.","Reach for a library like Zustand when context re-renders become a measured problem, and for TanStack Query when the data lives on a server."],"further":[{"title":"Scaling Up with Reducer and Context","url":"https://react.dev/learn/scaling-up-with-reducer-and-context"},{"title":"Choosing the State Structure","url":"https://react.dev/learn/choosing-the-state-structure"},{"title":"useReducer","url":"https://react.dev/reference/react/useReducer"}],"quiz":[{"q":"<p>The Stats tab stores <code dir=\"ltr\">totalKm</code> in its own <code dir=\"ltr\">useState</code>, updated in an effect. After a user deletes a hike, the total is wrong until the app restarts. What’s the best fix?</p>","options":[{"text":"Add the hikes array to the effect’s dependency list.","why":"That patches the symptom, but you still have two copies of the truth and an extra render on every change."},{"text":"Move <code dir=\"ltr\">totalKm</code> into the reducer and update it on every action.","why":"Now every action has to remember to keep the total in sync, which is where this kind of bug comes from."},{"text":"Re-mount the Stats tab whenever it gains focus.","why":"Throwing away the screen’s state to recover from a sync bug costs more than the bug."},{"text":"Delete the state and compute the total from the shared hikes with <code dir=\"ltr\">useMemo</code>.","why":"Correct. Derived values computed from one source of truth can’t drift out of sync."}],"answer":3},{"q":"<p>What’s wrong with this reducer case?</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">JavaScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">case</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'favoriteToggled'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  const hike </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> state.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">find</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">h</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> h.id </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">===</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> action.id);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  hike.favorite </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> !</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">hike.favorite;</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  return state;</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n","options":[{"text":"It mutates the existing hike and returns the same array, so React sees no change and nothing re-renders.","why":"Correct. Return a new array with a new object for the changed hike, for example with <code dir=\"ltr\">state.map</code>."},{"text":"Reducers can’t contain block-scoped <code dir=\"ltr\">const</code> declarations.","why":"Block-scoped declarations inside a <code dir=\"ltr\">case</code> with braces are fine."},{"text":"<code dir=\"ltr\">find</code> is too slow for a reducer; it needs an index lookup.","why":"For hundreds of hikes <code dir=\"ltr\">find</code> is instant. The bug is mutation, not speed."}],"answer":0},{"q":"<p>Where should Trailhead’s <code dir=\"ltr\">HikesProvider</code> be rendered so the Log tab, the Stats tab and the <code dir=\"ltr\">/hikes/[id]</code> screen can all read it?</p>","options":[{"text":"Inside <code dir=\"ltr\">(tabs)/index.tsx</code>, since the log owns the hikes.","why":"Siblings and the detail screen are outside that component, so they can’t read its context."},{"text":"In <code dir=\"ltr\">src/app/_layout.tsx</code>, around the root Stack.","why":"Correct. Everything rendered by the root layout, tabs and stack screens alike, is inside the provider."},{"text":"In every screen that needs it, each with its own copy.","why":"Separate providers mean separate state; an edit on one screen wouldn’t show on the others."}],"answer":1}],"exercise":{"kind":"web","mode":"js","starter":{"js":"// Trailhead keeps hikes sorted newest first (dates are 'YYYY-MM-DD').\nfunction hikesReducer(state, action) {\n  switch (action.type) {\n    case 'added':\n      state.push(action.hike);\n      return state;\n    case 'updated': {\n      const hike = state.find((h) => h.id === action.id);\n      Object.assign(hike, action.changes);\n      return state;\n    }\n    case 'deleted':\n      return state.filter((h) => h.id !== action.id);\n    case 'favoriteToggled': {\n      const hike = state.find((h) => h.id === action.id);\n      hike.favorite = !hike.favorite;\n      return state;\n    }\n    default:\n      throw new Error('Unknown action ' + action.type);\n  }\n}\n\nconst start = [\n  { id: 'a', name: 'Lake Trail', date: '2026-10-19', distanceKm: 11.2, favorite: false },\n  { id: 'b', name: 'Ridge Loop', date: '2026-10-04', distanceKm: 8.4, favorite: true },\n];\nconsole.log(hikesReducer(start, { type: 'added', hike: { id: 'c', name: 'Fern Gully', date: '2026-10-10', distanceKm: 3.6, favorite: false } }).map((h) => h.id));\n"},"solution":{"js":"const byNewest = (a, b) => b.date.localeCompare(a.date);\n\nfunction hikesReducer(state, action) {\n  switch (action.type) {\n    case 'added':\n      if (state.some((h) => h.id === action.hike.id)) return state;\n      return [...state, action.hike].sort(byNewest);\n    case 'updated': {\n      if (!state.some((h) => h.id === action.id)) return state;\n      return state\n        .map((h) => (h.id === action.id ? { ...h, ...action.changes, id: h.id } : h))\n        .sort(byNewest);\n    }\n    case 'deleted':\n      return state.filter((h) => h.id !== action.id);\n    case 'favoriteToggled':\n      return state.map((h) => (h.id === action.id ? { ...h, favorite: !h.favorite } : h));\n    default:\n      return state;\n  }\n}\n\nconst start = [\n  { id: 'a', name: 'Lake Trail', date: '2026-10-19', distanceKm: 11.2, favorite: false },\n  { id: 'b', name: 'Ridge Loop', date: '2026-10-04', distanceKm: 8.4, favorite: true },\n];\nconsole.log(hikesReducer(start, { type: 'added', hike: { id: 'c', name: 'Fern Gully', date: '2026-10-10', distanceKm: 3.6, favorite: false } }).map((h) => h.id));\n"},"checks":[{"id":"add-sorted","test":"const mk = () => [{ id: 'a', name: 'Lake Trail', date: '2026-10-19', distanceKm: 11.2, favorite: false }, { id: 'b', name: 'Ridge Loop', date: '2026-10-04', distanceKm: 8.4, favorite: true }]; const s = mk(); const n = hikesReducer(s, { type: 'added', hike: { id: 'c', name: 'Fern', date: '2026-10-10', distanceKm: 3.6, favorite: false } }); return n !== s && n.map((h) => h.id).join() === 'a,c,b' && s.length === 2;"},{"id":"add-duplicate","test":"const mk = () => [{ id: 'a', name: 'Lake Trail', date: '2026-10-19', distanceKm: 11.2, favorite: false }, { id: 'b', name: 'Ridge Loop', date: '2026-10-04', distanceKm: 8.4, favorite: true }]; const s = mk(); const n = hikesReducer(s, { type: 'added', hike: { id: 'a', name: 'Copy', date: '2026-01-01', distanceKm: 1, favorite: false } }); return n === s;"},{"id":"update","test":"const mk = () => [{ id: 'a', name: 'Lake Trail', date: '2026-10-19', distanceKm: 11.2, favorite: false }, { id: 'b', name: 'Ridge Loop', date: '2026-10-04', distanceKm: 8.4, favorite: true }]; const s = mk(); const n = hikesReducer(s, { type: 'updated', id: 'b', changes: { name: 'Ridge Loop East', date: '2026-10-25', id: 'zzz' } }); return n !== s && n[0].id === 'b' && n[0].name === 'Ridge Loop East' && s[1].name === 'Ridge Loop' && n.length === 2;"},{"id":"toggle","test":"const mk = () => [{ id: 'a', name: 'Lake Trail', date: '2026-10-19', distanceKm: 11.2, favorite: false }, { id: 'b', name: 'Ridge Loop', date: '2026-10-04', distanceKm: 8.4, favorite: true }]; const s = mk(); const n = hikesReducer(s, { type: 'favoriteToggled', id: 'a' }); return n !== s && n[0].favorite === true && s[0].favorite === false && n[1] === s[1];"},{"id":"delete","test":"const mk = () => [{ id: 'a', name: 'Lake Trail', date: '2026-10-19', distanceKm: 11.2, favorite: false }, { id: 'b', name: 'Ridge Loop', date: '2026-10-04', distanceKm: 8.4, favorite: true }]; const s = mk(); const n = hikesReducer(s, { type: 'deleted', id: 'a' }); return n.length === 1 && n[0].id === 'b' && s.length === 2;"},{"id":"unknown","test":"const mk = () => [{ id: 'a', name: 'Lake Trail', date: '2026-10-19', distanceKm: 11.2, favorite: false }, { id: 'b', name: 'Ridge Loop', date: '2026-10-04', distanceKm: 8.4, favorite: true }]; const s = mk(); let n; try { n = hikesReducer(s, { type: 'somethingElse' }); } catch { return false; } return n === s;"}],"title":"Make the hikes reducer immutable","prompt":"<p>Trailhead’s <code dir=\"ltr\">hikesReducer</code> mutates state, so screens don’t update. Rewrite it so <strong>every</strong> case returns new arrays and objects and never changes <code dir=\"ltr\">state</code>. Keep the list sorted <strong>newest first</strong> by <code dir=\"ltr\">date</code> (<code dir=\"ltr\">YYYY-MM-DD</code> strings).</p>\n<ul>\n<li><code dir=\"ltr\">added</code>: insert <code dir=\"ltr\">action.hike</code> in date order. If a hike with that <code dir=\"ltr\">id</code> already exists, return the <strong>same</strong> <code dir=\"ltr\">state</code>.</li>\n<li><code dir=\"ltr\">updated</code>: merge <code dir=\"ltr\">action.changes</code> into the hike with <code dir=\"ltr\">action.id</code>, re-sort, and never let <code dir=\"ltr\">changes</code> overwrite the <code dir=\"ltr\">id</code>.</li>\n<li><code dir=\"ltr\">deleted</code>: remove the hike.</li>\n<li><code dir=\"ltr\">favoriteToggled</code>: flip <code dir=\"ltr\">favorite</code>; hikes that didn’t change keep their <strong>same object</strong>, so memoized rows skip re-rendering.</li>\n<li>Any unknown action returns the same <code dir=\"ltr\">state</code> (don’t throw).</li>\n</ul>\n","hints":["Create a comparator once: <code dir=\"ltr\">const byNewest = (a, b) =&gt; b.date.localeCompare(a.date);</code>. <code dir=\"ltr\">[...state, hike].sort(byNewest)</code> sorts a copy.","<code dir=\"ltr\">state.map((h) =&gt; (h.id === action.id ? { ...h, ...changes } : h))</code> copies only the changed hike. Put <code dir=\"ltr\">id: h.id</code> after the spread so it wins.","Returning <code dir=\"ltr\">state</code> itself (same reference) is how a reducer tells React that nothing changed."],"explanation":"<p>React decides whether to re-render by comparing references, so a reducer that mutates and returns the same array hides every change. The fixed version builds new arrays with spread, <code dir=\"ltr\">map</code>, <code dir=\"ltr\">filter</code> and a sort on a copy, creates a new object only for the hike that changed, and returns the original <code dir=\"ltr\">state</code> when nothing should change (a duplicate add or an unknown action). Unchanged hikes keep their identity, which is exactly what lets <code dir=\"ltr\">memo(HikeRow)</code> skip them.</p>\n","checkLabels":{"add-sorted":"Added hikes land in date order in a new array","add-duplicate":"Adding an existing id returns the same state","update":"Updates merge, re-sort, keep the id and don’t mutate","toggle":"Toggling a favorite copies only the changed hike","delete":"Deleting returns a new, shorter array","unknown":"Unknown actions return the same state without throwing"}},"words":641,"readMinutes":3}