{
    "version": "https://jsonfeed.org/version/1",
    "title": "Alex Chan",
    "home_page_url": "https://alexmchan.com",
    "feed_url": "https://alexmchan.com/rss/feed.json",
    "description": "Your blog description",
    "icon": "https://alexmchan.com/favicon.ico",
    "author": {
        "name": "Alex Chan"
    },
    "items": [
        {
            "id": "https://alexmchan.com/blog/2026-06-29-concurrent-optimistic-updates",
            "content_html": "<p>Imagine a small task tracker — the kind of kanban board every team eventually\nbuilds. There are columns (<code>Todo</code>, <code>In Progress</code>, <code>Done</code>), cards you drag between\nthem, an order within each column, and tasks can block other tasks. Each card has\na <code>status</code>, a <code>position</code>, and maybe some <code>blockers</code>. You drag a card to a new\ncolumn, it moves, the server is told. Simple.</p>\n<p>I&#x27;ve used React Query for years, and my reflex for mutations has always been the\nsame: <strong>fire the mutation, then <code>invalidateQueries</code> to get the real deal from the\nserver.</strong> That reflex is correct — for forms. It fell apart the moment I made the\nboard draggable, and digging into why taught me more about mutations than years of\nCRUD forms ever did.</p>\n<h2>The reflex that works for forms</h2>\n<p>A create/update/delete form is slow and deliberate. The user clicks submit, waits,\nsees the result. You want server truth — generated ids, computed fields, side\neffects — so you just invalidate:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#DCDCAA\"> invalidate</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#E6E6E6\"> () </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#9CDCFE\"> queryClient</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">invalidateQueries</span><span style=\"color:#E6E6E6\">({ </span><span style=\"color:#9CDCFE\">queryKey:</span><span style=\"color:#E6E6E6\"> [</span><span style=\"color:#CE9178\">&#x27;tasks&#x27;</span><span style=\"color:#E6E6E6\">] })</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">useMutation</span><span style=\"color:#E6E6E6\">({ </span><span style=\"color:#9CDCFE\">mutationFn:</span><span style=\"color:#9CDCFE\"> createTask</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">onSuccess:</span><span style=\"color:#9CDCFE\"> invalidate</span><span style=\"color:#E6E6E6\"> })</span></span></code></div></pre></figure>\n<p>Nothing wrong with this. The round-trip is fine because the human is already\nwaiting on it. The mistake is assuming this is <em>the</em> pattern for mutations rather\nthan <em>one</em> pattern, suited to one kind of interaction.</p>\n<h2>Drag-and-drop breaks the reflex</h2>\n<p>Drag is the opposite of a form. It&#x27;s fast, it&#x27;s optimistic (the card must move\n<em>now</em>, not after a round-trip), and you can fire several before any of them\nsettle. Drag card A to Done, then immediately drag card B to In Progress, then nudge\nA up a slot. Three mutations in flight at once.</p>\n<p>With &quot;invalidate on settle&quot; the board started <strong>thrashing</strong> — cards visibly\nsnapping back to where they&#x27;d been a moment ago, then forward again. Two distinct\nbugs were hiding in there.</p>\n<h3>Bug 1: concurrent invalidation thrash</h3>\n<p><code>invalidateQueries</code> doesn&#x27;t just mark data stale — it forces an <strong>immediate\nrefetch</strong> of any active query. (If you pause your polling with <code>refetchInterval</code>,\nnote that invalidation ignores it.) So with two drags in flight:</p>\n<ol>\n<li>Drag A optimistically moves the board to state <code>n</code>.</li>\n<li>Drag B optimistically moves it to <code>n+1</code>.</li>\n<li><strong>Drag A settles first → invalidate → a GET fires while B&#x27;s write hasn&#x27;t\ncommitted yet</strong> → the server returns the whole board at state <code>n</code> (has A, not B).</li>\n<li>That whole-board response replaces the cache, wiping B&#x27;s move. Snap-back.</li>\n</ol>\n<p>A list GET is a <em>global replace</em> — a stale snapshot clobbers changes it knows\nnothing about. The fix isn&#x27;t to stop invalidating; it&#x27;s to <strong>only invalidate after\nthe last mutation in the burst settles</strong> (more on the exact check below).</p>\n<h3>Bug 2: the same-card out-of-order race</h3>\n<p>The subtler one. If you reconcile by writing the <strong>server response</strong> into the cache\nin <code>onSuccess</code>, two mutations on the <em>same</em> card can resolve out of order:</p>\n<div class=\"not-prose my-8 flex justify-center overflow-x-auto [&amp;_svg]:h-auto [&amp;_svg]:max-w-full\"></div>\n<p>A per-id <code>onSuccess</code> patch is perfectly safe across <em>different</em> cards (each touches\nits own row). It&#x27;s only the <em>same</em> card, mutated twice, that bites. And it bites\nprecisely because we&#x27;re treating the response as the source of truth.</p>\n<h2>The insight: the optimistic write is the truth</h2>\n<p>Here&#x27;s what unlocked it. For a drag, <strong>the value I&#x27;m optimistically writing is\nexactly what I&#x27;m persisting.</strong> I compute the new status and position on the client,\nwrite them to the cache, and send the very same thing to the server. The server\njust stores it and echoes it back. The response carries <em>no new information</em>.</p>\n<p>So I shouldn&#x27;t apply the response at all. The optimistic writes already happen in\ninteraction order (drag 1, then drag 2), so the latest one wins by construction.\nDrop <code>onSuccess</code> entirely and the same-card race disappears — there&#x27;s nothing left\nto land out of order.</p>\n<blockquote>\n<p>The exception that proves the rule: reconcile from the response only when the\nserver mints data the client didn&#x27;t have — a generated id, a server-computed\nfield. And even then, merge it <em>by id</em>; don&#x27;t replace the list.</p>\n</blockquote>\n<h2>The rules I landed on</h2>\n<p>Putting it together, an optimistic drag mutation looks like this:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> KEY</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#E6E6E6\"> [</span><span style=\"color:#CE9178\">&#x27;tasks&#x27;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#CE9178\">&#x27;status-move&#x27;</span><span style=\"color:#E6E6E6\">] </span><span style=\"color:#6A9955\">// shared across all the drag mutations</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">useMutation</span><span style=\"color:#E6E6E6\">({</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  mutationKey:</span><span style=\"color:#9CDCFE\"> KEY</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  mutationFn:</span><span style=\"color:#9CDCFE\"> moveTask</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">  onMutate</span><span style=\"color:#9CDCFE\">:</span><span style=\"color:#569CD6\"> async</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#9CDCFE\">vars</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">    // 1. cancel first — abort any in-flight GET so it can&#x27;t land on top</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">    //    of our optimistic write. THIS is what makes optimism safe, not invalidate.</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">    await</span><span style=\"color:#9CDCFE\"> queryClient</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">cancelQueries</span><span style=\"color:#E6E6E6\">({ </span><span style=\"color:#9CDCFE\">queryKey:</span><span style=\"color:#E6E6E6\"> [</span><span style=\"color:#CE9178\">&#x27;tasks&#x27;</span><span style=\"color:#E6E6E6\">] })</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">    // 2. write the new state immediately</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    queryClient</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">setQueryData</span><span style=\"color:#E6E6E6\">([</span><span style=\"color:#CE9178\">&#x27;tasks&#x27;</span><span style=\"color:#E6E6E6\">], (</span><span style=\"color:#9CDCFE\">old</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#DCDCAA\"> applyMove</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">old</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">vars</span><span style=\"color:#E6E6E6\">))</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">    // 3. no snapshot — see onSettled</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  },</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">  // no onSuccess: the optimistic write is authoritative</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">  onSettled</span><span style=\"color:#9CDCFE\">:</span><span style=\"color:#E6E6E6\"> () </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#DCDCAA\"> invalidateIfLastMove</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">queryClient</span><span style=\"color:#E6E6E6\">),</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">})</span></span></code></div></pre></figure>\n<p>And the gated invalidate:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#569CD6\">function</span><span style=\"color:#DCDCAA\"> invalidateIfLastMove</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">qc</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#4EC9B0\"> QueryClient</span><span style=\"color:#E6E6E6\">) {</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">  // imperative isMutating() — NOT the useIsMutating() hook (stale closure)</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  if</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#9CDCFE\">qc</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">isMutating</span><span style=\"color:#E6E6E6\">({ </span><span style=\"color:#9CDCFE\">mutationKey:</span><span style=\"color:#E6E6E6\"> [</span><span style=\"color:#CE9178\">&#x27;tasks&#x27;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#CE9178\">&#x27;status-move&#x27;</span><span style=\"color:#E6E6E6\">] }) </span><span style=\"color:#D4D4D4\">===</span><span style=\"color:#B5CEA8\"> 1</span><span style=\"color:#E6E6E6\">) {</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">    void</span><span style=\"color:#9CDCFE\"> qc</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">invalidateQueries</span><span style=\"color:#E6E6E6\">({ </span><span style=\"color:#9CDCFE\">queryKey:</span><span style=\"color:#E6E6E6\"> [</span><span style=\"color:#CE9178\">&#x27;tasks&#x27;</span><span style=\"color:#E6E6E6\">] })</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  }</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span></code></div></pre></figure>\n<p>A few things worth calling out:</p>\n<ul>\n<li><strong><code>cancelQueries</code> in <code>onMutate</code> is the load-bearing call.</strong> It&#x27;s not the\ninvalidate that makes optimistic updates safe — it&#x27;s cancelling the in-flight\nread so a poll or earlier fetch can&#x27;t overwrite what you just wrote.</li>\n<li><strong><code>=== 1</code>, not <code>=== 0</code>.</strong> When <code>onSettled</code> runs, <em>this</em> mutation is still counted\nas pending — query-core dispatches <code>success</code>/<code>error</code> only <em>after</em> the <code>onSettled</code>\ncallbacks finish. So <code>1</code> means &quot;I&#x27;m the last one standing,&quot; which is exactly when\nit&#x27;s safe to refetch. I only believed this after reading the source.</li>\n<li><strong><code>onSettled</code> runs on success <em>and</em> error.</strong> So a failed drag&#x27;s wrong optimistic\nvalue self-heals on the same refetch. No manual rollback.</li>\n<li><strong>That&#x27;s why I don&#x27;t roll back from a saved snapshot.</strong> Under concurrency,\nrestoring a snapshot taken at <em>this</em> mutation&#x27;s <code>onMutate</code> can clobber a <em>later</em>\nsuccessful drag of the same card. A refetch gives true current state; a snapshot\ngives a stale guess.</li>\n<li><strong>Pause polling while moving.</strong> Gate <code>refetchInterval</code> on <code>useIsMutating(...) &gt; 0</code> —\n<code>false</code> while a move is in flight, your normal interval otherwise. The\n<code>useIsMutating</code> <em>hook</em> is right here (render-time, wants reactivity); the\nimperative <code>qc.isMutating()</code> is right inside the callback.</li>\n</ul>\n<p>The single post-burst refetch isn&#x27;t wasted, either — it catches <strong>server-side side\neffects</strong> the response wouldn&#x27;t include. Closing a blocker can flip its dependents\nfrom Blocked to Ready in other columns; the gated reconcile picks that up.</p>\n<h2>Where each concern belongs</h2>\n<p>Once the bugs were gone, the bigger lesson was about <em>placement</em>. A <code>useMutation</code>\nruns through a fixed sequence of handlers, and knowing the order tells you where\neach concern goes:</p>\n<div class=\"not-prose my-8 flex justify-center overflow-x-auto [&amp;_svg]:h-auto [&amp;_svg]:max-w-full\"></div>\n<p>Two things fall out of this order. By the time your <code>await</code> returns, the hook&#x27;s\n<code>onMutate</code> and <code>onSettled</code> have already run — so a success toast is purely\ncosmetic, fired after the reconcile is already kicked off. And because <code>onSettled</code>\nruns on both paths, the reconcile (and any cleanup) belongs there, not duplicated\nacross <code>onSuccess</code>/<code>onError</code>.</p>\n<h3>Keep the hook UI-agnostic; toast at the call site</h3>\n<p>This is the part I&#x27;d push hardest on. The hook should own <em>what changed</em> (cache and\nstate); the component should own <em>telling the user</em> (toasts, banners,\nnotifications). The way you get that separation is to call the mutation with\n<code>await mutateAsync(...)</code> and handle UX in the surrounding <code>try/catch</code>:</p>\n<div class=\"not-prose my-8 flex justify-center overflow-x-auto [&amp;_svg]:h-auto [&amp;_svg]:max-w-full\"></div>\n<p>Same hook, two call sites, completely different feedback. Because the hook never\nimports a toast library, it stays portable — you can lift it into another screen,\nor another app, and it just works. If the toast lived inside the hook, you&#x27;d be\nforking it the first time a second screen needed a different message.</p>\n<p><strong>Prefer <code>mutateAsync</code> over <code>mutate</code> for this.</strong> <code>await</code> gives you a linear flow —\nsuccess line, then a <code>catch</code> for the error — right where the component already has\nits notification API in scope. The one rule you can&#x27;t forget: <code>mutateAsync</code> rejects\non error, so it <strong>must</strong> be wrapped in <code>try/catch</code> (or a <code>.catch</code>), or you get an\nunhandled rejection. The hook&#x27;s <code>onSettled</code> still runs either way.</p>\n<blockquote>\n<p>When plain <code>mutate</code> is fine: an interaction whose only feedback is the optimistic\nchange itself, where errors self-heal via the gated refetch — there&#x27;s no toast to\nshow, and <code>mutate</code> can&#x27;t throw. Just don&#x27;t reach for <code>mutateAsync</code> and <em>then</em>\nignore the returned promise; that&#x27;s the worst of both.</p>\n</blockquote>\n<h2>A bonus from the key factory: exact vs. partial matching</h2>\n<p>All of this leans on query keys, and there&#x27;s one behaviour worth internalising.\nA hierarchical key factory:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> taskKeys</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  all:</span><span style=\"color:#E6E6E6\"> [</span><span style=\"color:#CE9178\">&#x27;tasks&#x27;</span><span style=\"color:#E6E6E6\">] </span><span style=\"color:#C586C0\">as</span><span style=\"color:#569CD6\"> const</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">  list</span><span style=\"color:#9CDCFE\">:</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#9CDCFE\">q</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#E6E6E6\"> [</span><span style=\"color:#CE9178\">&#x27;tasks&#x27;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#CE9178\">&#x27;list&#x27;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">q</span><span style=\"color:#E6E6E6\">] </span><span style=\"color:#C586C0\">as</span><span style=\"color:#569CD6\"> const</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">  detail</span><span style=\"color:#9CDCFE\">:</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#9CDCFE\">id</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#E6E6E6\"> [</span><span style=\"color:#CE9178\">&#x27;tasks&#x27;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#CE9178\">&#x27;detail&#x27;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">id</span><span style=\"color:#E6E6E6\">] </span><span style=\"color:#C586C0\">as</span><span style=\"color:#569CD6\"> const</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span></code></div></pre></figure>\n<p><code>invalidateQueries</code>, <code>cancelQueries</code>, and the <code>isMutating</code>/<code>isFetching</code> filters all\nmatch <strong>partially by default</strong> — a query matches if its key <em>starts with</em> the key\nyou pass. That prefix behaviour is the entire point of the factory: one key, many\nblast radii.</p>\n<table><thead><tr><th>You pass</th><th>Matches</th></tr></thead><tbody><tr><td><code>[&#x27;tasks&#x27;]</code></td><td>every tasks query — all boards <strong>and</strong> all details</td></tr><tr><td><code>[&#x27;tasks&#x27;, &#x27;list&#x27;]</code></td><td>all boards, any filter</td></tr><tr><td><code>taskKeys.list(q)</code></td><td>that one filtered board</td></tr><tr><td><code>[&#x27;tasks&#x27;, &#x27;detail&#x27;, id]</code></td><td>a single task</td></tr></tbody></table>\n<p>Two gotchas. Objects inside a key are matched <em>partially</em> too (a subset match, not\nby reference), so an under-specified object filter can hit more queries than you\nmeant. And when a prefix would catch siblings you want to leave alone, pin it with\n<code>{ exact: true }</code>. My rule of thumb: <strong>invalidate at the broadest key that&#x27;s still\ncorrect.</strong> A board move uses <code>[&#x27;tasks&#x27;]</code> deliberately, because a move can ripple\ninto other columns — the wide prefix is the <em>right</em> radius, and the gated call keeps\nit to a single refetch per burst.</p>\n<h2>The takeaway</h2>\n<p>&quot;Just invalidate&quot; is a fine default until interactions get fast and concurrent.\nThen the questions change: cancel the in-flight read first, decide whether the\noptimistic value or the response is the source of truth (for a drag, it&#x27;s the\noptimistic value), gate the reconcile to the last mutation in the burst, and let\nerrors heal by refetching rather than by restoring a stale snapshot. Keep all of\nthat in the hook, and keep the toast at the call site so the hook stays reusable.</p>\n<p>A checklist I now run before shipping any mutation:</p>\n<ul>\n<li>Form or fast-optimistic interaction? Picked the matching strategy?</li>\n<li><code>onMutate</code> does <code>cancelQueries</code> <strong>before</strong> <code>setQueryData</code>?</li>\n<li>If optimistic: no <code>onSuccess</code> reconcile (unless the server returns new data)?</li>\n<li>Invalidate gated with imperative <code>qc.isMutating(...) === 1</code>?</li>\n<li>Related mutations share a <code>mutationKey</code>, and polling pauses on it?</li>\n<li>No snapshot rollback relied on under concurrency?</li>\n<li>UI feedback at the call site via <code>await mutateAsync</code> in a <code>try/catch</code>, not in the hook?</li>\n</ul>\n<p>Much of this is distilled from TkDodo&#x27;s excellent\n<a href=\"https://tkdodo.eu/blog/concurrent-optimistic-updates-in-react-query\">Concurrent Optimistic Updates in React Query</a>\nand <a href=\"https://tkdodo.eu/blog/practical-react-query\">Practical React Query</a> — both\nworth reading in full.</p>",
            "url": "https://alexmchan.com/blog/2026-06-29-concurrent-optimistic-updates",
            "title": "Drag, drop, and the optimistic update race",
            "summary": "My usual React Query reflex — just invalidate to get the real deal — fell apart the moment I added drag-and-drop to a kanban board. Here's what concurrent optimistic updates actually require, and where each concern belongs.",
            "date_modified": "2026-06-29T00:00:00.000Z",
            "author": {
                "name": "Alex Chan"
            }
        },
        {
            "id": "https://alexmchan.com/blog/2026-03-07-architecture-decisions",
            "content_html": "<p>After years working with software, I&#x27;ve developed a set of principles that guide my architectural decision-making. These aren&#x27;t hard rules — they&#x27;re lenses to look through when faced with choices that will outlast the sprint they were made in. After each section I&#x27;ve added some questions to ask yourself or your llm.</p>\n<h2>Simplicity over complexity</h2>\n<p>Prefer simple APIs over complex ones. A smaller surface area and a more focused role make a system easier to understand, reason about, and eventually replace.</p>\n<blockquote>\n<p>Everything should be as simple as it can be, but not simpler\n― Einstein</p>\n</blockquote>\n<p>If something offers a larger surface area, stay in the simpler parts of the API rather than reaching for complex features — indexes in Postgres carry less lock-in than stored procedures, for instance. Prefer proxy APIs that allow for simpler switching later.</p>\n<blockquote>\n<p>Perfection is achieved not when there is nothing more to add, but when there is nothing more to remove.\n— Antoine de Saint-Exupéry</p>\n</blockquote>\n<p><a href=\"https://grugbrain.dev/\">Grug</a> puts it more bluntly:</p>\n<blockquote>\n<p>The eternal enemy: Complexity</p>\n<p>given choice between complexity or one on one against t-rex, grug take t-rex: at least grug see t-rex</p>\n</blockquote>\n<ul>\n<li>Can I explain this in an hour and master it in a day?</li>\n<li>Does this reduce complexity, or does it introduce new abstractions?</li>\n<li>Is the API surface area as small as possible?</li>\n</ul>\n<h2>There is no black and white — it&#x27;s a spectrum</h2>\n<p>Everything is on a spectrum, and &quot;correct&quot; can mean different things depending on your goals, timelines, and constraints. This is why it&#x27;s worth spending a little time upfront to save days or years of pain — and why writing down the <em>why</em> behind a decision matters, so you can re-evaluate it later. Understand that there&#x27;s sometimes different solutions for the short, medium and long term.</p>\n<ul>\n<li>What are the constraints — timeline, team size, risk tolerance?</li>\n<li>Have I written down the <em>why</em> so I can revisit this later?</li>\n<li>Am I optimising for now, or for longer-term maintainability?</li>\n</ul>\n<h2>Always compare</h2>\n<p>Starting from the best solutions in the space gives you the chance of landing on the right one.</p>\n<p>Make sure decisions are evaluated against the top competitors, and that you have clear criteria for why you&#x27;re choosing one thing over another. Check real-world reports of each in production use, and build a proof of concept — of the candidate <em>and</em> its competitors. Even a mental walkthrough of how an API or tool would be used in practice is more valuable than committing to a full implementation.</p>\n<p>Always explain why you recommend what you do and what you considered along the way.</p>\n<p>Note that some people skip this step -- some of it might come down to <a href=\"https://www.psychologytoday.com/ca/blog/science-choice/201506/satisficing-vs-maximizing\">satisficing vs. maximising</a> — we&#x27;d all be stuck forever trying to find the optimal solution. Still, it&#x27;s worth understanding why people make the choices they do and why we ourselves might skip it. LLMs are a good starting point if we ensure that they search for the most up to date information and not use only the knoweldge cutoff.</p>\n<ul>\n<li>Have I evaluated the top alternatives with clear criteria?</li>\n<li>Have I checked real-world reports and built (or mentally walked through) a POC of each?</li>\n<li>What are the gaps, and how did competitors solve them?</li>\n<li>Have people already converged on a standard here?</li>\n<li>Is there something I heard about lately or read about that seems better?</li>\n</ul>\n<h2>Prefer low vendor lock-in</h2>\n<p>Favour open standards, small API surface areas, and simple libraries over entirely proprietary solutions.</p>\n<p>Databases are <a href=\"https://boringtechnology.club/\">boring technology</a> — and that&#x27;s usually a good thing. MySQL, Postgres, and Redis (Valkey) are typically better choices than something like <a href=\"https://www.infoworld.com/article/3853569/fauna-to-shut-down-faunadb-service-in-may.html\">FaunaDB</a> unless there&#x27;s an order-of-magnitude performance gain or a meaningful speed-to-develop advantage and an easy migration off.</p>\n<ul>\n<li>Is this based on open standards?</li>\n<li>How hard would it be to swap this out in six months? A year?</li>\n<li>Is there a proxy or abstraction I can sit behind?</li>\n</ul>\n<h2>One-way vs. two-way decisions</h2>\n<p>Returning to simplicity and implementation cost: can this be walked back in a week, a month, a quarter, or a year? Can migration be automated, or does it require a dedicated team effort?</p>\n<p>If it&#x27;s a two-way decision, it&#x27;s safer to try it and back out with your learnings intact. If it&#x27;s a one-way decision, are you taking enough time to be happy living with it for much longer?</p>\n<p><a href=\"https://www.youtube.com/watch?v=rxsdOQa_QkM\">This talk</a> covers the idea of reversible vs. irreversible decisions well — worth the watch.</p>\n<ul>\n<li>Can this be reversed in a week / month / year?</li>\n<li>Can migration be automated, or does it need a dedicated team?</li>\n<li>Am I treating this as reversible when it actually isn&#x27;t?</li>\n</ul>\n<h2>The &quot;do nothing&quot; option is always an option</h2>\n<p>Choosing none of the above is always valid — but be honest about its costs and trade-offs. Sometimes thinking through the &quot;do nothing&quot; path is exactly what helps clarify the <em>why</em> behind whichever direction you eventually choose.</p>\n<ul>\n<li>What is the cost of waiting or choosing nothing?</li>\n<li>Does thinking through &quot;do nothing&quot; sharpen the case for the chosen direction?</li>\n</ul>\n<h2>Supported and maintained</h2>\n<p>Weight more toward projects whose maintainers have a long history of support. In the absence of that, prefer open-source projects or APIs close enough to a competitor that switching remains feasible. Prefer funded, profitable ventures over brand-new ones, and consider whether self-hosting is possible if needed.</p>\n<p>All else being equal, the larger, more established community will usually be the safer bet.</p>\n<div class=\"grid place-content-center\"><a href=\"https://xkcd.com/2347/\" target=\"_blank\" rel=\"noopener noreferrer\"><img alt=\"XKCD 2347: Dependency — modern digital infrastructure balanced precariously on a single unmaintained project\" loading=\"lazy\" width=\"370\" height=\"275.5\" decoding=\"async\" data-nimg=\"1\" style=\"color:transparent\" srcSet=\"/_next/image?url=%2F_next%2Fstatic%2Fimmutable%2Fmedia%2Fdependency_2x.3-ro_8pmtd3fz.png&amp;w=384&amp;q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fimmutable%2Fmedia%2Fdependency_2x.3-ro_8pmtd3fz.png&amp;w=750&amp;q=75 2x\" src=\"/_next/image?url=%2F_next%2Fstatic%2Fimmutable%2Fmedia%2Fdependency_2x.3-ro_8pmtd3fz.png&amp;w=750&amp;q=75\"/></a></div>\n<ul>\n<li>Does the maintainer have a track record of long-term support?</li>\n<li>Is this funded and profitable, or brand-new?</li>\n<li>Could I self-host if needed?</li>\n</ul>\n<h2>Do try this at home</h2>\n<p>One genuine exception to the &quot;boring tech&quot; guideline is personal, low-impact projects, or intentionally cutting-edge experiments. Testing bleeding-edge ideas in a low-risk environment is one of the best ways to learn — you hit real-world problems while picking up new ways of thinking, and those learnings carry back into the main work.</p>\n<ul>\n<li>Did it work?</li>\n<li>Was it fun?</li>\n<li>What part of the API or technology was best?</li>\n</ul>\n<h2>Consider total cost of ownership and ROI</h2>\n<p>Development and support time, onboarding speed, LLM familiarity (is it in the training data, or does context cost thousands of tokens?), documentation quality, build-vs-buy trade-offs, upgrade burden, security, testability, observability, tooling integration, and usability all factor into total cost of ownership.</p>\n<p>What are the uptime risks? How proven is this, and are there real production adopters?</p>\n<p>ROI matters too. Spending $20 to save $100 is making money — if your estimates are correct. Measure before you adopt and monitor after to validate your assumptions. Spending money isn&#x27;t wasting money if the alternative is burning time or compounding technical debt.</p>\n<ul>\n<li>What are the ongoing support, upgrade, and security costs?</li>\n<li>How fast can new people get up to speed?</li>\n<li>Does the spending save more than it costs? Have I measured before and will I monitor after?</li>\n<li>What are the uptime risks, and are there real production adopters?</li>\n</ul>\n<h2>When is the crowd wrong, and why?</h2>\n<p>Usually when there&#x27;s pure hype without substance — the tool hasn&#x27;t passed the tests above, or it has the wrong abstraction for your context. It&#x27;s also possible that someone hasn&#x27;t measured the right things, or has weighted them differently than you would.</p>\n<p>Actively seek out dissenting opinions to stress-test your assumptions. <a href=\"https://randsinrepose.com/archives/incrementalists-completionists/\">Sometimes two people can both be right</a> about the same thing — it&#x27;s about weighing the trade-offs and planning for the risks. Look for the most critical takes and ask honestly whether you can live with those trade-offs.</p>\n<p>When is the crowd right? Sometimes they actually are, so cross-check whether you&#x27;ve missed anything. Critical mass and community knowledge have real value: shared knowledge is searchable, baked into LLMs, and means more people have already faced — and solved — the problems you&#x27;re about to hit.</p>\n<ul>\n<li>Is this hype, or has it passed the tests above?</li>\n<li>Have I sought out dissenting opinions?</li>\n<li>Can I live with the criticisms?</li>\n</ul>\n<h2>Evaluate critically after the fact</h2>\n<p>Check whether your assumptions held as you move forward. Watch for friction, and don&#x27;t be afraid to choose a different path — but try to converge on a single solution rather than maintaining multiple approaches in parallel. The principles of one-way vs. two-way decisions and simple APIs should support you here. Does it actually speed things up? Keep a decision registry and look back to understand when you were right or wrong and more importantly why.</p>\n<div class=\"grid place-content-center\"><a href=\"https://xkcd.com/927/\" target=\"_blank\" rel=\"noopener noreferrer\"><img alt=\"XKCD 927: Standards — how competing standards proliferate\" loading=\"lazy\" width=\"500\" height=\"283\" decoding=\"async\" data-nimg=\"1\" style=\"color:transparent\" srcSet=\"/_next/image?url=%2F_next%2Fstatic%2Fimmutable%2Fmedia%2Fstandards_2x.18wm0wftiwc1z.png&amp;w=640&amp;q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fimmutable%2Fmedia%2Fstandards_2x.18wm0wftiwc1z.png&amp;w=1080&amp;q=75 2x\" src=\"/_next/image?url=%2F_next%2Fstatic%2Fimmutable%2Fmedia%2Fstandards_2x.18wm0wftiwc1z.png&amp;w=1080&amp;q=75\"/></a></div>\n<ul>\n<li>Were my assumptions correct and what could I do better next time?</li>\n<li>Where is there friction, and what is it telling me?</li>\n<li>Should I consolidate to a single solution?</li>\n</ul>\n<h2>Architectural decision records</h2>\n<p>One way to ensure that these decisions are revisited is to create an architectural decision record. This is a document that outlines the decision, the context, the criteria, the options, and the reasoning behind the decision. Make sure there&#x27;s a recommendation even on first draft so that people reviewing know what your initial thoughts are and then the discussion can begin. When people differ on a given point that&#x27;s when the interesting discussions happen. The thing to remember is that people are basing their decisions on both a different past and team/timeframe/constraints and the future may have different estimates when people are basing things from feelings or even estimates. ADRs are a whole topic on their own but the main thing is to create one, even if it&#x27;s just for yourself to refer to later.</p>\n<h2>Conclusion</h2>\n<blockquote>\n<p>“When the facts change, I change my mind - what do you do, sir?”\n― John Maynard Keynes</p>\n</blockquote>\n<p>Good architectural decisions help narrow down the range of possible solutions in an unpredictable world. Ideally they balance risk, cost and reward but ultimately they&#x27;re about being intentional, comparing honestly, and setting yourself up to either change course or commit with confidence. Know what you&#x27;re optimising for, write down the why, and revisit when the world changes. Good luck!</p>\n<p>Do you agree or disagree on some points? Why? What things do you value and why? Hopefully this helps you think about your own design principles.</p>",
            "url": "https://alexmchan.com/blog/2026-03-07-architecture-decisions",
            "title": "Architectural Decisions",
            "summary": "Architectural decisions shape codebases for years. Here are the principles I've developed over 25 years to make better ones — covering simplicity, vendor lock-in, reversibility, and total cost of ownership.",
            "date_modified": "2026-03-07T00:00:00.000Z",
            "author": {
                "name": "Alex Chan"
            }
        },
        {
            "id": "https://alexmchan.com/blog/2026-02-25-submit-button-outside-form",
            "content_html": "<h2>The problem</h2>\n<p>Modal and dialog layouts usually look something like this:</p>\n<pre class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code>┌─────────────────────────────────┐\n│  Header                         │\n├─────────────────────────────────┤\n│  Content (scrollable)           │\n│    &lt;YourForm /&gt;                 │\n├─────────────────────────────────┤\n│  Footer                         │\n│    [Cancel]  [Save Changes]     │\n└─────────────────────────────────┘\n</code></div></pre>\n<p>The <code>&lt;form&gt;</code> lives in the scrollable content area. The Save button lives in the footer — which is a <strong>sibling</strong> of the content in the DOM, not a descendant of the form. So <code>type=&quot;submit&quot;</code> won&#x27;t trigger it.</p>\n<p>The usual workarounds are either ugly (call <code>form.submit()</code> imperatively, or lift state up) or they force you to restructure your JSX in awkward ways just to get the button inside the form.</p>\n<h2>The native fix: the <code>form</code> attribute</h2>\n<p>HTML has had a solution for this forever. Any <code>&lt;button&gt;</code>, <code>&lt;input&gt;</code>, <code>&lt;select&gt;</code>, or <code>&lt;textarea&gt;</code> can be associated with a form anywhere on the page using the <code>form</code> attribute — just point it at the form&#x27;s <code>id</code>:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"html\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"html\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#808080\">&lt;</span><span style=\"color:#569CD6\">form</span><span style=\"color:#9CDCFE\"> id</span><span style=\"color:#E6E6E6\">=</span><span style=\"color:#CE9178\">&quot;my-form&quot;</span><span style=\"color:#9CDCFE\"> onsubmit</span><span style=\"color:#E6E6E6\">=</span><span style=\"color:#CE9178\">&quot;</span><span style=\"color:#D4D4D4\">...</span><span style=\"color:#CE9178\">&quot;</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">  &lt;!-- fields --&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">&lt;/</span><span style=\"color:#569CD6\">form</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">&lt;!-- completely elsewhere in the DOM --&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">&lt;</span><span style=\"color:#569CD6\">button</span><span style=\"color:#9CDCFE\"> type</span><span style=\"color:#E6E6E6\">=</span><span style=\"color:#CE9178\">&quot;submit&quot;</span><span style=\"color:#9CDCFE\"> form</span><span style=\"color:#E6E6E6\">=</span><span style=\"color:#CE9178\">&quot;my-form&quot;</span><span style=\"color:#808080\">&gt;</span><span style=\"color:#E6E6E6\">Save</span><span style=\"color:#808080\">&lt;/</span><span style=\"color:#569CD6\">button</span><span style=\"color:#808080\">&gt;</span></span></code></div></pre></figure>\n<p>Clicking that button behaves exactly as if it were inside the form. The browser handles it natively.</p>\n<h2>With React Hook Form</h2>\n<p>This plays perfectly with RHF. Give your form an <code>id</code>, put that same id on the external button, and everything — validation, error messages, the <code>handleSubmit</code> wrapper — works as normal:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#569CD6\">function</span><span style=\"color:#DCDCAA\"> MyForm</span><span style=\"color:#E6E6E6\">({ </span><span style=\"color:#9CDCFE\">id</span><span style=\"color:#E6E6E6\"> }</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">id</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#4EC9B0\"> string</span><span style=\"color:#E6E6E6\"> }) {</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">  const</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">register</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">handleSubmit</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">formState</span><span style=\"color:#E6E6E6\">: { </span><span style=\"color:#9CDCFE\">errors</span><span style=\"color:#E6E6E6\"> } } </span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#DCDCAA\"> useForm</span><span style=\"color:#E6E6E6\">&lt;</span><span style=\"color:#4EC9B0\">Fields</span><span style=\"color:#E6E6E6\">&gt;({</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    resolver:</span><span style=\"color:#DCDCAA\"> zodResolver</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">schema</span><span style=\"color:#E6E6E6\">),</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  });</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  return</span><span style=\"color:#E6E6E6\"> (</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">    &lt;</span><span style=\"color:#569CD6\">form</span><span style=\"color:#9CDCFE\"> id</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#569CD6\">{</span><span style=\"color:#9CDCFE\">id</span><span style=\"color:#569CD6\">}</span><span style=\"color:#9CDCFE\"> onSubmit</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#569CD6\">{</span><span style=\"color:#DCDCAA\">handleSubmit</span><span style=\"color:#D4D4D4\">(</span><span style=\"color:#9CDCFE\">onSubmit</span><span style=\"color:#D4D4D4\">)</span><span style=\"color:#569CD6\">}</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">      &lt;</span><span style=\"color:#569CD6\">input</span><span style=\"color:#569CD6\"> {</span><span style=\"color:#D4D4D4\">...</span><span style=\"color:#DCDCAA\">register</span><span style=\"color:#D4D4D4\">(</span><span style=\"color:#CE9178\">&#x27;name&#x27;</span><span style=\"color:#D4D4D4\">)</span><span style=\"color:#569CD6\">}</span><span style=\"color:#808080\"> /&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">      {</span><span style=\"color:#9CDCFE\">errors</span><span style=\"color:#D4D4D4\">.</span><span style=\"color:#9CDCFE\">name</span><span style=\"color:#D4D4D4\"> &amp;&amp; </span><span style=\"color:#808080\">&lt;</span><span style=\"color:#569CD6\">p</span><span style=\"color:#808080\">&gt;</span><span style=\"color:#569CD6\">{</span><span style=\"color:#9CDCFE\">errors</span><span style=\"color:#D4D4D4\">.</span><span style=\"color:#9CDCFE\">name</span><span style=\"color:#D4D4D4\">.</span><span style=\"color:#9CDCFE\">message</span><span style=\"color:#569CD6\">}</span><span style=\"color:#808080\">&lt;/</span><span style=\"color:#569CD6\">p</span><span style=\"color:#808080\">&gt;</span><span style=\"color:#569CD6\">}</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">    &lt;/</span><span style=\"color:#569CD6\">form</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  );</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">function</span><span style=\"color:#DCDCAA\"> MyModal</span><span style=\"color:#E6E6E6\">({ </span><span style=\"color:#9CDCFE\">onClose</span><span style=\"color:#E6E6E6\"> }</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#DCDCAA\">onClose</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#E6E6E6\"> () </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#4EC9B0\"> void</span><span style=\"color:#E6E6E6\"> }) {</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">  const</span><span style=\"color:#9CDCFE\"> formId</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> useId</span><span style=\"color:#E6E6E6\">(); </span><span style=\"color:#6A9955\">// stable unique ID — safe with SSR</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  return</span><span style=\"color:#E6E6E6\"> (</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">    &lt;</span><span style=\"color:#569CD6\">div</span><span style=\"color:#9CDCFE\"> className</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#CE9178\">&quot;modal&quot;</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">      &lt;</span><span style=\"color:#569CD6\">div</span><span style=\"color:#9CDCFE\"> className</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#CE9178\">&quot;modal-content&quot;</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">        &lt;</span><span style=\"color:#4EC9B0\">MyForm</span><span style=\"color:#9CDCFE\"> id</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#569CD6\">{</span><span style=\"color:#9CDCFE\">formId</span><span style=\"color:#569CD6\">}</span><span style=\"color:#808080\"> /&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">      &lt;/</span><span style=\"color:#569CD6\">div</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">      &lt;</span><span style=\"color:#569CD6\">div</span><span style=\"color:#9CDCFE\"> className</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#CE9178\">&quot;modal-footer&quot;</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">        &lt;</span><span style=\"color:#569CD6\">button</span><span style=\"color:#9CDCFE\"> type</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#CE9178\">&quot;button&quot;</span><span style=\"color:#9CDCFE\"> onClick</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#569CD6\">{</span><span style=\"color:#9CDCFE\">onClose</span><span style=\"color:#569CD6\">}</span><span style=\"color:#808080\">&gt;</span><span style=\"color:#E6E6E6\">Cancel</span><span style=\"color:#808080\">&lt;/</span><span style=\"color:#569CD6\">button</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">        &lt;</span><span style=\"color:#569CD6\">button</span><span style=\"color:#9CDCFE\"> type</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#CE9178\">&quot;submit&quot;</span><span style=\"color:#9CDCFE\"> form</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#569CD6\">{</span><span style=\"color:#9CDCFE\">formId</span><span style=\"color:#569CD6\">}</span><span style=\"color:#808080\">&gt;</span><span style=\"color:#E6E6E6\">Save Changes</span><span style=\"color:#808080\">&lt;/</span><span style=\"color:#569CD6\">button</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">      &lt;/</span><span style=\"color:#569CD6\">div</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">    &lt;/</span><span style=\"color:#569CD6\">div</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  );</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span></code></div></pre></figure>\n<p>The footer button triggers RHF&#x27;s <code>handleSubmit</code>, which validates first and only calls <code>onSubmit</code> if everything passes. No imperative refs, no lifted state.</p>\n<h2>Use <code>useId()</code> for the ID</h2>\n<p>React 18&#x27;s <a href=\"https://react.dev/reference/react/useId\"><code>useId</code></a> is the right tool here. It generates a stable ID that&#x27;s unique per component instance, works with SSR, and avoids collisions if the modal can be mounted more than once simultaneously.</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> formId</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> useId</span><span style=\"color:#E6E6E6\">();</span></span></code></div></pre></figure>\n<p>Don&#x27;t just hardcode <code>id=&quot;my-form&quot;</code> unless you&#x27;re certain only one instance will ever exist.</p>\n<h2>Stub forms for non-form tabs</h2>\n<p>One other pattern worth knowing: if you have a multi-tab modal with a single Save button in the footer, some tabs might not have a form at all (e.g. a read-only info tab). You can render a stub form with the same ID that just calls <code>onClose</code>:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#6A9955\">// Read-only tab — no real form, but footer button still needs to work</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">&lt;</span><span style=\"color:#569CD6\">form</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  id</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#569CD6\">{</span><span style=\"color:#9CDCFE\">formId</span><span style=\"color:#569CD6\">}</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  onSubmit</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#569CD6\">{</span><span style=\"color:#D4D4D4\">(</span><span style=\"color:#9CDCFE\">e</span><span style=\"color:#D4D4D4\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#D4D4D4\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    e</span><span style=\"color:#D4D4D4\">.</span><span style=\"color:#DCDCAA\">preventDefault</span><span style=\"color:#D4D4D4\">();</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">    onClose</span><span style=\"color:#D4D4D4\">();</span></span>\n<span data-line=\"\"><span style=\"color:#D4D4D4\">  }</span><span style=\"color:#569CD6\">}</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">/&gt;</span></span></code></div></pre></figure>\n<p>This keeps the footer button consistent across all tabs without special-casing the click handler.</p>\n<h2>References</h2>\n<ul>\n<li><a href=\"https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#form\">MDN: <code>&lt;button&gt;</code> form attribute</a></li>\n<li><a href=\"https://react-hook-form.com/docs/useform/handlesubmit\">React Hook Form: <code>handleSubmit</code></a></li>\n<li><a href=\"https://react.dev/reference/react/useId\">React: <code>useId</code></a></li>\n</ul>",
            "url": "https://alexmchan.com/blog/2026-02-25-submit-button-outside-form",
            "title": "Submitting a form with a button that lives outside it",
            "summary": "A quick trick using the HTML form attribute to wire up a submit button in a modal footer to a React Hook Form that's rendered elsewhere in the DOM.",
            "date_modified": "2026-02-25T00:00:00.000Z",
            "author": {
                "name": "Alex Chan"
            }
        },
        {
            "id": "https://alexmchan.com/blog/2026-01-25-updated-cli",
            "content_html": "<h1>Modern CLI Tips for LLMs (and Curious Humans)</h1>\n<p>Let&#x27;s face it: you&#x27;re probably not going to read this. Just point your LLM here and let it work its magic. If you&#x27;re curious about what it&#x27;s going to do, read ahead.</p>\n<p><strong>TL;DR for humans</strong>: The fastest way to build a CLI in 2026 isn&#x27;t learning all the libraries—it&#x27;s teaching an LLM your stack preferences once. This guide is optimized for LLM consumption but works for humans too.</p>\n<h2>Claude Code CLI Skill</h2>\n<p>If you&#x27;re using <a href=\"https://claude.com/claude-code\">Claude Code</a>, you can install the <a href=\"https://github.com/alexanderchan/cli-skill\">CLI skill</a> to get all these best practices automatically:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"bash\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"bash\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#DCDCAA\">npx</span><span style=\"color:#CE9178\"> skills</span><span style=\"color:#CE9178\"> add</span><span style=\"color:#CE9178\"> alexanderchan/cli-skill</span></span></code></div></pre></figure>\n<p>Then just use <code>/cli</code> and describe what you want to build. The skill encapsulates all the patterns from this post into a reusable prompt template.</p>\n<h2>The Modern Stack</h2>\n<p>After years of iteration, here&#x27;s an iteration for a modern TypeScript CLI:</p>\n<ul>\n<li><strong>@commander-js/extra-typings</strong> - Command parsing with <em>inferred</em> TypeScript types (better than plain <code>commander</code>)</li>\n<li><strong>@clack/prompts</strong> - Beautiful, modern interactive prompts (replaces the older <code>prompts</code> or <code>@inquirer/prompts</code> library)</li>\n<li><strong>zx</strong> - Shell command execution that actually works like shell scripts</li>\n<li><strong>TypeScript</strong> - Just use real TypeScript, not JSDoc</li>\n</ul>\n<h2>Why Not Shell Scripts?</h2>\n<p>You might ask: &quot;Why not just write a bash script?&quot; Fair question. Here&#x27;s why TypeScript or Go is often better:</p>\n<p><strong>Maintainability</strong>: Shell scripts become unreadable quickly. Complex logic with proper error handling, JSON parsing, and conditionals turns into a mess of <code>[[</code>, <code>$(())</code>, and cryptic syntax.</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"bash\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"bash\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#6A9955\"># Shell script complexity escalates fast</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">if</span><span style=\"color:#E6E6E6\"> [[ </span><span style=\"color:#D4D4D4\">-f</span><span style=\"color:#CE9178\"> &quot;</span><span style=\"color:#9CDCFE\">$CONFIG_FILE</span><span style=\"color:#CE9178\">&quot;</span><span style=\"color:#E6E6E6\"> ]]; </span><span style=\"color:#C586C0\">then</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  VALUE</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#E6E6E6\">$(</span><span style=\"color:#DCDCAA\">cat</span><span style=\"color:#CE9178\"> &quot;</span><span style=\"color:#9CDCFE\">$CONFIG_FILE</span><span style=\"color:#CE9178\">&quot;</span><span style=\"color:#D4D4D4\"> |</span><span style=\"color:#DCDCAA\"> jq</span><span style=\"color:#CE9178\"> -r</span><span style=\"color:#CE9178\"> &#x27;.key // empty&#x27;</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  if</span><span style=\"color:#E6E6E6\"> [[ </span><span style=\"color:#D4D4D4\">-z</span><span style=\"color:#CE9178\"> &quot;</span><span style=\"color:#9CDCFE\">$VALUE</span><span style=\"color:#CE9178\">&quot;</span><span style=\"color:#E6E6E6\"> ]]; </span><span style=\"color:#C586C0\">then</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">    echo</span><span style=\"color:#CE9178\"> &quot;Error: key not found&quot;</span><span style=\"color:#D4D4D4\"> &gt;&amp;2</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">    exit</span><span style=\"color:#B5CEA8\"> 1</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  fi</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">fi</span></span></code></div></pre></figure>\n<p>vs TypeScript:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"typescript\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"typescript\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> config</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#9CDCFE\"> JSON</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">parse</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#C586C0\">await</span><span style=\"color:#9CDCFE\"> fs</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">readFile</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">configFile</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#CE9178\">&#x27;utf-8&#x27;</span><span style=\"color:#E6E6E6\">))</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">if</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#D4D4D4\">!</span><span style=\"color:#9CDCFE\">config</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">key</span><span style=\"color:#E6E6E6\">) {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  console</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">error</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;Error: key not found&#x27;</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  process</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">exit</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#B5CEA8\">1</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span></code></div></pre></figure>\n<p><strong>LLM accuracy</strong>: Modern LLMs are <em>significantly</em> better at TypeScript and Go than bash. They&#x27;ll generate correct error handling, proper quoting, and safe variable expansion in TypeScript/Go. With bash, they often produce scripts that work in simple cases but fail with edge cases (spaces in filenames, special characters, etc.).</p>\n<p><strong>Portability</strong>: Shell scripts are less portable than you think:</p>\n<ul>\n<li>Bash vs sh vs zsh differences</li>\n<li>macOS vs Linux command differences (<code>sed</code>, <code>date</code>, <code>find</code> all have different flags)</li>\n<li>Windows requires WSL or Git Bash</li>\n<li>TypeScript runs anywhere Node.js runs</li>\n<li>Go compiles to a single binary for any platform</li>\n</ul>\n<p><strong>Type safety and tooling</strong>: You get autocomplete, type checking, and refactoring support. Shell scripts have none of this.</p>\n<p><strong>When shell scripts ARE the right choice</strong>:</p>\n<ul>\n<li>Very simple glue code (&lt; 20 lines)</li>\n<li>System administration tasks that heavily use pipes and standard Unix tools</li>\n<li>When you need to run on systems without Node.js/Go and can&#x27;t distribute binaries</li>\n</ul>\n<p>For everything else, use TypeScript with <code>zx</code> (which gives you shell-like syntax with TypeScript safety) or Go for production tools.</p>\n<h2>The LLM Prompt Pattern</h2>\n<p>Instead of memorizing APIs, give your LLM this context:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"md\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"md\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#E6E6E6\">Using @commander-js/extra-typings, @clack/prompts, and zx</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">build a cli that does the following...</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">[describe what you want the CLI to do]</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">example header:</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">#!/usr/bin/env tsx</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">import { Command } from &#x27;@commander-js/extra-typings&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">import * as p from &#x27;@clack/prompts&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">import { $ } from &#x27;zx&#x27;</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">$.verbose = true</span></span></code></div></pre></figure>\n<p>That&#x27;s it. Modern LLMs know these libraries well and will generate working code.</p>\n<h2>Why These Libraries?</h2>\n<h3>@commander-js/extra-typings over plain commander</h3>\n<p>Plain <code>commander</code> has TypeScript types, but they&#x27;re generic. The <code>extra-typings</code> package adds <em>inferred</em> types based on your actual option and argument definitions:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"typescript\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"typescript\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#6A9955\">// With plain commander - opts is generic</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">Command</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;commander&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> program</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#569CD6\"> new</span><span style=\"color:#DCDCAA\"> Command</span><span style=\"color:#E6E6E6\">()</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  .</span><span style=\"color:#DCDCAA\">option</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;-p, --port &lt;number&gt;&#x27;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#CE9178\">&#x27;port number&#x27;</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  .</span><span style=\"color:#DCDCAA\">action</span><span style=\"color:#E6E6E6\">((</span><span style=\"color:#9CDCFE\">opts</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">    // opts is just Record&lt;string, any&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    opts</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">port</span><span style=\"color:#6A9955\"> // no autocomplete ❌</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  })</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// With extra-typings - opts is inferred</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">Command</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;@commander-js/extra-typings&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> program</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#569CD6\"> new</span><span style=\"color:#DCDCAA\"> Command</span><span style=\"color:#E6E6E6\">()</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  .</span><span style=\"color:#DCDCAA\">option</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;-p, --port &lt;number&gt;&#x27;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#CE9178\">&#x27;port number&#x27;</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  .</span><span style=\"color:#DCDCAA\">action</span><span style=\"color:#E6E6E6\">((</span><span style=\"color:#9CDCFE\">opts</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">    // opts.port is automatically typed! ✅</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    opts</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">port</span><span style=\"color:#6A9955\"> // full autocomplete</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  })</span></span></code></div></pre></figure>\n<p>The types build up as you chain methods, giving you full IntelliSense.</p>\n<h3>@clack/prompts over prompts</h3>\n<p>The older <code>prompts</code> library hasn&#x27;t been updated in 4 years. <code>@clack/prompts</code> is actively maintained and offers:</p>\n<ul>\n<li>Better default styling out of the box</li>\n<li>Built-in intro/outro for session framing</li>\n<li>Better TypeScript support</li>\n<li>More prompt types (autocomplete, grouped prompts)</li>\n<li>Used by major projects (Svelte, shadcn, etc.)</li>\n</ul>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"typescript\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"typescript\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#569CD6\"> *</span><span style=\"color:#C586C0\"> as</span><span style=\"color:#9CDCFE\"> p</span><span style=\"color:#C586C0\"> from</span><span style=\"color:#CE9178\"> &#x27;@clack/prompts&#x27;</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">p</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">intro</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;My CLI Tool&#x27;</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> name</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#C586C0\"> await</span><span style=\"color:#9CDCFE\"> p</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">text</span><span style=\"color:#E6E6E6\">({</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  message:</span><span style=\"color:#CE9178\"> &#x27;What is your name?&#x27;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">  validate</span><span style=\"color:#9CDCFE\">:</span><span style=\"color:#9CDCFE\"> value</span><span style=\"color:#569CD6\"> =&gt;</span><span style=\"color:#D4D4D4\"> !</span><span style=\"color:#9CDCFE\">value</span><span style=\"color:#D4D4D4\"> ?</span><span style=\"color:#CE9178\"> &#x27;Name is required&#x27;</span><span style=\"color:#D4D4D4\"> :</span><span style=\"color:#569CD6\"> undefined</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">})</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> confirmed</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#C586C0\"> await</span><span style=\"color:#9CDCFE\"> p</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">confirm</span><span style=\"color:#E6E6E6\">({</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  message:</span><span style=\"color:#CE9178\"> &#x27;Are you sure?&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">})</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">p</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">outro</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;Done!&#x27;</span><span style=\"color:#E6E6E6\">)</span></span></code></div></pre></figure>\n<h3>zx over execa</h3>\n<p>While <code>execa</code> is great for programmatic command execution, <code>zx</code> is better for replacing shell scripts because:</p>\n<ul>\n<li>Pipes work naturally: <code>await $\\</code>cat file.txt | grep pattern``</li>\n<li>No special escaping needed for most cases</li>\n<li>Conditional arguments are simpler</li>\n<li>Feels more like writing bash</li>\n</ul>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"typescript\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"typescript\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#6A9955\">// Complex pipelines just work</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">await</span><span style=\"color:#DCDCAA\"> $</span><span style=\"color:#CE9178\">`docker ps | grep my-app | awk &#x27;{print $1}&#x27;`</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// Template literals handle variables naturally</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> tag</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#CE9178\"> &#x27;v1.0.0&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">await</span><span style=\"color:#DCDCAA\"> $</span><span style=\"color:#CE9178\">`git tag </span><span style=\"color:#569CD6\">${</span><span style=\"color:#9CDCFE\">tag</span><span style=\"color:#569CD6\">}</span><span style=\"color:#CE9178\"> &amp;&amp; git push origin </span><span style=\"color:#569CD6\">${</span><span style=\"color:#9CDCFE\">tag</span><span style=\"color:#569CD6\">}</span><span style=\"color:#CE9178\">`</span></span></code></div></pre></figure>\n<h2>Running Your CLI</h2>\n<p>If you want something more opinionated than Commander, check out <a href=\"https://github.com/drizzle-team/brocli\">brocli</a> from the Drizzle team. It has a cleaner API for defining commands and better built-in help generation.</p>\n<h2>Example: Complete CLI in One Prompt</h2>\n<p>Here&#x27;s what you might ask an LLM:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"md\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"md\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#E6E6E6\">Using @commander-js/extra-typings, @clack/prompts, and zx</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">Build a CLI that:</span></span>\n<span data-line=\"\"><span style=\"color:#6796E6\">-</span><span style=\"color:#E6E6E6\"> Accepts a list of git repos as arguments or from a --repos-file</span></span>\n<span data-line=\"\"><span style=\"color:#6796E6\">-</span><span style=\"color:#E6E6E6\"> For each repo, checks if there are uncommitted changes</span></span>\n<span data-line=\"\"><span style=\"color:#6796E6\">-</span><span style=\"color:#E6E6E6\"> If clean, creates a branch (default name: YYYY-MM-DD-update-deps)</span></span>\n<span data-line=\"\"><span style=\"color:#6796E6\">-</span><span style=\"color:#E6E6E6\"> Runs npm update or yarn upgrade depending on package manager</span></span>\n<span data-line=\"\"><span style=\"color:#6796E6\">-</span><span style=\"color:#E6E6E6\"> Creates a PR using gh CLI if there are changes</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">Include proper error handling and a spinner for long operations.</span></span></code></div></pre></figure>\n<p>The LLM will generate a complete, working CLI with proper types, error handling, and user feedback.</p>\n<h2>Running Your CLI</h2>\n<p>Development:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"bash\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"bash\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#DCDCAA\">chmod</span><span style=\"color:#CE9178\"> +x</span><span style=\"color:#CE9178\"> script.ts</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\"># Run with tsx (recommended)</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">tsx</span><span style=\"color:#CE9178\"> script.ts</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\"># Or with bun</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">bun</span><span style=\"color:#CE9178\"> script.ts</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\"># Or with node + tsx loader (slower)</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">node</span><span style=\"color:#CE9178\"> --import</span><span style=\"color:#CE9178\"> tsx</span><span style=\"color:#CE9178\"> script.ts</span></span></code></div></pre></figure>\n<p>Distribution (add to package.json):</p>\n<h3>Option 1: esbuild (simple, single file)</h3>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"json\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"json\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#E6E6E6\">{</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  &quot;bin&quot;</span><span style=\"color:#E6E6E6\">: {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    &quot;my-cli&quot;</span><span style=\"color:#E6E6E6\">: </span><span style=\"color:#CE9178\">&quot;./dist/cli.js&quot;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  },</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  &quot;scripts&quot;</span><span style=\"color:#E6E6E6\">: {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    &quot;build&quot;</span><span style=\"color:#E6E6E6\">: </span><span style=\"color:#CE9178\">&quot;esbuild --bundle --platform=node --target=node22 src/cli.ts --outfile=dist/cli.js&quot;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  }</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span></code></div></pre></figure>\n<p>For multiple CLIs with esbuild, you need to build each separately:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"json\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"json\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#E6E6E6\">{</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  &quot;bin&quot;</span><span style=\"color:#E6E6E6\">: {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    &quot;my-cli&quot;</span><span style=\"color:#E6E6E6\">: </span><span style=\"color:#CE9178\">&quot;./dist/my-cli.js&quot;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    &quot;another-cli&quot;</span><span style=\"color:#E6E6E6\">: </span><span style=\"color:#CE9178\">&quot;./dist/another-cli.js&quot;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    &quot;third-cli&quot;</span><span style=\"color:#E6E6E6\">: </span><span style=\"color:#CE9178\">&quot;./dist/third-cli.js&quot;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  },</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  &quot;scripts&quot;</span><span style=\"color:#E6E6E6\">: {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    &quot;build&quot;</span><span style=\"color:#E6E6E6\">: </span><span style=\"color:#CE9178\">&quot;npm run build:my-cli &amp;&amp; npm run build:another-cli &amp;&amp; npm run build:third-cli&quot;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    &quot;build:my-cli&quot;</span><span style=\"color:#E6E6E6\">: </span><span style=\"color:#CE9178\">&quot;esbuild --bundle --platform=node --target=node22 src/cli/my-cli.ts --outfile=dist/my-cli.js&quot;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    &quot;build:another-cli&quot;</span><span style=\"color:#E6E6E6\">: </span><span style=\"color:#CE9178\">&quot;esbuild --bundle --platform=node --target=node22 src/cli/another-cli.ts --outfile=dist/another-cli.js&quot;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    &quot;build:third-cli&quot;</span><span style=\"color:#E6E6E6\">: </span><span style=\"color:#CE9178\">&quot;esbuild --bundle --platform=node --target=node22 src/cli/third-cli.ts --outfile=dist/third-cli.js&quot;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  }</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span></code></div></pre></figure>\n<p>This works but gets verbose quickly. That&#x27;s where tsdown shines.</p>\n<h3>Option 2: tsdown (multiple CLIs, better DX)</h3>\n<p>For projects with multiple CLI entry points, <a href=\"https://tsdown.vercel.app/\">tsdown</a> provides a cleaner config-based approach:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"bash\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"bash\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#DCDCAA\">npm</span><span style=\"color:#CE9178\"> install</span><span style=\"color:#CE9178\"> -D</span><span style=\"color:#CE9178\"> tsdown</span></span></code></div></pre></figure>\n<p>Create <code>tsdown.config.js</code>:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"js\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"js\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">defineConfig</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &quot;tsdown&quot;</span><span style=\"color:#E6E6E6\">;</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#C586C0\"> default</span><span style=\"color:#DCDCAA\"> defineConfig</span><span style=\"color:#E6E6E6\">({</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  entry:</span><span style=\"color:#E6E6E6\"> [</span></span>\n<span data-line=\"\"><span style=\"color:#CE9178\">    &quot;./src/cli/my-cli.ts&quot;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#CE9178\">    &quot;./src/cli/another-cli.ts&quot;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  ],</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  format:</span><span style=\"color:#CE9178\"> &quot;commonjs&quot;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">});</span></span></code></div></pre></figure>\n<p>Update <code>package.json</code>:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"json\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"json\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#E6E6E6\">{</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  &quot;bin&quot;</span><span style=\"color:#E6E6E6\">: {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    &quot;my-cli&quot;</span><span style=\"color:#E6E6E6\">: </span><span style=\"color:#CE9178\">&quot;./dist/my-cli.js&quot;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    &quot;another-cli&quot;</span><span style=\"color:#E6E6E6\">: </span><span style=\"color:#CE9178\">&quot;./dist/another-cli.js&quot;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  },</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  &quot;scripts&quot;</span><span style=\"color:#E6E6E6\">: {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    &quot;build&quot;</span><span style=\"color:#E6E6E6\">: </span><span style=\"color:#CE9178\">&quot;tsdown&quot;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    &quot;build:watch&quot;</span><span style=\"color:#E6E6E6\">: </span><span style=\"color:#CE9178\">&quot;tsdown --watch&quot;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  },</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  &quot;files&quot;</span><span style=\"color:#E6E6E6\">: [</span><span style=\"color:#CE9178\">&quot;dist&quot;</span><span style=\"color:#E6E6E6\">]</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span></code></div></pre></figure>\n<p>Benefits of tsdown:</p>\n<ul>\n<li>Handles multiple entry points automatically</li>\n<li>Built-in watch mode</li>\n<li>Cleaner config for complex builds</li>\n<li>Better defaults for Node.js CLIs</li>\n</ul>\n<p>Then <code>npm publish</code> or <code>npm link</code> for local testing.</p>\n<h3>Option 3: bun build (fast, modern)</h3>\n<p><a href=\"https://bun.sh\">Bun</a> has a built-in bundler that&#x27;s extremely fast and has great defaults for building CLIs:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"bash\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"bash\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#DCDCAA\">bun</span><span style=\"color:#CE9178\"> install</span></span></code></div></pre></figure>\n<p>For a single CLI:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"json\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"json\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#E6E6E6\">{</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  &quot;bin&quot;</span><span style=\"color:#E6E6E6\">: {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    &quot;my-cli&quot;</span><span style=\"color:#E6E6E6\">: </span><span style=\"color:#CE9178\">&quot;./dist/cli.js&quot;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  },</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  &quot;scripts&quot;</span><span style=\"color:#E6E6E6\">: {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    &quot;build&quot;</span><span style=\"color:#E6E6E6\">: </span><span style=\"color:#CE9178\">&quot;bun build --compile --target=node --outfile=dist/cli.js src/cli.ts&quot;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  }</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span></code></div></pre></figure>\n<p>For multiple CLIs:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"json\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"json\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#E6E6E6\">{</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  &quot;bin&quot;</span><span style=\"color:#E6E6E6\">: {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    &quot;my-cli&quot;</span><span style=\"color:#E6E6E6\">: </span><span style=\"color:#CE9178\">&quot;./dist/my-cli.js&quot;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    &quot;another-cli&quot;</span><span style=\"color:#E6E6E6\">: </span><span style=\"color:#CE9178\">&quot;./dist/another-cli.js&quot;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    &quot;third-cli&quot;</span><span style=\"color:#E6E6E6\">: </span><span style=\"color:#CE9178\">&quot;./dist/third-cli.js&quot;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  },</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  &quot;scripts&quot;</span><span style=\"color:#E6E6E6\">: {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    &quot;build&quot;</span><span style=\"color:#E6E6E6\">: </span><span style=\"color:#CE9178\">&quot;bun build --target=node --outdir=dist src/cli/*.ts&quot;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  }</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span></code></div></pre></figure>\n<p>Benefits of bun build:</p>\n<ul>\n<li><strong>Extremely fast</strong> - often 10-20x faster than esbuild</li>\n<li><strong>Simple glob patterns</strong> - <code>src/cli/*.ts</code> automatically creates multiple outputs</li>\n<li><strong>Built-in minification</strong> - <code>--minify</code> flag for smaller bundles</li>\n<li><strong>Standalone executables</strong> - <code>--compile</code> creates a single executable with bun runtime embedded (great for distribution)</li>\n<li><strong>Modern defaults</strong> - TypeScript, JSX, and modern JS features work out of the box</li>\n</ul>\n<p>Creating standalone executables (no Node.js required):</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"bash\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"bash\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#6A9955\"># Build a completely standalone binary</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">bun</span><span style=\"color:#CE9178\"> build</span><span style=\"color:#CE9178\"> --compile</span><span style=\"color:#CE9178\"> --target=bun</span><span style=\"color:#CE9178\"> src/cli.ts</span><span style=\"color:#CE9178\"> --outfile=my-cli</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\"># Now distribute the single binary - no runtime needed!</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">./my-cli</span></span></code></div></pre></figure>\n<p>This creates a single executable that includes the Bun runtime, making distribution incredibly simple - just ship one file.</p>\n<p>For development, you can still use <code>bun run</code>:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"bash\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"bash\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#DCDCAA\">bun</span><span style=\"color:#CE9178\"> run</span><span style=\"color:#CE9178\"> src/cli.ts</span></span></code></div></pre></figure>\n<p>When to use bun build over esbuild/tsdown:</p>\n<ul>\n<li>You want the fastest build times</li>\n<li>You&#x27;re building standalone executables for easy distribution</li>\n<li>You want simple configuration with good defaults</li>\n<li>Your project already uses bun as the runtime</li>\n</ul>\n<h2>If Go Is Your Thing</h2>\n<p>TypeScript is great for quick scripts and Node.js ecosystems, but if you&#x27;re building a production CLI that needs to be distributed as a standalone binary, <strong>Go is the superior choice</strong>. Here&#x27;s the modern Go CLI stack based on real-world production use:</p>\n<h3>The Go Stack</h3>\n<ul>\n<li><strong><a href=\"https://github.com/charmbracelet/bubbletea\">Bubble Tea</a></strong> - TUI framework for interactive interfaces</li>\n<li><strong><a href=\"https://github.com/charmbracelet/lipgloss\">Lip Gloss</a></strong> - Terminal styling and layout</li>\n<li><strong><a href=\"https://github.com/charmbracelet/huh\">Huh</a></strong> - Interactive forms and prompts</li>\n<li><strong><a href=\"https://gitlab.com/cznic/sqlite\">modernc.org/sqlite</a></strong> - Pure Go SQLite (no CGO!)</li>\n<li><strong>Standard library <code>flag</code></strong> - Built-in flag parsing (or <a href=\"https://github.com/spf13/cobra\">cobra</a> for complex CLIs)</li>\n</ul>\n<h3>Why Go for CLIs?</h3>\n<p><strong>Single binary distribution</strong>: Compile once, run anywhere. No runtime dependencies.</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"bash\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"bash\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#6A9955\"># Build for all platforms from one machine</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">GOOS</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#CE9178\">darwin</span><span style=\"color:#9CDCFE\"> GOARCH</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#CE9178\">arm64</span><span style=\"color:#DCDCAA\"> go</span><span style=\"color:#CE9178\"> build</span><span style=\"color:#CE9178\"> -o</span><span style=\"color:#CE9178\"> alex-runner-darwin-arm64</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">GOOS</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#CE9178\">linux</span><span style=\"color:#9CDCFE\"> GOARCH</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#CE9178\">amd64</span><span style=\"color:#DCDCAA\"> go</span><span style=\"color:#CE9178\"> build</span><span style=\"color:#CE9178\"> -o</span><span style=\"color:#CE9178\"> alex-runner-linux-amd64</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">GOOS</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#CE9178\">windows</span><span style=\"color:#9CDCFE\"> GOARCH</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#CE9178\">amd64</span><span style=\"color:#DCDCAA\"> go</span><span style=\"color:#CE9178\"> build</span><span style=\"color:#CE9178\"> -o</span><span style=\"color:#CE9178\"> alex-runner-windows-amd64.exe</span></span></code></div></pre></figure>\n<p><strong>No CGO = Easy cross-compilation</strong>: Using pure Go libraries like <code>modernc.org/sqlite</code> means you can cross-compile without C toolchains:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"go\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"go\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#6A9955\">// go.mod</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">require</span><span style=\"color:#E6E6E6\"> (</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    modernc</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">org</span><span style=\"color:#D4D4D4\">/</span><span style=\"color:#9CDCFE\">sqlite</span><span style=\"color:#9CDCFE\"> v1</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#B5CEA8\">34.4</span><span style=\"color:#6A9955\">  // Pure Go - no CGO!</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">)</span></span></code></div></pre></figure>\n<p><strong>Fast startup</strong>: Go binaries start in milliseconds, not hundreds of milliseconds like Node.js.</p>\n<p><strong>Small binaries</strong>: With proper build flags, Go binaries are 5-15MB. TypeScript bundles + Node.js runtime are much larger.</p>\n<h3>Cross-Platform Build Setup</h3>\n<p>Use <strong>GoReleaser</strong> for automated multi-platform builds and releases:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"yaml\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"yaml\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#6A9955\"># .goreleaser.yaml</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">version</span><span style=\"color:#E6E6E6\">: </span><span style=\"color:#B5CEA8\">2</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">builds</span><span style=\"color:#E6E6E6\">:</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  - </span><span style=\"color:#569CD6\">id</span><span style=\"color:#E6E6E6\">: </span><span style=\"color:#CE9178\">my-cli</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">    main</span><span style=\"color:#E6E6E6\">: </span><span style=\"color:#CE9178\">./cmd/my-cli</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">    binary</span><span style=\"color:#E6E6E6\">: </span><span style=\"color:#CE9178\">my-cli</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">    env</span><span style=\"color:#E6E6E6\">:</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">      - </span><span style=\"color:#CE9178\">CGO_ENABLED=0</span><span style=\"color:#6A9955\">  # Pure Go - no CGO!</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">    goos</span><span style=\"color:#E6E6E6\">:</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">      - </span><span style=\"color:#CE9178\">linux</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">      - </span><span style=\"color:#CE9178\">darwin</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">      - </span><span style=\"color:#CE9178\">windows</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">    goarch</span><span style=\"color:#E6E6E6\">:</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">      - </span><span style=\"color:#CE9178\">amd64</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">      - </span><span style=\"color:#CE9178\">arm64</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">    ldflags</span><span style=\"color:#E6E6E6\">:</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">      - </span><span style=\"color:#CE9178\">-s -w</span><span style=\"color:#6A9955\">  # Strip debug info for smaller binaries</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">      - </span><span style=\"color:#CE9178\">-X main.version={{.Version}}</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">      - </span><span style=\"color:#CE9178\">-X main.commit={{.Commit}}</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">archives</span><span style=\"color:#E6E6E6\">:</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  - </span><span style=\"color:#569CD6\">formats</span><span style=\"color:#E6E6E6\">: [</span><span style=\"color:#CE9178\">tar.gz</span><span style=\"color:#E6E6E6\">]</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">    format_overrides</span><span style=\"color:#E6E6E6\">:</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">      - </span><span style=\"color:#569CD6\">goos</span><span style=\"color:#E6E6E6\">: </span><span style=\"color:#CE9178\">windows</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">        formats</span><span style=\"color:#E6E6E6\">: [</span><span style=\"color:#CE9178\">zip</span><span style=\"color:#E6E6E6\">]</span></span></code></div></pre></figure>\n<p><strong>GitHub Actions integration</strong>:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"yaml\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"yaml\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#6A9955\"># .github/workflows/release.yml</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">name</span><span style=\"color:#E6E6E6\">: </span><span style=\"color:#CE9178\">Release</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">on</span><span style=\"color:#E6E6E6\">:</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">  push</span><span style=\"color:#E6E6E6\">:</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">    tags</span><span style=\"color:#E6E6E6\">:</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">      - </span><span style=\"color:#CE9178\">&#x27;v*&#x27;</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">jobs</span><span style=\"color:#E6E6E6\">:</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">  goreleaser</span><span style=\"color:#E6E6E6\">:</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">    runs-on</span><span style=\"color:#E6E6E6\">: </span><span style=\"color:#CE9178\">ubuntu-latest</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">    steps</span><span style=\"color:#E6E6E6\">:</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">      - </span><span style=\"color:#569CD6\">uses</span><span style=\"color:#E6E6E6\">: </span><span style=\"color:#CE9178\">actions/checkout@v4</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">      - </span><span style=\"color:#569CD6\">uses</span><span style=\"color:#E6E6E6\">: </span><span style=\"color:#CE9178\">actions/setup-go@v5</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">        with</span><span style=\"color:#E6E6E6\">:</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">          go-version</span><span style=\"color:#E6E6E6\">: </span><span style=\"color:#CE9178\">&#x27;1.23&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">      - </span><span style=\"color:#569CD6\">uses</span><span style=\"color:#E6E6E6\">: </span><span style=\"color:#CE9178\">goreleaser/goreleaser-action@v5</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">        with</span><span style=\"color:#E6E6E6\">:</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">          version</span><span style=\"color:#E6E6E6\">: </span><span style=\"color:#CE9178\">latest</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">          args</span><span style=\"color:#E6E6E6\">: </span><span style=\"color:#CE9178\">release --clean</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">        env</span><span style=\"color:#E6E6E6\">:</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">          GITHUB_TOKEN</span><span style=\"color:#E6E6E6\">: </span><span style=\"color:#CE9178\">${{ secrets.GITHUB_TOKEN }}</span></span></code></div></pre></figure>\n<p>Push a tag, get binaries for all platforms automatically.</p>\n<h3>Library Choices: Pure Go Over CGO</h3>\n<p><strong>Always prefer pure Go implementations</strong> to avoid CGO complexity:</p>\n<table><thead><tr><th>Need</th><th>❌ CGO Version</th><th>✅ Pure Go Version</th></tr></thead><tbody><tr><td>SQLite</td><td>mattn/go-sqlite3</td><td>modernc.org/sqlite</td></tr><tr><td>Image processing</td><td>Avoid C libs</td><td>golang.org/x/image</td></tr><tr><td>Compression</td><td>C libraries</td><td>compress/* (stdlib)</td></tr></tbody></table>\n<p><strong>Why avoid CGO?</strong></p>\n<ul>\n<li>Cross-compilation requires C toolchains for each target</li>\n<li>Slower builds</li>\n<li>Harder to debug</li>\n<li>Breaks <code>go get</code> simplicity</li>\n</ul>\n<h3>Project Structure</h3>\n<pre class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code>my-cli/\n├── cmd/\n│   └── my-cli/\n│       └── main.go          # Entry point\n├── internal/\n│   ├── ui.go                # Bubble Tea UI\n│   ├── db.go                # Database logic\n│   ├── completion.go        # Shell completion\n│   └── *_test.go            # Tests\n├── .goreleaser.yaml         # Release config\n├── Makefile                 # Build shortcuts\n└── go.mod\n</code></div></pre>\n<p><strong>Makefile for common tasks</strong>:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"makefile\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"makefile\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#DCDCAA\">.PHONY</span><span style=\"color:#E6E6E6\">: build test install</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">build</span><span style=\"color:#E6E6E6\">:</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">\tgo build -o my-cli ./cmd/my-cli</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">test</span><span style=\"color:#E6E6E6\">:</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">\tgo test ./internal/... -v</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">install</span><span style=\"color:#E6E6E6\">:</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">\tgo install ./cmd/my-cli</span></span></code></div></pre></figure>\n<h3>Shell Completion</h3>\n<p>Go makes it easy to generate shell completions:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"go\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"go\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#6A9955\">// In your main.go</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">func</span><span style=\"color:#DCDCAA\"> generateCompletion</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">shell</span><span style=\"color:#4EC9B0\"> string</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#4EC9B0\">string</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">    switch</span><span style=\"color:#9CDCFE\"> shell</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">    case</span><span style=\"color:#CE9178\"> &quot;bash&quot;</span><span style=\"color:#E6E6E6\">:</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">        return</span><span style=\"color:#9CDCFE\"> bashCompletionScript</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">    case</span><span style=\"color:#CE9178\"> &quot;zsh&quot;</span><span style=\"color:#E6E6E6\">:</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">        return</span><span style=\"color:#9CDCFE\"> zshCompletionScript</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">    case</span><span style=\"color:#CE9178\"> &quot;fish&quot;</span><span style=\"color:#E6E6E6\">:</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">        return</span><span style=\"color:#9CDCFE\"> fishCompletionScript</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    }</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">    return</span><span style=\"color:#CE9178\"> &quot;&quot;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// Usage:</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// my-cli --generate-completion bash &gt; /etc/bash_completion.d/my-cli</span></span></code></div></pre></figure>\n<h3>Version Management with Changesets</h3>\n<p>Even for Go projects, use <strong>Changesets</strong> for version management:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"bash\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"bash\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#6A9955\"># Install changesets</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">pnpm</span><span style=\"color:#CE9178\"> add</span><span style=\"color:#CE9178\"> -D</span><span style=\"color:#CE9178\"> @changesets/cli</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\"># Add a changeset during development</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">pnpm</span><span style=\"color:#CE9178\"> changeset</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\"># Release (bumps version, updates changelog)</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">pnpm</span><span style=\"color:#CE9178\"> release</span></span></code></div></pre></figure>\n<p>This works great with GoReleaser - changesets manage the version, GoReleaser builds the binaries.</p>\n<h3>Real-World Example: alex-runner</h3>\n<p>Check out <strong><a href=\"https://github.com/alexanderchan/alex-runner\">alex-runner</a></strong> - a production-ready frecency-based npm/pnpm/yarn script runner that demonstrates all these best practices:</p>\n<ul>\n<li>✅ Pure Go SQLite (no CGO) - uses <code>modernc.org/sqlite</code></li>\n<li>✅ Cross-platform builds with GoReleaser - binaries for macOS, Linux, Windows</li>\n<li>✅ Beautiful TUI with Bubble Tea - interactive script selection with live filtering</li>\n<li>✅ Shell completion for bash/zsh/fish - frecency-aware tab completion</li>\n<li>✅ Automated releases via GitHub Actions - push a tag, get binaries</li>\n<li>✅ Frecency algorithm for smart suggestions - learns which scripts you use most</li>\n<li>✅ Per-directory usage tracking - project-specific suggestions</li>\n</ul>\n<p><strong>Key files to study</strong>:</p>\n<ul>\n<li><a href=\"https://github.com/alexanderchan/alex-runner/blob/main/.goreleaser.yaml\"><code>.goreleaser.yaml</code></a> - Multi-platform build config with CGO_ENABLED=0</li>\n<li><a href=\"https://github.com/alexanderchan/alex-runner/blob/main/internal/ui.go\"><code>internal/ui.go</code></a> - Bubble Tea interactive UI implementation</li>\n<li><a href=\"https://github.com/alexanderchan/alex-runner/blob/main/internal/completion.go\"><code>internal/completion.go</code></a> - Shell completion generation for bash/zsh/fish</li>\n<li><a href=\"https://github.com/alexanderchan/alex-runner/blob/main/internal/db.go\"><code>internal/db.go</code></a> - Pure Go SQLite usage with modernc.org/sqlite</li>\n<li><a href=\"https://github.com/alexanderchan/alex-runner/blob/main/cmd/alex-runner/main.go\"><code>cmd/alex-runner/main.go</code></a> - Flag parsing and command structure</li>\n</ul>\n<p>The entire project is a great reference for building production CLIs in Go.</p>\n<h3>When to Choose Go vs TypeScript</h3>\n<p><strong>Choose Go when</strong>:</p>\n<ul>\n<li>You need a standalone binary</li>\n<li>Cross-platform distribution is important</li>\n<li>Startup time matters</li>\n<li>You want simple deployment (single file)</li>\n<li>You&#x27;re building a tool for developers who may not have Node.js</li>\n</ul>\n<p><strong>Choose TypeScript when</strong>:</p>\n<ul>\n<li>You&#x27;re already in a Node.js ecosystem</li>\n<li>You need rapid prototyping</li>\n<li>The CLI is project-specific (not distributed)</li>\n<li>You want to leverage npm packages</li>\n<li>Your team is more comfortable with JavaScript</li>\n</ul>\n<h3>The Go LLM Prompt Pattern</h3>\n<p>Just like with TypeScript, teach your LLM the Go stack once:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"md\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"md\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#E6E6E6\">Using Go with Bubble Tea, Lip Gloss, and modernc.org/sqlite</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">Build a CLI that does the following...</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">[describe what you want]</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">Example structure:</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">package main</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">import (</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    &quot;flag&quot;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    tea &quot;github.com/charmbracelet/bubbletea&quot;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    &quot;github.com/charmbracelet/lipgloss&quot;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">func main() {</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    // Flag parsing</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    var verbose bool</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    flag.BoolVar(&amp;verbose, &quot;v&quot;, false, &quot;verbose output&quot;)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    flag.Parse()</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    // Run Bubble Tea program</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    p := tea.NewProgram(initialModel())</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    if _, err := p.Run(); err != nil {</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">        log.Fatal(err)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    }</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span></code></div></pre></figure>\n<h2>CLI Best Practices</h2>\n<p>When building CLIs, it&#x27;s worth following established best practices for user experience and interface design. Two excellent resources:</p>\n<ul>\n<li><strong><a href=\"https://clig.dev/\">Command Line Interface Guidelines (clig.dev)</a></strong> - A comprehensive guide to CLI UX, covering everything from argument parsing to output formatting, error messages, and configuration. Essential reading for building user-friendly CLIs.</li>\n<li><strong><a href=\"https://github.com/jlevy/the-art-of-command-line\">The Art of Command Line</a></strong> - A curated guide to mastering the command line, useful both for understanding how users expect CLIs to behave and for building better tools.</li>\n</ul>\n<p>These resources will help you build CLIs that feel natural to experienced command-line users while remaining accessible to newcomers.</p>\n<h3>Key Conventions to Follow</h3>\n<p><strong>Be non-destructive by default</strong>. Always prompt before mutating operations:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"typescript\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"typescript\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#569CD6\"> *</span><span style=\"color:#C586C0\"> as</span><span style=\"color:#9CDCFE\"> p</span><span style=\"color:#C586C0\"> from</span><span style=\"color:#CE9178\"> &#x27;@clack/prompts&#x27;</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> shouldDelete</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#C586C0\"> await</span><span style=\"color:#9CDCFE\"> p</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">confirm</span><span style=\"color:#E6E6E6\">({</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  message:</span><span style=\"color:#CE9178\"> `Delete </span><span style=\"color:#569CD6\">${</span><span style=\"color:#9CDCFE\">files</span><span style=\"color:#D4D4D4\">.</span><span style=\"color:#9CDCFE\">length</span><span style=\"color:#569CD6\">}</span><span style=\"color:#CE9178\"> files?`</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  initialValue:</span><span style=\"color:#569CD6\"> false</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">})</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">if</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#D4D4D4\">!</span><span style=\"color:#9CDCFE\">shouldDelete</span><span style=\"color:#E6E6E6\">) {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  p</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">cancel</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;Operation cancelled&#x27;</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  process</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">exit</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#B5CEA8\">0</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span></code></div></pre></figure>\n<p><strong>Support <code>--yes</code> or <code>--non-interactive</code> mode</strong> for CI/CD and automation:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"typescript\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"typescript\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">Command</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;@commander-js/extra-typings&#x27;</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> program</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#569CD6\"> new</span><span style=\"color:#DCDCAA\"> Command</span><span style=\"color:#E6E6E6\">()</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  .</span><span style=\"color:#DCDCAA\">option</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;-y, --yes&#x27;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#CE9178\">&#x27;skip confirmation prompts&#x27;</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  .</span><span style=\"color:#DCDCAA\">option</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;--non-interactive&#x27;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#CE9178\">&#x27;run in non-interactive mode&#x27;</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  .</span><span style=\"color:#DCDCAA\">action</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#569CD6\">async</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#9CDCFE\">opts</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">    const</span><span style=\"color:#9CDCFE\"> shouldProceed</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#9CDCFE\"> opts</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">yes</span><span style=\"color:#D4D4D4\"> ||</span><span style=\"color:#9CDCFE\"> opts</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">nonInteractive</span></span>\n<span data-line=\"\"><span style=\"color:#D4D4D4\">      ?</span><span style=\"color:#569CD6\"> true</span></span>\n<span data-line=\"\"><span style=\"color:#D4D4D4\">      :</span><span style=\"color:#C586C0\"> await</span><span style=\"color:#9CDCFE\"> p</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">confirm</span><span style=\"color:#E6E6E6\">({ </span><span style=\"color:#9CDCFE\">message:</span><span style=\"color:#CE9178\"> &#x27;Continue?&#x27;</span><span style=\"color:#E6E6E6\"> })</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">    if</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#D4D4D4\">!</span><span style=\"color:#9CDCFE\">shouldProceed</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#C586C0\">return</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">    // ... do the work</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  })</span></span></code></div></pre></figure>\n<p><strong>Store config in standard locations</strong> using well-known formats:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"typescript\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"typescript\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">homedir</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;os&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">join</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;path&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">readFileSync</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">writeFileSync</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;fs&#x27;</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// Simple config: JSON or YAML in ~/.config/package-name/</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> configDir</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> join</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#DCDCAA\">homedir</span><span style=\"color:#E6E6E6\">(), </span><span style=\"color:#CE9178\">&#x27;.config&#x27;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#CE9178\">&#x27;my-cli&#x27;</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> configPath</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> join</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">configDir</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#CE9178\">&#x27;config.json&#x27;</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// For complex state: SQLite</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#9CDCFE\"> Database</span><span style=\"color:#C586C0\"> from</span><span style=\"color:#CE9178\"> &#x27;better-sqlite3&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> dbPath</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> join</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">configDir</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#CE9178\">&#x27;data.db&#x27;</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> db</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#569CD6\"> new</span><span style=\"color:#DCDCAA\"> Database</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">dbPath</span><span style=\"color:#E6E6E6\">)</span></span></code></div></pre></figure>\n<p>For Go:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"go\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"go\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> (</span></span>\n<span data-line=\"\"><span style=\"color:#CE9178\">    &quot;os&quot;</span></span>\n<span data-line=\"\"><span style=\"color:#CE9178\">    &quot;path/filepath&quot;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// Get config directory</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">configDir</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">_</span><span style=\"color:#D4D4D4\"> :=</span><span style=\"color:#9CDCFE\"> os</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">UserConfigDir</span><span style=\"color:#E6E6E6\">() </span><span style=\"color:#6A9955\">// Returns ~/.config on Unix</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">appConfigDir</span><span style=\"color:#D4D4D4\"> :=</span><span style=\"color:#9CDCFE\"> filepath</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">Join</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">configDir</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#CE9178\">&quot;my-cli&quot;</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// Simple config: JSON</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">configPath</span><span style=\"color:#D4D4D4\"> :=</span><span style=\"color:#9CDCFE\"> filepath</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">Join</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">appConfigDir</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#CE9178\">&quot;config.json&quot;</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// Complex state: SQLite with modernc.org/sqlite</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">dbPath</span><span style=\"color:#D4D4D4\"> :=</span><span style=\"color:#9CDCFE\"> filepath</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">Join</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">appConfigDir</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#CE9178\">&quot;data.db&quot;</span><span style=\"color:#E6E6E6\">)</span></span></code></div></pre></figure>\n<p><strong>Keep project-local scripts separate from distributed CLIs</strong>:</p>\n<p><strong>TypeScript projects</strong>: Use <code>./scripts</code> for local utilities:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"bash\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"bash\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#6A9955\"># Project-local scripts (not distributed)</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">./scripts/</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">├──</span><span style=\"color:#CE9178\"> dev-setup.ts</span><span style=\"color:#6A9955\">          # Project-specific setup</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">├──</span><span style=\"color:#CE9178\"> migrate-data.ts</span><span style=\"color:#6A9955\">       # One-off migrations</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">└──</span><span style=\"color:#CE9178\"> generate-types.ts</span><span style=\"color:#6A9955\">     # Build-time codegen</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\"># Run with tsx directly</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">tsx</span><span style=\"color:#CE9178\"> scripts/dev-setup.ts</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\"># Global tools (distributed via npm)</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">npm</span><span style=\"color:#CE9178\"> install</span><span style=\"color:#CE9178\"> -g</span><span style=\"color:#CE9178\"> my-cli</span></span></code></div></pre></figure>\n<p><strong>TypeScript project structure</strong>:</p>\n<pre class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code>my-ts-project/\n├── scripts/              # Project-local utilities (not published)\n│   ├── setup.ts\n│   └── migrate.ts\n├── src/\n│   └── cli/             # Publishable CLIs (if any)\n│       └── my-tool.ts\n└── package.json\n    {\n      &quot;scripts&quot;: {\n        &quot;setup&quot;: &quot;tsx scripts/setup.ts&quot;  // Local scripts\n      },\n      &quot;bin&quot;: {\n        &quot;my-tool&quot;: &quot;./dist/my-tool.js&quot;   // Global CLI\n      }\n    }\n</code></div></pre>\n<p><strong>Go projects</strong>: Use <code>cmd/</code> for all CLIs (both local and distributed):</p>\n<pre class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code>my-go-project/\n├── cmd/\n│   ├── my-cli/          # Main distributed CLI\n│   │   └── main.go\n│   └── dev-setup/       # Project-local utility\n│       └── main.go\n├── internal/            # Shared internal packages\n│   ├── ui.go\n│   └── db.go\n└── go.mod\n\n# Run local utilities during development\ngo run ./cmd/dev-setup\n\n# Install distributed CLI globally\ngo install github.com/user/my-go-project/cmd/my-cli@latest\n</code></div></pre>\n<p>The distinction:</p>\n<ul>\n<li><strong>TypeScript</strong>: <code>scripts/</code> for maintainers, <code>bin/</code> for end users</li>\n<li><strong>Go</strong>: <code>cmd/</code> for both, but only main CLIs get published/documented</li>\n</ul>\n<h2>The Bottom Line</h2>\n<p>In 2026, building CLIs is less about memorizing APIs and more about:</p>\n<ol>\n<li><strong>Knowing the right stack</strong>:\n<ul>\n<li>TypeScript: @clack/prompts + commander + zx</li>\n<li>Go: Bubble Tea + Lip Gloss + pure Go libraries</li>\n</ul>\n</li>\n<li><strong>Teaching your LLM once</strong> via a good prompt template</li>\n<li><strong>Iterating quickly</strong> with AI assistance</li>\n<li><strong>Choosing the right tool</strong>:\n<ul>\n<li>TypeScript for quick scripts and Node.js ecosystems</li>\n<li>Go for production CLIs that need standalone distribution</li>\n</ul>\n</li>\n<li><strong>Following best practices</strong> from resources like clig.dev and The Art of Command Line</li>\n</ol>\n<p>The tools have matured, the LLMs know them well, and you can focus on what your CLI should do rather than how to build it.</p>\n<p>For more detailed examples and utilities, check out my <a href=\"/blog/2023-10-10-cli\">original CLI guide</a> which covers additional libraries for specific use cases (file processing, config loading, parallelization, etc.).</p>",
            "url": "https://alexmchan.com/blog/2026-01-25-updated-cli",
            "title": "Modern CLI Tips for LLMs (and Curious Humans)",
            "summary": "Modern CLI development with TypeScript, Go, and AI assistance - a guide optimized for LLMs",
            "date_modified": "2026-01-25T00:00:00.000Z",
            "author": {
                "name": "Alex Chan"
            }
        },
        {
            "id": "https://alexmchan.com/blog/2025-10-21-pragmatic-eslint",
            "content_html": "<h1>Pragmatic ESLint: Recommendations</h1>\n<p>ESLint can feel like it&#x27;s fighting you. Red squiggles everywhere. Auto-fix battles with Prettier. Rules that seem arbitrary. After many years I have this philosophy:</p>\n<ul>\n<li>auto fix when possible</li>\n<li>errors on things that have proven to cause bugs, security issues, or slow developers down</li>\n<li>errors to help debug/test code leaking into prod</li>\n</ul>\n<p>Here&#x27;s the configuration I&#x27;ve converged on, and will update it as time goes on. I try to keep it minimal unless a rule matches one of the above points.</p>\n<h2>The Philosophy</h2>\n<p>Before we dive into the configuration, let&#x27;s establish some principles:</p>\n<ol>\n<li>\n<p><strong>Errors should prevent bugs</strong> - If something is marked as an error, it should represent a real problem that could break your application or confuse developers.</p>\n</li>\n<li>\n<p><strong>Style is Prettier&#x27;s job</strong> - ESLint shouldn&#x27;t fight with Prettier over formatting. One tool for style, one for logic.</p>\n</li>\n<li>\n<p><strong>Make migration practical</strong> - Perfect is the enemy of shipped. Use grandfathering and incremental adoption to get your team on board.</p>\n</li>\n<li>\n<p><strong>Grandfather old code, enforce new standards</strong> - When introducing strict rules, it&#x27;s often impractical to fix everything at once. Allow existing violations while preventing new ones.</p>\n</li>\n</ol>\n<h2>The Configuration</h2>\n<p>This uses ESLint 9&#x27;s new flat config format with the latest typescript-eslint recommendations:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"ts\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"ts\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#6A9955\">// eslint.config.mjs</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// @ts-check</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#9CDCFE\"> eslint</span><span style=\"color:#C586C0\"> from</span><span style=\"color:#CE9178\"> &quot;@eslint/js&quot;</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">defineConfig</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &quot;eslint/config&quot;</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#9CDCFE\"> tseslint</span><span style=\"color:#C586C0\"> from</span><span style=\"color:#CE9178\"> &quot;typescript-eslint&quot;</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#9CDCFE\"> reactHooks</span><span style=\"color:#C586C0\"> from</span><span style=\"color:#CE9178\"> &quot;eslint-plugin-react-hooks&quot;</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#9CDCFE\"> noOnlyTests</span><span style=\"color:#C586C0\"> from</span><span style=\"color:#CE9178\"> &quot;eslint-plugin-no-only-tests&quot;</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#C586C0\"> default</span><span style=\"color:#DCDCAA\"> defineConfig</span><span style=\"color:#E6E6E6\">(</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  eslint</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">configs</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">recommended</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  tseslint</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">configs</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">recommended</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  tseslint</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">configs</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">stylistic</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    plugins:</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#CE9178\">      &quot;react-hooks&quot;</span><span style=\"color:#9CDCFE\">:</span><span style=\"color:#9CDCFE\"> reactHooks</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#CE9178\">      &quot;no-only-tests&quot;</span><span style=\"color:#9CDCFE\">:</span><span style=\"color:#9CDCFE\"> noOnlyTests</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    },</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    rules:</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#CE9178\">      &quot;react-hooks/rules-of-hooks&quot;</span><span style=\"color:#9CDCFE\">:</span><span style=\"color:#CE9178\"> &quot;error&quot;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#CE9178\">      &quot;react-hooks/exhaustive-deps&quot;</span><span style=\"color:#9CDCFE\">:</span><span style=\"color:#CE9178\"> &quot;error&quot;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#CE9178\">      &quot;no-console&quot;</span><span style=\"color:#9CDCFE\">:</span><span style=\"color:#E6E6E6\"> [</span></span>\n<span data-line=\"\"><span style=\"color:#CE9178\">        &quot;error&quot;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">        { </span><span style=\"color:#9CDCFE\">allow:</span><span style=\"color:#E6E6E6\"> [</span><span style=\"color:#CE9178\">&quot;info&quot;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#CE9178\">&quot;warn&quot;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#CE9178\">&quot;error&quot;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#CE9178\">&quot;dir&quot;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#CE9178\">&quot;table&quot;</span><span style=\"color:#E6E6E6\">] },</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">      ],</span></span>\n<span data-line=\"\"><span style=\"color:#CE9178\">      &quot;no-warning-comments&quot;</span><span style=\"color:#9CDCFE\">:</span><span style=\"color:#E6E6E6\"> [</span></span>\n<span data-line=\"\"><span style=\"color:#CE9178\">        &quot;error&quot;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">        {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">          terms:</span><span style=\"color:#E6E6E6\"> [</span><span style=\"color:#CE9178\">&quot;fixme&quot;</span><span style=\"color:#E6E6E6\">],</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">          location:</span><span style=\"color:#CE9178\"> &quot;anywhere&quot;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">        },</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">      ],</span></span>\n<span data-line=\"\"><span style=\"color:#CE9178\">      &quot;@typescript-eslint/no-unused-vars&quot;</span><span style=\"color:#9CDCFE\">:</span><span style=\"color:#E6E6E6\"> [</span></span>\n<span data-line=\"\"><span style=\"color:#CE9178\">        &quot;error&quot;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">        {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">          args:</span><span style=\"color:#CE9178\"> &quot;all&quot;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">          argsIgnorePattern:</span><span style=\"color:#CE9178\"> &quot;^_&quot;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">          caughtErrors:</span><span style=\"color:#CE9178\"> &quot;all&quot;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">          caughtErrorsIgnorePattern:</span><span style=\"color:#CE9178\"> &quot;^_&quot;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">          destructuredArrayIgnorePattern:</span><span style=\"color:#CE9178\"> &quot;^_&quot;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">          varsIgnorePattern:</span><span style=\"color:#CE9178\"> &quot;^_&quot;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">          ignoreRestSiblings:</span><span style=\"color:#569CD6\"> true</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">        },</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">      ],</span></span>\n<span data-line=\"\"><span style=\"color:#CE9178\">      &quot;@typescript-eslint/no-explicit-any&quot;</span><span style=\"color:#9CDCFE\">:</span><span style=\"color:#CE9178\"> &quot;off&quot;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#CE9178\">      &quot;no-only-tests/no-only-tests&quot;</span><span style=\"color:#9CDCFE\">:</span><span style=\"color:#E6E6E6\"> [</span></span>\n<span data-line=\"\"><span style=\"color:#CE9178\">        &quot;error&quot;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">        {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">          focus:</span><span style=\"color:#E6E6E6\"> [</span><span style=\"color:#CE9178\">&quot;only&quot;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#CE9178\">&quot;skip&quot;</span><span style=\"color:#E6E6E6\">],</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">        },</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">      ],</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    },</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  },</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    ignores:</span><span style=\"color:#E6E6E6\"> [</span><span style=\"color:#CE9178\">&quot;dist/**&quot;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#CE9178\">&quot;node_modules/**&quot;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#CE9178\">&quot;*.config.js&quot;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#CE9178\">&quot;jest.config.js&quot;</span><span style=\"color:#E6E6E6\">],</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  },</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">)</span></span></code></div></pre></figure>\n<h2>Rule-by-Rule Rationale</h2>\n<h3>React Hooks: Error, Always</h3>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"javascript\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"javascript\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#CE9178\">&quot;react-hooks/rules-of-hooks&quot;</span><span style=\"color:#E6E6E6\">: </span><span style=\"color:#CE9178\">&quot;error&quot;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#CE9178\">&quot;react-hooks/exhaustive-deps&quot;</span><span style=\"color:#E6E6E6\">: </span><span style=\"color:#CE9178\">&quot;error&quot;</span><span style=\"color:#E6E6E6\">,</span></span></code></div></pre></figure>\n<p><strong>Why errors?</strong> Violating the Rules of Hooks causes runtime bugs that are confusing to debug. Missing dependencies in <code>useEffect</code> leads to stale closures that silently use old values.</p>\n<p><strong>What about legacy code?</strong> Use the grandfathering script (see appendix below) to add <code>eslint-disable-next-line</code> comments to existing violations. This lets you enforce the rule going forward while giving you time to fix old code.</p>\n<p><strong>Why not warnings?</strong> Warnings get ignored. If something matters enough to catch, make it an error that blocks your CI/CD pipeline.</p>\n<h3>Explicit Any: Off (But Use Sparingly)</h3>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"javascript\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"javascript\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#CE9178\">&quot;@typescript-eslint/no-explicit-any&quot;</span><span style=\"color:#E6E6E6\">: </span><span style=\"color:#CE9178\">&quot;off&quot;</span><span style=\"color:#E6E6E6\">,</span></span></code></div></pre></figure>\n<p><strong>Why off?</strong> In real codebases, there are legitimate uses for <code>any</code> - especially when dealing with third-party libraries without types, or truly dynamic data structures. The strict prohibition creates friction without much value.</p>\n<p><strong>Best practice:</strong> Even though it&#x27;s off, avoid <code>any</code> where possible. Consider using:</p>\n<ul>\n<li><code>unknown</code> when you don&#x27;t know the type yet</li>\n<li>Generic type parameters for flexible but type-safe code</li>\n<li>Type assertions with care: <code>as SomeType</code></li>\n</ul>\n<p><strong>Future consideration:</strong> You could enable this as a warning and use <code>eslint-disable-next-line any</code> comments for legitimate cases. This makes <code>any</code> usage explicit and searchable.</p>\n<p>Leaving implicit any as an error forces you to type that <code>any</code> and feel guilty about it. And you should feel guilty (especially those llms which like to cheat this).</p>\n<h3>Block FIXME Comments</h3>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"javascript\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"javascript\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#CE9178\">&quot;no-warning-comments&quot;</span><span style=\"color:#E6E6E6\">: [</span></span>\n<span data-line=\"\"><span style=\"color:#CE9178\">  &quot;error&quot;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    terms:</span><span style=\"color:#E6E6E6\"> [</span><span style=\"color:#CE9178\">&quot;fixme&quot;</span><span style=\"color:#E6E6E6\">],</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    location:</span><span style=\"color:#CE9178\"> &quot;anywhere&quot;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  },</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">],</span></span></code></div></pre></figure>\n<p><strong>Why?</strong> FIXME comments are incredibly useful during development - they mark technical debt that you <em>must</em> address before committing. Making this an error ensures you can&#x27;t forget about them.</p>\n<p><strong>Workflow:</strong></p>\n<ol>\n<li>During development: Use <code>// FIXME: handle error case</code> freely</li>\n<li>Before committing: ESLint blocks the commit</li>\n<li>You either fix it or convert it to <code>// TODO: ...</code> if it&#x27;s truly future work</li>\n</ol>\n<p><strong>Why not TODO?</strong> TODO comments document intended future improvements. FIXME comments document <em>current problems</em> that shouldn&#x27;t ship. Use them sparingly and have a plan to do them or remove them from the code. Adding dates can help and there are lint rules that even error when the date is too old.</p>\n<h3>Console.log: Development Tool Only</h3>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"javascript\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"javascript\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#CE9178\">&quot;no-console&quot;</span><span style=\"color:#E6E6E6\">: [</span></span>\n<span data-line=\"\"><span style=\"color:#CE9178\">  &quot;error&quot;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  { </span><span style=\"color:#9CDCFE\">allow:</span><span style=\"color:#E6E6E6\"> [</span><span style=\"color:#CE9178\">&quot;info&quot;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#CE9178\">&quot;warn&quot;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#CE9178\">&quot;error&quot;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#CE9178\">&quot;dir&quot;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#CE9178\">&quot;table&quot;</span><span style=\"color:#E6E6E6\">] },</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">],</span></span></code></div></pre></figure>\n<p><strong>Why block console.log?</strong> It&#x27;s a debugging tool. You added it to figure something out, but it shouldn&#x27;t stay in production code.</p>\n<p><strong>What about logging?</strong> Use the allowed methods for intentional logging:</p>\n<ul>\n<li><code>console.info()</code> - Informational messages</li>\n<li><code>console.warn()</code> - Warnings that users should see</li>\n<li><code>console.error()</code> - Error messages</li>\n<li><code>console.dir()</code> / <code>console.table()</code> - Rich debugging that&#x27;s intentionally left in</li>\n</ul>\n<p><strong>Frontend logging:</strong> For production frontend logging, use a proper logger (<code>logger.debug()</code>, <code>logger.info()</code>, etc.) that can be configured per environment.</p>\n<h3>Test .skip and .only: Prevent Accidents</h3>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"javascript\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"javascript\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#CE9178\">&quot;no-only-tests/no-only-tests&quot;</span><span style=\"color:#E6E6E6\">: [</span></span>\n<span data-line=\"\"><span style=\"color:#CE9178\">  &quot;error&quot;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    focus:</span><span style=\"color:#E6E6E6\"> [</span><span style=\"color:#CE9178\">&quot;only&quot;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#CE9178\">&quot;skip&quot;</span><span style=\"color:#E6E6E6\">],</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  },</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">],</span></span></code></div></pre></figure>\n<p><strong>Why?</strong> It&#x27;s incredibly easy to use <code>.only</code> while debugging a test, then forget to remove it. This breaks the entire test suite - but only in CI, after you&#x27;ve pushed.</p>\n<p>Similarly, <code>.skip</code> can accumulate forgotten tests that no longer run. They should only be used for flaky tests that we should come back to fixing later. If the test is no longer useful just delete it.</p>\n<p><strong>Why the plugin?</strong> The old approach using <code>no-restricted-syntax</code> highlighted the entire test block in red. The <code>eslint-plugin-no-only-tests</code> plugin only highlights the <code>.only</code> or <code>.skip</code> portion, making it much easier to spot and fix.</p>\n<p><strong>Legitimate uses?</strong> Use an inline disable comment:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"javascript\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"javascript\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#6A9955\">// eslint-disable-next-line no-only-tests/no-only-tests</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">it</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">only</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;should debug this specific case&#x27;</span><span style=\"color:#E6E6E6\">, () </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">  // ...</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">})</span></span></code></div></pre></figure>\n<p>This makes the exception explicit and searchable.</p>\n<h3>Unused Variables: Error, Not Warning</h3>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"javascript\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"javascript\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#6A9955\">// Using default from @typescript-eslint/recommended</span></span>\n<span data-line=\"\"><span style=\"color:#CE9178\">&quot;@typescript-eslint/no-unused-vars&quot;</span><span style=\"color:#E6E6E6\">: </span><span style=\"color:#CE9178\">&quot;error&quot;</span><span style=\"color:#E6E6E6\">,</span></span></code></div></pre></figure>\n<p><strong>Why error?</strong> I&#x27;ve experimented with making this a warning in the past. People ignored warnings and things that aren&#x27;t in PR checks. Unused variables pollute the codebase and create confusion:</p>\n<ul>\n<li>Are they used somewhere I can&#x27;t see?</li>\n<li>Is this old code that should be removed?</li>\n<li>Is this a typo? Did they mean to use it?</li>\n<li>A tiny bit less context for those llms 😄</li>\n</ul>\n<p>Making it an error forces the cleanup. The cost is minimal - just remove the variable or prefix it with underscore if it&#x27;s intentionally unused:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"typescript\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"typescript\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#E6E6E6\"> [</span><span style=\"color:#9CDCFE\">_data</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">setData</span><span style=\"color:#E6E6E6\">] </span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#DCDCAA\"> useState</span><span style=\"color:#E6E6E6\">() </span><span style=\"color:#6A9955\">// Intentionally unused</span></span></code></div></pre></figure>\n<h3>No Stylistic Linting</h3>\n<p><strong>What&#x27;s missing?</strong> Notice we&#x27;re not using <code>@typescript-eslint/stylistic</code> rules for things like:</p>\n<ul>\n<li>Semicolons</li>\n<li>Quotes (single vs double)</li>\n<li>Trailing commas</li>\n<li>Indentation</li>\n<li>Line length</li>\n</ul>\n<p><strong>Why?</strong> This is Prettier&#x27;s job. Running both ESLint stylistic rules and Prettier creates conflict:</p>\n<ol>\n<li>ESLint auto-fix changes formatting</li>\n<li>Prettier auto-format changes it back</li>\n<li>ESLint complains again</li>\n<li>Infinite loop of fixes</li>\n</ol>\n<p><strong>Single source of truth:</strong> Prettier handles all formatting. ESLint handles logic and bug prevention. No overlap, no conflicts.</p>\n<h2>Prettier: The One True Formatter</h2>\n<p>While ESLint handles logic, Prettier handles style. The beauty of Prettier is captured in the Go community&#x27;s saying about <code>gofmt</code>:</p>\n<blockquote>\n<p>&quot;Gofmt is no one&#x27;s favorite, yet gofmt is everyone&#x27;s favorite.&quot;</p>\n</blockquote>\n<p>Prettier embodies this philosophy. You might not agree with every decision, but having <em>one</em> decision that everyone follows is more valuable than having the &quot;perfect&quot; style.</p>\n<h3>The Configuration</h3>\n<p>Here&#x27;s my minimal Prettier config:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"json\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"json\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#E6E6E6\">{</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  &quot;semi&quot;</span><span style=\"color:#E6E6E6\">: </span><span style=\"color:#569CD6\">false</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span></code></div></pre></figure>\n<p><strong>That&#x27;s it.</strong> Everything else uses Prettier&#x27;s defaults.</p>\n<h3>Why <code>semi: false</code>?</h3>\n<p>You&#x27;ll often hear these arguments for why semicolons are &quot;best practice&quot; in JavaScript:</p>\n<blockquote>\n<p><strong>Clarity and Readability</strong> - Explicit semicolons clearly delineate the end of a statement, making the code easier to read and understand.</p>\n<p><strong>Avoiding Ambiguity and Bugs</strong> - ASI can sometimes lead to unexpected behavior or subtle bugs, particularly when line breaks are placed in unusual locations. Explicit semicolons eliminate this ambiguity.</p>\n<p><strong>Consistency with C-style Languages</strong> - If you work with other C-style languages (like Java, C++, or C#) that require semicolons, using them in JavaScript maintains consistency.</p>\n<p><strong>Team Standards and Linters</strong> - Many professional development environments and teams adopt coding style guides that mandate the use of semicolons to ensure code quality and consistency.</p>\n</blockquote>\n<p>These sound reasonable, but I&#x27;ve found the opposite to be true in practice. I picked up the no-semicolon approach from <a href=\"https://standardjs.com/rules.html#semicolons\">StandardJS</a>, which provides compelling arguments along with links to these three deep-dives:</p>\n<ul>\n<li><a href=\"https://blog.izs.me/2010/12/an-open-letter-to-javascript-leaders-regarding/\">An Open Letter to JavaScript Leaders Regarding Semicolons</a> by Isaac Z. Schlueter</li>\n<li><a href=\"https://web.archive.org/web/20201206065632/http://inimino.org/~inimino/blog/javascript_semicolons\">JavaScript Semicolon Insertion: Everything you need to know</a> by inimino</li>\n<li><a href=\"https://www.youtube.com/watch?v=gsfbh17Ax9I\">Are Semicolons Necessary in JavaScript?</a> video discussion</li>\n</ul>\n<h4>My Counterpoints</h4>\n<p><strong>&quot;Clarity and readability&quot;</strong> - Actually, semicolons add visual noise for a character that&#x27;s not needed. Removing them makes code cleaner and faster to parse.</p>\n<p><strong>&quot;Avoiding ambiguity and bugs&quot;</strong> - This hasn&#x27;t caused a bug for me or my teams in the last 10 years of not using semicolons. Automatic semicolon insertion handles edge cases, and unreachable code on returns is quite obvious. When semicolons are actually needed, Prettier automatically inserts them, so we can see explicitly where they&#x27;ve been injected purposefully.</p>\n<p><strong>&quot;Consistency with C-style languages&quot;</strong> - <a href=\"https://go.dev/\">Go</a>, which I consider one of the best designed modern languages, also forgoes semicolons (and Python, though do we even count that?). To be fair, Rust has chosen semicolons, but Go is a modern C-style language that explicitly chose to remove them from their standard formatter.</p>\n<p>It&#x27;s worth noting that Go&#x27;s designers — Robert Griesemer, Rob Pike, and Ken Thompson — had extensive experience with C and systems programming. Ken Thompson co-created the C programming language and Unix, and Rob Pike worked at Bell Labs on Plan 9. Their deep understanding of C&#x27;s strengths and weaknesses heavily influenced Go&#x27;s design, including the decision to drop mandatory semicolons.</p>\n<p>As explained in <a href=\"https://medium.com/golangspec/automatic-semicolon-insertion-in-go-1990338f2649\">Automatic Semicolon Insertion in Go</a>:</p>\n<blockquote>\n<p>What is the reason for language designers to even start working on getting rid of tokens like semicolons? The answer is quite simple. It&#x27;s all about readability. The less artifacts code has, the easier it&#x27;s to work with. It&#x27;s important since once written piece of code will be probably read many times by different people.</p>\n</blockquote>\n<p><strong>&quot;Team standards and linters&quot;</strong> - Prettier is exactly what this is for, so the style is what&#x27;s in the repo.</p>\n<h4>My Two Reasons for No Semicolons</h4>\n<p><strong>1. Code clarity</strong> - Knowing where semicolons are needed and removing visual clutter makes code appear cleaner and easier/faster to parse.</p>\n<p><strong>2. Moving code around is easier</strong> - Without trailing semicolons, refactoring becomes more fluid. Think about operations like <code>.map().filter()</code> where you&#x27;re constantly adding, removing, or reordering chains.</p>\n<p>Consider this common scenario:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"typescript\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"typescript\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#6A9955\">// With semicolons - awkward to refactor</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> users</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> getUsers</span><span style=\"color:#E6E6E6\">();</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> activeUsers</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#9CDCFE\"> users</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">filter</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">u</span><span style=\"color:#569CD6\"> =&gt;</span><span style=\"color:#9CDCFE\"> u</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">active</span><span style=\"color:#E6E6E6\">);</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// Want to chain another operation? You need to:</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// 1. Remove the semicolon from line 1</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// 2. Add your new operation</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> users</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> getUsers</span><span style=\"color:#E6E6E6\">()</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  .</span><span style=\"color:#DCDCAA\">filter</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">u</span><span style=\"color:#569CD6\"> =&gt;</span><span style=\"color:#9CDCFE\"> u</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">active</span><span style=\"color:#E6E6E6\">);</span></span></code></div></pre></figure>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"typescript\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"typescript\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#6A9955\">// Without semicolons - natural refactoring</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> users</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> getUsers</span><span style=\"color:#E6E6E6\">()</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> activeUsers</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#9CDCFE\"> users</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">filter</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">u</span><span style=\"color:#569CD6\"> =&gt;</span><span style=\"color:#9CDCFE\"> u</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">active</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// Want to chain? Just move the code</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> users</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> getUsers</span><span style=\"color:#E6E6E6\">()</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  .</span><span style=\"color:#DCDCAA\">filter</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">u</span><span style=\"color:#569CD6\"> =&gt;</span><span style=\"color:#9CDCFE\"> u</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">active</span><span style=\"color:#E6E6E6\">)</span></span></code></div></pre></figure>\n<p>When copying and pasting code, adding filters, or splitting chains, semicolons create friction. Remove them, and code movement becomes fluid.</p>\n<p><a href=\"https://mianlabs.com/2022/06/05/why-you-dont-really-need-semicolons-in-javascript-and-typescript-anymore/\">JavaScript&#x27;s Automatic Semicolon Insertion (ASI)</a> handles this automatically, and TypeScript catches the edge cases where ASI might cause problems.</p>\n<p><strong>Note:</strong> I&#x27;ll change code to use semicolons just to speed things up when needed, but more for saving time from bikeshedding this one and less because I think they&#x27;re necessary.</p>\n<h3>Biome: A Modern Alternative</h3>\n<p><a href=\"https://biomejs.dev/formatter/option-philosophy/\">Biome</a> is a newer formatter that&#x27;s gaining traction. It&#x27;s:</p>\n<ul>\n<li><strong>Faster</strong> than Prettier (written in Rust)</li>\n<li><strong>Opinionated</strong> with even fewer configuration options</li>\n<li><strong>Combined</strong> linter and formatter in one tool</li>\n</ul>\n<p>Biome&#x27;s philosophy: &quot;A formatter should have as few options as possible.&quot; They provide even less configuration than Prettier, which is a good thing - fewer decisions mean less bikeshedding.</p>\n<p>If you&#x27;re starting a new project and want bleeding-edge tooling, Biome is worth considering. For existing projects, Prettier&#x27;s ecosystem maturity is hard to beat.</p>\n<h3>Key Takeaway</h3>\n<p>Pick <strong>one</strong> formatter (Prettier or Biome), configure it minimally, and enforce it everywhere. The specific choices matter less than everyone using the same tool.</p>\n<h2>Migration Strategy: Grandfathering Exhaustive Deps</h2>\n<p>The hardest rule to adopt is <code>react-hooks/exhaustive-deps</code>. In a large codebase, you might have hundreds of violations. Fixing them all at once is impractical and risky.</p>\n<p>Here&#x27;s the strategy:</p>\n<ol>\n<li>Add the rule as an error</li>\n<li>Run a script to add <code>eslint-disable-next-line</code> to all existing violations</li>\n<li>Fix them incrementally over time</li>\n<li>New code must follow the rule</li>\n</ol>\n<h3>The Script</h3>\n<p>Save this as <code>scripts/eslint-one-time-allow.ts</code>:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"typescript\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"typescript\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#6A9955\">// This script reads all the .ts and .tsx files in the src directory and disables</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// all the eslint errors of a specific rule (default: react-hooks/exhaustive-deps).</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// This is intended to be used to grandfather existing problems so that future</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// problems can be fixed. It may also be useful to disable other future eslint</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// errors that need to be allowed until such a time that they can be fixed.</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// usage: tsx scripts/eslint-mass-allow.ts --add-allow-comments</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// usage: tsx scripts/eslint-mass-allow.ts --rule @typescript-eslint/no-unused-vars --add-allow-comments</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">Command</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &quot;@commander-js/extra-typings&quot;</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#569CD6\"> *</span><span style=\"color:#C586C0\"> as</span><span style=\"color:#9CDCFE\"> fs</span><span style=\"color:#C586C0\"> from</span><span style=\"color:#CE9178\"> &quot;fs&quot;</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">ESLint</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &quot;eslint&quot;</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> program</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#569CD6\"> new</span><span style=\"color:#DCDCAA\"> Command</span><span style=\"color:#E6E6E6\">()</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">program</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  .</span><span style=\"color:#DCDCAA\">name</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&quot;eslint-mass-allow&quot;</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  .</span><span style=\"color:#DCDCAA\">description</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&quot;Mass fix ESLint errors by adding disable comments&quot;</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  .</span><span style=\"color:#DCDCAA\">option</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&quot;--add-allow-comments&quot;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#CE9178\">&quot;Actually add the allow comments (dry run by default)&quot;</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  .</span><span style=\"color:#DCDCAA\">option</span><span style=\"color:#E6E6E6\">(</span></span>\n<span data-line=\"\"><span style=\"color:#CE9178\">    &quot;--rule &lt;rule&gt;&quot;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#CE9178\">    &quot;ESLint rule to allow&quot;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#CE9178\">    &quot;react-hooks/exhaustive-deps&quot;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  )</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  .</span><span style=\"color:#DCDCAA\">parse</span><span style=\"color:#E6E6E6\">()</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> options</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#9CDCFE\"> program</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">opts</span><span style=\"color:#E6E6E6\">() </span><span style=\"color:#C586C0\">as</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">addAllowComments</span><span style=\"color:#D4D4D4\">?:</span><span style=\"color:#4EC9B0\"> boolean</span><span style=\"color:#E6E6E6\">; </span><span style=\"color:#9CDCFE\">rule</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#4EC9B0\"> string</span><span style=\"color:#E6E6E6\"> }</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">async</span><span style=\"color:#569CD6\"> function</span><span style=\"color:#DCDCAA\"> main</span><span style=\"color:#E6E6E6\">() {</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">  const</span><span style=\"color:#9CDCFE\"> eslint</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#569CD6\"> new</span><span style=\"color:#DCDCAA\"> ESLint</span><span style=\"color:#E6E6E6\">()</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">  const</span><span style=\"color:#9CDCFE\"> results</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#C586C0\"> await</span><span style=\"color:#9CDCFE\"> eslint</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">lintFiles</span><span style=\"color:#E6E6E6\">([</span><span style=\"color:#CE9178\">&quot;src/**/*.ts&quot;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#CE9178\">&quot;src/**/*.tsx&quot;</span><span style=\"color:#E6E6E6\">])</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">  let</span><span style=\"color:#9CDCFE\"> totalErrors</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#B5CEA8\"> 0</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">  let</span><span style=\"color:#9CDCFE\"> filesToFix</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#B5CEA8\"> 0</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  for</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> result</span><span style=\"color:#569CD6\"> of</span><span style=\"color:#9CDCFE\"> results</span><span style=\"color:#E6E6E6\">) {</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">    if</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#9CDCFE\">result</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">errorCount</span><span style=\"color:#D4D4D4\"> &gt;</span><span style=\"color:#B5CEA8\"> 0</span><span style=\"color:#E6E6E6\">) {</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">      const</span><span style=\"color:#9CDCFE\"> targetErrors</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#9CDCFE\"> result</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">messages</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">filter</span><span style=\"color:#E6E6E6\">(</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">        (</span><span style=\"color:#9CDCFE\">message</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#4EC9B0\"> any</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#9CDCFE\"> message</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">ruleId</span><span style=\"color:#D4D4D4\"> ===</span><span style=\"color:#9CDCFE\"> options</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">rule</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">      )</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">      if</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#9CDCFE\">targetErrors</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">length</span><span style=\"color:#D4D4D4\"> &gt;</span><span style=\"color:#B5CEA8\"> 0</span><span style=\"color:#E6E6E6\">) {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">        filesToFix</span><span style=\"color:#D4D4D4\">++</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">        totalErrors</span><span style=\"color:#D4D4D4\"> +=</span><span style=\"color:#9CDCFE\"> targetErrors</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">length</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">        console</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">log</span><span style=\"color:#E6E6E6\">(</span></span>\n<span data-line=\"\"><span style=\"color:#CE9178\">          `</span><span style=\"color:#569CD6\">${</span><span style=\"color:#9CDCFE\">result</span><span style=\"color:#D4D4D4\">.</span><span style=\"color:#9CDCFE\">filePath</span><span style=\"color:#569CD6\">}</span><span style=\"color:#CE9178\">: </span><span style=\"color:#569CD6\">${</span><span style=\"color:#9CDCFE\">targetErrors</span><span style=\"color:#D4D4D4\">.</span><span style=\"color:#9CDCFE\">length</span><span style=\"color:#569CD6\">}</span><span style=\"color:#569CD6\"> ${</span><span style=\"color:#9CDCFE\">options</span><span style=\"color:#D4D4D4\">.</span><span style=\"color:#9CDCFE\">rule</span><span style=\"color:#569CD6\">}</span><span style=\"color:#CE9178\"> errors`</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">        )</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">        if</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#9CDCFE\">options</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">addAllowComments</span><span style=\"color:#E6E6E6\">) {</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">          const</span><span style=\"color:#9CDCFE\"> fileContent</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#9CDCFE\"> fs</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">readFileSync</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">result</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">filePath</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#CE9178\">&quot;utf-8&quot;</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">            .</span><span style=\"color:#DCDCAA\">split</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&quot;</span><span style=\"color:#D7BA7D\">\\n</span><span style=\"color:#CE9178\">&quot;</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">          let</span><span style=\"color:#9CDCFE\"> offset</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#B5CEA8\"> 0</span><span style=\"color:#6A9955\"> // Track the number of lines added</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">          for</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> message</span><span style=\"color:#569CD6\"> of</span><span style=\"color:#9CDCFE\"> targetErrors</span><span style=\"color:#E6E6E6\">) {</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">            const</span><span style=\"color:#9CDCFE\"> lineIndex</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#9CDCFE\"> message</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">line</span><span style=\"color:#D4D4D4\"> -</span><span style=\"color:#B5CEA8\"> 1</span><span style=\"color:#D4D4D4\"> +</span><span style=\"color:#9CDCFE\"> offset</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">            fileContent</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">splice</span><span style=\"color:#E6E6E6\">(</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">              lineIndex</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#B5CEA8\">              0</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#CE9178\">              `// eslint-disable-next-line </span><span style=\"color:#569CD6\">${</span><span style=\"color:#9CDCFE\">message</span><span style=\"color:#D4D4D4\">.</span><span style=\"color:#9CDCFE\">ruleId</span><span style=\"color:#569CD6\">}</span><span style=\"color:#CE9178\">`</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">            )</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">            offset</span><span style=\"color:#D4D4D4\">++</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">          }</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">          fs</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">writeFileSync</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">result</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">filePath</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">fileContent</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">join</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&quot;</span><span style=\"color:#D7BA7D\">\\n</span><span style=\"color:#CE9178\">&quot;</span><span style=\"color:#E6E6E6\">), </span><span style=\"color:#CE9178\">&quot;utf-8&quot;</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">        }</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">      }</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    }</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  }</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  console</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">log</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">`</span><span style=\"color:#D7BA7D\">\\n</span><span style=\"color:#CE9178\">Summary:`</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  console</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">log</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">`Files with </span><span style=\"color:#569CD6\">${</span><span style=\"color:#9CDCFE\">options</span><span style=\"color:#D4D4D4\">.</span><span style=\"color:#9CDCFE\">rule</span><span style=\"color:#569CD6\">}</span><span style=\"color:#CE9178\"> errors: </span><span style=\"color:#569CD6\">${</span><span style=\"color:#9CDCFE\">filesToFix</span><span style=\"color:#569CD6\">}</span><span style=\"color:#CE9178\">`</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  console</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">log</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">`Total errors: </span><span style=\"color:#569CD6\">${</span><span style=\"color:#9CDCFE\">totalErrors</span><span style=\"color:#569CD6\">}</span><span style=\"color:#CE9178\">`</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  if</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#D4D4D4\">!</span><span style=\"color:#9CDCFE\">options</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">addAllowComments</span><span style=\"color:#E6E6E6\">) {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    console</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">log</span><span style=\"color:#E6E6E6\">(</span></span>\n<span data-line=\"\"><span style=\"color:#CE9178\">      `</span><span style=\"color:#D7BA7D\">\\n</span><span style=\"color:#CE9178\">This was a dry run. Use --add-allow-comments to apply the changes.`</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    )</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  } </span><span style=\"color:#C586C0\">else</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    console</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">log</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">`</span><span style=\"color:#D7BA7D\">\\n</span><span style=\"color:#CE9178\">Added allow comments for </span><span style=\"color:#569CD6\">${</span><span style=\"color:#9CDCFE\">totalErrors</span><span style=\"color:#569CD6\">}</span><span style=\"color:#CE9178\"> errors in </span><span style=\"color:#569CD6\">${</span><span style=\"color:#9CDCFE\">filesToFix</span><span style=\"color:#569CD6\">}</span><span style=\"color:#CE9178\"> files.`</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  }</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">main</span><span style=\"color:#E6E6E6\">()</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  .</span><span style=\"color:#DCDCAA\">then</span><span style=\"color:#E6E6E6\">(() </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    console</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">info</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&quot;Done&quot;</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  })</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  .</span><span style=\"color:#DCDCAA\">catch</span><span style=\"color:#E6E6E6\">((</span><span style=\"color:#9CDCFE\">error</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    console</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">error</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&quot;Error:&quot;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">error</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  })</span></span></code></div></pre></figure>\n<h3>Using the Script</h3>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"bash\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"bash\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#6A9955\"># Dry run - see what would be allowed</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">npx</span><span style=\"color:#CE9178\"> tsx</span><span style=\"color:#CE9178\"> scripts/eslint-mass-allow.ts</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\"># Actually add the allow comments</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">npx</span><span style=\"color:#CE9178\"> tsx</span><span style=\"color:#CE9178\"> scripts/eslint-mass-allow.ts</span><span style=\"color:#CE9178\"> --add-allow-comments</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\"># Allow a different rule (e.g., unused variables)</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">npx</span><span style=\"color:#CE9178\"> tsx</span><span style=\"color:#CE9178\"> scripts/eslint-mass-allow.ts</span><span style=\"color:#CE9178\"> --rule</span><span style=\"color:#CE9178\"> @typescript-eslint/no-unused-vars</span><span style=\"color:#CE9178\"> --add-allow-comments</span></span></code></div></pre></figure>\n<p>This approach lets you:</p>\n<ol>\n<li>Enable strict rules immediately</li>\n<li>Not break your existing codebase</li>\n<li>Fix violations incrementally</li>\n<li>Prevent new violations from being added</li>\n</ol>\n<h2>The Modern Config Format</h2>\n<p>You might have noticed we&#x27;re using <code>defineConfig()</code> instead of the older <code>tseslint.config()</code>:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"javascript\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"javascript\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">defineConfig</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &quot;eslint/config&quot;</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#C586C0\"> default</span><span style=\"color:#DCDCAA\"> defineConfig</span><span style=\"color:#E6E6E6\">(</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  eslint</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">configs</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">recommended</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  tseslint</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">configs</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">recommended</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">  // No spread operators needed!</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">)</span></span></code></div></pre></figure>\n<p>This is the new recommended approach from typescript-eslint. Benefits:</p>\n<ul>\n<li>Cleaner syntax (no spread operators)</li>\n<li>Better type inference</li>\n<li>Aligns with ESLint 9&#x27;s flat config philosophy</li>\n</ul>\n<p>If you&#x27;re still using the old format:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"javascript\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"javascript\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#C586C0\"> default</span><span style=\"color:#9CDCFE\"> tseslint</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">config</span><span style=\"color:#E6E6E6\">(</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  eslint</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">configs</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">recommended</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#D4D4D4\">  ...</span><span style=\"color:#9CDCFE\">tseslint</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">configs</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">recommended</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#6A9955\">// Spreading required</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">)</span></span></code></div></pre></figure>\n<p>Update to the new format. It&#x27;s a simple find-and-replace.</p>\n<h2>Practical Tips for Teams</h2>\n<h3>Rolling It Out</h3>\n<ol>\n<li><strong>Start with recommended configs</strong> - Don&#x27;t customize too much initially</li>\n<li><strong>Add rules incrementally</strong> - One rule at a time, with team discussion</li>\n<li><strong>Use the grandfathering script</strong> - For rules with many violations</li>\n<li><strong>Document your reasoning</strong> - Keep a <code>LINTING.md</code> file explaining each custom rule</li>\n</ol>\n<h3>When to Use eslint-disable</h3>\n<p>Inline disable comments are fine when:</p>\n<ul>\n<li>You&#x27;re interfacing with untyped third-party code</li>\n<li>The rule doesn&#x27;t apply to your specific case</li>\n<li>You&#x27;re intentionally using a pattern for a good reason</li>\n</ul>\n<p>Just make it explicit:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"typescript\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"typescript\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#6A9955\">// We need any here because the API returns dynamic structure</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// eslint-disable-next-line @typescript-eslint/no-explicit-any</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> data</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#4EC9B0\"> any</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#C586C0\"> await</span><span style=\"color:#DCDCAA\"> fetchDynamicData</span><span style=\"color:#E6E6E6\">()</span></span></code></div></pre></figure>\n<h3>CI/CD Integration</h3>\n<p>Run linting before tests:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"yaml\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"yaml\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#E6E6E6\">- </span><span style=\"color:#569CD6\">name</span><span style=\"color:#E6E6E6\">: </span><span style=\"color:#CE9178\">Lint</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">  run</span><span style=\"color:#E6E6E6\">: </span><span style=\"color:#CE9178\">npm run lint</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">- </span><span style=\"color:#569CD6\">name</span><span style=\"color:#E6E6E6\">: </span><span style=\"color:#CE9178\">Type Check</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">  run</span><span style=\"color:#E6E6E6\">: </span><span style=\"color:#CE9178\">npm run typecheck</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">- </span><span style=\"color:#569CD6\">name</span><span style=\"color:#E6E6E6\">: </span><span style=\"color:#CE9178\">Test</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">  run</span><span style=\"color:#E6E6E6\">: </span><span style=\"color:#CE9178\">npm test</span></span></code></div></pre></figure>\n<p>Fast feedback on code quality issues.</p>\n<h2>Future Considerations</h2>\n<h3>Type-Aware Linting</h3>\n<p>The configuration above uses <code>tseslint.configs.recommended</code>. For deeper type checking, consider:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"javascript\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"javascript\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#C586C0\"> default</span><span style=\"color:#DCDCAA\"> defineConfig</span><span style=\"color:#E6E6E6\">(</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  eslint</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">configs</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">recommended</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#D4D4D4\">  ...</span><span style=\"color:#9CDCFE\">tseslint</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">configs</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">recommendedTypeChecked</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    languageOptions:</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">      parserOptions:</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">        project:</span><span style=\"color:#CE9178\"> &#x27;./tsconfig.json&#x27;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">      },</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    },</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  },</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">)</span></span></code></div></pre></figure>\n<p>This enables rules like:</p>\n<ul>\n<li><code>@typescript-eslint/no-floating-promises</code> - Catch unhandled promises</li>\n<li><code>@typescript-eslint/no-misused-promises</code> - Type-check async/await usage</li>\n<li><code>@typescript-eslint/await-thenable</code> - Only await actual promises</li>\n</ul>\n<p><strong>Trade-off:</strong> Significantly slower linting (requires full TypeScript compilation).</p>\n<h3>Strict Mode</h3>\n<p>For new projects with no legacy code:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"javascript\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"javascript\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#C586C0\"> default</span><span style=\"color:#DCDCAA\"> defineConfig</span><span style=\"color:#E6E6E6\">(</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  eslint</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">configs</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">recommended</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  tseslint</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">configs</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">strict</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#6A9955\">// Instead of recommended</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  tseslint</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">configs</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">stylistic</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">  // ... your rules</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">)</span></span></code></div></pre></figure>\n<p>The <code>strict</code> preset includes more opinionated rules that catch subtle bugs.</p>\n<h2>Key Takeaways</h2>\n<ol>\n<li><strong>ESLint catches bugs, Prettier handles style</strong> - Don&#x27;t overlap</li>\n<li><strong>Errors block CI, warnings get ignored</strong> - Use errors for things that matter</li>\n<li><strong>Grandfather legacy code</strong> - Use the mass-allow script for incremental adoption</li>\n<li><strong>Document your reasoning</strong> - Help your team understand <em>why</em> each rule exists</li>\n<li><strong>The new flat config is better</strong> - Use <code>defineConfig()</code> with ESLint 9</li>\n</ol>\n<h2>The Result</h2>\n<p>This configuration has been battle-tested across multiple teams and projects. It:</p>\n<ul>\n<li>Catches real bugs before they hit production</li>\n<li>Doesn&#x27;t get in the way during development</li>\n<li>Plays nicely with other tools</li>\n<li>Can be adopted incrementally</li>\n</ul>\n<p>Most importantly, developers don&#x27;t fight it. When ESLint complains, there&#x27;s usually a good reason.</p>\n<hr/>\n<p><em>Have a different approach to ESLint configuration? I&#x27;d love to hear what works for your team!</em></p>",
            "url": "https://alexmchan.com/blog/2025-10-21-pragmatic-eslint",
            "title": "Pragmatic ESLint: My recommendations",
            "summary": "A philosophy-driven approach to ESLint configuration that catches bugs without getting in your way, with practical migration strategies for real codebases.",
            "date_modified": "2025-10-21T00:00:00.000Z",
            "author": {
                "name": "Alex Chan"
            }
        },
        {
            "id": "https://alexmchan.com/blog/2025-10-17-msw-jest-vite",
            "content_html": "<h1>Testing with MSW jest and vitest</h1>\n<p>If you&#x27;ve tried setting up Mock Service Worker (MSW) v2 with Jest, you&#x27;ve probably hit this error:</p>\n<pre class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code>SyntaxError: Unexpected token &#x27;export&#x27;\n</code></div></pre>\n<p>I spent an afternoon figuring out why, and the answer reveals something important about the JavaScript testing ecosystem in 2024.</p>\n<h2>The Problem: ESM in a CommonJS World</h2>\n<p>MSW v2 embraced modern JavaScript by shipping as <strong>ESM (ECMAScript Modules)</strong>. This is great for the ecosystem, but it creates a challenge: most test runners still operate in a CommonJS world.</p>\n<p>When Jest tries to run your tests, it encounters ESM code in <code>node_modules</code> and doesn&#x27;t know what to do with it. By default, Jest ignores everything in <code>node_modules</code> - it assumes packages are pre-compiled and ready to run.</p>\n<p>But MSW v2 isn&#x27;t. Neither is its dependency <code>until-async</code>. They contain raw <code>export</code> statements that Node&#x27;s CommonJS loader can&#x27;t parse.</p>\n<h2>The Journey to a Solution</h2>\n<h3>Attempt 1: &quot;Just add msw to transformIgnorePatterns&quot;</h3>\n<p>My first attempt was the standard advice you&#x27;ll find online:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"javascript\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"javascript\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#E6E6E6\">transformIgnorePatterns: [</span></span>\n<span data-line=\"\"><span style=\"color:#CE9178\">  &#x27;node_modules/(?!(msw)/)&#x27;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">]</span></span></code></div></pre></figure>\n<p><strong>Result:</strong> Still broken. The error was coming from <code>until-async</code>, a transitive dependency.</p>\n<h3>Attempt 2: &quot;List all MSW dependencies&quot;</h3>\n<p>Okay, let me enumerate all the ESM packages:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"javascript\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"javascript\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#E6E6E6\">transformIgnorePatterns: [</span></span>\n<span data-line=\"\"><span style=\"color:#CE9178\">  &#x27;node_modules/(?!(msw|@mswjs|@open-draft|until-async|...)/)&#x27;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">]</span></span></code></div></pre></figure>\n<p><strong>Result:</strong> Still broken. The regex wasn&#x27;t matching packages in pnpm&#x27;s nested structure: <code>node_modules/.pnpm/until-async@3.0.2/node_modules/until-async/</code></p>\n<h3>Attempt 3: Nuclear option</h3>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"javascript\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"javascript\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#E6E6E6\">transformIgnorePatterns: []</span></span></code></div></pre></figure>\n<p><strong>Finally!</strong> Tests pass. But now Jest is transforming <em>everything</em> in <code>node_modules</code>, including all of axios, and any other package. This works but doesn&#x27;t scale.</p>\n<h3>Attempt 4: The Goldilocks solution</h3>\n<p>The breakthrough was understanding that the pattern needs to match the package name <strong>anywhere in the path</strong>, not just immediately after <code>node_modules/</code>:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"javascript\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"javascript\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#E6E6E6\">transformIgnorePatterns: [</span></span>\n<span data-line=\"\"><span style=\"color:#CE9178\">  &#x27;node_modules/(?!.*(msw|until-async))&#x27;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">]</span></span></code></div></pre></figure>\n<p>This pattern:</p>\n<ul>\n<li>Works with npm, yarn, and pnpm&#x27;s nested structures</li>\n<li>Only transforms the packages that actually need it</li>\n<li>Keeps the transform list minimal for maintainability</li>\n</ul>\n<h2>The Full Jest Configuration</h2>\n<p>Here&#x27;s what you actually need for Jest + MSW v2:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"javascript\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"javascript\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#6A9955\">// jest.config.js</span></span>\n<span data-line=\"\"><span style=\"color:#4EC9B0\">module</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#4EC9B0\">exports</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  preset:</span><span style=\"color:#CE9178\"> &#x27;ts-jest&#x27;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  testEnvironment:</span><span style=\"color:#CE9178\"> &#x27;node&#x27;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  setupFilesAfterEnv:</span><span style=\"color:#E6E6E6\"> [</span><span style=\"color:#CE9178\">&#x27;&lt;rootDir&gt;/src/setupTests.ts&#x27;</span><span style=\"color:#E6E6E6\">],</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">  // The critical part: transform MSW&#x27;s ESM dependencies</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  transformIgnorePatterns:</span><span style=\"color:#E6E6E6\"> [</span></span>\n<span data-line=\"\"><span style=\"color:#CE9178\">    &#x27;node_modules/(?!.*(msw|until-async))&#x27;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  ],</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">  // ts-jest needs to transform both TS and JS files</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  transform:</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#CE9178\">    &#x27;^.+</span><span style=\"color:#D7BA7D\">\\\\</span><span style=\"color:#CE9178\">.tsx?$&#x27;</span><span style=\"color:#9CDCFE\">:</span><span style=\"color:#CE9178\"> &#x27;ts-jest&#x27;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#CE9178\">    &#x27;^.+</span><span style=\"color:#D7BA7D\">\\\\</span><span style=\"color:#CE9178\">.jsx?$&#x27;</span><span style=\"color:#9CDCFE\">:</span><span style=\"color:#E6E6E6\"> [</span><span style=\"color:#CE9178\">&#x27;ts-jest&#x27;</span><span style=\"color:#E6E6E6\">, {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">      tsconfig:</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">        allowJs:</span><span style=\"color:#569CD6\"> true</span><span style=\"color:#E6E6E6\">,  </span><span style=\"color:#6A9955\">// Allow ts-jest to process JavaScript</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">      },</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    }],</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  },</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">};</span></span></code></div></pre></figure>\n<p><strong>Why the JS transform matters:</strong> By default, <code>ts-jest</code> only transforms TypeScript files. But MSW&#x27;s dependencies are JavaScript. The explicit <code>transform</code> config with <code>allowJs: true</code> tells TypeScript&#x27;s compiler to process those JS files too.</p>\n<h2>The Vitest Alternative</h2>\n<p>Want to skip all that complexity? Here&#x27;s the equivalent Vitest config:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"typescript\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"typescript\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#6A9955\">// vitest.config.ts</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">defineConfig</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;vitest/config&#x27;</span><span style=\"color:#E6E6E6\">;</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#C586C0\"> default</span><span style=\"color:#DCDCAA\"> defineConfig</span><span style=\"color:#E6E6E6\">({</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  test:</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    globals:</span><span style=\"color:#569CD6\"> true</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    environment:</span><span style=\"color:#CE9178\"> &#x27;node&#x27;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    setupFiles:</span><span style=\"color:#E6E6E6\"> [</span><span style=\"color:#CE9178\">&#x27;./src/setupTests.ts&#x27;</span><span style=\"color:#E6E6E6\">],</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    include:</span><span style=\"color:#E6E6E6\"> [</span><span style=\"color:#CE9178\">&#x27;src/**/*.test.ts&#x27;</span><span style=\"color:#E6E6E6\">],</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  },</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">});</span></span></code></div></pre></figure>\n<p><strong>That&#x27;s it.</strong> No <code>transformIgnorePatterns</code>. No special handling for ESM. No listing dependencies.</p>\n<p>Vitest uses Vite under the hood, which was designed for the ESM era. It handles MSW&#x27;s modern module format natively.</p>\n<h2>Performance Comparison</h2>\n<p>I ran the same two tests with both runners:</p>\n<table><thead><tr><th>Test Runner</th><th>Time</th><th>Configuration Complexity</th></tr></thead><tbody><tr><td>Jest</td><td>1.08s</td><td>High (transform patterns, allowJs, etc.)</td></tr><tr><td>Vitest</td><td>0.38s</td><td>Low (basic test config only)</td></tr></tbody></table>\n<p>Vitest is <strong>2.8x faster</strong> and requires significantly less configuration.</p>\n<h2>Key Takeaways</h2>\n<h3>1. The <code>transformIgnorePatterns</code> breakthrough</h3>\n<p>The pattern <code>&#x27;node_modules/(?!.*(msw|until-async))&#x27;</code> is the key. It matches package names at any nesting level, so it works with:</p>\n<ul>\n<li>npm/yarn: <code>node_modules/msw/lib/index.js</code></li>\n<li>pnpm: <code>node_modules/.pnpm/until-async@3.0.2/node_modules/until-async/lib/index.js</code></li>\n</ul>\n<h3>2. Start minimal, expand when needed</h3>\n<p>I initially listed 8+ packages in the transform pattern. In reality, only <code>msw</code> and <code>until-async</code> needed it.</p>\n<p><strong>Principle:</strong> Add packages to the transform list only when you encounter actual errors. This keeps your config maintainable and your transform step fast.</p>\n<h3>3. Vitest is ready for modern JavaScript</h3>\n<p>Jest was designed in 2014 when CommonJS was the standard. It works great, but it shows its age when dealing with ESM.</p>\n<p>Vitest was designed in 2021 with ESM as a first-class citizen. The difference is striking.</p>\n<h3>4. TypeScript needs <code>allowJs: true</code></h3>\n<p>When using <code>ts-jest</code>, you need to explicitly allow JavaScript transformation:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"javascript\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"javascript\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#CE9178\">&#x27;^.+</span><span style=\"color:#D7BA7D\">\\\\</span><span style=\"color:#CE9178\">.jsx?$&#x27;</span><span style=\"color:#E6E6E6\">: [</span><span style=\"color:#CE9178\">&#x27;ts-jest&#x27;</span><span style=\"color:#E6E6E6\">, {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  tsconfig:</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">allowJs:</span><span style=\"color:#569CD6\"> true</span><span style=\"color:#E6E6E6\"> },</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}],</span></span></code></div></pre></figure>\n<p>Without this, ts-jest skips the ESM files in node_modules, and they remain unparseable.</p>\n<h2>Recommendations</h2>\n<p><strong>For new projects:</strong> Use Vitest. The simplicity speaks for itself.</p>\n<p><strong>For existing Jest projects:</strong> Use the optimized pattern shown above. It scales well and works across package managers.</p>\n<p><strong>When debugging:</strong> If you hit ESM errors, check the actual file path in the error message. That tells you exactly which package needs to be added to your transform pattern.</p>\n<h2>The Code</h2>\n<p>Want to see the full setup? Check out the demo repo: <a href=\"https://github.com/alexanderchan/jest-with-msw\">jest-with-msw</a></p>\n<p>The repo includes:</p>\n<ul>\n<li>Working Jest configuration with minimal transform pattern</li>\n<li>Working Vitest configuration</li>\n<li>MSW v2 handlers and setup</li>\n<li>TypeScript configuration</li>\n<li>Tests that run with both runners</li>\n</ul>\n<h2>Final Thoughts</h2>\n<p>The JavaScript ecosystem is in a transition period. ESM is the future, but CommonJS is still deeply embedded in our tooling.</p>\n<p>Jest remains an excellent test runner - mature, feature-rich, and widely adopted. But when dealing with modern ESM packages like MSW v2, you&#x27;ll need to bridge that gap with careful configuration.</p>\n<p>Vitest represents the next generation: built for ESM from day one, faster by default, and refreshingly simple to configure.</p>\n<p>The choice depends on your constraints. But after spending an afternoon wrestling with <code>transformIgnorePatterns</code>, I know which one I&#x27;ll reach for next time.</p>\n<hr/>\n<p><em>What&#x27;s your experience with Jest and ESM? Have you made the switch to Vitest? Let me know in the comments!</em></p>",
            "url": "https://alexmchan.com/blog/2025-10-17-msw-jest-vite",
            "title": "Fixing msw with jest compilitation",
            "summary": "MSW is great just takes some additional config for jest for esm support",
            "date_modified": "2025-10-17T00:00:00.000Z",
            "author": {
                "name": "Alex Chan"
            }
        },
        {
            "id": "https://alexmchan.com/blog/2024-10-19-ten-things-i-learnt-from-go-as-a-js-developer",
            "content_html": "<h1>Introduction</h1>\n<p>There&#x27;s always been enough changing in the Javascript and TypeScript ecosystem that there&#x27;s always been another thing to learn. Although I knew go would be faster, I had always thought that it would complicate the stack as a single developer or even a small team of developers to have to clutter the stack with another language.</p>\n<p>What I found was that once past the initial learning curve, the speed and simplicity of go made it a great addition to my toolbelt. Here are ten things I learnt that made me a better JS developer.</p>\n<h3>Simplicity</h3>\n<p>Go actually has a relatively small surface area. It has fewer choices in many areas because the go team and community have made so many decisions and placed a lot into the standard library.</p>\n<h3>Interface oriented design</h3>\n<p>Coming from languages like javascript there are always the option to use classes. Interfaces provide a clean way to implement a contract without the need for classes. If it walks like a duck and talks like a duck, it&#x27;s a duck (ok Ruby I know you taught that to us too but classes tempt everyone to the dark side).</p>\n<p>I&#x27;ve never loved inheritance and it&#x27;s great to see that you can design great things without them entirely. Rust is also a modern language that has completely done away with classes.</p>\n<h3>Error handling</h3>\n<p>There&#x27;s no <code>try</code> <code>catch</code> in go. The preferred interface is to return an error as a second argument. This means a few things:</p>\n<ul>\n<li>Errors are handled when they occur</li>\n<li>Errors are not silently ignored</li>\n<li>You can use the successful return <em>without</em> extra boilerplate</li>\n</ul>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"go\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"go\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#569CD6\">func</span><span style=\"color:#DCDCAA\"> main</span><span style=\"color:#E6E6E6\">() {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    result</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">err</span><span style=\"color:#D4D4D4\"> :=</span><span style=\"color:#DCDCAA\"> doSomething</span><span style=\"color:#E6E6E6\">()</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">    if</span><span style=\"color:#9CDCFE\"> err</span><span style=\"color:#D4D4D4\"> !=</span><span style=\"color:#569CD6\"> nil</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">        log</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">Fatal</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">err</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    }</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    fmt</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">Println</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">result</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    sum</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">err</span><span style=\"color:#D4D4D4\"> :=</span><span style=\"color:#DCDCAA\"> doSomethingElse</span><span style=\"color:#E6E6E6\">()</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">    if</span><span style=\"color:#9CDCFE\"> err</span><span style=\"color:#D4D4D4\"> !=</span><span style=\"color:#569CD6\"> nil</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">        log</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">Fatal</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">err</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    }</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span></code></div></pre></figure>\n<p>The main thing to note here is that the result can be used without the problem in javascript of defining a value outside of a closure. Also we don&#x27;t have a giant catchall that has to figure out far from the error what the error was.</p>\n<p>The more javascript equivalent would be:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"javascript\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"javascript\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#569CD6\">function</span><span style=\"color:#DCDCAA\"> main</span><span style=\"color:#E6E6E6\">() {</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">  let</span><span style=\"color:#9CDCFE\"> result</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  try</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    result</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> doSomething</span><span style=\"color:#E6E6E6\">()</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  } </span><span style=\"color:#C586C0\">catch</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#9CDCFE\">err</span><span style=\"color:#E6E6E6\">) {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    console</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">error</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">err</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  }</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  console</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">log</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">result</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">  let</span><span style=\"color:#9CDCFE\"> sum</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  try</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    sum</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> doSomethingElse</span><span style=\"color:#E6E6E6\">()</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  } </span><span style=\"color:#C586C0\">catch</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#9CDCFE\">err</span><span style=\"color:#E6E6E6\">) {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    console</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">error</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">err</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  }</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span></code></div></pre></figure>\n<p>But in reality we normally see a giant <code>try</code> <code>catch</code> block around the entire function and so many errors are silently ignored.</p>\n<p>In JS I&#x27;ve experimented with <code>neverthrow</code> which definitely helps to use a more explicity return type but there&#x27;s definitely a learning curve to the rest of the team and while the small try catch loop is a bit ugly, there&#x27;s not necessarily as much to learn. Which leads me to...</p>\n<h3>More features aren&#x27;t always better</h3>\n<p>It&#x27;s better to have simpler code, fewer good ways to do something, and have code look the same than to have hundreds and everyone doing something different.</p>\n<p>A better api is often the simpler one.</p>\n<h3>It&#x27;s ok to have short variable names</h3>\n<p>This is a hard one for me, I&#x27;ve always liked descriptive names but in go it&#x27;s common to have short variable names. This is because the scope of the variable is often very small and the type is often very clear. Lean to having smaller more focused scopes and good interfaces describing the variables. Even declaring a <code>func</code> is shortened!</p>\n<h3>Early return</h3>\n<p>Early return isn&#x27;t just recommended, it&#x27;s the norm. This is because the error handling is so explicit and the return values are so clear that it&#x27;s easy to see what&#x27;s happening. This helps prevent a lot of nested if&#x27;s and code that is hard to follow because it&#x27;s not clear what the return value is or has shifted so much to the right.</p>\n<h3>Comments are documentation</h3>\n<p>Comments are just <code>//</code> in go. There&#x27;s no go doc special formatting to learn. If the comment appears above a function, it magically becomes the documentation for it.\n<code>func add(a int, b int)</code> example:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"go\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"go\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#6A9955\">// Adds two numbers together</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">func</span><span style=\"color:#DCDCAA\"> add</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">a</span><span style=\"color:#4EC9B0\"> int</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">b</span><span style=\"color:#4EC9B0\"> int</span><span style=\"color:#E6E6E6\">) {</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">    return</span><span style=\"color:#9CDCFE\"> a</span><span style=\"color:#D4D4D4\"> +</span><span style=\"color:#9CDCFE\"> b</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span></code></div></pre></figure>\n<p>contrast this with jsdoc</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"javascript\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"javascript\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#6A9955\">/**</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\"> * Adds two numbers together</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\"> * </span><span style=\"color:#569CD6\">@param</span><span style=\"color:#4EC9B0\"> {number}</span><span style=\"color:#9CDCFE\"> a</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\"> * </span><span style=\"color:#569CD6\">@param</span><span style=\"color:#4EC9B0\"> {number}</span><span style=\"color:#9CDCFE\"> b</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\"> * </span><span style=\"color:#569CD6\">@returns</span><span style=\"color:#4EC9B0\"> {number}</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\"> */</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">function</span><span style=\"color:#DCDCAA\"> add</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">a</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">b</span><span style=\"color:#E6E6E6\">) {</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  return</span><span style=\"color:#9CDCFE\"> a</span><span style=\"color:#D4D4D4\"> +</span><span style=\"color:#9CDCFE\"> b</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span></code></div></pre></figure>\n<h3>Context</h3>\n<p>Context is a special concept in go that allows to pass around both values (which is normal but not idiomatic in js), but also a way to timeout or cancel a request. This is a great way to handle timeouts and cancellations in a way that is built into the language.</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"go\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"go\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">func</span><span style=\"color:#DCDCAA\"> main</span><span style=\"color:#E6E6E6\">() {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    ctx</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">cancel</span><span style=\"color:#D4D4D4\"> :=</span><span style=\"color:#9CDCFE\"> context</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">WithTimeout</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">context</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">Background</span><span style=\"color:#E6E6E6\">(), </span><span style=\"color:#B5CEA8\">5</span><span style=\"color:#D4D4D4\">*</span><span style=\"color:#9CDCFE\">time</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">Second</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">    defer</span><span style=\"color:#DCDCAA\"> cancel</span><span style=\"color:#E6E6E6\">()</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    result</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">err</span><span style=\"color:#D4D4D4\"> :=</span><span style=\"color:#DCDCAA\"> doSomething</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">ctx</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">    if</span><span style=\"color:#9CDCFE\"> err</span><span style=\"color:#D4D4D4\"> !=</span><span style=\"color:#569CD6\"> nil</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">        log</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">Fatal</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">err</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    }</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    fmt</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">Println</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">result</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">func</span><span style=\"color:#DCDCAA\"> doSomething</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">ctx</span><span style=\"color:#4EC9B0\"> context</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#4EC9B0\">Context</span><span style=\"color:#E6E6E6\">) (</span><span style=\"color:#4EC9B0\">string</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#4EC9B0\">error</span><span style=\"color:#E6E6E6\">) {</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">    select</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">    case</span><span style=\"color:#D4D4D4\"> &lt;-</span><span style=\"color:#9CDCFE\">ctx</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">Done</span><span style=\"color:#E6E6E6\">(): </span><span style=\"color:#6A9955\">// this check allows a function to check for the timeout and exit gracefully</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">        return</span><span style=\"color:#CE9178\"> &quot;&quot;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">ctx</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">Err</span><span style=\"color:#E6E6E6\">()</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">    default</span><span style=\"color:#E6E6E6\">:</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">        return</span><span style=\"color:#CE9178\"> &quot;done&quot;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#569CD6\">nil</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    }</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span></code></div></pre></figure>\n<h3>Ordered params are ok</h3>\n<p>Ordered params are ok in other languages but I&#x27;ve long held the belief that named params are better. In go, the idiomatic way is to use ordered params. This is because the function signature is often very clear and the function is often very small.</p>\n<p>So I&#x27;ve updated my belief that if the function is small and the params are clear, ordered params are ok. I&#x27;m still going to dread refactoring back to a named param function (or single interface in go) when I want to go back.</p>\n<h3>Don&#x27;t use util packages</h3>\n<p>This is not a hard and fast rule but something I learned from more people in the go community than not. This is because it&#x27;s often better to have a small package that does one thing well and is easy to understand. This is a great way to keep the codebase clean and easy to understand.</p>\n<p>This <a href=\"https://dave.cheney.net/2019/01/08/avoid-package-names-like-base-util-or-common\">post from Dave Cheney</a> explains it well.</p>\n<blockquote>\n<p>Name your packages after what they provide, not what they contain.</p>\n</blockquote>\n<h3>Still there&#x27;s things I miss</h3>\n<p>There&#x27;s still a few thorns in the roses.</p>\n<ul>\n<li>I miss default initializers for params. There&#x27;s no easy way to do <code>const { a = 1 } = {}</code> in go.</li>\n</ul>\n<h2>Conclusion</h2>\n<p>There&#x27;s far more to like about go than dislike, and I&#x27;m glad I took the time to learn it. It&#x27;s made me a better JS developer and I&#x27;m sure it will make me a better developer in general. I&#x27;m looking forward to using it more in the future.</p>",
            "url": "https://alexmchan.com/blog/2024-10-19-ten-things-i-learnt-from-go-as-a-js-developer",
            "title": "Ten things I learnt from go as a JS developer",
            "summary": "I avoided learning go thinking it was too different from JS. Here are ten things I learnt that made me a better JS developer.",
            "date_modified": "2024-10-19T00:00:00.000Z",
            "author": {
                "name": "Alex Chan"
            }
        },
        {
            "id": "https://alexmchan.com/blog/2024-10-14-more-cli-tips",
            "content_html": "<h1>Easier CLIs with TypeScript</h1>\n<p>It&#x27;s even faster than I originally described for a fast one off script.</p>\n<p>The prompt is</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"md\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"md\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#E6E6E6\">Using @commander-js/extra-typings and @inquirer/prompts, and jsdoc typescript types</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">build a cli that does the following...</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">[describe the cli]</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">example header</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">#!/usr/bin/env node</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">// @ts-check</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">import { Command } from &#x27;@commander-js/extra-typings&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">import { confirm } from &#x27;@inquirer/prompts&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">import { $ } from &#x27;zx&#x27;</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">$.verbose = true</span></span></code></div></pre></figure>\n<h2>Also promising</h2>\n<p><a href=\"https://github.com/drizzle-team/brocli\">brocli</a> looks very nice interface to a cli than commander.</p>\n<h2>Why no <code>execa</code></h2>\n<p>Execa works well for simple cases but it had a few extra protections when just wanting to replace a shell script.</p>\n<p>This included</p>\n<ul>\n<li>pipes working differently (you have to .pipe things instead of using <code>|</code>)</li>\n<li>some auto escaped characters such as empty string</li>\n<li>harder to use conditional arguments (requires [].filter(Boolean))</li>\n</ul>\n<p>These were enough gotchas to want to use <code>zx</code> instead. The extra escaping might be useful if you are running user input,\nbut that seems incredibly scary and would want to avoid that for most cases so for now <code>zx</code> as the default seems ok.</p>",
            "url": "https://alexmchan.com/blog/2024-10-14-more-cli-tips",
            "title": "More CLI Tips",
            "summary": "Generate a cli using your favorite tools.",
            "date_modified": "2024-10-14T00:00:00.000Z",
            "author": {
                "name": "Alex Chan"
            }
        },
        {
            "id": "https://alexmchan.com/blog/2024-06-17-node-cache",
            "content_html": "<h2>Update: 2024-10-14</h2>\n<p>I&#x27;ve written a package that does this and more: <a href=\"https://cache.alexmchan.com\">https://cache.alexmchan.com</a>, features include</p>\n<ul>\n<li>automatic caching of a function</li>\n<li>multi level stores (memory, sqlite, redis, etc) built in</li>\n<li>stale while revalidate</li>\n<li>easy cache invalidation</li>\n</ul>\n<h2>Introduction</h2>\n<p>If one wants cross request cache queries in a node server, one can use a simple cache api inspired by react-query with only a redis library implementation.</p>\n<h2>Caching by query key</h2>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#9CDCFE\"> Redis</span><span style=\"color:#C586C0\"> from</span><span style=\"color:#CE9178\"> &#x27;ioredis&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// or setup a redis connection in a separate file with creds etc</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// import { redis } from &#x27;@/server/redis&#x27; // ioredis instance redis = new Redis()</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> redis</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#569CD6\"> new</span><span style=\"color:#DCDCAA\"> Redis</span><span style=\"color:#E6E6E6\">()</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">type</span><span style=\"color:#4EC9B0\"> CacheOptions</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  ttlInSeconds</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#4EC9B0\"> number</span><span style=\"color:#6A9955\"> // seconds</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  skipCache</span><span style=\"color:#D4D4D4\">?:</span><span style=\"color:#4EC9B0\"> boolean</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// An api similar to react query&#x27;s useQuery</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#569CD6\"> async</span><span style=\"color:#569CD6\"> function</span><span style=\"color:#DCDCAA\"> cacheQuery</span><span style=\"color:#E6E6E6\">&lt;</span><span style=\"color:#4EC9B0\">T</span><span style=\"color:#E6E6E6\">&gt;({</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  queryKey</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  queryFn</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  options</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    ttlInSeconds:</span><span style=\"color:#B5CEA8\"> 60</span><span style=\"color:#D4D4D4\"> *</span><span style=\"color:#B5CEA8\"> 5</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    skipCache:</span><span style=\"color:#569CD6\"> false</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  },</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  queryKey</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#4EC9B0\"> QueryKey</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">  queryFn</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#E6E6E6\"> () </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#4EC9B0\"> Promise</span><span style=\"color:#E6E6E6\">&lt;</span><span style=\"color:#4EC9B0\">T</span><span style=\"color:#E6E6E6\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  options</span><span style=\"color:#D4D4D4\">?:</span><span style=\"color:#4EC9B0\"> CacheOptions</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">})</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#4EC9B0\"> Promise</span><span style=\"color:#E6E6E6\">&lt;</span><span style=\"color:#4EC9B0\">T</span><span style=\"color:#E6E6E6\">&gt; {</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">  const</span><span style=\"color:#9CDCFE\"> key</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> hashKey</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">queryKey</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  if</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#D4D4D4\">!</span><span style=\"color:#9CDCFE\">options</span><span style=\"color:#E6E6E6\">?.</span><span style=\"color:#9CDCFE\">skipCache</span><span style=\"color:#E6E6E6\">) {</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">    const</span><span style=\"color:#9CDCFE\"> cached</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#C586C0\"> await</span><span style=\"color:#9CDCFE\"> redis</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">get</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">key</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">    if</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#569CD6\">typeof</span><span style=\"color:#9CDCFE\"> cached</span><span style=\"color:#D4D4D4\"> ===</span><span style=\"color:#CE9178\"> &#x27;string&#x27;</span><span style=\"color:#E6E6E6\">) {</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">      try</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">        if</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#9CDCFE\">process</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">env</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">LOG_LEVEL</span><span style=\"color:#D4D4D4\"> ===</span><span style=\"color:#CE9178\"> &#x27;debug&#x27;</span><span style=\"color:#E6E6E6\">) {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">          console</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">log</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;Cache hit&#x27;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">key</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">        }</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">        const</span><span style=\"color:#9CDCFE\"> parsed</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#9CDCFE\"> JSON</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">parse</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">cached</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">        return</span><span style=\"color:#9CDCFE\"> parsed</span><span style=\"color:#C586C0\"> as</span><span style=\"color:#4EC9B0\"> T</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">      } </span><span style=\"color:#C586C0\">catch</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#9CDCFE\">e</span><span style=\"color:#E6E6E6\">) {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">        console</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">error</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;Error parsing cached data&#x27;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">e</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">      }</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    }</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  }</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  return</span><span style=\"color:#DCDCAA\"> queryFn</span><span style=\"color:#E6E6E6\">?.().</span><span style=\"color:#DCDCAA\">then</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#569CD6\">async</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#9CDCFE\">data</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">    if</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#9CDCFE\">process</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">env</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">LOG_LEVEL</span><span style=\"color:#D4D4D4\"> ===</span><span style=\"color:#CE9178\"> &#x27;debug&#x27;</span><span style=\"color:#E6E6E6\">) {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">      console</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">log</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;Cache miss&#x27;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">key</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    }</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">    try</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">      await</span><span style=\"color:#9CDCFE\"> redis</span><span style=\"color:#E6E6E6\">?.</span><span style=\"color:#DCDCAA\">set</span><span style=\"color:#E6E6E6\">?.(</span><span style=\"color:#9CDCFE\">key</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">JSON</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">stringify</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">data</span><span style=\"color:#E6E6E6\">), </span><span style=\"color:#CE9178\">&#x27;EX&#x27;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">options</span><span style=\"color:#E6E6E6\">?.</span><span style=\"color:#9CDCFE\">ttlInSeconds</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    } </span><span style=\"color:#C586C0\">catch</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#9CDCFE\">e</span><span style=\"color:#E6E6E6\">) {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">      console</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">error</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;Error caching data&#x27;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">e</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    }</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">    return</span><span style=\"color:#9CDCFE\"> data</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  })</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">/**</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\"> * Default query &amp; mutation keys hash function.</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\"> * Hashes the value into a stable hash.</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\"> * https://github.com/TanStack/query/blob/69d37f33bdee50d73d0f05256f243113a857a1ee/packages/query-core/src/utils.ts#L177</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\"> */</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#569CD6\"> type</span><span style=\"color:#4EC9B0\"> QueryKey</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#4EC9B0\"> ReadonlyArray</span><span style=\"color:#E6E6E6\">&lt;</span><span style=\"color:#4EC9B0\">unknown</span><span style=\"color:#E6E6E6\">&gt;</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#569CD6\"> function</span><span style=\"color:#DCDCAA\"> hashKey</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">queryKey</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#4EC9B0\"> QueryKey</span><span style=\"color:#E6E6E6\">)</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#4EC9B0\"> string</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  return</span><span style=\"color:#9CDCFE\"> JSON</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">stringify</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">queryKey</span><span style=\"color:#E6E6E6\">, (</span><span style=\"color:#9CDCFE\">_</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">val</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#569CD6\">=&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">    isPlainObject</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">val</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#D4D4D4\">      ?</span><span style=\"color:#9CDCFE\"> Object</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">keys</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">val</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">          .</span><span style=\"color:#DCDCAA\">sort</span><span style=\"color:#E6E6E6\">()</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">          .</span><span style=\"color:#DCDCAA\">reduce</span><span style=\"color:#E6E6E6\">((</span><span style=\"color:#9CDCFE\">result</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">key</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">            result</span><span style=\"color:#E6E6E6\">[</span><span style=\"color:#9CDCFE\">key</span><span style=\"color:#E6E6E6\">] </span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#9CDCFE\"> val</span><span style=\"color:#E6E6E6\">[</span><span style=\"color:#9CDCFE\">key</span><span style=\"color:#E6E6E6\">]</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">            return</span><span style=\"color:#9CDCFE\"> result</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">          }, {} </span><span style=\"color:#C586C0\">as</span><span style=\"color:#4EC9B0\"> any</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#D4D4D4\">      :</span><span style=\"color:#9CDCFE\"> val</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  )</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">function</span><span style=\"color:#DCDCAA\"> hasObjectPrototype</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">o</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#4EC9B0\"> any</span><span style=\"color:#E6E6E6\">)</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#4EC9B0\"> boolean</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  return</span><span style=\"color:#4EC9B0\"> Object</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">prototype</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">toString</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">call</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">o</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#D4D4D4\">===</span><span style=\"color:#CE9178\"> &#x27;[object Object]&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// Copied from: https://github.com/jonschlinkert/is-plain-object</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#569CD6\"> function</span><span style=\"color:#DCDCAA\"> isPlainObject</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">o</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#4EC9B0\"> any</span><span style=\"color:#E6E6E6\">)</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#9CDCFE\"> o</span><span style=\"color:#569CD6\"> is</span><span style=\"color:#4EC9B0\"> Object</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  if</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#D4D4D4\">!</span><span style=\"color:#DCDCAA\">hasObjectPrototype</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">o</span><span style=\"color:#E6E6E6\">)) {</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">    return</span><span style=\"color:#569CD6\"> false</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  }</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">  // If has no constructor</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">  const</span><span style=\"color:#9CDCFE\"> ctor</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#9CDCFE\"> o</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">constructor</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  if</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#9CDCFE\">ctor</span><span style=\"color:#D4D4D4\"> ===</span><span style=\"color:#569CD6\"> undefined</span><span style=\"color:#E6E6E6\">) {</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">    return</span><span style=\"color:#569CD6\"> true</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  }</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">  // If has modified prototype</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">  const</span><span style=\"color:#9CDCFE\"> prot</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#4EC9B0\"> ctor</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">prototype</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  if</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#D4D4D4\">!</span><span style=\"color:#DCDCAA\">hasObjectPrototype</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">prot</span><span style=\"color:#E6E6E6\">)) {</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">    return</span><span style=\"color:#569CD6\"> false</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  }</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">  // If constructor does not have an Object-specific method</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  if</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#D4D4D4\">!</span><span style=\"color:#9CDCFE\">prot</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">hasOwnProperty</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;isPrototypeOf&#x27;</span><span style=\"color:#E6E6E6\">)) {</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">    return</span><span style=\"color:#569CD6\"> false</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  }</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">  // Most likely a plain Object</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  return</span><span style=\"color:#569CD6\"> true</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#569CD6\"> function</span><span style=\"color:#DCDCAA\"> isPlainArray</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">value</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#4EC9B0\"> unknown</span><span style=\"color:#E6E6E6\">) {</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  return</span><span style=\"color:#9CDCFE\"> Array</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">isArray</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">value</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#D4D4D4\">&amp;&amp;</span><span style=\"color:#9CDCFE\"> value</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">length</span><span style=\"color:#D4D4D4\"> ===</span><span style=\"color:#9CDCFE\"> Object</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">keys</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">value</span><span style=\"color:#E6E6E6\">).</span><span style=\"color:#9CDCFE\">length</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span></code></div></pre></figure>\n<h2>Security notes</h2>\n<p>Remember that this is a simple cache and either needs user id validation prior and caching with the user id or a more complex cache key generation.</p>",
            "url": "https://alexmchan.com/blog/2024-06-17-node-cache",
            "title": "A simple node cache api",
            "summary": "Trying a react-query inspired node cache",
            "date_modified": "2024-06-17T00:00:00.000Z",
            "author": {
                "name": "Alex Chan"
            }
        },
        {
            "id": "https://alexmchan.com/blog/2023-12-08-cli-tip-package-managers",
            "content_html": "<h2>Pnpm is great and so is Yarn</h2>\n<p>They&#x27;re great. The only problem is when I don&#x27;t remember which I chose for a project.</p>\n<p>Add this to your <code>.zshrc</code> or <code>.bashrc</code> and give the command an alias that you like.</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"sh\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"sh\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#569CD6\">function</span><span style=\"color:#DCDCAA\"> p</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">  # Get the root of the Git repo for mono-repos</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">  local</span><span style=\"color:#9CDCFE\"> git_root</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  git_root</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#E6E6E6\">$(</span><span style=\"color:#DCDCAA\">git</span><span style=\"color:#CE9178\"> rev-parse</span><span style=\"color:#CE9178\"> --show-toplevel</span><span style=\"color:#D4D4D4\"> 2&gt;</span><span style=\"color:#CE9178\">/dev/null</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">  # If the Git root is empty, then we&#x27;re not in a Git repo so use the current folder</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  if</span><span style=\"color:#E6E6E6\"> [ </span><span style=\"color:#D4D4D4\">-z</span><span style=\"color:#CE9178\"> &quot;</span><span style=\"color:#9CDCFE\">$git_root</span><span style=\"color:#CE9178\">&quot;</span><span style=\"color:#E6E6E6\"> ]; </span><span style=\"color:#C586C0\">then</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    git_root</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#CE9178\">&quot;.&quot;</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  fi</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">  local</span><span style=\"color:#9CDCFE\"> lock_file</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  if</span><span style=\"color:#E6E6E6\"> [ </span><span style=\"color:#D4D4D4\">-e</span><span style=\"color:#CE9178\"> &quot;</span><span style=\"color:#9CDCFE\">$git_root</span><span style=\"color:#CE9178\">/yarn.lock&quot;</span><span style=\"color:#E6E6E6\"> ]; </span><span style=\"color:#C586C0\">then</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    lock_file</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#CE9178\">&quot;yarn.lock&quot;</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  elif</span><span style=\"color:#E6E6E6\"> [ </span><span style=\"color:#D4D4D4\">-e</span><span style=\"color:#CE9178\"> &quot;</span><span style=\"color:#9CDCFE\">$git_root</span><span style=\"color:#CE9178\">/pnpm-lock.yaml&quot;</span><span style=\"color:#E6E6E6\"> ]; </span><span style=\"color:#C586C0\">then</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    lock_file</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#CE9178\">&quot;pnpm-lock.yaml&quot;</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  elif</span><span style=\"color:#E6E6E6\"> [ </span><span style=\"color:#D4D4D4\">-e</span><span style=\"color:#CE9178\"> &quot;</span><span style=\"color:#9CDCFE\">$git_root</span><span style=\"color:#CE9178\">/package-lock.json&quot;</span><span style=\"color:#E6E6E6\"> ]; </span><span style=\"color:#C586C0\">then</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    lock_file</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#CE9178\">&quot;package-lock.json&quot;</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  else</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">    echo</span><span style=\"color:#CE9178\"> &quot;No lock file found in the Git root. Initialize the project with Yarn, pnpm, or npm.&quot;</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  fi</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">  # Run the command with the lock file</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  if</span><span style=\"color:#E6E6E6\"> [ </span><span style=\"color:#CE9178\">&quot;</span><span style=\"color:#9CDCFE\">$lock_file</span><span style=\"color:#CE9178\">&quot;</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#CE9178\"> &quot;yarn.lock&quot;</span><span style=\"color:#E6E6E6\"> ]; </span><span style=\"color:#C586C0\">then</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">   #  shortcut to make this more like npm i, yarn doesn&#x27;t have yarn i so we&#x27;ll add it in to mirror that functionality</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">   if</span><span style=\"color:#E6E6E6\"> [ </span><span style=\"color:#CE9178\">&quot;</span><span style=\"color:#9CDCFE\">$1</span><span style=\"color:#CE9178\">&quot;</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#CE9178\"> &quot;i&quot;</span><span style=\"color:#E6E6E6\"> ]; </span><span style=\"color:#C586C0\">then</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">        shift</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">        yarn</span><span style=\"color:#CE9178\"> install</span><span style=\"color:#CE9178\"> &quot;</span><span style=\"color:#9CDCFE\">$@</span><span style=\"color:#CE9178\">&quot;</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">    else</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">        yarn</span><span style=\"color:#569CD6\"> $*</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">    fi</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  elif</span><span style=\"color:#E6E6E6\"> [ </span><span style=\"color:#CE9178\">&quot;</span><span style=\"color:#9CDCFE\">$lock_file</span><span style=\"color:#CE9178\">&quot;</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#CE9178\"> &quot;pnpm-lock.yaml&quot;</span><span style=\"color:#E6E6E6\"> ]; </span><span style=\"color:#C586C0\">then</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">    pnpm</span><span style=\"color:#569CD6\"> $*</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  elif</span><span style=\"color:#E6E6E6\"> [ </span><span style=\"color:#CE9178\">&quot;</span><span style=\"color:#9CDCFE\">$lock_file</span><span style=\"color:#CE9178\">&quot;</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#CE9178\"> &quot;package-lock.json&quot;</span><span style=\"color:#E6E6E6\"> ]; </span><span style=\"color:#C586C0\">then</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">    npm</span><span style=\"color:#569CD6\"> $*</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  else</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">    echo</span><span style=\"color:#CE9178\"> &quot;No lock file found in the Git root. Initialize the project with Yarn, pnpm, or npm. Falling back to pnpm&quot;</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">    pnpm</span><span style=\"color:#569CD6\"> $*</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  fi</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span></code></div></pre></figure>",
            "url": "https://alexmchan.com/blog/2023-12-08-cli-tip-package-managers",
            "title": "A tip to auto run the package manager for your project",
            "summary": "If you have different package managers and don't remember which one you chose, I've got a cli command for you",
            "date_modified": "2023-12-08T00:00:00.000Z",
            "author": {
                "name": "Alex Chan"
            }
        },
        {
            "id": "https://alexmchan.com/blog/2023-11-08-named-props-and-plurality",
            "content_html": "<h2>The plan for plurality</h2>\n<p>Pre-emptive pluralization is described by <a href=\"https://www.swyx.io/preemptive-pluralization\">swyx</a> here. The TLDR is that we should try to assume plurality where we can since going from plural to handle singular is easier than the reverse.</p>\n<p>Here&#x27;s a few concrete examples of quick wins that can pre-emptively pluralize your react code.</p>\n<h2>Named props</h2>\n<p>There&#x27;s a few ways to declare functions.</p>\n<p>Option 1) There&#x27;s ordered functions:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#569CD6\">function</span><span style=\"color:#DCDCAA\"> doWork</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">first</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#4EC9B0\"> string</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">second</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#4EC9B0\"> number</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">third</span><span style=\"color:#D4D4D4\">?:</span><span style=\"color:#4EC9B0\"> boolean</span><span style=\"color:#E6E6E6\">) {}</span></span></code></div></pre></figure>\n<p>Option 2) the next version is a single prop with everything:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#569CD6\">function</span><span style=\"color:#DCDCAA\"> MyComponent</span><span style=\"color:#E6E6E6\">({</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  first</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  second</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#B5CEA8\"> 5</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  third</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  first</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#4EC9B0\"> string</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  second</span><span style=\"color:#D4D4D4\">?:</span><span style=\"color:#4EC9B0\"> number</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  third</span><span style=\"color:#D4D4D4\">?:</span><span style=\"color:#4EC9B0\"> boolean</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}) {</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">  //..</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span></code></div></pre></figure>\n<p>Option 3) The last is a mix of the two:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#569CD6\">type</span><span style=\"color:#4EC9B0\"> Props</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  first</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#4EC9B0\"> string</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  options</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">second</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#4EC9B0\"> number</span><span style=\"color:#E6E6E6\">; </span><span style=\"color:#9CDCFE\">third</span><span style=\"color:#D4D4D4\">?:</span><span style=\"color:#4EC9B0\"> boolean</span><span style=\"color:#E6E6E6\"> }</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">function</span><span style=\"color:#DCDCAA\"> MyComponent</span><span style=\"color:#E6E6E6\">(</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  first</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#4EC9B0\"> string</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  options</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">second</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#4EC9B0\"> number</span><span style=\"color:#E6E6E6\">; </span><span style=\"color:#9CDCFE\">third</span><span style=\"color:#D4D4D4\">?:</span><span style=\"color:#4EC9B0\"> boolean</span><span style=\"color:#E6E6E6\"> }</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">) {</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">  //..</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span></code></div></pre></figure>\n<p>Which do we choose and why? In almost all cases, it&#x27;s easier to start with and never change option 2) a single prop with everything. This has the advantages of:</p>\n<ul>\n<li>props can be declared out of order</li>\n<li>when calling the function, the props are readable by the user to see what they are</li>\n<li>can help with debugging because what if the parameters are all the same type? passing in <code>({ orderId, customerId })</code> can be safer than <code>(customerId, orderId)</code> if the function is typed <code>(orderId: string, customerId: string) =&gt; null</code></li>\n<li>they work more similarly to react components</li>\n<li>default params don&#x27;t need to come last</li>\n<li>there&#x27;s no magic prop order to learn</li>\n</ul>\n<p>The only downside is that it&#x27;s a bit more verbose and there&#x27;s more typing. Edit 2025: LLMs often prefer to declare ordered props, however they can be instructed to prefer named via memory (cursor rules etc).</p>\n<p>Why avoid the third option with a convenience mandatory field and then options next? Just going by <a href=\"https://tanstack.com/query/latest/docs/framework/react/guides/migrating-to-v5\">react-query&#x27;s recent move</a> they shifted to having named parameters for everything. This helps clarify the intent and makes it easier to add new options. It&#x27;s also easier to add new options without breaking existing code.</p>\n<p>The other reason to start with option 2) is that it takes away the guesswork, should I start with ordered then move to plural? What&#x27;s the magic number where it&#x27;s better? Do I really want to refactor when I come in next time and try to replace all the instances where this is called? What do I do when I want to make one of the first parameters optional?</p>\n<p>Start with a single prop and don&#x27;t look back.</p>\n<h2>Named exports</h2>\n<p>This is a bit of a weird one but it&#x27;s worth doing. If you have a file with a single export, it&#x27;s worth naming it. This is because if you ever need to add a second export, it&#x27;s as easy as adding a second export. If you don&#x27;t name it, you need to go back and change all the imports. This is a bit of a pain and it&#x27;s easier to just name it from the start. They also have the advantage of not forcing the user to guess the filename and can help with autocomplete (but this is better now with many editors).</p>\n<p>Popular libraries that have started with defaults and moved to named include <code>zod</code>, <code>immer</code> and even <code>react</code>.</p>\n<p>Default exports are the most useful for frameworks where the default indicates something of importance such as the page in nextjs, if this isn&#x27;t you, it&#x27;s better to start pluralized here.</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#6A9955\">// bad</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#C586C0\"> default</span><span style=\"color:#9CDCFE\"> const</span><span style=\"color:#DCDCAA\"> Something</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#E6E6E6\"> () </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#E6E6E6\"> {}</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// prefer</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#569CD6\"> function</span><span style=\"color:#DCDCAA\"> Something</span><span style=\"color:#E6E6E6\">() { }</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// import</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">Something</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;./something&#x27;</span></span></code></div></pre></figure>\n<p>React says this about <a href=\"https://react.dev/learn/importing-and-exporting-components\">exports</a>:</p>\n<blockquote>\n<p>People often use default exports if the file exports only one component, and use named exports if it exports multiple components and values</p>\n</blockquote>\n<p>However, we as developers have a lot of decisions to make and starting with named exports means we can always have the same rule and never have to refactor when we go from default to named. Predicting the future is hard and I like having the decision that supports faster iteration. As an added bonus, you don&#x27;t need to type <code>default</code>! Win win!</p>\n<h2>Things that might be arrays - multiple items</h2>\n<p>This one is actually a little harder to do. When building an api it&#x27;s sometimes tempting to return just the singular version of an item. <code>/api/items/:id</code>. But in reality it&#x27;s probably good to plan for an array of items <code>/api/items?ids=</code>. This is because it&#x27;s easier to go from an array to a single item than the other way around. It&#x27;s also easier to add a second item to an array than to change a single item to an array. The plural case also handles the singular case.</p>\n<h2>Apis out of your control may have <code>undefined</code> or <code>null</code> - multiple undefined types</h2>\n<p>This is plan for the plurality of undefineds (a stretch I know). In js it&#x27;s typically better to use optional chaining to access anything outside of the direct control of the current function and <em>especially</em> for any api out of one&#x27;s control. The easiest way to do that is to use optional chaining where the <code>data?.item?.value</code> is accessed rather than throwing and making the entire application unusable for an unplanned undefined. Usually it&#x27;s better to have most of the app work than just a subsection. If it&#x27;s really important to have errors when this happens, use zod to define the schema and throw an error if it&#x27;s not valid.</p>\n<h2>Use js as config - multiple configuration options</h2>\n<p>What we&#x27;ve seen is that most of the larger js tooling that uses config ends up moving towards a <code>.js</code> or <code>ts</code> file at least as an option rather than just a pure <code>.json</code> file. The reason for this is the plurality of environments and configuration. This is actually relatively easy to do up front by using something like <a href=\"https://github.com/cosmiconfig/cosmiconfig\">cosmicconfig</a>. Here we can provide the options of ts, js, mjs, and even yaml for yaml lovers (all 3 of you). Examples of this are eslint, babel, webpack, prettier and tailwind. These files allow imports, plugins, dynamic setup, and code based configuration and merging of external config. This is a huge win for plurality by just using cosmicconfig.</p>\n<h2>Requests - multiple requests</h2>\n<p>I bet you didn&#x27;t think we could fit more <a href=\"https://tanstack.com/query\">@tanstack/react-query</a> into this, but there&#x27;s a subtle plurality hidden within this that we can&#x27;t get by managing requests ourselves and that&#x27;s out of order requests (race conditions) as well as automatic retries for network failures. <a href=\"https://tkdodo.eu/blog/why-you-want-react-query\">TkDodo&#x27;s blog post</a> on this goes in depth.</p>\n<h2>Conclusion</h2>\n<p>There&#x27;s a lot of quick wins that don&#x27;t cost much in the land of preemptive pluralization. It&#x27;s worth doing them early and often. It&#x27;s also worth doing them even if you don&#x27;t think you&#x27;ll need them. In many cases it can be easier to go from plural to singular than the other way around so starting with more options early can save time in the long run.</p>",
            "url": "https://alexmchan.com/blog/2023-11-08-named-props-and-plurality",
            "title": "Small plans for plurality in react and ts",
            "summary": "Some plans for plurality have a low cost so it is worth doing them even if you are not sure if you will need them.",
            "date_modified": "2023-11-08T00:00:00.000Z",
            "author": {
                "name": "Alex Chan"
            }
        },
        {
            "id": "https://alexmchan.com/blog/2023-10-10-cli",
            "content_html": "<h2>Adding a cli</h2>\n<p>I recently noticed that shadcn&#x27;s cli is really well organized. Sometimes it&#x27;s useful to have a single one off command to run in a project. There&#x27;s also a lot of useful utilities to use within scripts but finding the right library can be challenging.</p>\n<p>There&#x27;s lots of other options in cli frameworks such as <a href=\"https://github.com/vadimdemedes/ink\">ink</a> and <a href=\"https://oclif.io/\">oclif</a> but <a href=\"https://github.com/tj/commander.js\">commander</a> can often solve a lot of the one off script writing issues quickly and with the template below we can require minimal doc reading before getting the part we all came for, writing the script. <code>clipanion</code>, <code>yargs</code> and others have all been on the menu at some stage but I keep coming back to commander. It&#x27;s pretty easy to get setup, has a decent default help setup, and has support for multiple commands.</p>\n<p>By using Commander, dotenv and zod (or extra-typings) we can make a type safe command really quickly. Also we&#x27;ll go into a set of useful utility libraries to solve some more common cli issues. I&#x27;ll try to update this as I find more useful libraries.</p>\n<p>I&#x27;ve had to write a lot of bash scripts and I much prefer writing conditionals, handling async work, and writing the helpfiles in ts for anything beyond a few lines of shell scripts.</p>\n<h2>TLDR using LLMs</h2>\n<p>If you don&#x27;t want to learn all of the libs, most modern llms recognize them although the npm <code>prompts</code> library can be sometimes wrong.</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"md\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"md\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#E6E6E6\">write a ts shell script that takes an array of directories and an array packages to bump.</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">git clone each repo</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">cd into each repo</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">checkout a branch provided from the cli for the name and default to yyyy-mm-dd-hhmmss-update-deps</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">create a pr if there are changes with the gh cli</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">make sure to check the package directory to know which package manager to use</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">also accept --repo-file that has each repo on a new line</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">the checkout will be into the current directory by default but it can be specified with --checkout-directory</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">use these libraries:</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">import prompts from &#x27;prompts&#x27; // https://www.npmjs.com/package/prompts</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">import ora from &#x27;ora&#x27; // for spinners</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">import { $ } from &#x27;execa&#x27; // for executing external cli items, zx is sometimes better for stdout processing</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">import { Command } from &#x27;@commander-js/extra-typings&#x27;</span></span></code></div></pre></figure>\n<p>Then just install and run the ts file with <code>tsx</code>, <code>bun</code> or <code>deno</code></p>\n<h2>Get your deps</h2>\n<p><code>yarn/pnpm add dotenv commander and zod</code></p>\n<h2>Setup the cli</h2>\n<p>Setup the cli, customize the schema and add the command.</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"ts\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"ts\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#6A9955\">#!/usr/bin/env node</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">config</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;dotenv&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">Command</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;commander&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">z</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;zod&#x27;</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">config</span><span style=\"color:#E6E6E6\">() </span><span style=\"color:#6A9955\">// initialize anything from .env</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> exampleCommand</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#569CD6\"> new</span><span style=\"color:#DCDCAA\"> Command</span><span style=\"color:#E6E6E6\">()</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  .</span><span style=\"color:#DCDCAA\">command</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;example-command&#x27;</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  .</span><span style=\"color:#DCDCAA\">argument</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;[toppings...]&#x27;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#CE9178\">&#x27;toppings args&#x27;</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#6A9955\">// [optional...] array of strings</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  .</span><span style=\"color:#DCDCAA\">description</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;example command&#x27;</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  .</span><span style=\"color:#DCDCAA\">option</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;-e, --example &lt;example&gt;&#x27;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#CE9178\">&#x27;example option&#x27;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#CE9178\">&#x27;default&#x27;</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">  // The action handler gets passed a parameter for each `argument` declared, and two additional parameters which are the parsed options and the command object itself.</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  .</span><span style=\"color:#DCDCAA\">action</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#569CD6\">async</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#9CDCFE\">toppings</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">opts</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">command</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">    try</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">      const</span><span style=\"color:#9CDCFE\"> exampleCommandSchema</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#9CDCFE\"> z</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">object</span><span style=\"color:#E6E6E6\">({</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">        toppings:</span><span style=\"color:#9CDCFE\"> z</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">array</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">z</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">string</span><span style=\"color:#E6E6E6\">()).</span><span style=\"color:#DCDCAA\">optional</span><span style=\"color:#E6E6E6\">(),</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">        example:</span><span style=\"color:#9CDCFE\"> z</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">string</span><span style=\"color:#E6E6E6\">().</span><span style=\"color:#DCDCAA\">optional</span><span style=\"color:#E6E6E6\">(),</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">      })</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">      const</span><span style=\"color:#9CDCFE\"> options</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#9CDCFE\"> exampleCommandSchema</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">parse</span><span style=\"color:#E6E6E6\">({</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">        toppings</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#D4D4D4\">        ...</span><span style=\"color:#9CDCFE\">opts</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">      })</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">      // do something with those options</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">      console</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">log</span><span style=\"color:#E6E6E6\">({ </span><span style=\"color:#9CDCFE\">options</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">command</span><span style=\"color:#E6E6E6\"> })</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    } </span><span style=\"color:#C586C0\">catch</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#9CDCFE\">error</span><span style=\"color:#E6E6E6\">) {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">      console</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">error</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">error</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">      process</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">exit</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#B5CEA8\">1</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    }</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  })</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> program</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#569CD6\"> new</span><span style=\"color:#DCDCAA\"> Command</span><span style=\"color:#E6E6E6\">()</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  .</span><span style=\"color:#DCDCAA\">name</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;example-program&#x27;</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  .</span><span style=\"color:#DCDCAA\">description</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;example program&#x27;</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">program</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">addCommand</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">exampleCommand</span><span style=\"color:#E6E6E6\">, { </span><span style=\"color:#9CDCFE\">isDefault:</span><span style=\"color:#569CD6\"> true</span><span style=\"color:#E6E6E6\"> })</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">program</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">parse</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">process</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">argv</span><span style=\"color:#E6E6E6\">)</span></span></code></div></pre></figure>\n<h2>Arguments to <code>action</code></h2>\n<p>The action handler gets passed a parameter for each <code>argument</code> declared, and two additional parameters which are the parsed options and the command object itself. So when we copy a command we need to be careful of the position of the arguments to the action handler.</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"ts\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"ts\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> argsParseExample</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#569CD6\"> new</span><span style=\"color:#DCDCAA\"> Command</span><span style=\"color:#E6E6E6\">()</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  .</span><span style=\"color:#DCDCAA\">command</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;args-parse-example&#x27;</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  .</span><span style=\"color:#DCDCAA\">description</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;another way to parse args&#x27;</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">  // these determine the order of the arguments to the action handler</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  .</span><span style=\"color:#DCDCAA\">argument</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;&lt;source&gt;&#x27;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#CE9178\">&#x27;topping arg&#x27;</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#6A9955\">// &lt;required&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  .</span><span style=\"color:#DCDCAA\">argument</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;[destination]&#x27;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#CE9178\">&#x27;oven&#x27;</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#6A9955\">// [optional]</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">  // these are all in the second to last options parameter</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  .</span><span style=\"color:#DCDCAA\">option</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;-e, --example &lt;example&gt;&#x27;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#CE9178\">&#x27;example option&#x27;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#CE9178\">&#x27;default&#x27;</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\" data-highlighted-line=\"\"><span style=\"color:#E6E6E6\">  .</span><span style=\"color:#DCDCAA\">action</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#569CD6\">async</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#D4D4D4\">...</span><span style=\"color:#9CDCFE\">args</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">    try</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">      const</span><span style=\"color:#E6E6E6\"> [</span><span style=\"color:#9CDCFE\">source</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">destination</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">opts</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">command</span><span style=\"color:#E6E6E6\">] </span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#9CDCFE\"> args</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">      // if we only are ever interested in opts and command we can always take them from the end of the args array</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">      //   const opts = args?.[args.length - 2]</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">      //   const command = args?.[args.length - 1]</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">      const</span><span style=\"color:#9CDCFE\"> exampleCommandSchema</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#9CDCFE\"> z</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">object</span><span style=\"color:#E6E6E6\">({</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">        source:</span><span style=\"color:#9CDCFE\"> z</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">string</span><span style=\"color:#E6E6E6\">(),</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">        destination:</span><span style=\"color:#9CDCFE\"> z</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">string</span><span style=\"color:#E6E6E6\">().</span><span style=\"color:#DCDCAA\">optional</span><span style=\"color:#E6E6E6\">(),</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">        example:</span><span style=\"color:#9CDCFE\"> z</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">string</span><span style=\"color:#E6E6E6\">().</span><span style=\"color:#DCDCAA\">optional</span><span style=\"color:#E6E6E6\">(),</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">      })</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">      const</span><span style=\"color:#9CDCFE\"> options</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#9CDCFE\"> exampleCommandSchema</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">parse</span><span style=\"color:#E6E6E6\">({</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">        source</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">        destination</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#D4D4D4\">        ...</span><span style=\"color:#9CDCFE\">opts</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">      })</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">      // do something with those options</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">      console</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">log</span><span style=\"color:#E6E6E6\">({ </span><span style=\"color:#9CDCFE\">options</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">command</span><span style=\"color:#E6E6E6\"> })</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    } </span><span style=\"color:#C586C0\">catch</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#9CDCFE\">error</span><span style=\"color:#E6E6E6\">) {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">      console</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">error</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">error</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">      process</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">exit</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#B5CEA8\">1</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    }</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  })</span></span></code></div></pre></figure>\n<h2>Alternative to zod</h2>\n<p>If we don&#x27;t want to use zod, we can use the <a href=\"https://github.com/commander-js/extra-typings\">extra-typings</a> package.</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"ts\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"ts\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#6A9955\">#!/usr/bin/env node</span></span>\n<span data-line=\"\" data-highlighted-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">Command</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;@commander-js/extra-typings&#x27;</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> program</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#569CD6\"> new</span><span style=\"color:#DCDCAA\"> Command</span><span style=\"color:#E6E6E6\">()</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  .</span><span style=\"color:#DCDCAA\">argument</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;[toppings...]&#x27;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#CE9178\">&#x27;toppings args&#x27;</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  .</span><span style=\"color:#DCDCAA\">description</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;example command&#x27;</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  .</span><span style=\"color:#DCDCAA\">option</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;-e, --example &lt;example&gt;&#x27;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#CE9178\">&#x27;example option&#x27;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#CE9178\">&#x27;default&#x27;</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">  // The action handler gets passed a parameter for each `argument` declared, and two additional parameters which are the parsed options and the command object itself.</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  .</span><span style=\"color:#DCDCAA\">action</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#569CD6\">async</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#9CDCFE\">toppings</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">opts</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">command</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">    try</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">      // do something with those options</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">      console</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">log</span><span style=\"color:#E6E6E6\">({ </span><span style=\"color:#9CDCFE\">toppings</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">opts</span><span style=\"color:#E6E6E6\"> })</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    } </span><span style=\"color:#C586C0\">catch</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#9CDCFE\">error</span><span style=\"color:#E6E6E6\">) {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">      console</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">error</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">error</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">      process</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">exit</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#B5CEA8\">1</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    }</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  })</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">program</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">parse</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">process</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">argv</span><span style=\"color:#E6E6E6\">)</span></span></code></div></pre></figure>\n<h2>Adding more commands</h2>\n<p>Adding more commands just takes adding a new command and calling the <code>addCommand</code> method. As this expands, the commands can be moved into their own files as we can see from <a href=\"https://github.com/shadcn-ui/ui/blob/43c4023ed8f8c150cfbd01f5d7fe2773938b25b3/packages/cli/src/index.ts#L1C1-L29C7\">shadcn&#x27;s cli</a>.</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"ts\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"ts\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#6A9955\">#!/usr/bin/env node</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">add</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;@/src/commands/add&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">diff</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;@/src/commands/diff&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">init</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;@/src/commands/init&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">Command</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;commander&#x27;</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">getPackageInfo</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;./utils/get-package-info&#x27;</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">process</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">on</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;SIGINT&#x27;</span><span style=\"color:#E6E6E6\">, () </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#9CDCFE\"> process</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">exit</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#B5CEA8\">0</span><span style=\"color:#E6E6E6\">))</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">process</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">on</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;SIGTERM&#x27;</span><span style=\"color:#E6E6E6\">, () </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#9CDCFE\"> process</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">exit</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#B5CEA8\">0</span><span style=\"color:#E6E6E6\">))</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">async</span><span style=\"color:#569CD6\"> function</span><span style=\"color:#DCDCAA\"> main</span><span style=\"color:#E6E6E6\">() {</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">  const</span><span style=\"color:#9CDCFE\"> packageInfo</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#C586C0\"> await</span><span style=\"color:#DCDCAA\"> getPackageInfo</span><span style=\"color:#E6E6E6\">()</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">  const</span><span style=\"color:#9CDCFE\"> program</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#569CD6\"> new</span><span style=\"color:#DCDCAA\"> Command</span><span style=\"color:#E6E6E6\">()</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    .</span><span style=\"color:#DCDCAA\">name</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;shadcn-ui&#x27;</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    .</span><span style=\"color:#DCDCAA\">description</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;add components and dependencies to your project&#x27;</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    .</span><span style=\"color:#DCDCAA\">version</span><span style=\"color:#E6E6E6\">(</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">      packageInfo</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">version</span><span style=\"color:#D4D4D4\"> ||</span><span style=\"color:#CE9178\"> &#x27;1.0.0&#x27;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#CE9178\">      &#x27;-v, --version&#x27;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#CE9178\">      &#x27;display the version number&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    )</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\" data-highlighted-line=\"\"><span style=\"color:#9CDCFE\">  program</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">addCommand</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">init</span><span style=\"color:#E6E6E6\">).</span><span style=\"color:#DCDCAA\">addCommand</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">add</span><span style=\"color:#E6E6E6\">).</span><span style=\"color:#DCDCAA\">addCommand</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">diff</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  program</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">parse</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">process</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">argv</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">main</span><span style=\"color:#E6E6E6\">()</span></span></code></div></pre></figure>\n<h2>Running the command</h2>\n<p>If there aren&#x27;t any types defined, a <code>.mjs</code> and adding a <code>&quot;type&quot;: &quot;module&quot;</code> to the package.json will allow the command to be run.</p>\n<p>When using a <code>.ts</code> file, <code>tsx</code>, <code>bun</code> or <code>ts-node --swc</code> can be used to run the command.</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"sh\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"sh\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#6A9955\"># pick your favorite runner</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">$</span><span style=\"color:#CE9178\"> tsx</span><span style=\"color:#CE9178\"> ./src/example-cli.ts</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">$</span><span style=\"color:#CE9178\"> ts-node</span><span style=\"color:#CE9178\"> --swc</span><span style=\"color:#CE9178\"> ./src/example-cli.ts</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">$</span><span style=\"color:#CE9178\"> bun</span><span style=\"color:#CE9178\"> ./src/example-cli.ts</span></span></code></div></pre></figure>\n<p>Optionally include the shebang to run the command directly and chmod +x the file.</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"sh\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"sh\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#6A9955\">#!/usr/bin/env ts-node --swc</span></span></code></div></pre></figure>\n<h2>Adding to package json to run as an installed binary</h2>\n<p>Running from cli will require some bundling, adding a build script, a <code>bin</code>.</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"json\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"json\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#CE9178\">  &quot;scripts&quot;</span><span style=\"color:#E6E6E6\">: {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    &quot;build:cli&quot;</span><span style=\"color:#E6E6E6\">: </span><span style=\"color:#CE9178\">&quot;esbuild --bundle --outdir=dist --platform=node --target=node18 ./src/example-cli.ts&quot;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  },</span></span>\n<span data-line=\"\"><span style=\"color:#CE9178\">  &quot;bin&quot;</span><span style=\"color:#E6E6E6\">: {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    &quot;examplecommand&quot;</span><span style=\"color:#E6E6E6\">: </span><span style=\"color:#CE9178\">&quot;./dist/example-cli.js&quot;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  },</span></span></code></div></pre></figure>\n<p>Then publishing to npm <em>or</em> run <code>npm link</code> from the package folder if there&#x27;s only have a repo. If using volta and <code>npm link</code> to link the package, use <code>volta which example-cli</code> to find the package location afterwards.</p>\n<h2>More tools and libraries</h2>\n<h3>Running external commands</h3>\n<p>I&#x27;ve been using <a href=\"https://github.com/google/zx\">zx</a> from google to run external commands. It&#x27;s a nice way to run external commands and scripts. Other options in this space are <a href=\"https://github.com/sindresorhus/execa#readme\">execa</a> which also supports the <a href=\"https://github.com/sindresorhus/execa/blob/HEAD/docs/scripts.md\">scripts</a> interface which is a really nice alternative for calling other bash shell commands. (2024 update: I&#x27;ve been preferring <code>zx</code> more and more as it&#x27;s a bit more flexible and has a nicer interface.)</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"ts\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"ts\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">$</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;zx&#x27;</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">await</span><span style=\"color:#DCDCAA\"> $</span><span style=\"color:#CE9178\">`docker build -f </span><span style=\"color:#569CD6\">${</span><span style=\"color:#9CDCFE\">dockerfile</span><span style=\"color:#569CD6\">}</span><span style=\"color:#CE9178\"> -t </span><span style=\"color:#569CD6\">${</span><span style=\"color:#9CDCFE\">imageUri</span><span style=\"color:#569CD6\">}</span><span style=\"color:#569CD6\"> ${</span><span style=\"color:#9CDCFE\">dockerContext</span><span style=\"color:#569CD6\">}</span><span style=\"color:#CE9178\">`</span></span></code></div></pre></figure>\n<h3>Prompts</h3>\n<p>Use <a href=\"https://github.com/terkelg/prompts\">prompts</a> to get user input. Don&#x27;t forget those types.</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"sh\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"sh\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#DCDCAA\">$</span><span style=\"color:#CE9178\"> pnpm</span><span style=\"color:#CE9178\"> add</span><span style=\"color:#CE9178\"> prompts</span><span style=\"color:#CE9178\"> @types/prompts</span></span></code></div></pre></figure>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"ts\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"ts\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#9CDCFE\"> prompts</span><span style=\"color:#C586C0\"> from</span><span style=\"color:#CE9178\"> &#x27;prompts&#x27;</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> response</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#C586C0\"> await</span><span style=\"color:#DCDCAA\"> prompts</span><span style=\"color:#E6E6E6\">({</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  type:</span><span style=\"color:#CE9178\"> &#x27;number&#x27;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  name:</span><span style=\"color:#CE9178\"> &#x27;howMuch&#x27;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  message:</span><span style=\"color:#CE9178\"> &#x27;How much would you pay for this?&#x27;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">  validate</span><span style=\"color:#9CDCFE\">:</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#9CDCFE\">value</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#569CD6\">=&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    value</span><span style=\"color:#D4D4D4\"> &lt;</span><span style=\"color:#B5CEA8\"> 5</span><span style=\"color:#D4D4D4\"> ?</span><span style=\"color:#CE9178\"> `Sorry, please spend a bit more to run this cli`</span><span style=\"color:#D4D4D4\"> :</span><span style=\"color:#569CD6\"> true</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">})</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">console</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">log</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">response</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">howMuch</span><span style=\"color:#E6E6E6\">)</span></span></code></div></pre></figure>\n<h3>Spinners</h3>\n<p>Try <a href=\"https://github.com/sindresorhus/ora\">ora</a> for spinners.</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"ts\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"ts\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#9CDCFE\"> ora</span><span style=\"color:#C586C0\"> from</span><span style=\"color:#CE9178\"> &#x27;ora&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">$</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;execa&#x27;</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> spinner</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> ora</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">`Working...`</span><span style=\"color:#E6E6E6\">).</span><span style=\"color:#DCDCAA\">start</span><span style=\"color:#E6E6E6\">()</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">await</span><span style=\"color:#DCDCAA\"> $</span><span style=\"color:#CE9178\">`sleep 2`</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">spinner</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">text</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#CE9178\"> &#x27;More work...&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">await</span><span style=\"color:#DCDCAA\"> $</span><span style=\"color:#CE9178\">`sleep 2`</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">spinner</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">succeed</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">`Done.`</span><span style=\"color:#E6E6E6\">)</span></span></code></div></pre></figure>\n<h3>Colors</h3>\n<p>Use <a href=\"https://github.com/chalk/chalk\">chalk</a> for colors.</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"ts\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"ts\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#9CDCFE\"> chalk</span><span style=\"color:#C586C0\"> from</span><span style=\"color:#CE9178\"> &#x27;chalk&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// import { chalk } from &#x27;zx&#x27; // also included with zx</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">console</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">log</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">chalk</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">green</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;Success!&#x27;</span><span style=\"color:#E6E6E6\">))</span></span></code></div></pre></figure>\n<h3>Copy text to clipboard</h3>\n<p>Copy text to clipboard for mac/windows/wsl.</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"ts\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"ts\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#9CDCFE\"> clipboardy</span><span style=\"color:#C586C0\"> from</span><span style=\"color:#CE9178\"> &#x27;clipboardy&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#9CDCFE\"> isWsl</span><span style=\"color:#C586C0\"> from</span><span style=\"color:#CE9178\"> &#x27;is-wsl&#x27;</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#569CD6\"> function</span><span style=\"color:#DCDCAA\"> writeToClipboard</span><span style=\"color:#E6E6E6\">({ </span><span style=\"color:#9CDCFE\">text</span><span style=\"color:#E6E6E6\"> }) {</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  if</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#9CDCFE\">isWsl</span><span style=\"color:#E6E6E6\">) {</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">    exec</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">`echo &quot;</span><span style=\"color:#569CD6\">${</span><span style=\"color:#9CDCFE\">text</span><span style=\"color:#569CD6\">}</span><span style=\"color:#CE9178\">&quot; | clip.exe`</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  } </span><span style=\"color:#C586C0\">else</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    clipboardy</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">write</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">text</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  }</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span></code></div></pre></figure>\n<h3>Processing files</h3>\n<p>It&#x27;s often useful to list a pattern of files or all files in a subdirectory using a wildcard. <a href=\"https://github.com/mrmlnc/fast-glob\">Fast glob</a> patterns are a nice way to specify everything within a subdirectory.</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"ts\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"ts\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\" data-highlighted-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#9CDCFE\"> fg</span><span style=\"color:#C586C0\"> from</span><span style=\"color:#CE9178\"> &#x27;fast-glob&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#9CDCFE\"> zlib</span><span style=\"color:#C586C0\"> from</span><span style=\"color:#CE9178\"> &#x27;zlib&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">z</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;zod&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#9CDCFE\"> fs</span><span style=\"color:#C586C0\"> from</span><span style=\"color:#CE9178\"> &#x27;fs&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">Command</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;commander&#x27;</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> program</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#569CD6\"> new</span><span style=\"color:#DCDCAA\"> Command</span><span style=\"color:#E6E6E6\">()</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  .</span><span style=\"color:#DCDCAA\">name</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;list of files&#x27;</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  .</span><span style=\"color:#DCDCAA\">option</span><span style=\"color:#E6E6E6\">(</span></span>\n<span data-line=\"\"><span style=\"color:#CE9178\">    &#x27;-f, --files &lt;file&gt;&#x27;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#CE9178\">    &#x27;file glob see https://github.com/mrmlnc/fast-glob &#x27;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#CE9178\">    &#x27;./src/**/*.tsx&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  )</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  .</span><span style=\"color:#DCDCAA\">action</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#569CD6\">async</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#9CDCFE\">opts</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">    try</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">      const</span><span style=\"color:#9CDCFE\"> fileGlobSchema</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#9CDCFE\"> z</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">object</span><span style=\"color:#E6E6E6\">({</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">        files:</span><span style=\"color:#9CDCFE\"> z</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">string</span><span style=\"color:#E6E6E6\">().</span><span style=\"color:#DCDCAA\">min</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#B5CEA8\">1</span><span style=\"color:#E6E6E6\">),</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">      })</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">      const</span><span style=\"color:#9CDCFE\"> options</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#9CDCFE\"> fileGlobSchema</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">parse</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">opts</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\" data-highlighted-line=\"\"><span style=\"color:#569CD6\">      const</span><span style=\"color:#9CDCFE\"> filesGlob</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#C586C0\"> await</span><span style=\"color:#DCDCAA\"> fg</span><span style=\"color:#E6E6E6\">([</span><span style=\"color:#9CDCFE\">options</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">files</span><span style=\"color:#E6E6E6\">], { </span><span style=\"color:#9CDCFE\">dot:</span><span style=\"color:#569CD6\"> false</span><span style=\"color:#E6E6E6\"> })</span></span>\n<span data-line=\"\" data-highlighted-line=\"\"><span style=\"color:#9CDCFE\">      filesGlob</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">forEach</span><span style=\"color:#E6E6E6\">((</span><span style=\"color:#9CDCFE\">filePath</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\" data-highlighted-line=\"\"><span style=\"color:#9CDCFE\">        console</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">log</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">filePath</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\" data-highlighted-line=\"\"> </span>\n<span data-line=\"\" data-highlighted-line=\"\"><span style=\"color:#569CD6\">        let</span><span style=\"color:#9CDCFE\"> fileContents</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#CE9178\"> &#x27;&#x27;</span></span>\n<span data-line=\"\" data-highlighted-line=\"\"><span style=\"color:#C586C0\">        if</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#9CDCFE\">filePath</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">endsWith</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;.gz&#x27;</span><span style=\"color:#E6E6E6\">)) {</span></span>\n<span data-line=\"\" data-highlighted-line=\"\"><span style=\"color:#9CDCFE\">          fileContents</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#9CDCFE\"> zlib</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">gunzipSync</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">fs</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">readFileSync</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">filePath</span><span style=\"color:#E6E6E6\">)).</span><span style=\"color:#DCDCAA\">toString</span><span style=\"color:#E6E6E6\">()</span></span>\n<span data-line=\"\" data-highlighted-line=\"\"><span style=\"color:#E6E6E6\">        } </span><span style=\"color:#C586C0\">else</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\" data-highlighted-line=\"\"><span style=\"color:#9CDCFE\">          fileContents</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#9CDCFE\"> fs</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">readFileSync</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">filePath</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#CE9178\">&#x27;utf8&#x27;</span><span style=\"color:#E6E6E6\">).</span><span style=\"color:#DCDCAA\">toString</span><span style=\"color:#E6E6E6\">()</span></span>\n<span data-line=\"\" data-highlighted-line=\"\"><span style=\"color:#E6E6E6\">        }</span></span>\n<span data-line=\"\" data-highlighted-line=\"\"><span style=\"color:#E6E6E6\">      })</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    } </span><span style=\"color:#C586C0\">catch</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#9CDCFE\">error</span><span style=\"color:#E6E6E6\">) {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">      console</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">error</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">error</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">      process</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">exit</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#B5CEA8\">1</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    }</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  })</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  .</span><span style=\"color:#DCDCAA\">parse</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">process</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">argv</span><span style=\"color:#E6E6E6\">)</span></span></code></div></pre></figure>\n<h3>A config file</h3>\n<p>When config goes beyond a .env file, cosmicconfig helps to load all forms of config, including functions from js or ts files and not just json.</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"ts\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"ts\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">cosmiconfig</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;cosmiconfig&#x27;</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">async</span><span style=\"color:#569CD6\"> function</span><span style=\"color:#DCDCAA\"> getConfig</span><span style=\"color:#E6E6E6\">({ </span><span style=\"color:#9CDCFE\">region</span><span style=\"color:#E6E6E6\"> }) {</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">  const</span><span style=\"color:#9CDCFE\"> moduleName</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#CE9178\"> &#x27;example&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">  const</span><span style=\"color:#9CDCFE\"> explorer</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> cosmiconfig</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">moduleName</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  try</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">    const</span><span style=\"color:#9CDCFE\"> result</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#C586C0\"> await</span><span style=\"color:#9CDCFE\"> explorer</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">search</span><span style=\"color:#E6E6E6\">()</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">    let</span><span style=\"color:#9CDCFE\"> config</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#9CDCFE\"> result</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">config</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">    if</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#569CD6\">typeof</span><span style=\"color:#9CDCFE\"> result</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">config</span><span style=\"color:#D4D4D4\"> ===</span><span style=\"color:#CE9178\"> &#x27;function&#x27;</span><span style=\"color:#E6E6E6\">) {</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">      const</span><span style=\"color:#9CDCFE\"> accountId</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#C586C0\"> await</span><span style=\"color:#DCDCAA\"> getAccountId</span><span style=\"color:#E6E6E6\">()</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">      config</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#C586C0\"> await</span><span style=\"color:#9CDCFE\"> result</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">config</span><span style=\"color:#E6E6E6\">({ </span><span style=\"color:#9CDCFE\">region</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">accountId</span><span style=\"color:#E6E6E6\"> })</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    }</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">    return</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#D4D4D4\">      ...</span><span style=\"color:#9CDCFE\">result</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">      config</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    }</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  } </span><span style=\"color:#C586C0\">catch</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#9CDCFE\">error</span><span style=\"color:#E6E6E6\">) {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    console</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">log</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">`Error parsing or no </span><span style=\"color:#569CD6\">${</span><span style=\"color:#9CDCFE\">moduleName</span><span style=\"color:#569CD6\">}</span><span style=\"color:#CE9178\"> config found. Searched</span></span>\n<span data-line=\"\"><span style=\"color:#CE9178\">        &#x27;package.json&#x27;,</span></span>\n<span data-line=\"\"><span style=\"color:#CE9178\">        .</span><span style=\"color:#569CD6\">${</span><span style=\"color:#9CDCFE\">moduleName</span><span style=\"color:#569CD6\">}</span><span style=\"color:#CE9178\">rc,</span></span>\n<span data-line=\"\"><span style=\"color:#CE9178\">        .</span><span style=\"color:#569CD6\">${</span><span style=\"color:#9CDCFE\">moduleName</span><span style=\"color:#569CD6\">}</span><span style=\"color:#CE9178\">rc.json,</span></span>\n<span data-line=\"\"><span style=\"color:#CE9178\">        .</span><span style=\"color:#569CD6\">${</span><span style=\"color:#9CDCFE\">moduleName</span><span style=\"color:#569CD6\">}</span><span style=\"color:#CE9178\">rc.yaml,</span></span>\n<span data-line=\"\"><span style=\"color:#CE9178\">        .</span><span style=\"color:#569CD6\">${</span><span style=\"color:#9CDCFE\">moduleName</span><span style=\"color:#569CD6\">}</span><span style=\"color:#CE9178\">rc.yml,</span></span>\n<span data-line=\"\"><span style=\"color:#CE9178\">        .</span><span style=\"color:#569CD6\">${</span><span style=\"color:#9CDCFE\">moduleName</span><span style=\"color:#569CD6\">}</span><span style=\"color:#CE9178\">rc.js,</span></span>\n<span data-line=\"\"><span style=\"color:#CE9178\">        .</span><span style=\"color:#569CD6\">${</span><span style=\"color:#9CDCFE\">moduleName</span><span style=\"color:#569CD6\">}</span><span style=\"color:#CE9178\">rc.cjs,</span></span>\n<span data-line=\"\"><span style=\"color:#CE9178\">        .</span><span style=\"color:#569CD6\">${</span><span style=\"color:#9CDCFE\">moduleName</span><span style=\"color:#569CD6\">}</span><span style=\"color:#CE9178\">rc.ts,</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">        ${</span><span style=\"color:#9CDCFE\">moduleName</span><span style=\"color:#569CD6\">}</span><span style=\"color:#CE9178\">.config.js,</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">        ${</span><span style=\"color:#9CDCFE\">moduleName</span><span style=\"color:#569CD6\">}</span><span style=\"color:#CE9178\">.config.ts,</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">        ${</span><span style=\"color:#9CDCFE\">moduleName</span><span style=\"color:#569CD6\">}</span><span style=\"color:#CE9178\">.config.cjs,`</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    console</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">error</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">error</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">    return</span><span style=\"color:#569CD6\"> null</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  }</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span></code></div></pre></figure>\n<h2>Parallelization</h2>\n<p>If there&#x27;s a lot of work to be done, execution in parallel can speed things up. One thing to keep in mind while doing this is that we also don&#x27;t want to overwhelm the target or hit api limits. <a href=\"https://github.com/sindresorhus/p-map\">p-map</a> allows for concurrency limits.</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"ts\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"ts\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#9CDCFE\"> pMap</span><span style=\"color:#C586C0\"> from</span><span style=\"color:#CE9178\"> &#x27;p-map&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">setTimeout</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;node:timers/promises&#x27;</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> responses</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#C586C0\"> await</span><span style=\"color:#DCDCAA\"> pMap</span><span style=\"color:#E6E6E6\">(</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  items</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">  async</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#9CDCFE\">item</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    console</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">log</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">item</span><span style=\"color:#E6E6E6\">?.</span><span style=\"color:#9CDCFE\">id</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">    await</span><span style=\"color:#DCDCAA\"> setTimeout</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#B5CEA8\">1000</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  },</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    concurrency:</span><span style=\"color:#B5CEA8\"> 5</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    stopOnError:</span><span style=\"color:#569CD6\"> false</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#6A9955\">// this will still reject everything and throw an AggregateError</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  }</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">)</span></span></code></div></pre></figure>\n<h2>Summary</h2>\n<p>Adding a cli is a nice way to run scripts and commands in a project. Hopefully this will help get started with building your own cli. So go out, build your cli and may your time in bash scripts be limited.</p>",
            "url": "https://alexmchan.com/blog/2023-10-10-cli",
            "title": "Learn how to build a command line interface in 10 minutes ",
            "summary": "Adding a typed cli is a nice way to run scripts and commands in a project.",
            "date_modified": "2023-10-10T00:00:00.000Z",
            "author": {
                "name": "Alex Chan"
            }
        },
        {
            "id": "https://alexmchan.com/blog/2023-06-29-tailwind-customizations",
            "content_html": "<h2>Styling</h2>\n<p>Tailwind is great and ust a few customizations can make it even better. Most of these are embedded in this <a href=\"https://github.com/alexanderchan/preset-tailwind\">preset</a>.</p>\n<h2>Pixel widths</h2>\n<p>Tailwind uses an index based scale which can be cumbersome to memorize, hover over in a code editor, or print out for reference. Our team has used px scale and it&#x27;s easier to remember and for new devs to onboard faster. Compare <code>p-36px</code> vs <code>p-9</code>, it&#x27;s slightly more effort to nudge something but autocomplete works well and mostly it&#x27;s adding 4-8px at a time.</p>\n<h2>Not just me</h2>\n<p>These two tweets convinced me, one from the creator of tailwindcss and one from vanilla extract and css modules. Note that the creator of tailwind <em>didn&#x27;t remember</em> the scale value off the top of his head -- because he is human like most of us.</p>\n<blockquote class=\"twitter-tweet\"><p lang=\"en\" dir=\"ltr\">There was a little learning curve, but I like it now that I’m used to it. Also mt-8 is 32px 😉</p>— Jason Beggs (@jasonlbeggs) <a href=\"https://twitter.com/jasonlbeggs/status/1216734492653752320?ref_src=twsrc%5Etfw\">January 13, 2020</a></blockquote><blockquote class=\"twitter-tweet\"><p lang=\"en\" dir=\"ltr\">After working with an abstracted scale for years, I&#x27;ve ended up feeling like the abstraction didn&#x27;t actually buy me anything and only served to slow me down and make it difficult to predict what would be on screen. The main benefit was having a limited scale, which we still have.</p>— Mark Dalgleish (@markdalgleish) <a href=\"https://twitter.com/markdalgleish/status/1463641232102494208?ref_src=twsrc%5Etfw\">November 24, 2021</a></blockquote>",
            "url": "https://alexmchan.com/blog/2023-06-29-tailwind-customizations",
            "title": "tailwind customizations",
            "summary": "Small additions for tailwind customizations",
            "date_modified": "2023-06-29T00:00:00.000Z",
            "author": {
                "name": "Alex Chan"
            }
        },
        {
            "id": "https://alexmchan.com/blog/2023-03-20-a-slice-of-state",
            "content_html": "<h2>State management in react</h2>\n<p>We&#x27;re given a lot of choice of state management, but what&#x27;s the best choice for your app? I am looking into state libraries and wanted to see their apis all in one location. There&#x27;s not one best answer but I&#x27;ll share my thoughts on the different options.</p>\n<h2>A view of app state</h2>\n<p>Most apps I&#x27;ve worked with typically look like this in terms of state:</p>\n<div class=\"grid place-content-center\"><img loading=\"lazy\" width=\"600\" height=\"600\" decoding=\"async\" data-nimg=\"1\" style=\"color:transparent\" srcSet=\"/_next/image?url=%2F_next%2Fstatic%2Fimmutable%2Fmedia%2Fstate-triangle.1fwofa4oee8he.png&amp;w=640&amp;q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fimmutable%2Fmedia%2Fstate-triangle.1fwofa4oee8he.png&amp;w=1200&amp;q=75 2x\" src=\"/_next/image?url=%2F_next%2Fstatic%2Fimmutable%2Fmedia%2Fstate-triangle.1fwofa4oee8he.png&amp;w=1200&amp;q=75\"/></div>\n<h3>Async and form state</h3>\n<p>Before we dive in, it&#x27;s worth mentioning that <em>most</em> complex state in apps I&#x27;ve worked with are typically async data caches or form data. For this reason, in my experience I&#x27;ve rarely had to reach for global state libraries beyond context because minimal global state means minimal re-renders.</p>\n<p>For async choose one of</p>\n<ul>\n<li><code>trpc</code> (if you control the client and the server)</li>\n<li><code>apollo/urql</code> (if you can use graphql)</li>\n<li><code>@tanstack/react-query</code> (if you&#x27;re using REST based apis see tkdodo&#x27;s <a href=\"https://tkdodo.eu/blog/why-you-want-react-query\">blog post</a> on why you want react-query)</li>\n</ul>\n<p>For form libraries I&#x27;ve been happy with <code>react-hook-form</code> and <code>zod</code> for validation. I&#x27;ve liked that the zod adapter is built in and it minimizes re-renders if only the components that need to watch data watch for it.</p>\n<p>Comparing all of these would be it&#x27;s own post. So I&#x27;ll leave it that starting with an async lib and sharing component state with a form lib can go a very long way.</p>\n<p>Only when async, form and local state start to fall over, reach for a solution below.</p>\n<h3>General library questions</h3>\n<p>Perhaps what can help deciding on a library for yourself is answering some questions. Think about these as we look at each of the libraries and hopefully can inform your decision when a new hot state management library comes along.</p>\n<ul>\n<li>Do I even need this library (ie. do I even need global state or can I get by with alternatives of something like react-hook-form and react-query)</li>\n<li>Does one api map more closely to your current way of working or the way you want to work?</li>\n<li>Which api is simplest? What new concepts to developers need to learn as they onboard?</li>\n<li>Which features do I really need (pub/sub, change logs, developer tools)?</li>\n<li>What would happen if I had to change the this library? Am I ok with tying my app to this library for the foreseeable future? How easy would it be to migrate off of? How long would it take to migrate what I have to a new library? When will I get the benefit?</li>\n<li>How do I feel about reading the docs? How often do I even need to reference the docs while developing a proof of concept application?</li>\n<li>After trying this in a test project, does that help answer some of the above questions? How easy was it to get started? How easy was it to make changes?</li>\n<li>What are other people saying about the good/the bad/the great? Is the twitterverse cursing the library or is it a library that people are excited about?</li>\n<li>Will the team behind the library be around for the foreseeable future? How well does it interop with other libraries?</li>\n<li>How responsive has the dev team been to changes in react versions over time? How active are the commits, prs, issues?</li>\n<li>How easy is it to test the store and store actions?</li>\n<li>What are the impacts on bundle size?</li>\n<li>Are SSR/SSG important and how does the library handle it?</li>\n<li>How readable is the code if you actually had to fix a bug yourself?</li>\n</ul>\n<h3>What to look at in each library</h3>\n<p>Global state specific questions to think about:</p>\n<ul>\n<li>how to create a global store?</li>\n<li>how to create a selector (a slice of state)?</li>\n<li>how to create actions/methods that change state?</li>\n<li>how to read the store?</li>\n<li>how to get react to subscribe to changes if anything specific needs to be added to the component (oberver, top level context, middleware, providers)?</li>\n<li>how to manage pub/sub?</li>\n<li>what mental model is needed to be adapted to use the library?</li>\n<li>what does async look like if we need to manage async state updates?</li>\n</ul>\n<h3>Disclaimer</h3>\n<p>These are not optimal solutions, they&#x27;re just what I managed to glean from the docs and examples and it&#x27;s almost guaranteed that I&#x27;ve made a mistake somewhere. Hopefully even that is an indicator of how easy it was to follow the docs for their best practices.</p>\n<h2>Context</h2>\n<p>Context is fine for small apps and components where the shared state changes <em>infrequently</em> to reduce prop drilling. If state changes frequently\nsuch as an slider, or color picker then it&#x27;s better to use a state management library like react-hook-form (yes it&#x27;s a state library), recoil, zustand, jotai, valtio or a form library. If it&#x27;s just to reduce re-renders <a href=\"https://github.com/dai-shi/use-context-selector\">use-context-selector</a> could help.</p>\n<p>Here&#x27;s the a quick context setup example for the times when you need to share state between components.\nInspired by the <a href=\"https://kentcdodds.com/blog/how-to-use-react-context-effectively\">react context example</a> by kentcdodds.</p>\n<div></div><button type=\"button\" class=\"inline-flex items-center rounded-full border border-transparent bg-indigo-600 px-3.5 py-2 text-sm leading-4 font-medium text-white shadow-sm hover:bg-indigo-700 focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 focus:outline-hidden\">Click to toggle inactive</button>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  useContext</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  createContext</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  useState</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  SetStateAction</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  Dispatch</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">} </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;react&#x27;</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">type</span><span style=\"color:#4EC9B0\"> ExampleContextData</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  isActive</span><span style=\"color:#D4D4D4\">?:</span><span style=\"color:#4EC9B0\"> boolean</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  setIsActive</span><span style=\"color:#D4D4D4\">?:</span><span style=\"color:#4EC9B0\"> Dispatch</span><span style=\"color:#E6E6E6\">&lt;</span><span style=\"color:#4EC9B0\">SetStateAction</span><span style=\"color:#E6E6E6\">&lt;</span><span style=\"color:#4EC9B0\">boolean</span><span style=\"color:#E6E6E6\">&gt;&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> ExampleContext</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> createContext</span><span style=\"color:#E6E6E6\">&lt;</span><span style=\"color:#4EC9B0\">ExampleContextData</span><span style=\"color:#E6E6E6\">&gt;({})</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">function</span><span style=\"color:#DCDCAA\"> useExampleContext</span><span style=\"color:#E6E6E6\">() {</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">  const</span><span style=\"color:#9CDCFE\"> context</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> useContext</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">ExampleContext</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  if</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#9CDCFE\">context</span><span style=\"color:#D4D4D4\"> ===</span><span style=\"color:#569CD6\"> undefined</span><span style=\"color:#E6E6E6\">) {</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">    throw</span><span style=\"color:#569CD6\"> new</span><span style=\"color:#DCDCAA\"> Error</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;Context must be used within a provider&#x27;</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  }</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  return</span><span style=\"color:#9CDCFE\"> context</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">function</span><span style=\"color:#DCDCAA\"> ExampleContextComponent</span><span style=\"color:#E6E6E6\">() {</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">  const</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">isActive</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">setIsActive</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#DCDCAA\"> useExampleContext</span><span style=\"color:#E6E6E6\">()</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  return</span><span style=\"color:#E6E6E6\"> (</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">    &lt;</span><span style=\"color:#569CD6\">button</span><span style=\"color:#9CDCFE\"> type</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#CE9178\">&quot;button&quot;</span><span style=\"color:#9CDCFE\"> onClick</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#569CD6\">{</span><span style=\"color:#D4D4D4\">() </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#DCDCAA\"> setIsActive</span><span style=\"color:#D4D4D4\">((</span><span style=\"color:#9CDCFE\">isActive</span><span style=\"color:#D4D4D4\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#D4D4D4\"> !</span><span style=\"color:#9CDCFE\">isActive</span><span style=\"color:#D4D4D4\">)</span><span style=\"color:#569CD6\">}</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">      Click to toggle </span><span style=\"color:#569CD6\">{</span><span style=\"color:#9CDCFE\">isActive</span><span style=\"color:#D4D4D4\"> ? </span><span style=\"color:#CE9178\">&#x27;active&#x27;</span><span style=\"color:#D4D4D4\"> : </span><span style=\"color:#CE9178\">&#x27;inactive&#x27;</span><span style=\"color:#569CD6\">}</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">    &lt;/</span><span style=\"color:#569CD6\">button</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  )</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#569CD6\"> function</span><span style=\"color:#DCDCAA\"> ContextExample</span><span style=\"color:#E6E6E6\">() {</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">  const</span><span style=\"color:#E6E6E6\"> [</span><span style=\"color:#9CDCFE\">isActive</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">setIsActive</span><span style=\"color:#E6E6E6\">] </span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#DCDCAA\"> useState</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#569CD6\">false</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  return</span><span style=\"color:#E6E6E6\"> (</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">    &lt;</span><span style=\"color:#4EC9B0\">ExampleContext.Provider</span><span style=\"color:#9CDCFE\"> value</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#569CD6\">{</span><span style=\"color:#D4D4D4\">{ </span><span style=\"color:#9CDCFE\">isActive</span><span style=\"color:#D4D4D4\">, </span><span style=\"color:#9CDCFE\">setIsActive</span><span style=\"color:#D4D4D4\"> }</span><span style=\"color:#569CD6\">}</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">      {</span><span style=\"color:#6A9955\">/* Component value consumer */</span><span style=\"color:#569CD6\">}</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">      &lt;</span><span style=\"color:#4EC9B0\">ExampleContext.Consumer</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">        {</span><span style=\"color:#D4D4D4\">(</span><span style=\"color:#9CDCFE\">value</span><span style=\"color:#D4D4D4\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#808080\"> &lt;</span><span style=\"color:#569CD6\">div</span><span style=\"color:#808080\">&gt;</span><span style=\"color:#569CD6\">{</span><span style=\"color:#9CDCFE\">value</span><span style=\"color:#D4D4D4\">.</span><span style=\"color:#9CDCFE\">isActive</span><span style=\"color:#569CD6\">}</span><span style=\"color:#808080\">&lt;/</span><span style=\"color:#569CD6\">div</span><span style=\"color:#808080\">&gt;</span><span style=\"color:#569CD6\">}</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">      &lt;/</span><span style=\"color:#4EC9B0\">ExampleContext.Consumer</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">      {</span><span style=\"color:#6A9955\">/* Custom component */</span><span style=\"color:#569CD6\">}</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">      &lt;</span><span style=\"color:#4EC9B0\">ExampleContextComponent</span><span style=\"color:#808080\"> /&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">    &lt;/</span><span style=\"color:#4EC9B0\">ExampleContext.Provider</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  )</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span></code></div></pre></figure>\n<h3>Redux with Redux Toolkit (RTK)</h3>\n<p>It&#x27;s been a long time since looking at redux. Many years ago I tried <code>useReducer</code> and found the over-rendering to be frustrating and threw everything to do with reducers away . It was still a lot of boilerplate to use redux and moving to a form library + react-query removed 99% of the need for complex global state.</p>\n<p>RTK solves a lot of the things I didn&#x27;t like. It&#x27;s a lot easier to use and has much less boilerplate and actually lines up closer to some of the other state management libraries once the store is configured. There&#x27;s still some steps when setting up a new slice. I like how immer is pre-bundled and the user doesn&#x27;t need to necessarily do anything special to work with immutable state updates.</p>\n<p>Edit 2023-09 - Given the amount of boilerplate and the wide adoption of react-query it seems hard to recommend rtk query anymore. Removing the temptation to use redux as the global async store is another reason to keep the separation of concerns clean.</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#6A9955\">// setup the store.ts</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">configureStore</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">ThunkAction</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">Action</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;@reduxjs/toolkit&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#9CDCFE\"> counterReducer</span><span style=\"color:#C586C0\"> from</span><span style=\"color:#CE9178\"> &#x27;../features/counter/counterSlice&#x27;</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#569CD6\"> const</span><span style=\"color:#9CDCFE\"> store</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> configureStore</span><span style=\"color:#E6E6E6\">({</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  reducer:</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    counter:</span><span style=\"color:#9CDCFE\"> counterReducer</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  },</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">})</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#569CD6\"> type</span><span style=\"color:#4EC9B0\"> AppDispatch</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#569CD6\"> typeof</span><span style=\"color:#9CDCFE\"> store</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">dispatch</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#569CD6\"> type</span><span style=\"color:#4EC9B0\"> RootState</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#4EC9B0\"> ReturnType</span><span style=\"color:#E6E6E6\">&lt;</span><span style=\"color:#569CD6\">typeof</span><span style=\"color:#9CDCFE\"> store</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">getState</span><span style=\"color:#E6E6E6\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#569CD6\"> type</span><span style=\"color:#4EC9B0\"> AppThunk</span><span style=\"color:#E6E6E6\">&lt;</span><span style=\"color:#4EC9B0\">ReturnType</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#4EC9B0\"> void</span><span style=\"color:#E6E6E6\">&gt; </span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#4EC9B0\"> ThunkAction</span><span style=\"color:#E6E6E6\">&lt;</span></span>\n<span data-line=\"\"><span style=\"color:#4EC9B0\">  ReturnType</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#4EC9B0\">  RootState</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#4EC9B0\">  unknown</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#4EC9B0\">  Action</span><span style=\"color:#E6E6E6\">&lt;</span><span style=\"color:#4EC9B0\">string</span><span style=\"color:#E6E6E6\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">&gt;</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// implement the reducer</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">createAsyncThunk</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">createSlice</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">PayloadAction</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;@reduxjs/toolkit&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">RootState</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">AppThunk</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;../../app/store&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">fetchCount</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;./counterAPI&#x27;</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#569CD6\"> interface</span><span style=\"color:#4EC9B0\"> CounterState</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  value</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#4EC9B0\"> number</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> initialState</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#4EC9B0\"> CounterState</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  value:</span><span style=\"color:#B5CEA8\"> 0</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#569CD6\"> const</span><span style=\"color:#9CDCFE\"> counterSlice</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> createSlice</span><span style=\"color:#E6E6E6\">({</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  name:</span><span style=\"color:#CE9178\"> &#x27;counter&#x27;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  initialState</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">  // The `reducers` field lets us define reducers and generate associated actions</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  reducers:</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">    increment</span><span style=\"color:#9CDCFE\">:</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#9CDCFE\">state</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">      // Redux Toolkit allows us to write &quot;mutating&quot; logic in reducers. It</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">      // doesn&#x27;t actually mutate the state because it uses the Immer library,</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">      // which detects changes to a &quot;draft state&quot; and produces a brand new</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">      // immutable state based off those changes</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">      state</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">value</span><span style=\"color:#D4D4D4\"> +=</span><span style=\"color:#B5CEA8\"> 1</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    },</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">    // Use the PayloadAction type to declare the contents of `action.payload`</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">    incrementByAmount</span><span style=\"color:#9CDCFE\">:</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#9CDCFE\">state</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">action</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#4EC9B0\"> PayloadAction</span><span style=\"color:#E6E6E6\">&lt;</span><span style=\"color:#4EC9B0\">number</span><span style=\"color:#E6E6E6\">&gt;) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">      state</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">value</span><span style=\"color:#D4D4D4\"> +=</span><span style=\"color:#9CDCFE\"> action</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">payload</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    },</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  },</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">})</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#569CD6\"> const</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">increment</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">incrementByAmount</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#9CDCFE\"> counterSlice</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">actions</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// The function below is called a selector and allows us to select a value from</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// the state. Selectors can also be defined inline where they&#x27;re used instead of</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// in the slice file. For example: `useSelector((state: RootState) =&gt; state.counter.value)`</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#569CD6\"> const</span><span style=\"color:#DCDCAA\"> selectCount</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#9CDCFE\">state</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#4EC9B0\"> RootState</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#9CDCFE\"> state</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">counter</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">value</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// this is not required but perhaps is a way to access methods on store values</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// at first glance it seems like a lot of boilerplate to first grab the dispatcher</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// but as someone not used to redux always having to dispatch feels like extra work</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// this has to be a hook so that we can grab the dispatch ourselves</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#569CD6\"> function</span><span style=\"color:#DCDCAA\"> useIncrementByAmount</span><span style=\"color:#E6E6E6\">() {</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">  const</span><span style=\"color:#9CDCFE\"> dispatch</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> useAppDispatch</span><span style=\"color:#E6E6E6\">()</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  return</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">    incrementByAmount</span><span style=\"color:#9CDCFE\">:</span><span style=\"color:#E6E6E6\"> ({ </span><span style=\"color:#9CDCFE\">amount</span><span style=\"color:#E6E6E6\"> }</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">amount</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#4EC9B0\"> number</span><span style=\"color:#E6E6E6\"> }) </span><span style=\"color:#569CD6\">=&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">      dispatch</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#DCDCAA\">incrementByAmount</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">amount</span><span style=\"color:#E6E6E6\">)),</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  }</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#C586C0\"> default</span><span style=\"color:#9CDCFE\"> counterSlice</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">reducer</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// Counter.tsx</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// use it in a component</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#9CDCFE\"> React</span><span style=\"color:#E6E6E6\">, { </span><span style=\"color:#9CDCFE\">useState</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;react&#x27;</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">useAppSelector</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">useAppDispatch</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;@/app/hooks&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">decrement</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">increment</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">selectCount</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;@/store/counterSlice&#x27;</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#569CD6\"> function</span><span style=\"color:#DCDCAA\"> Counter</span><span style=\"color:#E6E6E6\">() {</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">  const</span><span style=\"color:#9CDCFE\"> count</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> useAppSelector</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">selectCount</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">  const</span><span style=\"color:#9CDCFE\"> dispatch</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> useAppDispatch</span><span style=\"color:#E6E6E6\">()</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  return</span><span style=\"color:#E6E6E6\"> (</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">    &lt;</span><span style=\"color:#569CD6\">div</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">      &lt;</span><span style=\"color:#569CD6\">div</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">        &lt;</span><span style=\"color:#569CD6\">span</span><span style=\"color:#9CDCFE\"> className</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#569CD6\">{</span><span style=\"color:#9CDCFE\">styles</span><span style=\"color:#D4D4D4\">.</span><span style=\"color:#9CDCFE\">value</span><span style=\"color:#569CD6\">}</span><span style=\"color:#808080\">&gt;</span><span style=\"color:#569CD6\">{</span><span style=\"color:#9CDCFE\">count</span><span style=\"color:#569CD6\">}</span><span style=\"color:#808080\">&lt;/</span><span style=\"color:#569CD6\">span</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">        &lt;</span><span style=\"color:#569CD6\">button</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">          className</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#569CD6\">{</span><span style=\"color:#9CDCFE\">styles</span><span style=\"color:#D4D4D4\">.</span><span style=\"color:#9CDCFE\">button</span><span style=\"color:#569CD6\">}</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">          aria-label</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#CE9178\">&quot;Increment value&quot;</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">          onClick</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#569CD6\">{</span><span style=\"color:#D4D4D4\">() </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#DCDCAA\"> dispatch</span><span style=\"color:#D4D4D4\">(</span><span style=\"color:#DCDCAA\">increment</span><span style=\"color:#D4D4D4\">())</span><span style=\"color:#569CD6\">}</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">        &gt;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">          increment</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">        &lt;/</span><span style=\"color:#569CD6\">button</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">      &lt;/</span><span style=\"color:#569CD6\">div</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">    &lt;/</span><span style=\"color:#569CD6\">div</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  )</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span></code></div></pre></figure>\n<h2><a href=\"https://docs.pmnd.rs/zustand\">Zustand</a></h2>\n<p>Zustand is a small state management library.</p>\n<div class=\"flex items-center gap-2\"><button class=\"theme-button inline-flex items-center gap-2 justify-center rounded-lg py-2 px-3 text-sm outline-offset-2 transition-all duration-200 active:transition-none bg-stone-800 font-semibold text-stone-100 hover:bg-stone-700 hover:scale-[1.02] active:bg-stone-800 active:text-stone-100/70 dark:bg-stone-700 dark:hover:bg-stone-600 dark:active:bg-stone-700 dark:active:text-stone-100/70\">increment</button><button class=\"theme-button inline-flex items-center gap-2 justify-center rounded-lg py-2 px-3 text-sm outline-offset-2 transition-all duration-200 active:transition-none bg-stone-50 font-medium text-stone-900 hover:bg-stone-100 hover:scale-[1.02] active:bg-stone-100 active:text-stone-900/60 dark:bg-zinc-800/50 dark:text-stone-300 dark:hover:bg-zinc-800 dark:hover:text-stone-50 dark:active:bg-zinc-800/50 dark:active:text-stone-50/70\">reset</button><div>The zustand count 0</div></div>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">create</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;zustand&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">subscribeWithSelector</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;zustand/middleware&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#9CDCFE\"> produce</span><span style=\"color:#C586C0\"> from</span><span style=\"color:#CE9178\"> &#x27;immer&#x27;</span><span style=\"color:#6A9955\"> // not required but simplifies state updates</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">Button</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;@/components/Button&#x27;</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">interface</span><span style=\"color:#4EC9B0\"> CountState</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  count</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#4EC9B0\"> number</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  increaseCount</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#E6E6E6\"> ({ </span><span style=\"color:#9CDCFE\">amount</span><span style=\"color:#E6E6E6\"> }</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">amount</span><span style=\"color:#D4D4D4\">?:</span><span style=\"color:#4EC9B0\"> number</span><span style=\"color:#E6E6E6\"> }) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#4EC9B0\"> void</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">  resetCount</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#E6E6E6\"> () </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#4EC9B0\"> void</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> useCountStore</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> create</span><span style=\"color:#E6E6E6\">(</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">  subscribeWithSelector</span><span style=\"color:#E6E6E6\">&lt;</span><span style=\"color:#4EC9B0\">CountState</span><span style=\"color:#E6E6E6\">&gt;((</span><span style=\"color:#9CDCFE\">set</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#E6E6E6\"> ({</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    count:</span><span style=\"color:#B5CEA8\"> 0</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">    increaseCount</span><span style=\"color:#9CDCFE\">:</span><span style=\"color:#E6E6E6\"> ({ </span><span style=\"color:#9CDCFE\">amount</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#B5CEA8\"> 1</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#E6E6E6\"> {}) </span><span style=\"color:#569CD6\">=&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">      set</span><span style=\"color:#E6E6E6\">(</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">        produce</span><span style=\"color:#E6E6E6\">((</span><span style=\"color:#9CDCFE\">draft</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">          draft</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">count</span><span style=\"color:#D4D4D4\"> +=</span><span style=\"color:#9CDCFE\"> amount</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">        })</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">      ),</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">    resetCount</span><span style=\"color:#9CDCFE\">:</span><span style=\"color:#E6E6E6\"> () </span><span style=\"color:#569CD6\">=&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">      set</span><span style=\"color:#E6E6E6\">(</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">        produce</span><span style=\"color:#E6E6E6\">((</span><span style=\"color:#9CDCFE\">draft</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">          draft</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">count</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#B5CEA8\"> 0</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">        })</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">      ),</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  }))</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// just a hook to get the count, probably overkill here but I&#x27;ve gotten used to</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// writing react query hooks for all slices of global state</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// this could decouple the component from the store and limits changes to just the &quot;query&quot; functions</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">function</span><span style=\"color:#DCDCAA\"> useGetCount</span><span style=\"color:#E6E6E6\">() {</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  return</span><span style=\"color:#DCDCAA\"> useCountStore</span><span style=\"color:#E6E6E6\">((</span><span style=\"color:#9CDCFE\">state</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#9CDCFE\"> state</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">count</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#569CD6\"> function</span><span style=\"color:#DCDCAA\"> ZustandCounter</span><span style=\"color:#E6E6E6\">() {</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">  const</span><span style=\"color:#9CDCFE\"> count</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> useCountStore</span><span style=\"color:#E6E6E6\">((</span><span style=\"color:#9CDCFE\">state</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#9CDCFE\"> state</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">count</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  return</span><span style=\"color:#808080\"> &lt;</span><span style=\"color:#569CD6\">div</span><span style=\"color:#808080\">&gt;</span><span style=\"color:#E6E6E6\">The zustand count </span><span style=\"color:#569CD6\">{</span><span style=\"color:#9CDCFE\">count</span><span style=\"color:#569CD6\">}</span><span style=\"color:#808080\">&lt;/</span><span style=\"color:#569CD6\">div</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#569CD6\"> function</span><span style=\"color:#DCDCAA\"> ZustandExample</span><span style=\"color:#E6E6E6\">() {</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">  const</span><span style=\"color:#9CDCFE\"> increaseCount</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> useCountStore</span><span style=\"color:#E6E6E6\">((</span><span style=\"color:#9CDCFE\">state</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#9CDCFE\"> state</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">increaseCount</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">  const</span><span style=\"color:#9CDCFE\"> resetCount</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> useCountStore</span><span style=\"color:#E6E6E6\">((</span><span style=\"color:#9CDCFE\">state</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#9CDCFE\"> state</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">resetCount</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  return</span><span style=\"color:#E6E6E6\"> (</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">    &lt;&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">      &lt;</span><span style=\"color:#569CD6\">div</span><span style=\"color:#9CDCFE\"> className</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#CE9178\">&quot;flex items-center gap-2&quot;</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">        &lt;</span><span style=\"color:#4EC9B0\">Button</span><span style=\"color:#9CDCFE\"> variant</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#CE9178\">&quot;primary&quot;</span><span style=\"color:#9CDCFE\"> onClick</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#569CD6\">{</span><span style=\"color:#D4D4D4\">() </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#DCDCAA\"> increaseCount</span><span style=\"color:#D4D4D4\">({ </span><span style=\"color:#9CDCFE\">amount:</span><span style=\"color:#B5CEA8\"> 1</span><span style=\"color:#D4D4D4\"> })</span><span style=\"color:#569CD6\">}</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">          increment</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">        &lt;/</span><span style=\"color:#4EC9B0\">Button</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">        &lt;</span><span style=\"color:#4EC9B0\">Button</span><span style=\"color:#9CDCFE\"> variant</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#CE9178\">&quot;secondary&quot;</span><span style=\"color:#9CDCFE\"> onClick</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#569CD6\">{</span><span style=\"color:#D4D4D4\">() </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#DCDCAA\"> resetCount</span><span style=\"color:#D4D4D4\">()</span><span style=\"color:#569CD6\">}</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">          reset</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">        &lt;/</span><span style=\"color:#4EC9B0\">Button</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">        &lt;</span><span style=\"color:#4EC9B0\">ZustandCounter</span><span style=\"color:#808080\"> /&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">      &lt;/</span><span style=\"color:#569CD6\">div</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">    &lt;/&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  )</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// all state changes</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// const unsubAll = useCountStore.subscribe(console.log)</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// with middleware we can watch only the count changes</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> unsubCount</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#9CDCFE\"> useCountStore</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">subscribe</span><span style=\"color:#E6E6E6\">(</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  (</span><span style=\"color:#9CDCFE\">state</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#9CDCFE\"> state</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">count</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  (</span><span style=\"color:#9CDCFE\">count</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    console</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">log</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;count changed to&#x27;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">count</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  }</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">)</span></span></code></div></pre></figure>\n<p>Immer simplifies updating state but even better is the immer middleware which reduces the boilerplate further. Just remember to follow <a href=\"https://immerjs.github.io/immer/pitfalls/\">the rules of immer</a>.</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">create</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;zustand&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">immer</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;zustand/middleware/immer&#x27;</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">type</span><span style=\"color:#4EC9B0\"> State</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  count</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#4EC9B0\"> number</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">type</span><span style=\"color:#4EC9B0\"> Actions</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  increaseCount</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#E6E6E6\"> ({ </span><span style=\"color:#9CDCFE\">amount</span><span style=\"color:#E6E6E6\"> }</span><span style=\"color:#D4D4D4\">?:</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">amount</span><span style=\"color:#D4D4D4\">?:</span><span style=\"color:#4EC9B0\"> number</span><span style=\"color:#E6E6E6\"> }) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#4EC9B0\"> void</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">  resetCount</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#E6E6E6\"> () </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#4EC9B0\"> void</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#569CD6\"> const</span><span style=\"color:#9CDCFE\"> useCountStore</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> create</span><span style=\"color:#E6E6E6\">(</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">  immer</span><span style=\"color:#E6E6E6\">&lt;</span><span style=\"color:#4EC9B0\">State</span><span style=\"color:#D4D4D4\"> &amp;</span><span style=\"color:#4EC9B0\"> Actions</span><span style=\"color:#E6E6E6\">&gt;((</span><span style=\"color:#9CDCFE\">set</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#E6E6E6\"> ({</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    count:</span><span style=\"color:#B5CEA8\"> 0</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">    increment</span><span style=\"color:#9CDCFE\">:</span><span style=\"color:#E6E6E6\"> ({ </span><span style=\"color:#9CDCFE\">amount</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#B5CEA8\"> 1</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#E6E6E6\"> {}) </span><span style=\"color:#569CD6\">=&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">      set</span><span style=\"color:#E6E6E6\">((</span><span style=\"color:#9CDCFE\">state</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">        state</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">count</span><span style=\"color:#D4D4D4\"> +=</span><span style=\"color:#9CDCFE\"> qty</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">      }),</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">    resetCount</span><span style=\"color:#9CDCFE\">:</span><span style=\"color:#E6E6E6\"> () </span><span style=\"color:#569CD6\">=&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">      set</span><span style=\"color:#E6E6E6\">((</span><span style=\"color:#9CDCFE\">state</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">        state</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">count</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#B5CEA8\"> 0</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">      }),</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  }))</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">)</span></span></code></div></pre></figure>\n<p>To draw some analagoes to redux, zustand has actions that update the store via the <code>set</code>. What RTK has is similar now with state and dispatchers co-located. Co-location of state and actions makes it easier to reason about.</p>\n<p>It&#x27;s still a bit strange that the <code>Actions</code> needed to be typed when they look like they could be inferred from the initial store state. Taking the action from the slice of the store takes some getting used to, but if one thinks of it more like a class that has it&#x27;s methods on it it seems less foreign and helps my mental model of how to use it and wrapping common methods in hooks might be another way to use them directly without having to grab a slice of the store each time.</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> resetCount</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> useCountStore</span><span style=\"color:#E6E6E6\">((</span><span style=\"color:#9CDCFE\">state</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#9CDCFE\"> state</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">resetCount</span><span style=\"color:#E6E6E6\">)</span></span></code></div></pre></figure>\n<h2>Jotai</h2>\n<p>Jotai is an state management library similar to recoil in that slices of state are treated as &quot;atoms&quot;. As an initial user of this what takes getting used to is composing and using atoms and adding atom methods.</p>\n<p>From the <a href=\"https://jotai.org/docs/basics/comparison\">jotai docs</a>:</p>\n<blockquote>\n<p>Analogy\nJotai is like Recoil. Zustand is like Redux.</p>\n</blockquote>\n<p>When to use which</p>\n<ul>\n<li>\n<p>If you want a simple module state, Zustand fits well.</p>\n</li>\n<li>\n<p>If you prefer Redux devtools, Zustand is good to go.</p>\n</li>\n<li>\n<p>If you need a replacement for useState+useContext, Jotai fits well.</p>\n</li>\n<li>\n<p>If you want to make use of Suspense, Jotai is the one.</p>\n</li>\n<li>\n<p>If code splitting is important, Jotai should perform well.</p>\n</li>\n</ul>\n<div class=\"flex items-center gap-2\"><button class=\"theme-button inline-flex items-center gap-2 justify-center rounded-lg py-2 px-3 text-sm outline-offset-2 transition-all duration-200 active:transition-none bg-stone-800 font-semibold text-stone-100 hover:bg-stone-700 hover:scale-[1.02] active:bg-stone-800 active:text-stone-100/70 dark:bg-stone-700 dark:hover:bg-stone-600 dark:active:bg-stone-700 dark:active:text-stone-100/70\">Increment</button><button class=\"theme-button inline-flex items-center gap-2 justify-center rounded-lg py-2 px-3 text-sm outline-offset-2 transition-all duration-200 active:transition-none bg-stone-800 font-semibold text-stone-100 hover:bg-stone-700 hover:scale-[1.02] active:bg-stone-800 active:text-stone-100/70 dark:bg-stone-700 dark:hover:bg-stone-600 dark:active:bg-stone-700 dark:active:text-stone-100/70\">Increment with incrementCountAtom</button><button class=\"theme-button inline-flex items-center gap-2 justify-center rounded-lg py-2 px-3 text-sm outline-offset-2 transition-all duration-200 active:transition-none bg-stone-800 font-semibold text-stone-100 hover:bg-stone-700 hover:scale-[1.02] active:bg-stone-800 active:text-stone-100/70 dark:bg-stone-700 dark:hover:bg-stone-600 dark:active:bg-stone-700 dark:active:text-stone-100/70\">Reset</button><div>1</div></div>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">Button</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;@/components/Button&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">atom</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">useAtom</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">createStore</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">Provider</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;jotai&#x27;</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> countAtom</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> atom</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#B5CEA8\">0</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> messageAtom</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> atom</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;hello&#x27;</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> myStore</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> createStore</span><span style=\"color:#E6E6E6\">()</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> initialCountState</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#B5CEA8\"> 1</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">myStore</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">set</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">countAtom</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">initialCountState</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> unsub</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#9CDCFE\"> myStore</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">sub</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">countAtom</span><span style=\"color:#E6E6E6\">, () </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  console</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">log</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;Jotai countAtom value is changed to&#x27;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">myStore</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">get</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">countAtom</span><span style=\"color:#E6E6E6\">))</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">})</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">function</span><span style=\"color:#DCDCAA\"> JotaiCounter</span><span style=\"color:#E6E6E6\">() {</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">  const</span><span style=\"color:#E6E6E6\"> [</span><span style=\"color:#9CDCFE\">value</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">setValue</span><span style=\"color:#E6E6E6\">] </span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#DCDCAA\"> useAtom</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">countAtom</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  return</span><span style=\"color:#E6E6E6\"> (</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">    &lt;</span><span style=\"color:#569CD6\">div</span><span style=\"color:#9CDCFE\"> className</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#CE9178\">&quot;flex items-center gap-2&quot;</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">      &lt;</span><span style=\"color:#4EC9B0\">Button</span><span style=\"color:#9CDCFE\"> onClick</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#569CD6\">{</span><span style=\"color:#D4D4D4\">() </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#DCDCAA\"> setValue</span><span style=\"color:#D4D4D4\">(</span><span style=\"color:#9CDCFE\">value</span><span style=\"color:#D4D4D4\"> + </span><span style=\"color:#B5CEA8\">1</span><span style=\"color:#D4D4D4\">)</span><span style=\"color:#569CD6\">}</span><span style=\"color:#808080\">&gt;</span><span style=\"color:#E6E6E6\">Increment</span><span style=\"color:#808080\">&lt;/</span><span style=\"color:#4EC9B0\">Button</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">      &lt;</span><span style=\"color:#4EC9B0\">Button</span><span style=\"color:#9CDCFE\"> onClick</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#569CD6\">{</span><span style=\"color:#D4D4D4\">() </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#DCDCAA\"> setValue</span><span style=\"color:#D4D4D4\">(</span><span style=\"color:#9CDCFE\">initialCountState</span><span style=\"color:#D4D4D4\">)</span><span style=\"color:#569CD6\">}</span><span style=\"color:#808080\">&gt;</span><span style=\"color:#E6E6E6\">Reset</span><span style=\"color:#808080\">&lt;/</span><span style=\"color:#4EC9B0\">Button</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">      &lt;</span><span style=\"color:#569CD6\">div</span><span style=\"color:#808080\">&gt;</span><span style=\"color:#569CD6\">{</span><span style=\"color:#9CDCFE\">value</span><span style=\"color:#569CD6\">}</span><span style=\"color:#808080\">&lt;/</span><span style=\"color:#569CD6\">div</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">    &lt;/</span><span style=\"color:#569CD6\">div</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  )</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#569CD6\"> function</span><span style=\"color:#DCDCAA\"> JotaiExample</span><span style=\"color:#E6E6E6\">() {</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  return</span><span style=\"color:#E6E6E6\"> (</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">    &lt;</span><span style=\"color:#4EC9B0\">Provider</span><span style=\"color:#9CDCFE\"> store</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#569CD6\">{</span><span style=\"color:#9CDCFE\">myStore</span><span style=\"color:#569CD6\">}</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">      &lt;</span><span style=\"color:#4EC9B0\">JotaiCounter</span><span style=\"color:#808080\"> /&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">    &lt;/</span><span style=\"color:#4EC9B0\">Provider</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  )</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span></code></div></pre></figure>\n<p>Nice things</p>\n<ul>\n<li>the provider is optional, which allows overriding context for things like components (zustand also now has this)</li>\n<li>one can think about things just at the atom level and not as much a single store</li>\n</ul>\n<p>Things to get used to</p>\n<ul>\n<li>action mutations are done against atoms when the setValue isn&#x27;t used. This could get harder to trace where things are changing but <code>setValue</code> in a form is similar so maybe is fine for smaller primitive changes but something such as calculating a total based on multiple things may require a composite action atom.</li>\n</ul>\n<p>The syntax for an &quot;action atom&quot; looks a bit strange initially.</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> incrementCountAtom</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> atom</span><span style=\"color:#E6E6E6\">(</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  (</span><span style=\"color:#9CDCFE\">get</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#DCDCAA\"> get</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">countAtom</span><span style=\"color:#E6E6E6\">),</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  (</span><span style=\"color:#9CDCFE\">get</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">set</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#DCDCAA\"> set</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">countAtom</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#DCDCAA\">get</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">countAtom</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#D4D4D4\">+</span><span style=\"color:#B5CEA8\"> 1</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// component uses it as</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#E6E6E6\"> [</span><span style=\"color:#9CDCFE\">count</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">incrementCount</span><span style=\"color:#E6E6E6\">] </span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#DCDCAA\"> useAtom</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">incrementCountAtom</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// alternative for just the setter</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> incrementCount</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> useSetAtom</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">incrementCountAtom</span><span style=\"color:#E6E6E6\">)</span></span></code></div></pre></figure>\n<h2>A valtio example</h2>\n<p>Valtio feels more natural for those who don&#x27;t mind mutating state directly.</p>\n<div class=\"flex items-center gap-2\"><button class=\"theme-button inline-flex items-center gap-2 justify-center rounded-lg py-2 px-3 text-sm outline-offset-2 transition-all duration-200 active:transition-none bg-stone-800 font-semibold text-stone-100 hover:bg-stone-700 hover:scale-[1.02] active:bg-stone-800 active:text-stone-100/70 dark:bg-stone-700 dark:hover:bg-stone-600 dark:active:bg-stone-700 dark:active:text-stone-100/70\">Increment directly</button><button class=\"theme-button inline-flex items-center gap-2 justify-center rounded-lg py-2 px-3 text-sm outline-offset-2 transition-all duration-200 active:transition-none bg-stone-800 font-semibold text-stone-100 hover:bg-stone-700 hover:scale-[1.02] active:bg-stone-800 active:text-stone-100/70 dark:bg-stone-700 dark:hover:bg-stone-600 dark:active:bg-stone-700 dark:active:text-stone-100/70\">Increment with incrementCount</button><button class=\"theme-button inline-flex items-center gap-2 justify-center rounded-lg py-2 px-3 text-sm outline-offset-2 transition-all duration-200 active:transition-none bg-stone-800 font-semibold text-stone-100 hover:bg-stone-700 hover:scale-[1.02] active:bg-stone-800 active:text-stone-100/70 dark:bg-stone-700 dark:hover:bg-stone-600 dark:active:bg-stone-700 dark:active:text-stone-100/70\">reset</button>0 </div>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">Button</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;@/components/Button&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">proxy</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">subscribe</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">useSnapshot</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;valtio&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">subscribeKey</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;valtio/utils&#x27;</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#569CD6\"> const</span><span style=\"color:#9CDCFE\"> state</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> proxy</span><span style=\"color:#E6E6E6\">({</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  count:</span><span style=\"color:#B5CEA8\"> 0</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  text:</span><span style=\"color:#CE9178\"> &#x27;hello&#x27;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">  inc</span><span style=\"color:#E6E6E6\">() {</span></span>\n<span data-line=\"\"><span style=\"color:#D4D4D4\">    ++</span><span style=\"color:#569CD6\">this</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">count</span><span style=\"color:#6A9955\"> // or see below for exporting directly from the module</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  },</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">})</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// actions defined this way are better for code splitting</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// https://github.com/pmndrs/valtio/blob/main/docs/how-tos/how-to-organize-actions.mdx#action-functions-defined-in-module</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#569CD6\"> function</span><span style=\"color:#DCDCAA\"> incrementCount</span><span style=\"color:#E6E6E6\">({ </span><span style=\"color:#9CDCFE\">amount</span><span style=\"color:#E6E6E6\"> }) {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  state</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">count</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#9CDCFE\"> state</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">count</span><span style=\"color:#D4D4D4\"> +</span><span style=\"color:#9CDCFE\"> amount</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#569CD6\"> function</span><span style=\"color:#DCDCAA\"> ValtioExample</span><span style=\"color:#E6E6E6\">() {</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">  const</span><span style=\"color:#9CDCFE\"> snap</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> useSnapshot</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">state</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  return</span><span style=\"color:#E6E6E6\"> (</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">    &lt;</span><span style=\"color:#569CD6\">div</span><span style=\"color:#9CDCFE\"> className</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#CE9178\">&quot;flex items-center gap-2&quot;</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">      &lt;</span><span style=\"color:#4EC9B0\">Button</span><span style=\"color:#9CDCFE\"> onClick</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#569CD6\">{</span><span style=\"color:#D4D4D4\">() </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#D4D4D4\"> (</span><span style=\"color:#9CDCFE\">state</span><span style=\"color:#D4D4D4\">.</span><span style=\"color:#9CDCFE\">count</span><span style=\"color:#D4D4D4\"> = </span><span style=\"color:#9CDCFE\">state</span><span style=\"color:#D4D4D4\">.</span><span style=\"color:#9CDCFE\">count</span><span style=\"color:#D4D4D4\"> + </span><span style=\"color:#B5CEA8\">1</span><span style=\"color:#D4D4D4\">)</span><span style=\"color:#569CD6\">}</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">        Increment directly</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">      &lt;/</span><span style=\"color:#4EC9B0\">Button</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">      &lt;</span><span style=\"color:#4EC9B0\">Button</span><span style=\"color:#9CDCFE\"> onClick</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#569CD6\">{</span><span style=\"color:#D4D4D4\">() </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#DCDCAA\"> incrementCount</span><span style=\"color:#D4D4D4\">({ </span><span style=\"color:#9CDCFE\">amount:</span><span style=\"color:#B5CEA8\"> 1</span><span style=\"color:#D4D4D4\"> })</span><span style=\"color:#569CD6\">}</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">        increment via function</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">      &lt;/</span><span style=\"color:#4EC9B0\">Button</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">      &lt;</span><span style=\"color:#4EC9B0\">Button</span><span style=\"color:#9CDCFE\"> onClick</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#569CD6\">{</span><span style=\"color:#D4D4D4\">() </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#D4D4D4\"> (</span><span style=\"color:#9CDCFE\">state</span><span style=\"color:#D4D4D4\">.</span><span style=\"color:#9CDCFE\">count</span><span style=\"color:#D4D4D4\"> = </span><span style=\"color:#B5CEA8\">0</span><span style=\"color:#D4D4D4\">)</span><span style=\"color:#569CD6\">}</span><span style=\"color:#808080\">&gt;</span><span style=\"color:#E6E6E6\">reset</span><span style=\"color:#808080\">&lt;/</span><span style=\"color:#4EC9B0\">Button</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">      {</span><span style=\"color:#9CDCFE\">snap</span><span style=\"color:#D4D4D4\">.</span><span style=\"color:#9CDCFE\">count</span><span style=\"color:#569CD6\">}{</span><span style=\"color:#CE9178\">&#x27; &#x27;</span><span style=\"color:#569CD6\">}</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">    &lt;/</span><span style=\"color:#569CD6\">div</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  )</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// Subscribe to all state changes</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> unsubscribe</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> subscribe</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">state</span><span style=\"color:#E6E6E6\">, () </span><span style=\"color:#569CD6\">=&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  console</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">log</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;valtio example state has changed to&#x27;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">state</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// a primitive needs to be subscribed to with subscribeKey</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> unsubscribeSlice</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> subscribeKey</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">state</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#CE9178\">&#x27;count&#x27;</span><span style=\"color:#E6E6E6\">, () </span><span style=\"color:#569CD6\">=&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  console</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">log</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;valtio count state has changed to&#x27;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">state</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">)</span></span></code></div></pre></figure>\n<h2>Legend state</h2>\n<p>Legend state is closeser to valtio and <code>createSignal</code> from solid (see below).</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">observable</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">observe</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;@legendapp/state&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">useSelector</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">observer</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;@legendapp/state/react&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">useTraceUpdates</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;@legendapp/state/trace&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">useTraceListeners</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;@legendapp/state/trace&#x27;</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> countStore</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> observable</span><span style=\"color:#E6E6E6\">({</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  count:</span><span style=\"color:#B5CEA8\"> 0</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  incrementAmount:</span><span style=\"color:#B5CEA8\"> 1</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">})</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// as a method in a module or mutate state directly</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">function</span><span style=\"color:#DCDCAA\"> inc</span><span style=\"color:#E6E6E6\">() {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  countStore</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">count</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">set</span><span style=\"color:#E6E6E6\">((</span><span style=\"color:#9CDCFE\">count</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#9CDCFE\"> count</span><span style=\"color:#D4D4D4\"> +</span><span style=\"color:#B5CEA8\"> 1</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> CounterObserver</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> observer</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#569CD6\">function</span><span style=\"color:#DCDCAA\"> CounterObserver</span><span style=\"color:#E6E6E6\">() {</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">  // some debugging tools</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">  useTraceUpdates</span><span style=\"color:#E6E6E6\">()</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">  useTraceListeners</span><span style=\"color:#E6E6E6\">()</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  return</span><span style=\"color:#808080\"> &lt;</span><span style=\"color:#569CD6\">div</span><span style=\"color:#808080\">&gt;</span><span style=\"color:#E6E6E6\">Count: </span><span style=\"color:#569CD6\">{</span><span style=\"color:#9CDCFE\">countStore</span><span style=\"color:#D4D4D4\">.</span><span style=\"color:#9CDCFE\">count</span><span style=\"color:#D4D4D4\">.</span><span style=\"color:#DCDCAA\">get</span><span style=\"color:#D4D4D4\">()</span><span style=\"color:#569CD6\">}</span><span style=\"color:#808080\">&lt;/</span><span style=\"color:#569CD6\">div</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">})</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">//</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">function</span><span style=\"color:#DCDCAA\"> CountSelector</span><span style=\"color:#E6E6E6\">() {</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">  const</span><span style=\"color:#9CDCFE\"> count</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> useSelector</span><span style=\"color:#E6E6E6\">(() </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#9CDCFE\"> countStore</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">count</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">  // Only re-renders if the return value changes</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  return</span><span style=\"color:#808080\"> &lt;</span><span style=\"color:#569CD6\">div</span><span style=\"color:#808080\">&gt;</span><span style=\"color:#E6E6E6\">Watching the count with a selector: </span><span style=\"color:#569CD6\">{</span><span style=\"color:#9CDCFE\">count</span><span style=\"color:#569CD6\">}</span><span style=\"color:#808080\">&lt;/</span><span style=\"color:#569CD6\">div</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">function</span><span style=\"color:#DCDCAA\"> Counter</span><span style=\"color:#E6E6E6\">() {</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  return</span><span style=\"color:#E6E6E6\"> (</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">    &lt;&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">      &lt;</span><span style=\"color:#569CD6\">button</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">        onClick</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#569CD6\">{</span><span style=\"color:#D4D4D4\">() </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#D4D4D4\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">          countStore</span><span style=\"color:#D4D4D4\">.</span><span style=\"color:#9CDCFE\">count</span><span style=\"color:#D4D4D4\">.</span><span style=\"color:#DCDCAA\">set</span><span style=\"color:#D4D4D4\">((</span><span style=\"color:#9CDCFE\">count</span><span style=\"color:#D4D4D4\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#9CDCFE\"> count</span><span style=\"color:#D4D4D4\"> + </span><span style=\"color:#B5CEA8\">1</span><span style=\"color:#D4D4D4\">)</span></span>\n<span data-line=\"\"><span style=\"color:#D4D4D4\">        }</span><span style=\"color:#569CD6\">}</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">      &gt;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">        increment</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">      &lt;/</span><span style=\"color:#569CD6\">button</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">      &lt;</span><span style=\"color:#569CD6\">button</span><span style=\"color:#9CDCFE\"> onClick</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#569CD6\">{</span><span style=\"color:#D4D4D4\">() </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#DCDCAA\"> inc</span><span style=\"color:#D4D4D4\">()</span><span style=\"color:#569CD6\">}</span><span style=\"color:#808080\">&gt;</span><span style=\"color:#E6E6E6\">inc</span><span style=\"color:#808080\">&lt;/</span><span style=\"color:#569CD6\">button</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">      &lt;</span><span style=\"color:#569CD6\">button</span><span style=\"color:#9CDCFE\"> onClick</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#569CD6\">{</span><span style=\"color:#D4D4D4\">() </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#9CDCFE\"> countStore</span><span style=\"color:#D4D4D4\">.</span><span style=\"color:#9CDCFE\">count</span><span style=\"color:#D4D4D4\">.</span><span style=\"color:#DCDCAA\">set</span><span style=\"color:#D4D4D4\">(</span><span style=\"color:#B5CEA8\">0</span><span style=\"color:#D4D4D4\">)</span><span style=\"color:#569CD6\">}</span><span style=\"color:#808080\">&gt;</span><span style=\"color:#E6E6E6\">reset</span><span style=\"color:#808080\">&lt;/</span><span style=\"color:#569CD6\">button</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">      &lt;</span><span style=\"color:#4EC9B0\">CounterObserver</span><span style=\"color:#808080\"> /&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">      &lt;</span><span style=\"color:#4EC9B0\">CountSelector</span><span style=\"color:#808080\"> /&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">    &lt;/&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  )</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">function</span><span style=\"color:#DCDCAA\"> LegendStateExample</span><span style=\"color:#E6E6E6\">() {</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  return</span><span style=\"color:#E6E6E6\"> (</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">    &lt;</span><span style=\"color:#569CD6\">div</span><span style=\"color:#9CDCFE\"> className</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#CE9178\">&quot;App&quot;</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">      &lt;</span><span style=\"color:#4EC9B0\">Counter</span><span style=\"color:#808080\"> /&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">    &lt;/</span><span style=\"color:#569CD6\">div</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  )</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// observe re-runs when any observables change values</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">//</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">observe</span><span style=\"color:#E6E6E6\">(() </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  console</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">log</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;watching for changes&#x27;</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">  // if the primitive doesn&#x27;t change, this oberver will not fire</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  console</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">log</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">countStore</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">count</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">get</span><span style=\"color:#E6E6E6\">())</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">})</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#C586C0\"> default</span><span style=\"color:#9CDCFE\"> LegendStateExample</span></span></code></div></pre></figure>\n<p>Super cool things about this lib are what aren&#x27;t shown here, the built in performance where observables are automatically turned <em>into</em> react components(!!) that only re-render when the value changes. :mindblown:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">enableLegendStateReact</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;@legendapp/state/react&#x27;</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">enableLegendStateReact</span><span style=\"color:#E6E6E6\">()</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> count</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> observable</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#B5CEA8\">0</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">function</span><span style=\"color:#DCDCAA\"> Optimized</span><span style=\"color:#E6E6E6\">() {</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">  // This never re-renders when observable is rendered directly</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  return</span><span style=\"color:#808080\"> &lt;</span><span style=\"color:#569CD6\">div</span><span style=\"color:#808080\">&gt;</span><span style=\"color:#E6E6E6\">Count: </span><span style=\"color:#569CD6\">{</span><span style=\"color:#9CDCFE\">count</span><span style=\"color:#569CD6\">}</span><span style=\"color:#808080\">&lt;/</span><span style=\"color:#569CD6\">div</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span></code></div></pre></figure>\n<p>Also their custom <a href=\"https://legendapp.com/open-source/state/fine-grained-reactivity/#memo\">performance components</a> such as <code>Memo</code> which automatically memoizes the children is pretty cool. Without this, the parent would typically re-render when the child changes.</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#569CD6\">function</span><span style=\"color:#DCDCAA\"> MemoExample</span><span style=\"color:#E6E6E6\">() {</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  return</span><span style=\"color:#E6E6E6\"> (</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">    &lt;</span><span style=\"color:#569CD6\">div</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">      &lt;</span><span style=\"color:#4EC9B0\">Memo</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">        {</span><span style=\"color:#D4D4D4\">() </span><span style=\"color:#569CD6\">=&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">          state</span><span style=\"color:#D4D4D4\">.</span><span style=\"color:#9CDCFE\">messages</span><span style=\"color:#D4D4D4\">.</span><span style=\"color:#DCDCAA\">map</span><span style=\"color:#D4D4D4\">((</span><span style=\"color:#9CDCFE\">message</span><span style=\"color:#D4D4D4\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#D4D4D4\"> (</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">            &lt;</span><span style=\"color:#569CD6\">div</span><span style=\"color:#9CDCFE\"> key</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#569CD6\">{</span><span style=\"color:#9CDCFE\">message</span><span style=\"color:#D4D4D4\">.</span><span style=\"color:#9CDCFE\">id</span><span style=\"color:#569CD6\">}</span><span style=\"color:#808080\">&gt;</span><span style=\"color:#569CD6\">{</span><span style=\"color:#9CDCFE\">message</span><span style=\"color:#D4D4D4\">.</span><span style=\"color:#9CDCFE\">text</span><span style=\"color:#569CD6\">}</span><span style=\"color:#808080\">&lt;/</span><span style=\"color:#569CD6\">div</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#D4D4D4\">          ))</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">        }</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">      &lt;/</span><span style=\"color:#4EC9B0\">Memo</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">    &lt;/</span><span style=\"color:#569CD6\">div</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  )</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span></code></div></pre></figure>\n<h2>Signals and solid state</h2>\n<p>It&#x27;s worth looking at how the solid js lib works by using signals. Performance is awesome and using signals as <code>useState</code> inside a component or outside as a store is really cool. Once a developer gets used to this way of working it&#x27;s nice that performance comes naturally and doesn&#x27;t require any <code>observer</code> tags since it&#x27;s built into the framework. A feature I am super envious of is also declaring effects without providing their dependencies explicitly.</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">render</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;solid-js/web&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">createSignal</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;solid-js&#x27;</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#E6E6E6\"> [</span><span style=\"color:#9CDCFE\">countStore</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">setCountStore</span><span style=\"color:#E6E6E6\">] </span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#DCDCAA\"> createSignal</span><span style=\"color:#E6E6E6\">({</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  count:</span><span style=\"color:#B5CEA8\"> 0</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  incrementAmount:</span><span style=\"color:#B5CEA8\"> 1</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">})</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">function</span><span style=\"color:#DCDCAA\"> Counter</span><span style=\"color:#E6E6E6\">() {</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">  createEffect</span><span style=\"color:#E6E6E6\">(() </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    console</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">log</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;The count is now&#x27;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#DCDCAA\">countStore</span><span style=\"color:#E6E6E6\">().</span><span style=\"color:#9CDCFE\">count</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  }) </span><span style=\"color:#6A9955\">// no dependency array!  similar to observe but nice to see it built in</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  return</span><span style=\"color:#E6E6E6\"> (</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">    &lt;</span><span style=\"color:#569CD6\">button</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">      onClick</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#569CD6\">{</span><span style=\"color:#D4D4D4\">() </span><span style=\"color:#569CD6\">=&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">        setCountStore</span><span style=\"color:#D4D4D4\">((</span><span style=\"color:#9CDCFE\">state</span><span style=\"color:#D4D4D4\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#D4D4D4\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">          console</span><span style=\"color:#D4D4D4\">.</span><span style=\"color:#DCDCAA\">info</span><span style=\"color:#D4D4D4\">(</span><span style=\"color:#9CDCFE\">state</span><span style=\"color:#D4D4D4\">.</span><span style=\"color:#9CDCFE\">incrementAmount</span><span style=\"color:#D4D4D4\">)</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">          return</span><span style=\"color:#D4D4D4\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#D4D4D4\">            ...</span><span style=\"color:#9CDCFE\">state</span><span style=\"color:#D4D4D4\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">            count:</span><span style=\"color:#9CDCFE\"> state</span><span style=\"color:#D4D4D4\">.</span><span style=\"color:#9CDCFE\">count</span><span style=\"color:#D4D4D4\"> + </span><span style=\"color:#9CDCFE\">state</span><span style=\"color:#D4D4D4\">.</span><span style=\"color:#9CDCFE\">incrementAmount</span><span style=\"color:#D4D4D4\">,</span></span>\n<span data-line=\"\"><span style=\"color:#D4D4D4\">          }</span></span>\n<span data-line=\"\"><span style=\"color:#D4D4D4\">        })</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">      }</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">    &gt;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">      Count: </span><span style=\"color:#569CD6\">{</span><span style=\"color:#DCDCAA\">countStore</span><span style=\"color:#D4D4D4\">().</span><span style=\"color:#9CDCFE\">count</span><span style=\"color:#569CD6\">}</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">    &lt;/</span><span style=\"color:#569CD6\">button</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  )</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">render</span><span style=\"color:#E6E6E6\">(() </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#808080\"> &lt;</span><span style=\"color:#4EC9B0\">Counter</span><span style=\"color:#808080\"> /&gt;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">document</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">getElementById</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;app&#x27;</span><span style=\"color:#E6E6E6\">))</span></span></code></div></pre></figure>\n<p>Like most proxy based solutions, one has to learn the differences between when to access the property directly and when to use a method based version.</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#DCDCAA\">countStore</span><span style=\"color:#E6E6E6\">().</span><span style=\"color:#9CDCFE\">count</span></span></code></div></pre></figure>\n<p>This is not a big deal but is a learning curve and can lead to some harder to debug issues when first getting used to them.</p>\n<p>Another example from <a href=\"https://tanstack.com/query/v4/docs/react/adapters/solid-query#important-differences-between-solid-query--react-query\">tanstack-query with solid</a>.</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#E6E6E6\"> [</span><span style=\"color:#9CDCFE\">enabled</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">setEnabled</span><span style=\"color:#E6E6E6\">] </span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#DCDCAA\"> createSignal</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#569CD6\">false</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> query</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> createQuery</span><span style=\"color:#E6E6E6\">(() </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#E6E6E6\"> [</span><span style=\"color:#CE9178\">&#x27;todos&#x27;</span><span style=\"color:#E6E6E6\">], </span><span style=\"color:#9CDCFE\">fetchTodos</span><span style=\"color:#E6E6E6\">, {</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">  // ❌ passing a signal directly is not reactive</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">  // enabled: enabled(),</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">  // ✅ passing a function that returns a signal is reactive</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">  get</span><span style=\"color:#DCDCAA\"> enabled</span><span style=\"color:#E6E6E6\">() {</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">    return</span><span style=\"color:#DCDCAA\"> enabled</span><span style=\"color:#E6E6E6\">()</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  },</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">})</span></span></code></div></pre></figure>\n<p>I imagine that once these are learned they get easier to use and seeing that signals are becoming popular across the libraries means this is definitely a space to watch.</p>\n<h3>Conclusion</h3>\n<p>Overall what I recommend is -- minimize global state so you hopefully don&#x27;t need a lib. All jokes aside, it&#x27;s not so much which is best, but which is best for your use case. One of the beautiful things is that the <a href=\"https://github.com/LegendApp/legend-state#2-%EF%B8%8F-the-fastest-react-state-library\">performance</a> is pretty good if the recommendations of the libraries are followed and changes in global state aren&#x27;t fired constantly.</p>\n<p>I previously recommended trying zustand, it&#x27;s simple and easy to read the single page doc to get an understanding. Just remember to always use selectors otherwise the component will notify the same as using context.</p>\n<p>However, with signals becoming ever more popular and reaching stage 1 <a href=\"https://github.com/tc39/proposal-signals\">RFC</a>, it&#x27;s worth looking at libraries that closely map to the new api. While most library authors might end up using signals under the covers, Legend state is actually the closest api wise. The primary downside is wrapping components in <code>observer</code> and getting used to needing to append <code>.get()</code> or <code>.set()</code> -- otherwise in my usage it&#x27;s worked well without additional gotchas.</p>\n<ul>\n<li>Do I even need this library (ie. do I even need global state or can I get by with alternatives of something like react-hook-form and react-query)\n<blockquote>\n<p>probably not but only for a small bit of context</p>\n</blockquote>\n</li>\n<li>Does one api map more closely to your current way of working or the way you want to work?\n<blockquote>\n<p>colocating state and actions is simple, I don&#x27;t love the actions having to be sliced off of the store in each component but wrapping in a hook is a nice way to get around that</p>\n</blockquote>\n</li>\n<li>Which api is simplest? What new concepts to developers need to learn as they onboard?\n<blockquote>\n<p>very small api, some of the other libraries work with proxies which are great but often have a few gotchas to learn about them. Things like when to use a function <code>value()</code>, when to access the store directly, and when to <code>observe</code>. It&#x27;s true that zustand with immer (zimmer?) is using proxies but I&#x27;ve learnt many of the gotchas from using immer directly so for my use case this is a lower barrier to entry. Using mobx in the past was great but I did find myself with proxies when I was expecting the raw object and there was a lot of time unwrapping things. I&#x27;d like to give valtio and legend state another try in another project to see if I can get over the hump of learning the new api.</p>\n<p>I really like the idea of signals and truly reactive state changes so it may be that for more reactive applications with minimal boilerplate so one of those may help. It might be that\nworking with proxy state is not as hard but would like to test this more before committing to it. Legend state is pushing the boundaries here so I&#x27;d like to give it a longer test drive than I was able to in a few hours.</p>\n</blockquote>\n</li>\n<li>Which features do I really need (pub/sub, change logs, developer tools)?\n<blockquote>\n<p>has all of those, and a small bundle size is nice</p>\n</blockquote>\n</li>\n<li>What would happen if I had to change the this library? Am I ok with tying my app to this library for the foreseeable future? How easy would it be to migrate off of?\n<blockquote>\n<p>hopefully the small api means this is easish to adatpt to a new library if needed</p>\n</blockquote>\n</li>\n<li>How do I feel about reading the docs? How often do I even need to reference the docs after I get started?\n<blockquote>\n<p><a href=\"https://docs.pmnd.rs/zustand/getting-started/introduction\">docs</a> are nice</p>\n</blockquote>\n</li>\n<li>After trying this in a test project, does that help answer some of the above questions? How easy was it to get started? How easy was it to make changes?\n<blockquote>\n<p>was quick to get running once immer example was used, pretty easy to add a new action. Types are the biggest pain to have to duplicate type, but perhaps there is a better way to infer types from the initial store state. Needs some investigation but not a deal breaker</p>\n</blockquote>\n</li>\n<li>What are other people saying about the good/the bad/the great? Is twitter cursing the library or is it a library that people are excited about?\n<blockquote>\n<p>anecdotal but most people seem happy with it on the twitters and haven&#x27;t seen too many complaints</p>\n</blockquote>\n</li>\n<li>Will the team behind the library be around for the foreseeable future? How well does it interop with other libraries?\n<blockquote>\n<p>pmndrs and dai-shi have great libs (including the aformentioned valtio, jotai), zustand used by 85k, and 158 contribs seem like it is healthy</p>\n</blockquote>\n</li>\n<li>How easy is it to test the store and store actions?\n<blockquote>\n<p>👍🏻 store can be accessed directly so not much setup</p>\n</blockquote>\n</li>\n</ul>\n<p>State management has come a long way in recent years and as react developers we have almost too much choice for good libraries! Good luck in your state management choice, I hope this helps you make the right choice for your project.</p>\n<h3>Props</h3>\n<ul>\n<li>rtk - for reducing the boilerplate of redux</li>\n<li>zustand - for a small and simple api</li>\n<li>jotai/recoil - nice way to work with small slices of state</li>\n<li>mobx - the original reactive proxy based state management library (and immer for being useful in lots of places)</li>\n<li>valtio - reactive mutable state for react</li>\n<li>legend state - really pushing for minimal re-renders with nice performance components and cool tricks like components from observable state</li>\n<li>useState - the workhorse of react state management</li>\n<li>react-query and use-swr - for getting so much state out of our other state managers</li>\n<li>react-hook-form - for making forms fun-ish again</li>\n</ul>\n<h3>Further reading</h3>\n<ul>\n<li>From the author of zustand/jotai/valtio - <a href=\"https://blog.axlight.com/posts/when-i-use-valtio-and-when-i-use-jotai/\">When I use valito and when I use jotai</a></li>\n</ul>",
            "url": "https://alexmchan.com/blog/2023-03-20-a-slice-of-state",
            "title": "A slice of state management in react",
            "summary": "A quick review of state management in react",
            "date_modified": "2023-03-21T00:00:00.000Z",
            "author": {
                "name": "Alex Chan"
            }
        },
        {
            "id": "https://alexmchan.com/blog/2023-03-18-why-prefer-tanstack-query",
            "content_html": "<h2>Background</h2>\n<p>I&#x27;ve been using tanstack query (react-query) for a while and have gotten used to the patterns around it. Before that, I&#x27;ve used <code>useSWR</code>, and apollo/urql. While I&#x27;d prefer to use graphql when available, react-query has been fantastic for working with REST APIs.</p>\n<p>A project I joined has redux but not yet rtk or rtk query so I thought I&#x27;d give both a try. RTK has been great for simplifying redux, but if we don&#x27;t need as much data in the global store anymore, I wonder why we&#x27;d want to use rtk query.</p>\n<h2>React Query</h2>\n<p>The <a href=\"https://tanstack.com/query/v4/docs/react/comparison\">feature comparison</a> sums up most of the differences so I&#x27;ll just talk about some of the things I noticed when first picking up rtk query and why I prefer react-query.</p>\n<h2>RTK query</h2>\n<h3>Example</h3>\n<p>From the docs there&#x27;s a createApi wrapper that takes a base url and endpoints. The endpoints are defined using a builder.</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#6A9955\">// Need to use the React-specific entry point to import createApi</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">createApi</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">fetchBaseQuery</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;@reduxjs/toolkit/query/react&#x27;</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// Define a service using a base URL and expected endpoints</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#569CD6\"> const</span><span style=\"color:#9CDCFE\"> pokemonApi</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> createApi</span><span style=\"color:#E6E6E6\">({</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  reducerPath:</span><span style=\"color:#CE9178\"> &#x27;pokemonApi&#x27;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  baseQuery:</span><span style=\"color:#DCDCAA\"> fetchBaseQuery</span><span style=\"color:#E6E6E6\">({ </span><span style=\"color:#9CDCFE\">baseUrl:</span><span style=\"color:#CE9178\"> &#x27;https://pokeapi.co/api/v2/&#x27;</span><span style=\"color:#E6E6E6\"> }),</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">  endpoints</span><span style=\"color:#9CDCFE\">:</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#9CDCFE\">builder</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#E6E6E6\"> ({</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    getPokemonByName:</span><span style=\"color:#9CDCFE\"> builder</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">query</span><span style=\"color:#E6E6E6\">&lt;</span><span style=\"color:#4EC9B0\">any</span><span style=\"color:#E6E6E6\">, { </span><span style=\"color:#9CDCFE\">name</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#4EC9B0\"> string</span><span style=\"color:#E6E6E6\"> }&gt;({</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">      query</span><span style=\"color:#9CDCFE\">:</span><span style=\"color:#E6E6E6\"> ({ </span><span style=\"color:#9CDCFE\">name</span><span style=\"color:#E6E6E6\"> }) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#CE9178\"> `pokemon/</span><span style=\"color:#569CD6\">${</span><span style=\"color:#9CDCFE\">name</span><span style=\"color:#569CD6\">}</span><span style=\"color:#CE9178\">`</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    }),</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  }),</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">})</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// Export hooks for usage in functional components, which are</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// auto-generated based on the defined endpoints</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#569CD6\"> const</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">useGetPokemonByNameQuery</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#9CDCFE\"> pokemonApi</span></span></code></div></pre></figure>\n<ul>\n<li><code>reducerPath</code> is needed to know where to put the slice of data.</li>\n<li><code>baseQuery</code> is the fetch function</li>\n<li><code>builder</code> is a function that provides a route with the query key</li>\n</ul>\n<p>The <code>query</code> being the query key is the most confusing part coming from react-query. I admit that understanding this part of react-query takes some time as well but the idea of query key and query path have been merged here. The nice thing is it&#x27;s true that <em>most</em> of the time this is the same. With a more complex API the body is also passed from here. If we want to transform the data, a <code>transformResponse</code> function can be provided or a <code>queryFn</code>.</p>\n<p>It turns out <code>queryFn</code> is closer to what react-query does, however, then we need to specify the serialization of query args <code>serializeQueryArgs</code>. Not overly complex but this stuff is all done by react-query without having to understand what is happening. react-query&#x27;s <code>queryKey</code> is stably serialized by default and not something I need to think about.</p>\n<h3>Wiring up the store</h3>\n<p>Each new slice of state also needs to be added as middleware to the router. While not horrible, it&#x27;s just one more thing to remember to do. Also there will then be some naming convention to follow to make sure the slice is named the same as the reducerPath. I&#x27;m lazy and I like minimal api surface area so less is more. I totally understand that registering it is kind of necessary, but react-query doesn&#x27;t need to declare things because it&#x27;s in full control of it&#x27;s global cache.</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">configureStore</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;@reduxjs/toolkit&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// Or from &#x27;@reduxjs/toolkit/query/react&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">setupListeners</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;@reduxjs/toolkit/query&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">pokemonApi</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;./services/pokemon&#x27;</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#569CD6\"> const</span><span style=\"color:#9CDCFE\"> store</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> configureStore</span><span style=\"color:#E6E6E6\">({</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  reducer:</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">    // Add the generated reducer as a specific top-level slice</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    [pokemonApi.reducerPath]:</span><span style=\"color:#9CDCFE\"> pokemonApi</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">reducer</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  },</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">  // Adding the api middleware enables caching, invalidation, polling,</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">  // and other useful features of `rtk-query`.</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">  middleware</span><span style=\"color:#9CDCFE\">:</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#9CDCFE\">getDefaultMiddleware</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#569CD6\">=&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">    getDefaultMiddleware</span><span style=\"color:#E6E6E6\">().</span><span style=\"color:#DCDCAA\">concat</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">pokemonApi</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">middleware</span><span style=\"color:#E6E6E6\">),</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">})</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// optional, but required for refetchOnFocus/refetchOnReconnect behaviors</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// see `setupListeners` docs - takes an optional callback as the 2nd arg for customization</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">setupListeners</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">store</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">dispatch</span><span style=\"color:#E6E6E6\">)</span></span></code></div></pre></figure>\n<h3>Cool stuff</h3>\n<p>Export hooks are auto generated based on the aformentioned <code>createApi</code>. This is really neat and reminiscent of trpc (which is also awesome). This is where we see the power of the factory function and there&#x27;s probably some cool string template types going on here. Still, with react-query things are just exported as the function that is created.</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#6A9955\">// Export hooks for usage in functional components, which are</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// auto-generated based on the defined endpoints</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#569CD6\"> const</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">useGetPokemonByNameQuery</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#9CDCFE\"> pokemonApi</span></span></code></div></pre></figure>\n<h3>Hooks in components</h3>\n<p>The base api is the same which is great, but it&#x27;s here that I notice the difference in control when making a wrapper query.</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#569CD6\"> *</span><span style=\"color:#C586C0\"> as</span><span style=\"color:#9CDCFE\"> React</span><span style=\"color:#C586C0\"> from</span><span style=\"color:#CE9178\"> &#x27;react&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">useGetPokemonByNameQuery</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;./services/pokemon&#x27;</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#C586C0\"> default</span><span style=\"color:#569CD6\"> function</span><span style=\"color:#DCDCAA\"> App</span><span style=\"color:#E6E6E6\">() {</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">  // Using a query hook automatically fetches data and returns query values</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">  const</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">data</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">error</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">isLoading</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#DCDCAA\"> useGetPokemonByNameQuery</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;bulbasaur&#x27;</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">  // Individual hooks are also accessible under the generated endpoints:</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">  // const { data, error, isLoading } = pokemonApi.endpoints.getPokemonByName.useQuery(&#x27;bulbasaur&#x27;)</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">  // render UI based on data and loading state</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span></code></div></pre></figure>\n<p>The main options that we have to adjust are:</p>\n<ul>\n<li>pollingInterval</li>\n<li>refetchOnFocus</li>\n<li>refetchOnMountOrArgChange</li>\n<li>refetchOnReconnect</li>\n<li>selectFromResult</li>\n</ul>\n<p>compare this to a react-query hook wrapper where we can add options inline</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">useQuery</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;@tanstack/react-query&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#9CDCFE\"> axios</span><span style=\"color:#C586C0\"> from</span><span style=\"color:#CE9178\"> &#x27;axios&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">Environment</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;@/selectors/environment&#x27;</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> queryKeyId</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#CE9178\"> &#x27;environments&#x27;</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#569CD6\"> function</span><span style=\"color:#DCDCAA\"> useGetEnvironmentsQuery</span><span style=\"color:#E6E6E6\">({</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  namespace</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  appName</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  namespace</span><span style=\"color:#D4D4D4\">?:</span><span style=\"color:#4EC9B0\"> string</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  appName</span><span style=\"color:#D4D4D4\">?:</span><span style=\"color:#4EC9B0\"> string</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}) {</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  return</span><span style=\"color:#DCDCAA\"> useQuery</span><span style=\"color:#E6E6E6\">({</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    queryKey:</span><span style=\"color:#E6E6E6\"> [</span><span style=\"color:#9CDCFE\">queryKeyId</span><span style=\"color:#E6E6E6\">, { </span><span style=\"color:#9CDCFE\">namespace</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">appName</span><span style=\"color:#E6E6E6\"> }],</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">    queryFn</span><span style=\"color:#9CDCFE\">:</span><span style=\"color:#569CD6\"> async</span><span style=\"color:#E6E6E6\"> () </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">      const</span><span style=\"color:#9CDCFE\"> res</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#C586C0\"> await</span><span style=\"color:#9CDCFE\"> axios</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">get</span><span style=\"color:#E6E6E6\">&lt;</span><span style=\"color:#4EC9B0\">Environment</span><span style=\"color:#E6E6E6\">[]&gt;(</span></span>\n<span data-line=\"\"><span style=\"color:#CE9178\">        `/api/apps/</span><span style=\"color:#569CD6\">${</span><span style=\"color:#9CDCFE\">namespace</span><span style=\"color:#569CD6\">}</span><span style=\"color:#CE9178\">/</span><span style=\"color:#569CD6\">${</span><span style=\"color:#9CDCFE\">appName</span><span style=\"color:#569CD6\">}</span><span style=\"color:#CE9178\">/environments`</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">      )</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">      return</span><span style=\"color:#9CDCFE\"> res</span><span style=\"color:#E6E6E6\">?.</span><span style=\"color:#9CDCFE\">data</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    },</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    enabled:</span><span style=\"color:#DCDCAA\"> Boolean</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">namespace</span><span style=\"color:#D4D4D4\"> &amp;&amp;</span><span style=\"color:#9CDCFE\"> appName</span><span style=\"color:#E6E6E6\">),</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  })</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">useGetEnvironmentsQuery</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">queryKeyId</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#9CDCFE\"> queryKeyId</span></span></code></div></pre></figure>\n<p>We have basically the same but several that are useful, including immediately having the lazy version in <code>refetch</code>, specifying the enabled with the query, specifying the initialData lower down, events on each of the success/error states, specifying the staletime.</p>\n<h3>Mutations</h3>\n<p>These are hard in almost any case. rtk-query has a neat concept of defining a <code>fixed-cache-key</code></p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#569CD6\"> const</span><span style=\"color:#DCDCAA\"> ComponentOne</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#E6E6E6\"> () </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">  // Triggering `updatePostOne` will affect the result in both this component,</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">  // but as well as the result in `ComponentTwo`, and vice-versa</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">  const</span><span style=\"color:#E6E6E6\"> [</span><span style=\"color:#9CDCFE\">updatePost</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">result</span><span style=\"color:#E6E6E6\">] </span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#DCDCAA\"> useUpdatePostMutation</span><span style=\"color:#E6E6E6\">({</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    fixedCacheKey:</span><span style=\"color:#CE9178\"> &#x27;shared-update-post&#x27;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  })</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  return</span><span style=\"color:#808080\"> &lt;</span><span style=\"color:#569CD6\">div</span><span style=\"color:#808080\">&gt;</span><span style=\"color:#E6E6E6\">...</span><span style=\"color:#808080\">&lt;/</span><span style=\"color:#569CD6\">div</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#569CD6\"> const</span><span style=\"color:#DCDCAA\"> ComponentTwo</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#E6E6E6\"> () </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">  const</span><span style=\"color:#E6E6E6\"> [</span><span style=\"color:#9CDCFE\">updatePost</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">result</span><span style=\"color:#E6E6E6\">] </span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#DCDCAA\"> useUpdatePostMutation</span><span style=\"color:#E6E6E6\">({</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    fixedCacheKey:</span><span style=\"color:#CE9178\"> &#x27;shared-update-post&#x27;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  })</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  return</span><span style=\"color:#808080\"> &lt;</span><span style=\"color:#569CD6\">div</span><span style=\"color:#808080\">&gt;</span><span style=\"color:#E6E6E6\">...</span><span style=\"color:#808080\">&lt;/</span><span style=\"color:#569CD6\">div</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span></code></div></pre></figure>\n<h2>Why use rtk query</h2>\n<p>It would only be if something in the store needs to access something else in the store. From my experience, most of the code my team and I write has absolutely minimal shared global state. Most of the &quot;slices&quot; of state can be contained in the common hook that is used to fetch the data or with a selector that is used to get the data from the store. Most of these cases are handled by react-query in a way that isn&#x27;t also tightly coupled to redux.</p>\n<p>It seems like rtk query is mostly trying to follow the features in react-query so newer features will almost always typically land in react-query first.</p>\n<p>Given that both operate in the same area, and that global shared mutable state should be avoided, I&#x27;m going to reach for react-query whenever I need to deal with rest based async data.</p>",
            "url": "https://alexmchan.com/blog/2023-03-18-why-prefer-tanstack-query",
            "title": "Tanstack react query or rtk query",
            "summary": "Why at first glance react query seems better than rtk query",
            "date_modified": "2023-03-18T00:00:00.000Z",
            "author": {
                "name": "Alex Chan"
            }
        },
        {
            "id": "https://alexmchan.com/blog/2023-02-13-enum-and-const",
            "content_html": "<h2>Enum&#x27;s are fine</h2>\n<p>I&#x27;ve been using enum for a long time and tried switching to const several times. For the most part, there&#x27;s very little syntax and not many downsides so long as the enums are assigned a value.</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"ts\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"ts\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#569CD6\">enum</span><span style=\"color:#4EC9B0\"> StatusAsEnum</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  Active</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#CE9178\"> &#x27;active&#x27;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  Deactive</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#CE9178\"> &#x27;deactive&#x27;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  None</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#CE9178\"> &#x27;none&#x27;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span></code></div></pre></figure>\n<h2>const better?</h2>\n<p>The flip side of this is to define a const <code>as const</code> and then use a keyof to get the type.</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"ts\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"ts\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> Status</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  Active:</span><span style=\"color:#CE9178\"> &#x27;Active&#x27;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  Deactive:</span><span style=\"color:#CE9178\"> &#x27;Deactive&#x27;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  None:</span><span style=\"color:#CE9178\"> &#x27;None&#x27;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">} </span><span style=\"color:#C586C0\">as</span><span style=\"color:#569CD6\"> const</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">type</span><span style=\"color:#4EC9B0\"> StatusAsConst</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#569CD6\"> keyof</span><span style=\"color:#569CD6\"> typeof</span><span style=\"color:#9CDCFE\"> status</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">function</span><span style=\"color:#DCDCAA\"> log</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">status</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#4EC9B0\"> StatusAsConst</span><span style=\"color:#E6E6E6\">) {</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  if</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#9CDCFE\">status</span><span style=\"color:#D4D4D4\"> ===</span><span style=\"color:#9CDCFE\"> status</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">Active</span><span style=\"color:#E6E6E6\">) {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    console</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">log</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;We are active&#x27;</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  }</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  console</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">log</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">status</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span></code></div></pre></figure>\n<p>This is ok but now we need the</p>\n<p>a) const as const declaration\nb) <code>keyof typeof</code> syntax, a secondary type declaration and then we also need to ensure that our key/value&#x27;s are actually identical when we want to use them.</p>\n<p>Using this other syntax is probably just laziness on my part. It&#x27;s always required importing both the type and the const and knowing which is which when checking against. Not having a naming standard for these PascalCase&#x27;d const&#x27;s. It was kind of nice that things that are typescript are PascalCase&#x27;d.</p>\n<p>Maybe I&#x27;ll try again in 2023 and see if I can get used to it since it&#x27;s closer to js.</p>\n<h2>Alternative as const</h2>\n<p>This way we use the <code>as const</code> syntax and then use the <code>typeof</code> to get the type. Then we can use the const enum when we need to provide the type.</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"ts\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"ts\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> PUSHER_STATUS</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  INITIALIZED:</span><span style=\"color:#CE9178\"> &#x27;initialized&#x27;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  CONNECTING:</span><span style=\"color:#CE9178\"> &#x27;connecting&#x27;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  CONNECTED:</span><span style=\"color:#CE9178\"> &#x27;connected&#x27;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  UNAVAILABLE:</span><span style=\"color:#CE9178\"> &#x27;unavailable&#x27;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  FAILED:</span><span style=\"color:#CE9178\"> &#x27;failed&#x27;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  DISCONNECTED:</span><span style=\"color:#CE9178\"> &#x27;disconnected&#x27;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">  //...</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">} </span><span style=\"color:#C586C0\">as</span><span style=\"color:#569CD6\"> const</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">type</span><span style=\"color:#4EC9B0\"> PusherStatus</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#569CD6\">typeof</span><span style=\"color:#9CDCFE\"> PUSHER_STATUS</span><span style=\"color:#E6E6E6\">)[</span><span style=\"color:#569CD6\">keyof</span><span style=\"color:#569CD6\"> typeof</span><span style=\"color:#9CDCFE\"> PUSHER_STATUS</span><span style=\"color:#E6E6E6\">]</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">//  type PusherStatus = &#x27;uninitialized&#x27; | &#x27;connecting&#x27; | &#x27;connected&#x27; | &#x27;unavailable&#x27; | &#x27;failed&#x27; | &#x27;disconnected&#x27;</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">function</span><span style=\"color:#DCDCAA\"> exampleStatus</span><span style=\"color:#E6E6E6\">({ </span><span style=\"color:#9CDCFE\">status</span><span style=\"color:#E6E6E6\"> }</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">status</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#4EC9B0\"> PusherStatus</span><span style=\"color:#E6E6E6\"> }) {</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  if</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#9CDCFE\">status</span><span style=\"color:#D4D4D4\"> ===</span><span style=\"color:#9CDCFE\"> PUSHER_STATUS</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">CONNECTED</span><span style=\"color:#E6E6E6\">) {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    console</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">log</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;We are connected&#x27;</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  } </span><span style=\"color:#C586C0\">else</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">    //...</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  }</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span></code></div></pre></figure>\n<h2>Conclusion</h2>\n<p>Making this into a utility may make it easier to remember.</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"ts\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"ts\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#569CD6\">type</span><span style=\"color:#4EC9B0\"> Values</span><span style=\"color:#E6E6E6\">&lt;</span><span style=\"color:#4EC9B0\">T</span><span style=\"color:#E6E6E6\">&gt; </span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#4EC9B0\"> T</span><span style=\"color:#E6E6E6\">[</span><span style=\"color:#569CD6\">keyof</span><span style=\"color:#4EC9B0\"> T</span><span style=\"color:#E6E6E6\">]</span></span></code></div></pre></figure>\n<p><a href=\"https://github.com/piotrwitek/utility-types#valuest\"><code>utility-types</code></a> has this included as</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"ts\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"ts\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">$Values</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;utility-types&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">type</span><span style=\"color:#4EC9B0\"> PusherStatus</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#4EC9B0\"> $Values</span><span style=\"color:#E6E6E6\">&lt;</span><span style=\"color:#569CD6\">typeof</span><span style=\"color:#9CDCFE\"> PUSHER_STATUS</span><span style=\"color:#E6E6E6\">&gt;</span></span></code></div></pre></figure>",
            "url": "https://alexmchan.com/blog/2023-02-13-enum-and-const",
            "title": "Enum or const",
            "summary": "I've been using enums for a while now, but I've been wondering if I should be using const instead.",
            "date_modified": "2023-02-13T00:00:00.000Z",
            "author": {
                "name": "Alex Chan"
            }
        },
        {
            "id": "https://alexmchan.com/blog/2023-02-08-simplify-use-effect",
            "content_html": "<h2>Introduction</h2>\n<p><code>useEffect</code> is one of the most important hooks but it can lead to some of the hardest to debug code. Here&#x27;s what I&#x27;ve learned about simplifying them.</p>\n<h2>Thinking about <code>useEffect</code></h2>\n<p>I usually think of <code>useEffect</code> as <code>useOnChange</code>, it will get fired at least once and then whenever the dependencies change. I found that shifting to think that the effect will fire whenever anything changes helps me think about a few things</p>\n<ol>\n<li>do I really want this to run every time one of it&#x27;s dependencies changes?</li>\n<li>what things do I want to watch for changes and can I simplify them?</li>\n<li>what dependencies do I have that are going to change?</li>\n<li>can we simplify the set of dependencies?</li>\n<li>do we need to protect the side effect when it does run</li>\n</ol>\n<h2>Do we really want this to run?</h2>\n<p>When first starting with <code>useEffect</code> it&#x27;s really easy to get unintended renders. It&#x27;s very easy to lose track of things that change easily.</p>\n<ul>\n<li>functions</li>\n<li>objects</li>\n<li>arrays</li>\n</ul>\n<h2>Reducing the number of dependencies</h2>\n<p>Ideally if the dependencies are within the function we can wrap them in a <code>useCallback</code> or <code>useMemo</code>. A lot has been written about that so I won&#x27;t go into it here.</p>\n<h2>Shrinking the dependencies - Watch the important things</h2>\n<p>Since <code>useEffect</code> gets triggered whenever dependencies change, it&#x27;s often better to reduce the dependencies to the smallest set possible. Often this can be a single boolean or state value that actually watches the dependencies and can minimize the chance of inadvertent side effect triggers.</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#569CD6\">function</span><span style=\"color:#DCDCAA\"> MyComponent</span><span style=\"color:#E6E6E6\">({</span><span style=\"color:#9CDCFE\">cars</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">people</span><span style=\"color:#E6E6E6\">}) {</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">    useEffect</span><span style=\"color:#E6E6E6\">({</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">        if</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#9CDCFE\">cars</span><span style=\"color:#D4D4D4\">?</span><span style=\"color:#E6E6E6\">.length &gt; 0 &amp;&amp; </span><span style=\"color:#9CDCFE\">people</span><span style=\"color:#D4D4D4\">?</span><span style=\"color:#E6E6E6\">.length &gt; 0) {</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">            // do something</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">        }</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    }, [</span><span style=\"color:#9CDCFE\">cars</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">people</span><span style=\"color:#E6E6E6\">])</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">    return</span><span style=\"color:#808080\"> &lt;</span><span style=\"color:#569CD6\">div</span><span style=\"color:#808080\">&gt;</span><span style=\"color:#E6E6E6\">...</span><span style=\"color:#808080\">&lt;/</span><span style=\"color:#569CD6\">div</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span></code></div></pre></figure>\n<p>We&#x27;re watching two arrays, but we&#x27;re only interested in the length. We can simplify this by using a <code>useMemo</code> to watch the length of the arrays and then only watch that value or even just check the value directly.</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#569CD6\">function</span><span style=\"color:#DCDCAA\"> MyComponent</span><span style=\"color:#E6E6E6\">({</span><span style=\"color:#9CDCFE\">cars</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">people</span><span style=\"color:#E6E6E6\">}) {</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">    useEffect</span><span style=\"color:#E6E6E6\">({</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">        if</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">cars</span><span style=\"color:#D4D4D4\">?</span><span style=\"color:#E6E6E6\">.length &gt; 0 &amp;&amp; </span><span style=\"color:#9CDCFE\">people</span><span style=\"color:#D4D4D4\">?</span><span style=\"color:#E6E6E6\">.length &gt; 0) {</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">            // do something</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">        }</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    }, [</span><span style=\"color:#9CDCFE\">cars</span><span style=\"color:#E6E6E6\">?.</span><span style=\"color:#9CDCFE\">length</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">people</span><span style=\"color:#E6E6E6\">?.</span><span style=\"color:#9CDCFE\">length</span><span style=\"color:#E6E6E6\">])</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">    return</span><span style=\"color:#808080\"> &lt;</span><span style=\"color:#569CD6\">div</span><span style=\"color:#808080\">&gt;</span><span style=\"color:#E6E6E6\">...</span><span style=\"color:#808080\">&lt;/</span><span style=\"color:#569CD6\">div</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span></code></div></pre></figure>\n<p>This will protect us a bit from arrays that are being recreated, but it&#x27;s still not ideal. What if we just watched a boolean instead?</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#569CD6\"> function</span><span style=\"color:#DCDCAA\"> checkHasValues</span><span style=\"color:#E6E6E6\">({ </span><span style=\"color:#9CDCFE\">cars</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">people</span><span style=\"color:#E6E6E6\"> }) {</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  return</span><span style=\"color:#9CDCFE\"> cars</span><span style=\"color:#E6E6E6\">?.</span><span style=\"color:#9CDCFE\">length</span><span style=\"color:#D4D4D4\"> &gt;</span><span style=\"color:#B5CEA8\"> 0</span><span style=\"color:#D4D4D4\"> &amp;&amp;</span><span style=\"color:#9CDCFE\"> people</span><span style=\"color:#E6E6E6\">?.</span><span style=\"color:#9CDCFE\">length</span><span style=\"color:#D4D4D4\"> &gt;</span><span style=\"color:#B5CEA8\"> 0</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">function</span><span style=\"color:#DCDCAA\"> MyComponent</span><span style=\"color:#E6E6E6\">({ </span><span style=\"color:#9CDCFE\">cars</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">people</span><span style=\"color:#E6E6E6\"> }) {</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">  const</span><span style=\"color:#9CDCFE\"> hasValues</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> checkHasValues</span><span style=\"color:#E6E6E6\">({ </span><span style=\"color:#9CDCFE\">cars</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">people</span><span style=\"color:#E6E6E6\"> }) </span><span style=\"color:#6A9955\">// optionally useMemo this depending on the time it runs for</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">  useEffect</span><span style=\"color:#E6E6E6\">(</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    {</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">      if</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">hasValues</span><span style=\"color:#E6E6E6\">) {</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">        // do something</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">      },</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    },</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    [</span><span style=\"color:#9CDCFE\">hasValues</span><span style=\"color:#E6E6E6\">]</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  )</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  return</span><span style=\"color:#808080\"> &lt;</span><span style=\"color:#569CD6\">div</span><span style=\"color:#808080\">&gt;</span><span style=\"color:#E6E6E6\">...</span><span style=\"color:#808080\">&lt;/</span><span style=\"color:#569CD6\">div</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span></code></div></pre></figure>\n<p>Now we&#x27;re only watching a single boolean value and we can even move the check into a function that we can test. Let&#x27;s call this a boolean based useEffect.</p>\n<p>It&#x27;s quick and easy to see what will trigger the change and it&#x27;s easier to test the function that is looking for the differences. Realistically we&#x27;ll also have to pass in a related function to trigger when the effect changes, but it&#x27;s a much smaller dependency array and as more values get passed it&#x27;ll reduce the number of times the useEffect might get called. It also protects us a bit from an object out of our control triggering the useEffect unintentionally.</p>\n<p>I&#x27;ve found this a good pattern to look for as the dependencies get large. For one or two it may be overkill but as the number of dependencies grows it&#x27;s a great way to simplify the code.</p>\n<h2>Additional methods</h2>\n<p>Edit 2025 - included additional methods:</p>\n<ul>\n<li>latest ref pattern <a href=\"https://www.epicreact.dev/the-latest-ref-pattern-in-react\">https://www.epicreact.dev/the-latest-ref-pattern-in-react</a></li>\n<li><a href=\"https://react.dev/learn/separating-events-from-effects#extracting-non-reactive-logic-out-of-effects\">useEffectEvent</a> (experimental in react 18 and stable in 19) instead</li>\n<li>perform actions in response to the event (user click etc)</li>\n</ul>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#E6E6E6\"> [</span><span style=\"color:#9CDCFE\">value</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">setValue</span><span style=\"color:#E6E6E6\">] </span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#DCDCAA\"> useState</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#B5CEA8\">0</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// run in response to value change</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">useEffect</span><span style=\"color:#E6E6E6\">(() </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  console</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">log</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">`Value is </span><span style=\"color:#569CD6\">${</span><span style=\"color:#9CDCFE\">value</span><span style=\"color:#569CD6\">}</span><span style=\"color:#CE9178\">`</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}, [</span><span style=\"color:#9CDCFE\">value</span><span style=\"color:#E6E6E6\">])</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">return</span><span style=\"color:#808080\"> &lt;</span><span style=\"color:#4EC9B0\">Button</span><span style=\"color:#9CDCFE\"> onClick</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#569CD6\">{</span><span style=\"color:#D4D4D4\">() </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#DCDCAA\"> setValue</span><span style=\"color:#D4D4D4\">(</span><span style=\"color:#9CDCFE\">value</span><span style=\"color:#D4D4D4\"> + </span><span style=\"color:#B5CEA8\">1</span><span style=\"color:#D4D4D4\">)</span><span style=\"color:#569CD6\">}</span><span style=\"color:#808080\">&gt;</span><span style=\"color:#E6E6E6\">Increment </span><span style=\"color:#569CD6\">{</span><span style=\"color:#9CDCFE\">value</span><span style=\"color:#569CD6\">}</span><span style=\"color:#808080\">&lt;/</span><span style=\"color:#4EC9B0\">Button</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// change instead be run in response to an event</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#E6E6E6\"> [</span><span style=\"color:#9CDCFE\">value</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">setValue</span><span style=\"color:#E6E6E6\">] </span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#DCDCAA\"> useState</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#B5CEA8\">0</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">return</span><span style=\"color:#E6E6E6\"> (</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">  &lt;</span><span style=\"color:#4EC9B0\">Button</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    onClick</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#569CD6\">{</span><span style=\"color:#D4D4D4\">() </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#D4D4D4\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">      const</span><span style=\"color:#9CDCFE\"> incrementedValue</span><span style=\"color:#D4D4D4\"> = </span><span style=\"color:#9CDCFE\">value</span><span style=\"color:#D4D4D4\"> + </span><span style=\"color:#B5CEA8\">1</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">      setValue</span><span style=\"color:#D4D4D4\">(</span><span style=\"color:#9CDCFE\">incrementedValue</span><span style=\"color:#D4D4D4\">)</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">      // run in response to a side effect</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">      console</span><span style=\"color:#D4D4D4\">.</span><span style=\"color:#DCDCAA\">log</span><span style=\"color:#D4D4D4\">(</span><span style=\"color:#9CDCFE\">incrementedValue</span><span style=\"color:#D4D4D4\">)</span></span>\n<span data-line=\"\"><span style=\"color:#D4D4D4\">    }</span><span style=\"color:#569CD6\">}</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">  &gt;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    Increment </span><span style=\"color:#569CD6\">{</span><span style=\"color:#9CDCFE\">value</span><span style=\"color:#569CD6\">}</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">  &lt;/</span><span style=\"color:#4EC9B0\">Button</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// or as a callback where we receive current state at the time of the event</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> incrementValue</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> useCallback</span><span style=\"color:#E6E6E6\">((</span><span style=\"color:#9CDCFE\">value</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">  const</span><span style=\"color:#9CDCFE\"> incrementedValue</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#9CDCFE\"> value</span><span style=\"color:#D4D4D4\"> +</span><span style=\"color:#B5CEA8\"> 1</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">  setValue</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">incrementedValue</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">  // run in response to a side effect</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  console</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">log</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">incrementedValue</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}, [])</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">return</span><span style=\"color:#E6E6E6\"> (</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">  &lt;</span><span style=\"color:#4EC9B0\">Button</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    onClick</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#569CD6\">{</span><span style=\"color:#D4D4D4\">() </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#D4D4D4\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">      incrementValue</span><span style=\"color:#D4D4D4\">(</span><span style=\"color:#9CDCFE\">value</span><span style=\"color:#D4D4D4\">)</span></span>\n<span data-line=\"\"><span style=\"color:#D4D4D4\">    }</span><span style=\"color:#569CD6\">}</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">  &gt;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    Increment </span><span style=\"color:#569CD6\">{</span><span style=\"color:#9CDCFE\">value</span><span style=\"color:#569CD6\">}</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">  &lt;/</span><span style=\"color:#4EC9B0\">Button</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">)</span></span></code></div></pre></figure>",
            "url": "https://alexmchan.com/blog/2023-02-08-simplify-use-effect",
            "title": "Simplify those useEffects",
            "summary": "useEffect's can cause a lot of headaches. Simplifying them can help spend less time debugging error prone code.",
            "date_modified": "2023-02-08T00:00:00.000Z",
            "author": {
                "name": "Alex Chan"
            }
        },
        {
            "id": "https://alexmchan.com/blog/2023-02-01-disabled-buttons-are-no-fun",
            "content_html": "<h2>Why users don&#x27;t like disabled buttons</h2>\n<p>Here&#x27;s a list of articles about why disabled buttons are poor for users:</p>\n<ul>\n<li><a href=\"https://www.smashingmagazine.com/2021/08/frustrating-design-patterns-disabled-buttons/\">frustrating design patterns - disabled buttons</a></li>\n<li><a href=\"https://axesslab.com/disabled-buttons-suck/\">disabled buttons suck</a></li>\n<li><a href=\"https://uxdesign.cc/why-you-shouldnt-include-disabled-interaction-elements-in-your-design-system-76a2d4307faf\">why you shouldn&#x27;t include disabled interaction elements</a></li>\n<li><a href=\"https://uxdesign.cc/why-heuristics-are-only-rules-of-thumb-the-case-of-the-disabled-button-4824958627e9\">uxdesign - the case of the disabled button</a></li>\n<li><a href=\"https://uxplanet.org/disabled-buttons-in-user-interface-4dafda3e6fe7#:~:text=When%20we%20design%20web%20forms,the%20first%20glance%2C%20seems%20reasonable\">Disabled buttons in user interfaces</a></li>\n</ul>\n<p>In my experience whenever we&#x27;ve run user tests on disabled buttons, users have always been confused by them. They don&#x27;t know why they&#x27;re disabled, and they don&#x27;t know what to do to enable them. Making them look disabled is possibly ok but the solution is often to leave them enabled and provide feedback when clicked about why, either beside the button or highlighting the fields that need to be filled in along with a message about what&#x27;s missing.</p>\n<h2>Why should we have disabled buttons?</h2>\n<p>Searching google for the <a href=\"https://www.google.com/search?q=why+should+we+have+disabled+buttons&amp;rlz=1C5GCEM_enCA1030CA1030&amp;oq=why+should+we+have+disabled+buttons\">why we should have disabled buttons</a> will actually give a list of the articles above 😂</p>\n<p>Reasons for disabled buttons:</p>\n<ul>\n<li>we want to frustrate our user</li>\n<li>we want to avoid having to explain why the button is disabled</li>\n</ul>",
            "url": "https://alexmchan.com/blog/2023-02-01-disabled-buttons-are-no-fun",
            "title": "Disabled buttons make for sad users",
            "summary": "A short list of other people explaining why disabled buttons should be avoided and a very short list about why not",
            "date_modified": "2023-02-01T00:00:00.000Z",
            "author": {
                "name": "Alex Chan"
            }
        },
        {
            "id": "https://alexmchan.com/blog/2022-11-21-css-layout-grid-example",
            "content_html": "<h2>Responsive grid</h2>\n<p>A quick even and responsive grid layout.</p>\n<div class=\"grid gap-4 md:auto-cols-max md:grid-flow-col\"><div class=\"rounded-md\n            bg-slate-700\n              bg-linear-to-br\n            from-cyan-500\n            to-blue-700 p-8\"><div class=\"text-xl font-extrabold text-white\">Some heading text</div><div class=\"text-slate-300\">Content text</div></div><div class=\"rounded-md\n            bg-slate-700\n              bg-linear-to-br\n            from-cyan-500\n            to-blue-700 p-8\"><div class=\"text-xl font-extrabold text-white\">Some heading text</div><div class=\"text-slate-300\">Content text</div></div><div class=\"rounded-md\n            bg-slate-700\n              bg-linear-to-br\n            from-cyan-500\n            to-blue-700 p-8\"><div class=\"text-xl font-extrabold text-white\">Some heading text</div><div class=\"text-slate-300\">Content text</div></div></div>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#569CD6\"> function</span><span style=\"color:#DCDCAA\"> ExampleGrid</span><span style=\"color:#E6E6E6\">() {</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  return</span><span style=\"color:#E6E6E6\"> (</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">    &lt;</span><span style=\"color:#569CD6\">div</span><span style=\"color:#9CDCFE\"> className</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#CE9178\">&quot;grid gap-4 md:auto-cols-max md:grid-flow-col&quot;</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">      {</span><span style=\"color:#9CDCFE\">Array</span><span style=\"color:#D4D4D4\">.</span><span style=\"color:#DCDCAA\">from</span><span style=\"color:#D4D4D4\">({ </span><span style=\"color:#9CDCFE\">length:</span><span style=\"color:#B5CEA8\"> 3</span><span style=\"color:#D4D4D4\"> }, (</span><span style=\"color:#9CDCFE\">_</span><span style=\"color:#D4D4D4\">, </span><span style=\"color:#9CDCFE\">i</span><span style=\"color:#D4D4D4\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#D4D4D4\"> (</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">        &lt;</span><span style=\"color:#569CD6\">div</span><span style=\"color:#9CDCFE\"> className</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#CE9178\">&quot;bg-slate-500 p-8&quot;</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">          &lt;</span><span style=\"color:#569CD6\">div</span><span style=\"color:#9CDCFE\"> className</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#CE9178\">&quot;text-xl font-extrabold text-white&quot;</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#D4D4D4\">            Some heading text</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">          &lt;/</span><span style=\"color:#569CD6\">div</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">          &lt;</span><span style=\"color:#569CD6\">div</span><span style=\"color:#808080\">&gt;</span><span style=\"color:#D4D4D4\">Content text</span><span style=\"color:#808080\">&lt;/</span><span style=\"color:#569CD6\">div</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">        &lt;/</span><span style=\"color:#569CD6\">div</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#D4D4D4\">      ))</span><span style=\"color:#569CD6\">}</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">    &lt;/</span><span style=\"color:#569CD6\">div</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  )</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span></code></div></pre></figure>",
            "url": "https://alexmchan.com/blog/2022-11-21-css-layout-grid-example",
            "title": "Quick grid",
            "summary": "Using grid instead of flex",
            "date_modified": "2022-11-21T00:00:00.000Z",
            "author": {
                "name": "Alex Chan"
            }
        },
        {
            "id": "https://alexmchan.com/blog/2023-02-28-vest-to-zod",
            "content_html": "<h2>Why zod for form validation?</h2>\n<p>In the past I&#x27;ve quite enjoyed the beauty of vest for it&#x27;s nearness to unit testing. It didn&#x27;t require a complex dsl, conditionals worked mostly as one would expect, and the error messages could be raised. Moving from <code>yup</code> before that was mostly because writing any complex validation required re-reading the docs and reading the validations also required re-reading the docs.</p>\n<p>Zod is taking over the typed world. There&#x27;s nothing wrong with vest, but we gain form types for free when defining a form schema which is one less thing to do. Additionally, zod is being used for other validation so consolidating on a single library simplifies the overall api surface area of our application for input, server, and client side validation.</p>\n<h3>Replacing vest with zod</h3>\n<p>I’ve been trying zod with trpc more and it might be the way, but it also wants a discriminated union where the type exists at the top level.</p>\n<p>I think we can replace vest with zod, here’s why</p>\n<ul>\n<li>parsing returns a typed object and can throw/filter out values that aren’t in the schema</li>\n<li>although I dislike domain specific languages (dsl’s) where you write things as chained().functions(), zod’s is relatively lightweight and pretty close to ts on purpose. It’s not a full blown dsl like yup or joi.</li>\n<li>normally dsl’s breakdown when there’s a conditional, but in zod it&#x27;s not horrible. We can accomplish most of our conditionals in a refine.</li>\n</ul>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#569CD6\">let</span><span style=\"color:#9CDCFE\"> exampleSchema</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#9CDCFE\"> z</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  .</span><span style=\"color:#DCDCAA\">object</span><span style=\"color:#E6E6E6\">({</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    test:</span><span style=\"color:#9CDCFE\"> z</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">string</span><span style=\"color:#E6E6E6\">(),</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    otherValue:</span><span style=\"color:#9CDCFE\"> z</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">object</span><span style=\"color:#E6E6E6\">({</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">      name:</span><span style=\"color:#9CDCFE\"> z</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">string</span><span style=\"color:#E6E6E6\">(),</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    }),</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  })</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  .</span><span style=\"color:#DCDCAA\">refine</span><span style=\"color:#E6E6E6\">(</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    (</span><span style=\"color:#9CDCFE\">data</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">      if</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#9CDCFE\">data</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">otherValue</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">name</span><span style=\"color:#D4D4D4\"> ===</span><span style=\"color:#CE9178\"> &#x27;invalid&#x27;</span><span style=\"color:#E6E6E6\">) {</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">        return</span><span style=\"color:#569CD6\"> false</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">      }</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">      return</span><span style=\"color:#569CD6\"> true</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    },</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">      message:</span><span style=\"color:#CE9178\"> &quot;Passwords don&#x27;t match&quot;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">      path:</span><span style=\"color:#E6E6E6\"> [</span><span style=\"color:#CE9178\">&#x27;otherValue.name&#x27;</span><span style=\"color:#E6E6E6\">], </span><span style=\"color:#6A9955\">// nested path of error</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    }</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  )</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  .</span><span style=\"color:#DCDCAA\">refine</span><span style=\"color:#E6E6E6\">(</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    (</span><span style=\"color:#9CDCFE\">data</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">      return</span><span style=\"color:#569CD6\"> false</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    },</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">      message:</span><span style=\"color:#CE9178\"> &#x27;another check&#x27;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">      path:</span><span style=\"color:#E6E6E6\"> [</span><span style=\"color:#CE9178\">&#x27;test&#x27;</span><span style=\"color:#E6E6E6\">], </span><span style=\"color:#6A9955\">// path of error</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    }</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  )</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">try</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">  let</span><span style=\"color:#9CDCFE\"> x</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#9CDCFE\"> exampleSchema</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">parse</span><span style=\"color:#E6E6E6\">({</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    test:</span><span style=\"color:#CE9178\"> &#x27;true&#x27;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    otherValue:</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">      name:</span><span style=\"color:#CE9178\"> &#x27;invalid&#x27;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    },</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  })</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  console</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">log</span><span style=\"color:#E6E6E6\">({ </span><span style=\"color:#9CDCFE\">x</span><span style=\"color:#E6E6E6\"> })</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">} </span><span style=\"color:#C586C0\">catch</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#9CDCFE\">error</span><span style=\"color:#E6E6E6\">) {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  console</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">log</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">error</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span></code></div></pre></figure>",
            "url": "https://alexmchan.com/blog/2023-02-28-vest-to-zod",
            "title": "Zod for react-hook-form",
            "summary": "Why move from vest to zod for form validation?",
            "date_modified": "2022-11-07T00:00:00.000Z",
            "author": {
                "name": "Alex Chan"
            }
        },
        {
            "id": "https://alexmchan.com/blog/2022-09-21-quick-context",
            "content_html": "<h2>A context example</h2>\n<p>Context is fine for small apps and components where the shared state changes infrequently to reduce prop drilling. If state changes frequently\nsuch as an input, slider, or color picker then it&#x27;s better to use a state management library like recoil, zustand, jotai, valtio or a form library.</p>\n<p>Here&#x27;s the a quick context setup example for the times when you need to share state between components.\nInspired by the <a href=\"https://kentcdodds.com/blog/how-to-use-react-context-effectively\">react context example</a> by kentcdodds.</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  useContext</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  createContext</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  useState</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  SetStateAction</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  Dispatch</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">} </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;react&#x27;</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">type</span><span style=\"color:#4EC9B0\"> ExampleContextData</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  isActive</span><span style=\"color:#D4D4D4\">?:</span><span style=\"color:#4EC9B0\"> boolean</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  setIsActive</span><span style=\"color:#D4D4D4\">?:</span><span style=\"color:#4EC9B0\"> Dispatch</span><span style=\"color:#E6E6E6\">&lt;</span><span style=\"color:#4EC9B0\">SetStateAction</span><span style=\"color:#E6E6E6\">&lt;</span><span style=\"color:#4EC9B0\">boolean</span><span style=\"color:#E6E6E6\">&gt;&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> ExampleContext</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> createContext</span><span style=\"color:#E6E6E6\">&lt;</span><span style=\"color:#4EC9B0\">ExampleContextData</span><span style=\"color:#E6E6E6\">&gt;({})</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">function</span><span style=\"color:#DCDCAA\"> useExampleContext</span><span style=\"color:#E6E6E6\">() {</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">  const</span><span style=\"color:#9CDCFE\"> context</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> useContext</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">ExampleContext</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  if</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#9CDCFE\">context</span><span style=\"color:#D4D4D4\"> ===</span><span style=\"color:#569CD6\"> undefined</span><span style=\"color:#E6E6E6\">) {</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">    throw</span><span style=\"color:#569CD6\"> new</span><span style=\"color:#DCDCAA\"> Error</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;Context must be used within a provider&#x27;</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  }</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  return</span><span style=\"color:#9CDCFE\"> context</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">function</span><span style=\"color:#DCDCAA\"> ExampleContextComponent</span><span style=\"color:#E6E6E6\">() {</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">  const</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">isActive</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">setIsActive</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#DCDCAA\"> useExampleContext</span><span style=\"color:#E6E6E6\">()</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  return</span><span style=\"color:#E6E6E6\"> (</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">    &lt;</span><span style=\"color:#569CD6\">button</span><span style=\"color:#9CDCFE\"> type</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#CE9178\">&quot;button&quot;</span><span style=\"color:#9CDCFE\"> onClick</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#569CD6\">{</span><span style=\"color:#D4D4D4\">() </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#DCDCAA\"> setIsActive</span><span style=\"color:#D4D4D4\">((</span><span style=\"color:#9CDCFE\">isActive</span><span style=\"color:#D4D4D4\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#D4D4D4\"> !</span><span style=\"color:#9CDCFE\">isActive</span><span style=\"color:#D4D4D4\">)</span><span style=\"color:#569CD6\">}</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">      Click to toggle </span><span style=\"color:#569CD6\">{</span><span style=\"color:#9CDCFE\">isActive</span><span style=\"color:#D4D4D4\"> ? </span><span style=\"color:#CE9178\">&#x27;active&#x27;</span><span style=\"color:#D4D4D4\"> : </span><span style=\"color:#CE9178\">&#x27;inactive&#x27;</span><span style=\"color:#569CD6\">}</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">    &lt;/</span><span style=\"color:#569CD6\">button</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  )</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#569CD6\"> function</span><span style=\"color:#DCDCAA\"> ContextExample</span><span style=\"color:#E6E6E6\">() {</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">  const</span><span style=\"color:#E6E6E6\"> [</span><span style=\"color:#9CDCFE\">isActive</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">setIsActive</span><span style=\"color:#E6E6E6\">] </span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#DCDCAA\"> useState</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#569CD6\">false</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  return</span><span style=\"color:#E6E6E6\"> (</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">    &lt;</span><span style=\"color:#4EC9B0\">ExampleContext.Provider</span><span style=\"color:#9CDCFE\"> value</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#569CD6\">{</span><span style=\"color:#D4D4D4\">{ </span><span style=\"color:#9CDCFE\">isActive</span><span style=\"color:#D4D4D4\">, </span><span style=\"color:#9CDCFE\">setIsActive</span><span style=\"color:#D4D4D4\"> }</span><span style=\"color:#569CD6\">}</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">      {</span><span style=\"color:#6A9955\">/* Component value consumer */</span><span style=\"color:#569CD6\">}</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">      &lt;</span><span style=\"color:#4EC9B0\">ExampleContext.Consumer</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">        {</span><span style=\"color:#D4D4D4\">(</span><span style=\"color:#9CDCFE\">value</span><span style=\"color:#D4D4D4\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#808080\"> &lt;</span><span style=\"color:#569CD6\">div</span><span style=\"color:#808080\">&gt;</span><span style=\"color:#569CD6\">{</span><span style=\"color:#9CDCFE\">value</span><span style=\"color:#D4D4D4\">.</span><span style=\"color:#9CDCFE\">isActive</span><span style=\"color:#569CD6\">}</span><span style=\"color:#808080\">&lt;/</span><span style=\"color:#569CD6\">div</span><span style=\"color:#808080\">&gt;</span><span style=\"color:#569CD6\">}</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">      &lt;/</span><span style=\"color:#4EC9B0\">ExampleContext.Consumer</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">      {</span><span style=\"color:#6A9955\">/* Custom component */</span><span style=\"color:#569CD6\">}</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">      &lt;</span><span style=\"color:#4EC9B0\">ExampleContextComponent</span><span style=\"color:#808080\"> /&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">    &lt;/</span><span style=\"color:#4EC9B0\">ExampleContext.Provider</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  )</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span></code></div></pre></figure>\n<h2>The example in action</h2>\n<div></div><button type=\"button\" class=\"inline-flex items-center rounded-full border border-transparent bg-indigo-600 px-3.5 py-2 text-sm leading-4 font-medium text-white shadow-sm hover:bg-indigo-700 focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 focus:outline-hidden\">Click to toggle inactive</button>\n<h2>A zustand example</h2>\n<p>Zustand is a good small state management library.</p>\n<div class=\"flex items-center gap-2\"><button class=\"theme-button inline-flex items-center gap-2 justify-center rounded-lg py-2 px-3 text-sm outline-offset-2 transition-all duration-200 active:transition-none bg-stone-800 font-semibold text-stone-100 hover:bg-stone-700 hover:scale-[1.02] active:bg-stone-800 active:text-stone-100/70 dark:bg-stone-700 dark:hover:bg-stone-600 dark:active:bg-stone-700 dark:active:text-stone-100/70\">increment</button><button class=\"theme-button inline-flex items-center gap-2 justify-center rounded-lg py-2 px-3 text-sm outline-offset-2 transition-all duration-200 active:transition-none bg-stone-50 font-medium text-stone-900 hover:bg-stone-100 hover:scale-[1.02] active:bg-stone-100 active:text-stone-900/60 dark:bg-zinc-800/50 dark:text-stone-300 dark:hover:bg-zinc-800 dark:hover:text-stone-50 dark:active:bg-zinc-800/50 dark:active:text-stone-50/70\">reset</button><div>The zustand count 0</div></div>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">create</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;zustand&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">subscribeWithSelector</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;zustand/middleware&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#9CDCFE\"> produce</span><span style=\"color:#C586C0\"> from</span><span style=\"color:#CE9178\"> &#x27;immer&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">Button</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;@/components/Button&#x27;</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">interface</span><span style=\"color:#4EC9B0\"> CountState</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  count</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#4EC9B0\"> number</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  increaseCount</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#E6E6E6\"> ({ </span><span style=\"color:#9CDCFE\">amount</span><span style=\"color:#E6E6E6\"> }</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">amount</span><span style=\"color:#D4D4D4\">?:</span><span style=\"color:#4EC9B0\"> number</span><span style=\"color:#E6E6E6\"> }) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#4EC9B0\"> void</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">  resetCount</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#E6E6E6\"> () </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#4EC9B0\"> void</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> useCountStore</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> create</span><span style=\"color:#E6E6E6\">(</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">  subscribeWithSelector</span><span style=\"color:#E6E6E6\">&lt;</span><span style=\"color:#4EC9B0\">CountState</span><span style=\"color:#E6E6E6\">&gt;((</span><span style=\"color:#9CDCFE\">set</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#E6E6E6\"> ({</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    count:</span><span style=\"color:#B5CEA8\"> 0</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">    increaseCount</span><span style=\"color:#9CDCFE\">:</span><span style=\"color:#E6E6E6\"> ({ </span><span style=\"color:#9CDCFE\">amount</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#B5CEA8\"> 1</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#E6E6E6\"> {}) </span><span style=\"color:#569CD6\">=&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">      set</span><span style=\"color:#E6E6E6\">(</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">        produce</span><span style=\"color:#E6E6E6\">((</span><span style=\"color:#9CDCFE\">draft</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">          draft</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">count</span><span style=\"color:#D4D4D4\"> +=</span><span style=\"color:#9CDCFE\"> amount</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">        })</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">      ),</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">    resetCount</span><span style=\"color:#9CDCFE\">:</span><span style=\"color:#E6E6E6\"> () </span><span style=\"color:#569CD6\">=&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">      set</span><span style=\"color:#E6E6E6\">(</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">        produce</span><span style=\"color:#E6E6E6\">((</span><span style=\"color:#9CDCFE\">draft</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">          draft</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">count</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#B5CEA8\"> 0</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">        })</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">      ),</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  }))</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#569CD6\"> function</span><span style=\"color:#DCDCAA\"> ZustandCounter</span><span style=\"color:#E6E6E6\">() {</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">  const</span><span style=\"color:#9CDCFE\"> count</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> useCountStore</span><span style=\"color:#E6E6E6\">((</span><span style=\"color:#9CDCFE\">state</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#9CDCFE\"> state</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">count</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  return</span><span style=\"color:#808080\"> &lt;</span><span style=\"color:#569CD6\">div</span><span style=\"color:#808080\">&gt;</span><span style=\"color:#E6E6E6\">The zustand count </span><span style=\"color:#569CD6\">{</span><span style=\"color:#9CDCFE\">count</span><span style=\"color:#569CD6\">}</span><span style=\"color:#808080\">&lt;/</span><span style=\"color:#569CD6\">div</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#569CD6\"> function</span><span style=\"color:#DCDCAA\"> ZustandExample</span><span style=\"color:#E6E6E6\">() {</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">  const</span><span style=\"color:#9CDCFE\"> increaseCount</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> useCountStore</span><span style=\"color:#E6E6E6\">((</span><span style=\"color:#9CDCFE\">state</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#9CDCFE\"> state</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">increaseCount</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">  const</span><span style=\"color:#9CDCFE\"> resetCount</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> useCountStore</span><span style=\"color:#E6E6E6\">((</span><span style=\"color:#9CDCFE\">state</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#9CDCFE\"> state</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">resetCount</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  return</span><span style=\"color:#E6E6E6\"> (</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">    &lt;&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">      &lt;</span><span style=\"color:#569CD6\">div</span><span style=\"color:#9CDCFE\"> className</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#CE9178\">&quot;flex items-center gap-2&quot;</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">        &lt;</span><span style=\"color:#4EC9B0\">Button</span><span style=\"color:#9CDCFE\"> variant</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#CE9178\">&quot;primary&quot;</span><span style=\"color:#9CDCFE\"> onClick</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#569CD6\">{</span><span style=\"color:#D4D4D4\">() </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#DCDCAA\"> increaseCount</span><span style=\"color:#D4D4D4\">({ </span><span style=\"color:#9CDCFE\">amount:</span><span style=\"color:#B5CEA8\"> 1</span><span style=\"color:#D4D4D4\"> })</span><span style=\"color:#569CD6\">}</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">          one up</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">        &lt;/</span><span style=\"color:#4EC9B0\">Button</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">        &lt;</span><span style=\"color:#4EC9B0\">Button</span><span style=\"color:#9CDCFE\"> variant</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#CE9178\">&quot;secondary&quot;</span><span style=\"color:#9CDCFE\"> onClick</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#569CD6\">{</span><span style=\"color:#D4D4D4\">() </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#DCDCAA\"> resetCount</span><span style=\"color:#D4D4D4\">()</span><span style=\"color:#569CD6\">}</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">          reset</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">        &lt;/</span><span style=\"color:#4EC9B0\">Button</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">        &lt;</span><span style=\"color:#4EC9B0\">ZustandCounter</span><span style=\"color:#808080\"> /&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">      &lt;/</span><span style=\"color:#569CD6\">div</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">    &lt;/&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  )</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// all state changes</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// const unsubAll = useCountStore.subscribe(console.log)</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// with middleware we can watch only the count changes</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> unsubCount</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#9CDCFE\"> useCountStore</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">subscribe</span><span style=\"color:#E6E6E6\">(</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  (</span><span style=\"color:#9CDCFE\">state</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#9CDCFE\"> state</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">count</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  (</span><span style=\"color:#9CDCFE\">count</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    console</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">log</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;count changed to&#x27;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">count</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  }</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">)</span></span></code></div></pre></figure>\n<h2>A valtio example</h2>\n<p>Valtio feels more natural for those who don&#x27;t mind mutating state directly.</p>\n<div class=\"flex items-center gap-2\"><button class=\"theme-button inline-flex items-center gap-2 justify-center rounded-lg py-2 px-3 text-sm outline-offset-2 transition-all duration-200 active:transition-none bg-stone-800 font-semibold text-stone-100 hover:bg-stone-700 hover:scale-[1.02] active:bg-stone-800 active:text-stone-100/70 dark:bg-stone-700 dark:hover:bg-stone-600 dark:active:bg-stone-700 dark:active:text-stone-100/70\">Increment directly</button><button class=\"theme-button inline-flex items-center gap-2 justify-center rounded-lg py-2 px-3 text-sm outline-offset-2 transition-all duration-200 active:transition-none bg-stone-800 font-semibold text-stone-100 hover:bg-stone-700 hover:scale-[1.02] active:bg-stone-800 active:text-stone-100/70 dark:bg-stone-700 dark:hover:bg-stone-600 dark:active:bg-stone-700 dark:active:text-stone-100/70\">Increment with incrementCount</button><button class=\"theme-button inline-flex items-center gap-2 justify-center rounded-lg py-2 px-3 text-sm outline-offset-2 transition-all duration-200 active:transition-none bg-stone-800 font-semibold text-stone-100 hover:bg-stone-700 hover:scale-[1.02] active:bg-stone-800 active:text-stone-100/70 dark:bg-stone-700 dark:hover:bg-stone-600 dark:active:bg-stone-700 dark:active:text-stone-100/70\">reset</button>0 </div>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">Button</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;@/components/Button&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">proxy</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">subscribe</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">useSnapshot</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;valtio&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">subscribeKey</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;valtio/utils&#x27;</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#569CD6\"> const</span><span style=\"color:#9CDCFE\"> state</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> proxy</span><span style=\"color:#E6E6E6\">({</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  count:</span><span style=\"color:#B5CEA8\"> 0</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  text:</span><span style=\"color:#CE9178\"> &#x27;hello&#x27;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">  inc</span><span style=\"color:#E6E6E6\">() {</span></span>\n<span data-line=\"\"><span style=\"color:#D4D4D4\">    ++</span><span style=\"color:#569CD6\">this</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">count</span><span style=\"color:#6A9955\"> // or see below for exporting directly from the module</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  },</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">})</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// actions defined this way are better for code splitting</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// https://github.com/pmndrs/valtio/blob/main/docs/how-tos/how-to-organize-actions.mdx#action-functions-defined-in-module</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#569CD6\"> function</span><span style=\"color:#DCDCAA\"> incrementCount</span><span style=\"color:#E6E6E6\">({ </span><span style=\"color:#9CDCFE\">amount</span><span style=\"color:#E6E6E6\"> }) {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  state</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">count</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#9CDCFE\"> state</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">count</span><span style=\"color:#D4D4D4\"> +</span><span style=\"color:#9CDCFE\"> amount</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// example component</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#569CD6\"> function</span><span style=\"color:#DCDCAA\"> ValtioExample</span><span style=\"color:#E6E6E6\">() {</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">  const</span><span style=\"color:#9CDCFE\"> snap</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> useSnapshot</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">state</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  return</span><span style=\"color:#E6E6E6\"> (</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">    &lt;</span><span style=\"color:#569CD6\">div</span><span style=\"color:#9CDCFE\"> className</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#CE9178\">&quot;flex items-center gap-2&quot;</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">      &lt;</span><span style=\"color:#4EC9B0\">Button</span><span style=\"color:#9CDCFE\"> onClick</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#569CD6\">{</span><span style=\"color:#D4D4D4\">() </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#D4D4D4\"> (</span><span style=\"color:#9CDCFE\">state</span><span style=\"color:#D4D4D4\">.</span><span style=\"color:#9CDCFE\">count</span><span style=\"color:#D4D4D4\"> = </span><span style=\"color:#9CDCFE\">state</span><span style=\"color:#D4D4D4\">.</span><span style=\"color:#9CDCFE\">count</span><span style=\"color:#D4D4D4\"> + </span><span style=\"color:#B5CEA8\">1</span><span style=\"color:#D4D4D4\">)</span><span style=\"color:#569CD6\">}</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">        Increment directly</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">      &lt;/</span><span style=\"color:#4EC9B0\">Button</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">      &lt;</span><span style=\"color:#4EC9B0\">Button</span><span style=\"color:#9CDCFE\"> onClick</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#569CD6\">{</span><span style=\"color:#D4D4D4\">() </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#DCDCAA\"> incrementCount</span><span style=\"color:#D4D4D4\">({ </span><span style=\"color:#9CDCFE\">amount:</span><span style=\"color:#B5CEA8\"> 1</span><span style=\"color:#D4D4D4\"> })</span><span style=\"color:#569CD6\">}</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">        increment via function</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">      &lt;/</span><span style=\"color:#4EC9B0\">Button</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">      &lt;</span><span style=\"color:#4EC9B0\">Button</span><span style=\"color:#9CDCFE\"> onClick</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#569CD6\">{</span><span style=\"color:#D4D4D4\">() </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#D4D4D4\"> (</span><span style=\"color:#9CDCFE\">state</span><span style=\"color:#D4D4D4\">.</span><span style=\"color:#9CDCFE\">count</span><span style=\"color:#D4D4D4\"> = </span><span style=\"color:#B5CEA8\">0</span><span style=\"color:#D4D4D4\">)</span><span style=\"color:#569CD6\">}</span><span style=\"color:#808080\">&gt;</span><span style=\"color:#E6E6E6\">reset</span><span style=\"color:#808080\">&lt;/</span><span style=\"color:#4EC9B0\">Button</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">      {</span><span style=\"color:#9CDCFE\">snap</span><span style=\"color:#D4D4D4\">.</span><span style=\"color:#9CDCFE\">count</span><span style=\"color:#569CD6\">}{</span><span style=\"color:#CE9178\">&#x27; &#x27;</span><span style=\"color:#569CD6\">}</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">    &lt;/</span><span style=\"color:#569CD6\">div</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  )</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// Subscribe to all state changes</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> unsubscribe</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> subscribe</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">state</span><span style=\"color:#E6E6E6\">, () </span><span style=\"color:#569CD6\">=&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  console</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">log</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;valtio example state has changed to&#x27;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">state</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// a primitive needs to be subscribed to with subscribeKey</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> unsubscribeSlice</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> subscribeKey</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">state</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#CE9178\">&#x27;count&#x27;</span><span style=\"color:#E6E6E6\">, () </span><span style=\"color:#569CD6\">=&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  console</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">log</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;valtio count state has changed to&#x27;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">state</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">)</span></span></code></div></pre></figure>\n<h2>Jotai</h2>\n<p>From the <a href=\"https://jotai.org/docs/basics/comparison\">jotai docs</a>:</p>\n<blockquote>\n<p>Analogy\nJotai is like Recoil. Zustand is like Redux.</p>\n</blockquote>\n<p>When to use which</p>\n<ul>\n<li>\n<p>If you want a simple module state, Zustand fits well.</p>\n</li>\n<li>\n<p>If you prefer Redux devtools, Zustand is good to go.</p>\n</li>\n<li>\n<p>If you need a replacement for useState+useContext, Jotai fits well.</p>\n</li>\n<li>\n<p>If you want to make use of Suspense, Jotai is the one.</p>\n</li>\n<li>\n<p>If code splitting is important, Jotai should perform well.</p>\n</li>\n</ul>\n<div class=\"flex items-center gap-2\"><button class=\"theme-button inline-flex items-center gap-2 justify-center rounded-lg py-2 px-3 text-sm outline-offset-2 transition-all duration-200 active:transition-none bg-stone-800 font-semibold text-stone-100 hover:bg-stone-700 hover:scale-[1.02] active:bg-stone-800 active:text-stone-100/70 dark:bg-stone-700 dark:hover:bg-stone-600 dark:active:bg-stone-700 dark:active:text-stone-100/70\">Increment</button><button class=\"theme-button inline-flex items-center gap-2 justify-center rounded-lg py-2 px-3 text-sm outline-offset-2 transition-all duration-200 active:transition-none bg-stone-800 font-semibold text-stone-100 hover:bg-stone-700 hover:scale-[1.02] active:bg-stone-800 active:text-stone-100/70 dark:bg-stone-700 dark:hover:bg-stone-600 dark:active:bg-stone-700 dark:active:text-stone-100/70\">Increment with incrementCountAtom</button><button class=\"theme-button inline-flex items-center gap-2 justify-center rounded-lg py-2 px-3 text-sm outline-offset-2 transition-all duration-200 active:transition-none bg-stone-800 font-semibold text-stone-100 hover:bg-stone-700 hover:scale-[1.02] active:bg-stone-800 active:text-stone-100/70 dark:bg-stone-700 dark:hover:bg-stone-600 dark:active:bg-stone-700 dark:active:text-stone-100/70\">Reset</button><div>1</div></div>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">Button</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;@/components/Button&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">atom</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">useAtom</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">createStore</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">Provider</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;jotai&#x27;</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> countAtom</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> atom</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#B5CEA8\">0</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> messageAtom</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> atom</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;hello&#x27;</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> myStore</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> createStore</span><span style=\"color:#E6E6E6\">()</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> initialCountState</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#B5CEA8\"> 1</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">myStore</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">set</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">countAtom</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">initialCountState</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> unsub</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#9CDCFE\"> myStore</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">sub</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">countAtom</span><span style=\"color:#E6E6E6\">, () </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  console</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">log</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;Jotai countAtom value is changed to&#x27;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">myStore</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#DCDCAA\">get</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">countAtom</span><span style=\"color:#E6E6E6\">))</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">})</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#569CD6\">function</span><span style=\"color:#DCDCAA\"> JotaiCounter</span><span style=\"color:#E6E6E6\">() {</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">  const</span><span style=\"color:#E6E6E6\"> [</span><span style=\"color:#9CDCFE\">value</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">setValue</span><span style=\"color:#E6E6E6\">] </span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#DCDCAA\"> useAtom</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">countAtom</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  return</span><span style=\"color:#E6E6E6\"> (</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">    &lt;</span><span style=\"color:#569CD6\">div</span><span style=\"color:#9CDCFE\"> className</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#CE9178\">&quot;flex items-center gap-2&quot;</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">      &lt;</span><span style=\"color:#4EC9B0\">Button</span><span style=\"color:#9CDCFE\"> onClick</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#569CD6\">{</span><span style=\"color:#D4D4D4\">() </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#DCDCAA\"> setValue</span><span style=\"color:#D4D4D4\">(</span><span style=\"color:#9CDCFE\">value</span><span style=\"color:#D4D4D4\"> + </span><span style=\"color:#B5CEA8\">1</span><span style=\"color:#D4D4D4\">)</span><span style=\"color:#569CD6\">}</span><span style=\"color:#808080\">&gt;</span><span style=\"color:#E6E6E6\">Increment</span><span style=\"color:#808080\">&lt;/</span><span style=\"color:#4EC9B0\">Button</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">      &lt;</span><span style=\"color:#4EC9B0\">Button</span><span style=\"color:#9CDCFE\"> onClick</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#569CD6\">{</span><span style=\"color:#D4D4D4\">() </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#DCDCAA\"> setValue</span><span style=\"color:#D4D4D4\">(</span><span style=\"color:#9CDCFE\">initialCountState</span><span style=\"color:#D4D4D4\">)</span><span style=\"color:#569CD6\">}</span><span style=\"color:#808080\">&gt;</span><span style=\"color:#E6E6E6\">Reset</span><span style=\"color:#808080\">&lt;/</span><span style=\"color:#4EC9B0\">Button</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">      &lt;</span><span style=\"color:#569CD6\">div</span><span style=\"color:#808080\">&gt;</span><span style=\"color:#569CD6\">{</span><span style=\"color:#9CDCFE\">value</span><span style=\"color:#569CD6\">}</span><span style=\"color:#808080\">&lt;/</span><span style=\"color:#569CD6\">div</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">    &lt;/</span><span style=\"color:#569CD6\">div</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  )</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#569CD6\"> function</span><span style=\"color:#DCDCAA\"> JotaiExample</span><span style=\"color:#E6E6E6\">() {</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  return</span><span style=\"color:#E6E6E6\"> (</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">    &lt;</span><span style=\"color:#4EC9B0\">Provider</span><span style=\"color:#9CDCFE\"> store</span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#569CD6\">{</span><span style=\"color:#9CDCFE\">myStore</span><span style=\"color:#569CD6\">}</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">      &lt;</span><span style=\"color:#4EC9B0\">JotaiCounter</span><span style=\"color:#808080\"> /&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#808080\">    &lt;/</span><span style=\"color:#4EC9B0\">Provider</span><span style=\"color:#808080\">&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  )</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span></code></div></pre></figure>\n<p>Nice things</p>\n<ul>\n<li>the provider is optional, which allows overriding context for things like components (zustand also now has this)</li>\n<li>one can think about things just at the atom level and not as much a single store</li>\n</ul>\n<p>Things to get used to</p>\n<ul>\n<li>action mutations are done against atoms when the setValue isn&#x27;t used. Could get harder to trace where things are changing but <code>setValue</code> in a form is similar so maybe is fine for smaller primitive changes but something such as calculating a total based on multiple things may require a composite action atom.</li>\n</ul>\n<p>The syntax for an &quot;action atom&quot; looks a bit strange initially.</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> incrementCountAtom</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> atom</span><span style=\"color:#E6E6E6\">(</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  (</span><span style=\"color:#9CDCFE\">get</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#DCDCAA\"> get</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">countAtom</span><span style=\"color:#E6E6E6\">),</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  (</span><span style=\"color:#9CDCFE\">get</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">set</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#DCDCAA\"> set</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">countAtom</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#DCDCAA\">get</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">countAtom</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#D4D4D4\">+</span><span style=\"color:#B5CEA8\"> 1</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// component uses it as</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#E6E6E6\"> [</span><span style=\"color:#9CDCFE\">count</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">incrementCount</span><span style=\"color:#E6E6E6\">] </span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#DCDCAA\"> useAtom</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">incrementCountAtom</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// alternative for just the setter</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> incrementCount</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> useSetAtom</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">incrementCountAtom</span><span style=\"color:#E6E6E6\">)</span></span></code></div></pre></figure>",
            "url": "https://alexmchan.com/blog/2022-09-21-quick-context",
            "title": "Quick context",
            "summary": "Setting up context quickly",
            "date_modified": "2022-09-21T00:00:00.000Z",
            "author": {
                "name": "Alex Chan"
            }
        },
        {
            "id": "https://alexmchan.com/blog/2022-04-09-aws-tips",
            "content_html": "<h2>Getting started using AWS can be hard.</h2>\n<p>Here are a few things I wish I&#x27;d known before starting aws.</p>\n<ol>\n<li>\n<p>Learn more about IAM as early as you can. If you don&#x27;t you&#x27;ll run into countless auth errors and the few hours of investment will pay off in spades.\nBrigid Johnson&#x27;s talk on this gives a great foundation.\n<a href=\"https://www.youtube.com/watch?v=YQsK4MtsELU&amp;ab_channel=AmazonWebServices\">https://www.youtube.com/watch?v=YQsK4MtsELU&amp;ab_channel=AmazonWebServices</a></p>\n</li>\n<li>\n<p>The names of aws services are weird. <a href=\"https://expeditedsecurity.com/aws-in-plain-english/\">https://expeditedsecurity.com/aws-in-plain-english/</a> helps a lot. I&#x27;ve just given up trying to make sense of any name and always try to map it to &quot;What problem does this service solve and what are the tradeoffs between the other products&quot;</p>\n</li>\n<li>\n<p>There&#x27;s no one messaging system that rules it all. It&#x27;s more than likely you&#x27;ll have to choose the right thing for the job and SQS will almost definitely factor in some part of a dead-letter-queue even if you land on something like eventbridge, sns or kinesis. Learn the tradeoffs between them and when to use which or combination of message queues.</p>\n</li>\n<li>\n<p>Prototype in aws console, but build using infrastructure as code (ie aws cdk). These help document which services you&#x27;re using and help migrating between environments.</p>\n</li>\n<li>\n<p>Try serverless services when doing the initial draft architecture before you know your actual loads can free you from many constraints and can be swapped out for non-serverless services either once you have an actual load picture or even just stay in the design.</p>\n</li>\n<li>\n<p>Kubernetes and EKS are complex and require a lot of management overhead. Consider serverless like lambda or less server like ECS/fargate if that&#x27;s an option.</p>\n</li>\n<li>\n<p>Keep multiple accounts for dev/qa/prod. This will greatly help keep your infrastructure and user accounts separate.</p>\n</li>\n<li>\n<p>Add alarms on everything. Tune them until only the important alerts and thresholds are hit and funnel them via sns into whatever messaging system your team uses</p>\n</li>\n<li>\n<p>Design for observability, on the flipside cloudwatch costs more than you think. Factor in costs related to logging lots of information and be able to turn on more logging with a switch.</p>\n</li>\n<li>\n<p>It&#x27;s worth learning at least one cloud provider well. The underlying architectures and systems can save you from having to build core infrastructure these and can greatly simplify the code you have to write.</p>\n</li>\n</ol>\n<h3>Bonus tips</h3>\n<ol start=\"11\">\n<li>\n<p>Lots of products are released around aws:re-invent so keep your eyes on if a problem you had is solved by a new product launch or feature</p>\n</li>\n<li>\n<p>Plan for multi region up front unless you can survive downtime in a region.</p>\n</li>\n<li>\n<p>Solutions from other providers may still be better for specific cases. Planetscale, temporal, supabase, and upstash may abstract a lot of the complexities of db from your infrastructure.</p>\n</li>\n<li>\n<p>If you don&#x27;t have a messaging system it&#x27;s likely you have points of failure that you need to account for. Even if you have a messaging system you need to account for it&#x27;s failures and how to reprocess them.</p>\n</li>\n</ol>",
            "url": "https://alexmchan.com/blog/2022-04-09-aws-tips",
            "title": "10 things I wish I knew before architecting AWS applications",
            "summary": "Here are a few things I wish I’d known before starting aws",
            "date_modified": "2022-04-09T00:00:00.000Z",
            "author": {
                "name": "Alex Chan"
            }
        },
        {
            "id": "https://alexmchan.com/blog/2021-09-06-opensourcery-theme-types",
            "content_html": "<p>Types are actually some of the hardest parts of getting a libary correct. Stitches is a css in js (and most importantly ts) library that has an incredible set of types so let&#x27;s dive into one of the most interesting ones.</p>\n<blockquote>\n<p>Any sufficiently advanced technology is indistinguishable from magic</p>\n<ul>\n<li>Arthur C. Clarke</li>\n</ul>\n</blockquote>\n<p>How stitches solved their theming types feels like advanced typescript. Luckily, they&#x27;ve shared their magic with us. Let&#x27;s dive in.</p>\n<h2>Stitches theme</h2>\n<p>Among other things, Stitches lets you define your own theme. So for <code>color</code> we could define a set of colors <code>blue-100</code>, <code>blue-200</code> and then use them in our component as a <code>css</code> prop.</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">createStitches</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#C586C0\">from</span><span style=\"color:#CE9178\"> &#x27;@stitches/react&#x27;</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#569CD6\"> const</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">styled</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">css</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">getCssText</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#DCDCAA\"> createStitches</span><span style=\"color:#E6E6E6\">({</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  theme:</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    colors:</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#CE9178\">      &#x27;blue-100&#x27;</span><span style=\"color:#9CDCFE\">:</span><span style=\"color:#CE9178\"> &#x27;#DBEAFE&#x27;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#CE9178\">      &#x27;blue-200&#x27;</span><span style=\"color:#9CDCFE\">:</span><span style=\"color:#CE9178\"> &#x27;#BFDBFE&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  },</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">})</span></span></code></div></pre></figure>\n<p>Then we can make a button that&#x27;s fully typed using object styles:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> Button</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> styled</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;button&#x27;</span><span style=\"color:#E6E6E6\">, {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  color:</span><span style=\"color:#CE9178\"> &#x27;$blue-100&#x27;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">})</span></span></code></div></pre></figure>\n<h2>The basics</h2>\n<p>The first thing to do is define what the theme is initially made up of. In our case we just have a <code>colors</code> scale that&#x27;s used by <code>color</code>. This is the theme that&#x27;ll be used by our function:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#569CD6\">declare</span><span style=\"color:#569CD6\"> namespace</span><span style=\"color:#4EC9B0\"> Config</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  export</span><span style=\"color:#569CD6\"> type</span><span style=\"color:#4EC9B0\"> Theme</span><span style=\"color:#E6E6E6\">&lt;</span><span style=\"color:#4EC9B0\">T</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#E6E6E6\"> {}&gt; </span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    colors</span><span style=\"color:#D4D4D4\">?:</span><span style=\"color:#E6E6E6\"> { [</span><span style=\"color:#4EC9B0\">key</span><span style=\"color:#569CD6\"> in</span><span style=\"color:#4EC9B0\"> string</span><span style=\"color:#E6E6E6\">]</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#4EC9B0\"> string</span><span style=\"color:#E6E6E6\"> }</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  }</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span></code></div></pre></figure>\n<h2>Define a function</h2>\n<p>For our example we want a <code>const { styled } = createStitches({theme})</code> typed function. This is just pseudo code and we&#x27;ll start adding to it:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#569CD6\"> type</span><span style=\"color:#4EC9B0\"> CreateStitches</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  (</span><span style=\"color:#9CDCFE\">config</span><span style=\"color:#E6E6E6\">)</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">    styled</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#9CDCFE\">elementToStyle</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">styleObject</span><span style=\"color:#E6E6E6\">)</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  }</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span></code></div></pre></figure>\n<h2>Dynamic theme</h2>\n<p>What we want is to allow one of the keys of the theme to be used. To do this we need the generics. <code>Theme</code> will be inferred from the passed in value.</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#569CD6\"> type</span><span style=\"color:#4EC9B0\"> CreateStitches</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  &lt;</span><span style=\"color:#4EC9B0\">Theme</span><span style=\"color:#569CD6\"> extends</span><span style=\"color:#4EC9B0\"> Config</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#4EC9B0\">Theme</span><span style=\"color:#E6E6E6\">&gt;(</span><span style=\"color:#9CDCFE\">config</span><span style=\"color:#D4D4D4\">?:</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">theme</span><span style=\"color:#D4D4D4\">?:</span><span style=\"color:#4EC9B0\"> Theme</span><span style=\"color:#E6E6E6\"> })</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">    styled</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#9CDCFE\">elementToStyle</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#9CDCFE\">styleObject</span><span style=\"color:#E6E6E6\">) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#4EC9B0\"> void</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  }</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span></code></div></pre></figure>\n<h2>Generic return</h2>\n<p>We want the value passed into the <code>styled</code> function for <code>color</code> to be one of the <code>colors</code> from the theme. To do this we can use the <code>keyof</code> to pull out the keys.</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#569CD6\"> type</span><span style=\"color:#4EC9B0\"> CreateStitches</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  &lt;</span><span style=\"color:#4EC9B0\">Theme</span><span style=\"color:#569CD6\"> extends</span><span style=\"color:#4EC9B0\"> Config</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#4EC9B0\">Theme</span><span style=\"color:#E6E6E6\">&gt;(</span><span style=\"color:#9CDCFE\">config</span><span style=\"color:#D4D4D4\">?:</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">theme</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#4EC9B0\"> Theme</span><span style=\"color:#E6E6E6\"> })</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">    styled</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#E6E6E6\"> (</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">      elementToStyle</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#4EC9B0\"> string</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">      { </span><span style=\"color:#9CDCFE\">color</span><span style=\"color:#E6E6E6\"> }</span><span style=\"color:#D4D4D4\">?:</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">color</span><span style=\"color:#D4D4D4\">?:</span><span style=\"color:#569CD6\"> keyof</span><span style=\"color:#4EC9B0\"> Theme</span><span style=\"color:#E6E6E6\">[</span><span style=\"color:#CE9178\">&#x27;colors&#x27;</span><span style=\"color:#E6E6E6\">] }</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    ) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#4EC9B0\"> void</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  }</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span></code></div></pre></figure>\n<h2>Aside: elementToStyle</h2>\n<p>It&#x27;d be nice to also restrict <code>elementToStyle</code> down to all the JSX elements such as <code>div</code>, <code>button</code> etc. To do this we can add</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#E6E6E6\">  ( </span><span style=\"color:#9CDCFE\">elementToStyle</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#569CD6\"> keyof</span><span style=\"color:#4EC9B0\"> JSX</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#4EC9B0\">IntrinsicElements</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#6A9955\">/**...*/</span><span style=\"color:#E6E6E6\">)</span></span></code></div></pre></figure>\n<h2>Some javascript</h2>\n<p>Finally we can use this type and all of the dynamic properties:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#DCDCAA\"> createStiches</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#4EC9B0\"> CreateStitches</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#E6E6E6\"> ({ </span><span style=\"color:#9CDCFE\">theme</span><span style=\"color:#E6E6E6\"> }) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#C586C0\">  return</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">    styled</span><span style=\"color:#9CDCFE\">:</span><span style=\"color:#E6E6E6\"> (</span><span style=\"color:#9CDCFE\">type</span><span style=\"color:#E6E6E6\">, { </span><span style=\"color:#9CDCFE\">color</span><span style=\"color:#E6E6E6\"> }) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#6A9955\"> /*...*/</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  }</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// call it with our theme</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">styled</span><span style=\"color:#E6E6E6\"> } </span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#DCDCAA\"> createStiches</span><span style=\"color:#E6E6E6\">({</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  theme:</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">    colors:</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#CE9178\">      &#x27;blue-100&#x27;</span><span style=\"color:#9CDCFE\">:</span><span style=\"color:#CE9178\"> &#x27;blue1&#x27;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#CE9178\">      &#x27;blue-200&#x27;</span><span style=\"color:#9CDCFE\">:</span><span style=\"color:#CE9178\"> &#x27;blue2&#x27;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    },</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  },</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">})</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">// types are suggested and can be autocompleted!</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">styled</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;button&#x27;</span><span style=\"color:#E6E6E6\">, {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  color:</span><span style=\"color:#CE9178\"> &#x27;blue-100&#x27;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">})</span></span>\n<span data-line=\"\"> </span></code></div></pre></figure>\n<p>This solves the token key problem, but stitches also maps to ALL of the other color values as well AND includes the css colors like <code>royalblue</code> and <code>rebeccapurple</code> and allows other string values <code>#fff</code>.</p>\n<h2>Challenge #1 - allow string types</h2>\n<p>The simplest thing to do to allow string types would be to change our type to also include colors right?</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#E6E6E6\">{ </span><span style=\"color:#9CDCFE\">color</span><span style=\"color:#E6E6E6\"> }</span><span style=\"color:#D4D4D4\">?:</span><span style=\"color:#E6E6E6\"> { color?</span><span style=\"color:#9CDCFE\">:</span><span style=\"color:#9CDCFE\"> keyof</span><span style=\"color:#9CDCFE\"> Theme</span><span style=\"color:#E6E6E6\">[</span><span style=\"color:#CE9178\">&#x27;colors&#x27;</span><span style=\"color:#E6E6E6\">] </span><span style=\"color:#D4D4D4\">|</span><span style=\"color:#9CDCFE\"> string</span><span style=\"color:#E6E6E6\"> }</span></span></code></div></pre></figure>\n<p>This works -- kinda. Now we can use <code>blue-100</code> or <code>#00f</code> but now we don&#x27;t get any suggestions of our own theme values. Oof indeed.</p>\n<h2>Challenge #1b - allow string types BUT still allow for type checking</h2>\n<p>The unsung hero <code>csstype</code> can actually come to our rescue here because if we look at any lowly <code>&lt;div style={{ color: &#x27;rebeccapurple&#x27;}}&gt;</code> we can see it allows strings AND does css spec defined values as well.</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#C586C0\">import</span><span style=\"color:#C586C0\"> type</span><span style=\"color:#569CD6\"> *</span><span style=\"color:#C586C0\"> as</span><span style=\"color:#9CDCFE\"> CSSType</span><span style=\"color:#C586C0\"> from</span><span style=\"color:#CE9178\"> &#x27;csstype&#x27;</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">type</span><span style=\"color:#4EC9B0\"> Color</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#4EC9B0\"> CSSType</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#4EC9B0\">Properties</span><span style=\"color:#E6E6E6\">[</span><span style=\"color:#CE9178\">&#x27;color&#x27;</span><span style=\"color:#E6E6E6\">]</span></span>\n<span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> c</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#4EC9B0\"> Color</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#CE9178\"> &#x27;cornflowerblue&#x27;</span></span></code></div></pre></figure>\n<h2>Challenge #2 - allow scale values</h2>\n<p>So to do this we can just refer to the original parent scale or our theme value:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#E6E6E6\">{ </span><span style=\"color:#9CDCFE\">color</span><span style=\"color:#E6E6E6\"> }</span><span style=\"color:#D4D4D4\">?:</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">color:</span><span style=\"color:#9CDCFE\"> CSSType</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#9CDCFE\">Properties</span><span style=\"color:#E6E6E6\">[</span><span style=\"color:#CE9178\">&#x27;color&#x27;</span><span style=\"color:#E6E6E6\">] </span><span style=\"color:#D4D4D4\">|</span><span style=\"color:#9CDCFE\"> keyof</span><span style=\"color:#9CDCFE\"> Theme</span><span style=\"color:#E6E6E6\">[</span><span style=\"color:#CE9178\">&#x27;colors&#x27;</span><span style=\"color:#E6E6E6\">] }</span></span></code></div></pre></figure>\n<p>So we finally have our type that allows us to use our theme values AND existing css types.</p>\n<h2>Prefix tokens with $</h2>\n<p>Lastly, Stitches adds a <code>$</code> in front of any token values. They have a type utility that does this:</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"ts\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"ts\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#569CD6\"> type</span><span style=\"color:#4EC9B0\"> Prefixed</span><span style=\"color:#E6E6E6\">&lt;</span><span style=\"color:#4EC9B0\">K</span><span style=\"color:#569CD6\"> extends</span><span style=\"color:#4EC9B0\"> string</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#4EC9B0\">T</span><span style=\"color:#E6E6E6\">&gt; </span><span style=\"color:#D4D4D4\">=</span><span style=\"color:#CE9178\"> `</span><span style=\"color:#569CD6\">${</span><span style=\"color:#4EC9B0\">K</span><span style=\"color:#569CD6\">}${</span><span style=\"color:#4EC9B0\">Extract</span><span style=\"color:#D4D4D4\">&lt;</span></span>\n<span data-line=\"\"><span style=\"color:#4EC9B0\">  T</span><span style=\"color:#D4D4D4\">,</span></span>\n<span data-line=\"\"><span style=\"color:#4EC9B0\">  boolean</span><span style=\"color:#D4D4D4\"> | </span><span style=\"color:#4EC9B0\">number</span><span style=\"color:#D4D4D4\"> | </span><span style=\"color:#4EC9B0\">string</span></span>\n<span data-line=\"\"><span style=\"color:#D4D4D4\">&gt;</span><span style=\"color:#569CD6\">}</span><span style=\"color:#CE9178\">`</span></span></code></div></pre></figure>\n<p>and now we can use the utility to have our auto-complete show us the <code>$blue-100</code> version instead</p>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#C586C0\">export</span><span style=\"color:#569CD6\"> type</span><span style=\"color:#4EC9B0\"> CreateStitches</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  &lt;</span><span style=\"color:#4EC9B0\">Theme</span><span style=\"color:#569CD6\"> extends</span><span style=\"color:#4EC9B0\"> Config</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#4EC9B0\">Theme</span><span style=\"color:#E6E6E6\">&gt;(</span><span style=\"color:#9CDCFE\">config</span><span style=\"color:#D4D4D4\">?:</span><span style=\"color:#E6E6E6\"> { </span><span style=\"color:#9CDCFE\">theme</span><span style=\"color:#D4D4D4\">?:</span><span style=\"color:#4EC9B0\"> Theme</span><span style=\"color:#E6E6E6\"> })</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#DCDCAA\">    styled</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#E6E6E6\"> (</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">      type</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#569CD6\"> keyof</span><span style=\"color:#4EC9B0\"> JSX</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#4EC9B0\">IntrinsicElements</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">      {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">        color</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">      }</span><span style=\"color:#D4D4D4\">?:</span><span style=\"color:#E6E6E6\"> {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">        color</span><span style=\"color:#D4D4D4\">:</span></span>\n<span data-line=\"\"><span style=\"color:#D4D4D4\">          |</span><span style=\"color:#4EC9B0\"> CSSType</span><span style=\"color:#E6E6E6\">.</span><span style=\"color:#4EC9B0\">Properties</span><span style=\"color:#E6E6E6\">[</span><span style=\"color:#CE9178\">&#x27;color&#x27;</span><span style=\"color:#E6E6E6\">]</span></span>\n<span data-line=\"\"><span style=\"color:#D4D4D4\">          |</span><span style=\"color:#4EC9B0\"> Prefixed</span><span style=\"color:#E6E6E6\">&lt;</span><span style=\"color:#CE9178\">&#x27;$&#x27;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#569CD6\">keyof</span><span style=\"color:#4EC9B0\"> Theme</span><span style=\"color:#E6E6E6\">[</span><span style=\"color:#CE9178\">&#x27;colors&#x27;</span><span style=\"color:#E6E6E6\">]&gt;</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">      }</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">    ) </span><span style=\"color:#569CD6\">=&gt;</span><span style=\"color:#4EC9B0\"> void</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">  }</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">}</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#6A9955\">//...</span></span>\n<span data-line=\"\"> </span>\n<span data-line=\"\"><span style=\"color:#4EC9B0\">styled</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;button&#x27;</span><span style=\"color:#E6E6E6\">, {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  color</span><span style=\"color:#D4D4D4\">:</span><span style=\"color:#CE9178\"> &#x27;$blue-100&#x27;</span><span style=\"color:#E6E6E6\">,</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">})</span></span></code></div></pre></figure>\n<h2>We&#x27;re not done yet</h2>\n<p>This is already a big step forward but there&#x27;s a few more type features provided by stitches that would be nice to understand. Stay tuned for future posts on:</p>\n<ul>\n<li>how did they get one scale used for multiple scales?</li>\n</ul>\n<figure data-rehype-pretty-code-figure=\"\"><pre style=\"background-color:#222222;color:#E6E6E6\" tabindex=\"0\" data-language=\"tsx\" data-theme=\"slack-dark\" class=\"relative\"><div class=\"text-slate-500 relative z-10 float-right hidden\"><button aria-label=\"Copy code\" class=\"h-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"></path></svg></button></div><div><code data-language=\"tsx\" data-theme=\"slack-dark\" style=\"display:grid\"><span data-line=\"\"><span style=\"color:#569CD6\">const</span><span style=\"color:#9CDCFE\"> Button</span><span style=\"color:#D4D4D4\"> =</span><span style=\"color:#DCDCAA\"> styled</span><span style=\"color:#E6E6E6\">(</span><span style=\"color:#CE9178\">&#x27;button&#x27;</span><span style=\"color:#E6E6E6\">, {</span></span>\n<span data-line=\"\"><span style=\"color:#9CDCFE\">  backgroundColor:</span><span style=\"color:#CE9178\"> &#x27;blue-100&#x27;</span><span style=\"color:#E6E6E6\">, </span><span style=\"color:#6A9955\">// magic</span></span>\n<span data-line=\"\"><span style=\"color:#E6E6E6\">})</span></span></code></div></pre></figure>\n<ul>\n<li>how are utility functions defined AND how can we infer the type passed into the utility function to know the right scale for it?</li>\n<li>how are variants defined AND type safe for the defaults?</li>\n<li>how does the <code>as</code> property allow changing the react props depending on which <code>as</code> is chosen? <code>material-ui</code> also has this</li>\n</ul>\n<p>The Stitches code is great and also has so much type sorcery happening under the covers so I&#x27;d recommend reading through the <code>/types</code> folder to explore how much of the typing is done.</p>",
            "url": "https://alexmchan.com/blog/2021-09-06-opensourcery-theme-types",
            "title": "Opensourcery: Theme types",
            "summary": "A dive into how Stitches uses generics to type a theme",
            "date_modified": "2021-09-06T00:00:00.000Z",
            "author": {
                "name": "Alex Chan"
            }
        }
    ]
}