<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Robin van der Vleuten</title>
  <subtitle>Writing by Robin van der Vleuten — an indie developer of all things internet.</subtitle>
  <link href="https://robinvdvleuten.nl/feed.xml" rel="self"/>
  <link href="https://robinvdvleuten.nl/"/>
  <updated>2026-06-13T00:00:00Z</updated>
  <id>https://robinvdvleuten.nl/</id>
  <author>
    <name>Robin van der Vleuten</name>
    <uri>https://robinvdvleuten.nl/</uri>
  </author>
  <entry>
    <title>Running my DSMR parser backwards into a virtual smart meter</title>
    <link href="https://robinvdvleuten.nl/post/a-virtual-smart-meter-in-liveview/"/>
    <updated>2026-06-13T00:00:00Z</updated>
    <id>https://robinvdvleuten.nl/post/a-virtual-smart-meter-in-liveview/</id>
    <summary>I taught my DSMR library to run in reverse, then wrapped it in a skeuomorphic Phoenix LiveView meter and put it online at p1meter.dev.</summary>
    <content type="html">&lt;p&gt;A while back I wrote about &lt;a href=&quot;https://robinvdvleuten.nl/post/how-erlangs-parser-tools-saved-my-dsmr-library/&quot;&gt;how Erlang&#39;s parser tools saved my DSMR library&lt;/a&gt;: the story of binning a pile of regex and letting &lt;code&gt;leex&lt;/code&gt; and &lt;code&gt;yecc&lt;/code&gt; read the telegrams that Dutch and Belgian smart meters spit out every second. Messy text in, tidy Elixir structs out. I was pretty happy with it.&lt;/p&gt;
&lt;p&gt;Then I got a little obsessed with running it the other way.&lt;/p&gt;
&lt;p&gt;It started, as these things do, while I was writing tests. Every time I wanted to check the parser handled some edge case, I&#39;d go digging for a captured telegram to paste in. And every time, the same nagging thought: I already have a complete description of what a telegram &lt;em&gt;is&lt;/em&gt;: the grammar, the field types, the structs. Why am I copy-pasting strings around like it&#39;s 2009? Surely I could hand the library a struct and get a byte-perfect telegram back out.&lt;/p&gt;
&lt;p&gt;That nagging thought turned into &lt;a href=&quot;https://p1meter.dev/&quot;&gt;p1meter.dev&lt;/a&gt;: a virtual smart meter you can talk to over plain TCP, or just leave open in a tab and watch tick over.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://robinvdvleuten.nl/images/p1meter.dev.jpg&quot; alt=&quot;The p1meter.dev landing page: a skeuomorphic DSMR 5.0 smart meter with a glowing green display, ticking over live in the browser.&quot;&gt;&lt;/p&gt;
&lt;h2&gt;The itch: a parser with nothing to parse&lt;/h2&gt;
&lt;p&gt;Here&#39;s the slightly absurd problem with building a smart meter library: to actually test it, you need a smart meter. Mine lives in a cupboard under the stairs, behind a P1 cable I have to physically go and plug into a laptop. Romantic, it is not.&lt;/p&gt;
&lt;p&gt;Other people hit the same problem. Anyone building a &lt;a href=&quot;https://www.home-assistant.io/&quot;&gt;Home Assistant&lt;/a&gt; integration, a P1 reader, or a parser like mine runs into the same wall: the only realistic test data is locked inside a box bolted to the wall.&lt;/p&gt;
&lt;p&gt;A meter only ever &lt;em&gt;emits&lt;/em&gt; telegrams. It never reads them, so my library only ever spoke half the protocol. Teach it the other half, struct to string, and suddenly I could generate as much realistic test data as I liked. No cupboard required.&lt;/p&gt;
&lt;h2&gt;First attempt: a Livebook&lt;/h2&gt;
&lt;p&gt;The first version wasn&#39;t a website. It wasn&#39;t really a project at all, just a &lt;a href=&quot;https://github.com/mijnverbruik/dsmr/blob/main/examples/dummy_smartmeter.livemd&quot;&gt;Livebook&lt;/a&gt;. A &lt;code&gt;Mix.install&lt;/code&gt; with &lt;code&gt;dsmr&lt;/code&gt; and &lt;code&gt;kino&lt;/code&gt;, a little &lt;code&gt;GenServer&lt;/code&gt; coughing out a telegram every second over a TCP socket, and a VegaLite chart watching the usage curve climb.&lt;/p&gt;
&lt;p&gt;And it worked! Which was sort of the problem. A Livebook is a wonderful place to prototype and a terrible place to leave something. You have to clone it, install Elixir, and run it yourself before it&#39;s any use to anyone else. This deserved a front door.&lt;/p&gt;
&lt;p&gt;Besides, once I had a meter quietly humming away on the BEAM, I couldn&#39;t stop thinking about &lt;em&gt;drawing&lt;/em&gt; it. Not graphing it. Drawing it. Something you could actually look at.&lt;/p&gt;
&lt;h2&gt;Running the parser in reverse&lt;/h2&gt;
&lt;p&gt;The whole thing rests on one small, satisfying trick: the same library that takes telegrams apart can put them back together. Forward is the day job. Backwards is the new party piece.&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;elixir&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;# Forward: the original dsmr job, text in, struct out.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{:ok&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; telegram} &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;DSMR&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;parse&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(raw_string)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;# Backwards: struct in, text out.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;telegram_string &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;DSMR&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;Telegram&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;to_string&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(telegram)&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;There&#39;s one detail that separates a telegram that&#39;s &lt;em&gt;real&lt;/em&gt; from one that&#39;s merely plausible, and it&#39;s the checksum. Every telegram signs off with a &lt;code&gt;!&lt;/code&gt; and a CRC16 over everything before it. Get that wrong and a strict parser throws the whole packet straight in the bin, which is exactly the behaviour you want your fixtures to respect. So the simulator builds the struct, renders it once, runs the checksum over that text, and stamps it back in:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;elixir&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;checksum &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  telegram&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;|&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;DSMR&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;Telegram&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;to_string&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;|&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;split&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;!&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; parts: &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;|&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;List&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;first&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;|&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;Kernel&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;&amp;lt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;!&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;|&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;DSMR&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;CRC16&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;checksum&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;This is dogfooding in the most literal sense I can manage: the simulator&#39;s output gets fed straight back through the parser in the test suite. If a round-trip doesn&#39;t survive, something&#39;s broken in one half or the other. And once or twice, I&#39;ll admit, it was the half I&#39;d already declared finished.&lt;/p&gt;
&lt;h2&gt;Making the numbers move&lt;/h2&gt;
&lt;p&gt;A meter that reports the same number forever is a deeply boring meter. I wanted the display to behave like an actual household over a day, so a &lt;code&gt;GenServer&lt;/code&gt; keeps its own virtual clock and nudges every reading on each tick.&lt;/p&gt;
&lt;p&gt;The usage curve is nothing clever, just a cosine wave. Low and sleepy overnight, climbing through the morning, peaking around dinner, with a dusting of random noise so it never looks suspiciously neat:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;elixir&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;time_of_day &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (hour &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;60&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; minute) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;24&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;60&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;phase &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (time_of_day &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;0.75&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; :math&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;pi&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;wave &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (:math&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;cos&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(phase) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;2&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;current_power &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;max&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;0.0&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;0.2&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; wave &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;2.3&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;noise_factor&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;())&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;On top of that sit the little details that make it feel properly Dutch: a day/night tariff that flips the active register after 23:00, the odd burst of solar being pushed back to the grid on a sunny afternoon, and a gas reading that jumps once an hour rather than creeping up continuously, because that&#39;s exactly how the gas meter reports in real life.&lt;/p&gt;
&lt;p&gt;None of this is physically rigorous. I&#39;m not modelling anybody&#39;s actual fridge. It just has to be convincing enough that whatever you point at it can&#39;t tell it&#39;s talking to a fake.&lt;/p&gt;
&lt;h2&gt;One meter, two faces&lt;/h2&gt;
&lt;p&gt;This is the part where the BEAM quietly shows off.&lt;/p&gt;
&lt;p&gt;There&#39;s one meter, generating one stream of telegrams, but two completely different crowds want it: people running &lt;code&gt;nc&lt;/code&gt; in a terminal, and people watching a LiveView in a browser. Rather than wire the generator up to either of them, it just broadcasts over &lt;code&gt;Phoenix.PubSub&lt;/code&gt; and gets on with its life:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;elixir&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;Phoenix&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;PubSub&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;broadcast&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;LiveMeter&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;PubSub&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;smart_meter:telegrams&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  {:smart_meter_telegram&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; telegram&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; telegram_string}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Everything else subscribes. The TCP server subscribes and fans each telegram out to its sockets. Every open web page subscribes and re-renders. The generator has no idea any of them exist. If I want a third audience tomorrow, I add a subscriber and nothing upstream has to change.&lt;/p&gt;
&lt;p&gt;The TCP side runs on the same instinct. Each new connection gets handed its own little &lt;code&gt;GenServer&lt;/code&gt;, monitored so a dropped socket tidies up after itself. In most runtimes a process per client would raise an eyebrow. Here it&#39;s just how you&#39;d write it.&lt;/p&gt;
&lt;h2&gt;Pretending to be slow hardware&lt;/h2&gt;
&lt;p&gt;Here&#39;s a detail nobody asked for, which is precisely why it&#39;s my favourite.&lt;/p&gt;
&lt;p&gt;A real P1 port doesn&#39;t hand you a telegram all in one go. It&#39;s a serial line, dribbling bytes out at 115200 baud, and firing the whole packet down the socket in a single &lt;code&gt;send&lt;/code&gt; felt like cheating. So each line sleeps for roughly as long as the wire would have taken to carry it:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;elixir&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;defp&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;sleep&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;({:baud&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; baud_rate}&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; line) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;do&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  line&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;|&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;byte_size&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;|&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;Kernel&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;10_000&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)      &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;# about 10 bits per byte, in tenths of a millisecond&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;|&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;ceil_div&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(baud_rate)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;|&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;max&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;|&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;Process&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;sleep&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;It&#39;s a tiny, faintly ridiculous touch. But it means a client reading the stream watches the telegram arrive line by line, exactly the way it would off real hardware. That is the kind of thing that flushes out a bug in how you buffer input.&lt;/p&gt;
&lt;h2&gt;The star: a fake meter in pure LiveView&lt;/h2&gt;
&lt;p&gt;The TCP stream is the genuinely useful bit. But the part I had no business enjoying as much as I did is the one you can see.&lt;/p&gt;
&lt;p&gt;It&#39;s a smart meter. A skeuomorphic one: CRT-green display, faint scanlines, little tariff lights, an LED blinking away at &amp;quot;1000 imp/kWh&amp;quot;, an SVG barcode on the type plate, even a chunky physical-looking button. All of it rendered server-side in LiveView, updating every second, without a single line of custom JavaScript.&lt;/p&gt;
&lt;p&gt;It&#39;s built entirely from HEEx function components, and it&#39;s fed by the very same PubSub message the &lt;code&gt;nc&lt;/code&gt; crowd gets. When one lands, the LiveView turns the struct into display rows and re-renders:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;elixir&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;handle_info&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;({:smart_meter_telegram&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; telegram&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;_string&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; socket) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;do&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  {:noreply&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;assign_snapshot&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(socket&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;Readings&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;from_telegram&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(telegram))}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;LiveView works out what actually changed and pushes only that down the socket. The numbers climb, the tariff light switches over at night, the little connection dot goes green. The only client-side code anywhere near it is the LiveView runtime everyone already ships.&lt;/p&gt;
&lt;p&gt;My favourite piece is the button. A real meter has one button to cycle through its readings, so naturally mine does too. It is just a &lt;code&gt;phx-click&lt;/code&gt; bumping an index in the server-side state:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;html&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;button&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;phx-click&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;cycle_reading&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;aria-label&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;Cycle through meter readings&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;There is no client state or fetch layer to keep in sync. You press a styled &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;, the server moves to the next reading and sends back the difference. Building hardware-shaped UI in something this plain turned out to be an absurd amount of fun. The skeuomorphism is all CSS and SVG, and the behaviour is all server.&lt;/p&gt;
&lt;h2&gt;Try it&lt;/h2&gt;
&lt;p&gt;It&#39;s live, and the quickest way in is your terminal:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: undefined&quot;&gt;$ nc p1meter.dev 8080&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;A fresh DSMR 5.0 telegram every second, the same bytes a real P1 reader would hand you, checksum and all. Or open &lt;a href=&quot;https://p1meter.dev/&quot;&gt;p1meter.dev&lt;/a&gt; and watch the meter tick, with copy-paste client examples for Node, Python and Go sitting right underneath.&lt;/p&gt;
&lt;p&gt;It&#39;s intentionally small, and it&#39;s all open source over on &lt;a href=&quot;https://github.com/mijnverbruik/p1meter.dev&quot;&gt;GitHub&lt;/a&gt;. I&#39;ve been collecting ideas for where to take it next: realistic daily patterns, configurable scenarios, the kind of nasty edge cases that exist purely to break parsers.&lt;/p&gt;
&lt;p&gt;What&#39;s stuck with me, building this, is how little of it is really code I &lt;em&gt;wrote&lt;/em&gt;. The parser was already there; I just ran it backwards. PubSub did the fan-out, OTP supervised the clients, LiveView did the live UI. My actual job came down to deciding what a believable meter should do, then getting out of the runtime&#39;s way.&lt;/p&gt;
&lt;p&gt;So if you&#39;re working on anything P1- or DSMR-shaped, point it at the simulator and tell me what breaks. That&#39;s half the reason it&#39;s out there.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Why a Dutch postcode regex is not enough</title>
    <link href="https://robinvdvleuten.nl/post/why-a-dutch-postcode-regex-is-not-enough/"/>
    <updated>2026-02-24T00:00:00Z</updated>
    <id>https://robinvdvleuten.nl/post/why-a-dutch-postcode-regex-is-not-enough/</id>
    <summary>A Dutch postcode regex catches the shape of a postcode, but Addressing gives your Ruby app the country-aware rules around it.</summary>
    <content type="html">&lt;p&gt;You search for &amp;quot;regex postcode Nederland&amp;quot; because you need the small answer.&lt;/p&gt;
&lt;p&gt;A Dutch postcode is four digits and two letters. Maybe there is a space in the middle, maybe there isn&#39;t. You do not want to pull in a library just to check &lt;code&gt;1012 AB&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So you write the obvious pattern:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;ruby&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;DUTCH_POSTCODE&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;/\A[1-9][0-9]&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;{3}&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;\s?[A-Z]&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;{2}&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;\z/i&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;DUTCH_POSTCODE&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;match?(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;1012 AB&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;# true&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;DUTCH_POSTCODE&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;match?(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;1012AB&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;# true&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;DUTCH_POSTCODE&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;match?(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;0123 AB&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;# false&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;For that one narrow question, this is fine.&lt;/p&gt;
&lt;p&gt;A regular expression can tell you whether a string looks like a Dutch postcode. The trouble starts when that tiny check slowly turns into &amp;quot;address validation.&amp;quot;&lt;/p&gt;
&lt;h2&gt;A postcode regex validates syntax&lt;/h2&gt;
&lt;p&gt;The common Dutch postcode pattern is not wrong. If you search for a Dutch postcode regex, you will usually find something close to this:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;ruby&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;\&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;A&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;9&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;][&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;9&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;]{&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}\s?[&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;A&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Z&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;]{&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}\z&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;It checks a shape:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the first digit is not zero&lt;/li&gt;
&lt;li&gt;then three more digits&lt;/li&gt;
&lt;li&gt;then an optional space&lt;/li&gt;
&lt;li&gt;then two letters&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Useful. Also very limited.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;9999 ZZ&lt;/code&gt; has the right shape. That does not mean a parcel can be delivered there. It does not tell you whether the postcode belongs to the city the user entered, or whether the street and house number make sense together.&lt;/p&gt;
&lt;p&gt;It says even less about the rest of the address: which fields are required, what the postal code is called, whether the country uses postal codes at all, and how the address should be printed later.&lt;/p&gt;
&lt;h2&gt;The address is bigger than the postcode&lt;/h2&gt;
&lt;p&gt;The first version usually starts with one country.&lt;/p&gt;
&lt;p&gt;For the Netherlands, you add a postcode regex. Then you add a street-and-house-number regex. Then a customer enters &lt;code&gt;1e Jacob van Campenstraat 10&lt;/code&gt;, &lt;code&gt;Burg. van der Werffstraat 1&lt;/code&gt;, or an address line with a suffix you did not think about.&lt;/p&gt;
&lt;p&gt;So the regex grows.&lt;/p&gt;
&lt;p&gt;Then the app opens up to Belgium. Germany. The United Kingdom. Ireland, where &amp;quot;county&amp;quot; might be the label. Japan, where the fields appear in a different order. Countries without postal codes at all.&lt;/p&gt;
&lt;p&gt;There is no global postal code regex hiding somewhere that makes this go away. Countries use different lengths, characters, separators, and rules. Even country-specific regexes only answer the pattern question. They do not give you the address format around it.&lt;/p&gt;
&lt;h2&gt;Use address data instead&lt;/h2&gt;
&lt;p&gt;This is why I built &lt;a href=&quot;https://github.com/robinvdvleuten/addressing&quot;&gt;Addressing&lt;/a&gt;, a Ruby gem for country-aware address formats, labels, formatting, and validation.&lt;/p&gt;
&lt;p&gt;I wrote about the bigger picture in &lt;a href=&quot;https://robinvdvleuten.nl/post/handle-international-addresses-in-ruby/&quot;&gt;Handle international addresses in Ruby&lt;/a&gt;. This post is the smaller version of the argument: if your validation starts with a postcode regex, the next step is probably not a bigger regex. It is address data.&lt;/p&gt;
&lt;p&gt;Addressing knows the Dutch postal code pattern, but it keeps that pattern inside the address format for &lt;code&gt;NL&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;ruby&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;format&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Addressing&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;AddressFormat&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;get(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;NL&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;format&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;required_fields&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;# =&amp;gt; [:address_line1, :postal_code, :locality]&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;format&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;label_for(:postal_code)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;# =&amp;gt; &quot;Postal code&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;format&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;postal_code_pattern&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;# =&amp;gt; &quot;\\d{4}\\s?[A-Z]{2}&quot;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Now the postcode rule lives next to the rest of the country-specific rules. Your application asks one object which fields matter, which labels to show, and which postal code pattern to apply.&lt;/p&gt;
&lt;p&gt;That boundary has saved me from scattering regex constants through forms, models, and import scripts. The pattern still exists. It just no longer pretends to be the whole address system.&lt;/p&gt;
&lt;h2&gt;Validation in Rails&lt;/h2&gt;
&lt;p&gt;If you store addresses on an Active Record model, the validator is deliberately boring:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;ruby&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;CustomerAddress&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;ApplicationRecord&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  validates_address_format&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;The validator uses the country code on the address and applies the matching format rules. A Dutch address gets Dutch required fields and the Dutch postal code pattern. A US address gets a ZIP code pattern.&lt;/p&gt;
&lt;p&gt;And a country without postal codes should not be forced through a postcode field because your form happened to start in the Netherlands.&lt;/p&gt;
&lt;p&gt;That is the useful shift. You are no longer asking, &amp;quot;Does this string match the pattern I remembered to copy?&amp;quot; You are asking, &amp;quot;Does this address match the rules for its country?&amp;quot;&lt;/p&gt;
&lt;h2&gt;What it does not solve&lt;/h2&gt;
&lt;p&gt;Addressing is not a postal authority and it is not an address verification service.&lt;/p&gt;
&lt;p&gt;It can validate the format. It can tell you which fields a country uses. It can format the address for display or postal labels. It keeps the country-specific details close to one API.&lt;/p&gt;
&lt;p&gt;It cannot promise that a postcode exists today, that a house number is deliverable, or that the resident still lives there. For that you need a postal database, carrier API, geocoding service, or country-specific lookup.&lt;/p&gt;
&lt;p&gt;I like keeping those jobs separate:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;use a regex for fast feedback on one field&lt;/li&gt;
&lt;li&gt;use Addressing for the country-aware rules around the address&lt;/li&gt;
&lt;li&gt;use a real verification source when failed delivery costs money&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Do not make one regex pretend to do all three jobs.&lt;/p&gt;
&lt;h2&gt;A better default&lt;/h2&gt;
&lt;p&gt;The Dutch postcode regex is not the problem. The problem is using it as the place where address knowledge accumulates.&lt;/p&gt;
&lt;p&gt;That is how you end up with a pattern nobody wants to touch, copied across projects, rejecting valid customers and accepting fake addresses with the confidence of a green test suite.&lt;/p&gt;
&lt;p&gt;Start with the country instead. Let the country pick the address format. Let the postcode rule be one small part of that format.&lt;/p&gt;
&lt;p&gt;If you are building this in Ruby, try &lt;a href=&quot;https://github.com/robinvdvleuten/addressing&quot;&gt;Addressing on GitHub&lt;/a&gt; and add it to your Gemfile:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;ruby&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;gem &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;addressing&quot;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;A regex can tell you whether &lt;code&gt;1012 AB&lt;/code&gt; looks like a Dutch postcode.&lt;/p&gt;
&lt;p&gt;For address validation, I want the rest of the rules nearby too.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Why my Ruby gem ships address formats twice</title>
    <link href="https://robinvdvleuten.nl/post/why-my-ruby-gem-ships-address-formats-twice/"/>
    <updated>2025-11-10T00:00:00Z</updated>
    <id>https://robinvdvleuten.nl/post/why-my-ruby-gem-ships-address-formats-twice/</id>
    <summary>Addressing keeps JSON for review, then builds a Marshal dump so Ruby can load static address formats without parsing the source file every boot.</summary>
    <content type="html">&lt;p&gt;I already wrote about why &lt;a href=&quot;https://robinvdvleuten.nl/post/handle-international-addresses-in-ruby/&quot;&gt;international address handling gets messy in Ruby&lt;/a&gt;. That post is about forms, labels, postal rules, and the public API around all of it.&lt;/p&gt;
&lt;p&gt;This one is smaller.&lt;/p&gt;
&lt;p&gt;It is about why &lt;a href=&quot;https://github.com/robinvdvleuten/addressing&quot;&gt;Addressing&lt;/a&gt; has both of these files:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;text&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: undefined&quot;&gt;data/address_formats.json&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: undefined&quot;&gt;data/address_formats.dump&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;At first that looks wasteful. Why ship the same address formats twice?&lt;/p&gt;
&lt;p&gt;Because the two files have different jobs.&lt;/p&gt;
&lt;h2&gt;The obvious version: JSON&lt;/h2&gt;
&lt;p&gt;The JSON file is the source of truth.&lt;/p&gt;
&lt;p&gt;Not because JSON is exciting. It isn&#39;t. That is the useful part.&lt;/p&gt;
&lt;p&gt;Address formats are data, and I want to review them as data. When a format changes, the diff should show the country code, the fields, the required values, and the postal code pattern.&lt;/p&gt;
&lt;p&gt;I do not want to review generated Ruby full of escaping, nested hashes, and noise.&lt;/p&gt;
&lt;p&gt;A simplified entry looks roughly like this:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;json&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;&quot;country_code&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;NL&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;&quot;format&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;%given_name %family_name\n%organization\n%address_line1\n%postal_code %locality&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;&quot;required_fields&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;address_line1&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;postal_code&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;locality&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;&quot;uppercase_fields&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;locality&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;&quot;postal_code_pattern&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;\\d{4}\\s?[A-Z]{2}&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;That is the kind of thing I want in Git. You can open it, search it, and compare it with the upstream source. If a country needs a tweak, the change is boring in the best possible way.&lt;/p&gt;
&lt;p&gt;JSON has a cost at runtime. Every process that needs address formats has to read the file, parse it, build Ruby objects, normalize keys, and then finally answer the actual question the application asked.&lt;/p&gt;
&lt;p&gt;For one request, who cares.&lt;/p&gt;
&lt;p&gt;For a gem that gets loaded during application boot, tests, background jobs, consoles, and one-off scripts, that repeated setup starts to feel wasteful. The data is static anyway. It only changes when the gem changes.&lt;/p&gt;
&lt;p&gt;I don&#39;t need to parse the same address definitions again and again.&lt;/p&gt;
&lt;h2&gt;The file Ruby actually loads&lt;/h2&gt;
&lt;p&gt;At runtime, Addressing reads the dump:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;ruby&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;definitions&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  @definitions &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;||=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Marshal&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;load&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;File&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;read(&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;File&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;expand_path(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;../../../data/address_formats.dump&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; __FILE__)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;to_s&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    )&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  )&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Marshal&lt;/code&gt; is Ruby&#39;s built-in object serialization format. Give it Ruby objects with &lt;code&gt;Marshal.dump&lt;/code&gt;, and it writes bytes. Read those bytes with &lt;code&gt;Marshal.load&lt;/code&gt;, and you get Ruby objects back.&lt;/p&gt;
&lt;p&gt;That is the whole trick. JSON parsing happens when the dump is generated, not every time an application boots.&lt;/p&gt;
&lt;p&gt;The dump is still generated from the readable source. It is not a second source of truth.&lt;/p&gt;
&lt;p&gt;Once the definitions are loaded, the gem also memoizes the &lt;code&gt;AddressFormat&lt;/code&gt; objects it builds from them:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;ruby&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-parameter)&quot;&gt;country_code&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  country_code &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; country_code&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;upcase&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  @address_formats &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;||=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;unless&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; @address_formats&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;key?(country_code)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    definition &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; process_definition(&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      definitions[country_code] &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; { country_code: country_code }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    )&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    @address_formats[country_code] &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(definition)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  @address_formats[country_code]&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;So there are two layers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;definitions&lt;/code&gt; caches the raw format definitions loaded from disk.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@address_formats&lt;/code&gt; caches the actual &lt;code&gt;AddressFormat&lt;/code&gt; objects by country code.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The common path stays small:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ask for &lt;code&gt;NL&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;get the Dutch format&lt;/li&gt;
&lt;li&gt;ask again&lt;/li&gt;
&lt;li&gt;get the same object back&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;No JSON parsing, repeated normalization, or walking through a big file line by line.&lt;/p&gt;
&lt;h2&gt;Where the dump comes from&lt;/h2&gt;
&lt;p&gt;The dump file is generated. Nobody edits it by hand, because that would be miserable and pointless.&lt;/p&gt;
&lt;p&gt;The Rake task is deliberately plain:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;ruby&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;namespace :addressing &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;do&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  task dump: :generate &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;do&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;json&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    root_dir &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;File&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;expand_path(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;../..&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; __FILE__)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    definitions &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;File&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;readlines&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;#{root_dir}&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;/data/address_formats.json&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;each &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;line&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;|&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      definition &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;JSON&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;parse(line&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; symbolize_names: &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      definitions[definition[:country_code]] &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; definition&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;File&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;open&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;#{root_dir}&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;/data/address_formats.dump&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;w&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;f&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;|&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Marshal&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;dump definitions&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; f&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Read each JSON line, parse it into a Ruby hash, index it by country code, and let &lt;code&gt;Marshal.dump&lt;/code&gt; write the result.&lt;/p&gt;
&lt;p&gt;There is no separate schema language hiding in the middle, no custom compiler, and no clever code generation step that I will forget how to debug six months later.&lt;/p&gt;
&lt;p&gt;That matters because generated files tend to become suspicious over time. If the generated file changes, I can rebuild it from the source data. If the source data changes, I can review the JSON and ignore the binary blob as an artifact.&lt;/p&gt;
&lt;h2&gt;Why not generate Ruby?&lt;/h2&gt;
&lt;p&gt;I did consider the obvious alternative: generate Ruby.&lt;/p&gt;
&lt;p&gt;Something like:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;ruby&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;ADDRESS_FORMATS&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;NL&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    format: &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;...&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    required_fields: [&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;]&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;That would avoid JSON parsing too. Ruby could load it like any other file.&lt;/p&gt;
&lt;p&gt;I don&#39;t like it here.&lt;/p&gt;
&lt;p&gt;Large generated constants are noisy to review, easy to format into chaos, and not especially pleasant to diff when a lot of data changes at once.&lt;/p&gt;
&lt;p&gt;More importantly, generated Ruby blurs the line between code and data. Address formats are definitions. I want the behavior in Ruby and the definitions in a data file.&lt;/p&gt;
&lt;p&gt;The Marshal dump is an implementation detail. It is not the thing you edit.&lt;/p&gt;
&lt;h2&gt;Marshal is not for strangers&lt;/h2&gt;
&lt;p&gt;There is one big warning with &lt;code&gt;Marshal&lt;/code&gt;: don&#39;t load untrusted data.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Marshal.load&lt;/code&gt; can instantiate Ruby objects. That makes it the wrong tool for user-provided files, uploads, API responses, or anything that came from outside your trust boundary.&lt;/p&gt;
&lt;p&gt;Addressing does something narrower.&lt;/p&gt;
&lt;p&gt;The dump file is generated as part of the gem&#39;s own data pipeline and shipped with the gem. Applications don&#39;t upload it. Users don&#39;t edit it. The library loads its own packaged data from its own gem.&lt;/p&gt;
&lt;p&gt;The distinction matters. &lt;code&gt;Marshal&lt;/code&gt; is a bad interchange format. It is Ruby-specific, opaque, and unsafe for untrusted input.&lt;/p&gt;
&lt;p&gt;As a private cache for static Ruby data inside a gem, it is a much narrower tool. That is the only reason I am comfortable using it here.&lt;/p&gt;
&lt;h2&gt;The boundary&lt;/h2&gt;
&lt;p&gt;The split only works because the boundary stays strict:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;edit JSON&lt;/li&gt;
&lt;li&gt;generate Marshal&lt;/li&gt;
&lt;li&gt;load Marshal&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If the cache ever becomes the source of truth, the whole thing gets worse. Then you have a binary file nobody can review and a maintenance process nobody wants to touch.&lt;/p&gt;
&lt;p&gt;So I try to keep the roles boring.&lt;/p&gt;
&lt;p&gt;JSON is for people. Marshal is for Ruby. The generated file can always be rebuilt.&lt;/p&gt;
&lt;p&gt;That is enough structure.&lt;/p&gt;
&lt;h2&gt;The useful lesson&lt;/h2&gt;
&lt;p&gt;I like this pattern more than I expected because it avoids a false choice.&lt;/p&gt;
&lt;p&gt;You can keep source data nice to maintain and runtime data cheap to load, as long as one is clearly generated from the other.&lt;/p&gt;
&lt;p&gt;It is not an architecture I would reach for everywhere. Most data files should probably stay as data files. Most caches do not need to ship with your package.&lt;/p&gt;
&lt;p&gt;But when the data is static, trusted, and expensive enough to shape repeatedly, this is a useful little trick.&lt;/p&gt;
&lt;p&gt;Keep the readable thing readable. Keep the fast thing generated.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>How Erlang&#39;s parser tools saved my DSMR library</title>
    <link href="https://robinvdvleuten.nl/post/how-erlangs-parser-tools-saved-my-dsmr-library/"/>
    <updated>2025-10-06T00:00:00Z</updated>
    <id>https://robinvdvleuten.nl/post/how-erlangs-parser-tools-saved-my-dsmr-library/</id>
    <summary>How leex and yecc helped me replace regex-heavy DSMR telegram parsing with a maintainable parser.</summary>
    <content type="html">&lt;p&gt;I&#39;ve always been the type of developer who starts with the simplest solution.&lt;/p&gt;
&lt;p&gt;&amp;quot;I&#39;ll just use a few regex patterns here&amp;quot; is a sentence that ages badly. Before long I had a tangled mess of string operations that technically worked, until it didn&#39;t. That is exactly what happened with my DSMR library.&lt;/p&gt;
&lt;p&gt;Since I started working with smart meter data, I&#39;ve been fascinated by the DSMR protocol (Dutch Smart Meter Requirements) used across the Netherlands, Belgium, and Luxembourg. These meters broadcast &amp;quot;telegrams&amp;quot; every few seconds: structured data packets that look something like this:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: undefined&quot;&gt;/KFM5KAIFA-METER&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: undefined&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: undefined&quot;&gt;1-3:0.2.8(42)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: undefined&quot;&gt;0-0:1.0.0(161113205757W)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: undefined&quot;&gt;1-0:1.8.1(001581.123*kWh)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: undefined&quot;&gt;1-0:2.8.1(000000.000*kWh)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: undefined&quot;&gt;1-0:21.7.0(00.170*kW)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: undefined&quot;&gt;0-1:24.2.1(161129200000W)(00981.443*m3)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: undefined&quot;&gt;!6796&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;My first approach was a chaotic collection of regex patterns and string splits that I somehow convinced myself was maintainable. It worked until edge cases started showing up.&lt;/p&gt;
&lt;p&gt;After battling mysterious parsing failures, I remembered something obvious: people have been solving parsing problems for decades. That&#39;s when I found Erlang&#39;s lexical analyzer, leex, and parser generator, yecc, hiding in plain sight.&lt;/p&gt;
&lt;h2&gt;Parser generators&lt;/h2&gt;
&lt;p&gt;Instead of writing imperative code that says &amp;quot;read character by character and if you see this pattern do that,&amp;quot; these tools let you describe what valid input looks like. They handle the scanning and parsing.&lt;/p&gt;
&lt;p&gt;The lexer&#39;s job is breaking the raw text into meaningful tokens. In my &lt;code&gt;src/dsmr_lexer.xrl&lt;/code&gt; file, I defined patterns using regular expressions:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;erlang&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;% OBIS code pattern&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{DIGIT}&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{DIGIT}:{DIGIT}\.{DIGIT}\.{DIGIT} : &lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    {token&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {obis&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; TokenLine&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;extract_obis_code(TokenChars)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}}.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;% Timestamp&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{DIGIT}{&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;12&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}[SW] : &lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    {token&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {timestamp&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; TokenLine&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;extract_timestamp(TokenChars)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}}.&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;What I like about this approach is the clarity. No nested conditionals checking characters one by one. I describe the shape of valid tokens, and the lexer handles the scanning. The syntax took some getting used to, but the trade-off was worth it.&lt;/p&gt;
&lt;p&gt;Once the lexer turns raw text into tokens, the parser needs to understand how those tokens fit together. That&#39;s where yecc comes in. In my &lt;code&gt;src/dsmr_parser.yrl&lt;/code&gt;, I defined grammar rules:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;erlang&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;telegram &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; header lines checksum : &lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;build_telegram&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&#39;$1&#39;, &#39;$2&#39;, &#39;$3&#39;).&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;line &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; obis attributes : &lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;map_obis_to_field&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&#39;$1&#39;, &#39;$2&#39;).&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;attributes &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; attribute attributes : [&#39;$1&#39; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &#39;$2&#39;].&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;attributes &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; attribute : [&#39;$1&#39;].&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;These rules describe how a valid DSMR telegram is structured. The parser walks through the token stream, applies these rules, and builds up the final data structure.&lt;/p&gt;
&lt;h2&gt;Making it friendlier&lt;/h2&gt;
&lt;p&gt;One thing that was important to me was making the library intuitive. OBIS codes like &lt;code&gt;1-0:1.8.1&lt;/code&gt; are standardized but not exactly human-readable. I wanted developers using my library to work with friendly field names like &lt;code&gt;:electricity_delivered_1&lt;/code&gt; instead.&lt;/p&gt;
&lt;p&gt;I centralized all the mappings in a dedicated module:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;elixir&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;defmodule&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;DSMR&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;OBIS&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;do&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;to_field_name&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;([&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;8&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;])&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; do: :electricity_delivered_1&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;to_field_name&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;([&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;8&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;])&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; do: :electricity_returned_1&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;to_field_name&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;([&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;24&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;])&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; do: :gas_consumption&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;# ...and many more&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;OBIS codes that are not in the mapping table, maybe proprietary extensions or newer codes I have not seen yet, get collected in an &lt;code&gt;unknown_fields&lt;/code&gt; list instead of causing a parse error. That matters in real usage because meters sometimes report unexpected things.&lt;/p&gt;
&lt;h2&gt;Why this approach won me over&lt;/h2&gt;
&lt;p&gt;Looking back, leex and yecc helped because they forced the parser into separate jobs.&lt;/p&gt;
&lt;p&gt;When I need to support a new DSMR version or add fields, I update a pattern in the &lt;code&gt;.xrl&lt;/code&gt; file and add a grammar rule. I do not have to hunt through nested conditionals or string operations.&lt;/p&gt;
&lt;p&gt;yecc gives me line numbers when something goes wrong. That is much better than a cryptic failure somewhere in a chain of string operations.&lt;/p&gt;
&lt;p&gt;The generated state machines are fast enough for batches of telegrams, but the bigger win is that each piece has a clear responsibility. The lexer handles what tokens look like. The parser handles how they fit together. The OBIS module handles what they mean.&lt;/p&gt;
&lt;p&gt;There is something satisfying about using tools that have been around since the 1970s. They are not exciting or new, but they work. Sometimes that is exactly what I want.&lt;/p&gt;
&lt;h2&gt;The result&lt;/h2&gt;
&lt;p&gt;The DSMR library is now open source at &lt;a href=&quot;https://github.com/mijnverbruik/dsmr&quot;&gt;github.com/mijnverbruik/dsmr&lt;/a&gt; and it cleanly parses telegrams from old and new model smart meters. It handles multiple versions, three-phase connections, various MBus devices, and all sorts of edge cases I didn&#39;t anticipate at the start.&lt;/p&gt;
&lt;p&gt;The biggest lesson for me was: don&#39;t reinvent wheels, especially old ones. Parser generators exist because parsing is genuinely hard. I could have spent weeks debugging edge cases in a hand-written parser. Instead, I spent time learning leex and yecc, and ended up with something more maintainable.&lt;/p&gt;
&lt;p&gt;It also reminded me that the BEAM ecosystem has a lot of sturdy tools that Elixir developers sometimes overlook. Erlang&#39;s standard library is full of things like this: not always well documented, but solid.&lt;/p&gt;
&lt;p&gt;Should I use leex and yecc for every parsing problem? Probably not. But for protocols like DSMR, where the grammar is complex and well defined, they have been the right tools.&lt;/p&gt;
&lt;p&gt;It is worth trying older tools when they fit the problem, even if they do not have the shiniest docs or the newest GitHub stars.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Handle international addresses in Ruby</title>
    <link href="https://robinvdvleuten.nl/post/handle-international-addresses-in-ruby/"/>
    <updated>2025-10-03T00:00:00Z</updated>
    <id>https://robinvdvleuten.nl/post/handle-international-addresses-in-ruby/</id>
    <summary>A Ruby gem for international address formats, country-specific fields, postal labels, and Active Record validation.</summary>
    <content type="html">&lt;p&gt;You&#39;re building an e-commerce site, and a customer from Japan reaches checkout. Your form asks for a &amp;quot;State&amp;quot; and a &amp;quot;ZIP code.&amp;quot;&lt;/p&gt;
&lt;p&gt;Japan does not use that shape.&lt;/p&gt;
&lt;p&gt;Or maybe you&#39;re printing shipping labels and discover that the USPS wants certain fields in ALL CAPS for automated sorting, but you have no idea which fields or why.&lt;/p&gt;
&lt;p&gt;International address handling gets messy fast.&lt;/p&gt;
&lt;h2&gt;The problem nobody warns you about&lt;/h2&gt;
&lt;p&gt;A checkout form has to deal with details like these:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The UK calls it a &amp;quot;postcode.&amp;quot; Americans call it a &amp;quot;ZIP code.&amp;quot; Canadians call it a &amp;quot;postal code.&amp;quot;&lt;/li&gt;
&lt;li&gt;Japanese addresses go from largest to smallest (the complete opposite of Western addresses)&lt;/li&gt;
&lt;li&gt;Some countries don&#39;t have postal codes at all&lt;/li&gt;
&lt;li&gt;Ireland uses &amp;quot;County&amp;quot; but the US uses &amp;quot;State&amp;quot; and Canada uses &amp;quot;Province&amp;quot; (or sometimes &amp;quot;Territory&amp;quot;)&lt;/li&gt;
&lt;li&gt;For automated mail sorting, you need to uppercase specific fields in specific countries&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can spend weeks researching postal systems across hundreds of countries. I would rather use a library that already packages that data.&lt;/p&gt;
&lt;p&gt;That is why I built &lt;strong&gt;&lt;a href=&quot;https://github.com/robinvdvleuten/addressing&quot;&gt;Addressing&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;What it does&lt;/h2&gt;
&lt;p&gt;Addressing is a Ruby gem for postal address details across countries:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;250+ country definitions with translations powered by CLDR data&lt;/li&gt;
&lt;li&gt;200+ address formats, so you know which fields to show and in what order&lt;/li&gt;
&lt;li&gt;subdivisions for 60 countries: states, provinces, prefectures, and whatever they call them locally&lt;/li&gt;
&lt;li&gt;formatting in HTML and plain text&lt;/li&gt;
&lt;li&gt;Active Record validation&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Your first address&lt;/h2&gt;
&lt;p&gt;A simple address looks like this:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;ruby&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;address &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Addressing&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Address&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  country_code: &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;US&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  administrative_area: &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;CA&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  locality: &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;Mountain View&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  address_line1: &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;1600 Amphitheatre Parkway&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  given_name: &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;Sundar&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  family_name: &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;Pichai&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;formatter &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Addressing&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;DefaultFormatter&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;new&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;puts&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; formatter&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;format&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(address)&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;That outputs HTML with the fields in the right order for the US, plus semantic CSS classes. The country name is localized automatically.&lt;/p&gt;
&lt;p&gt;You do not have to guess the field order or hardcode it into the form.&lt;/p&gt;
&lt;h2&gt;Real-world examples&lt;/h2&gt;
&lt;h3&gt;Building a checkout form that works everywhere&lt;/h3&gt;
&lt;p&gt;Different countries need different fields. Nobody in Japan needs a &amp;quot;State&amp;quot; dropdown, but they absolutely need a &amp;quot;Prefecture&amp;quot; field:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;ruby&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;# Get the address format for Japan&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;format&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Addressing&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;AddressFormat&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;get(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;JP&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;# Build your form dynamically&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;format&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;used_fields&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;each &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;field&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;|&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;puts&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;#{&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;format&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;label_for(field)}&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;: [input field]&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Your Japanese customers see the fields they expect. Your UK customers don&#39;t see irrelevant fields. That is the important part: the form matches the country.&lt;/p&gt;
&lt;h3&gt;Rails validation&lt;/h3&gt;
&lt;p&gt;Your model needs to accept addresses from anywhere:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;ruby&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;Order&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;ApplicationRecord&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  validates_address_format&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Postal codes get validated against country-specific patterns. Required fields get enforced. Customers can complete checkout without fighting a form built for another country.&lt;/p&gt;
&lt;h3&gt;Printing shipping labels&lt;/h3&gt;
&lt;p&gt;You&#39;re printing labels for international shipments. Some countries require specific fields in ALL CAPS for automated sorting:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;ruby&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;address &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Addressing&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Address&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  country_code: &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;US&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  administrative_area: &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;CA&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  locality: &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;Mountain View&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  address_line1: &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;1098 Alta Ave&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;formatter &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Addressing&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;PostalLabelFormatter&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(origin_country: &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;FR&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;puts&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; formatter&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;format&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(address)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;# Output:&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;# 1098 Alta Ave&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;# MOUNTAIN VIEW, CA 94043&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;# ÉTATS-UNIS - UNITED STATES&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Notice what happened:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;City and state got uppercased because USPS expects that.&lt;/li&gt;
&lt;li&gt;The country name appears in both French and English, following the Universal Postal Union recommendation.&lt;/li&gt;
&lt;li&gt;The label is formatted for international mail from France.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The formatter handles the postal label rules for you.&lt;/p&gt;
&lt;h3&gt;Country selectors in any language&lt;/h3&gt;
&lt;p&gt;Need a dropdown in the user&#39;s language?&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;ruby&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;# Get all countries in Japanese&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;countries &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Addressing&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Country&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;list(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;ja-JP&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;# =&amp;gt; { &quot;JP&quot; =&amp;gt; &quot;日本&quot;, &quot;US&quot; =&amp;gt; &quot;アメリカ合衆国&quot;, ... }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;# Or get detailed info&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;brazil &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Addressing&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Country&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;get(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;BR&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;puts&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; brazil&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;currency_code   &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;# &quot;BRL&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;puts&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; brazil&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;timezones       &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;# All timezones Brazil spans&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;French users see &amp;quot;États-Unis.&amp;quot; Japanese users see &amp;quot;アメリカ合衆国.&amp;quot; The labels come from the locale data.&lt;/p&gt;
&lt;h3&gt;Cascading location dropdowns&lt;/h3&gt;
&lt;p&gt;For forms where selecting a country shows states, then cities:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;ruby&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;# Get all states in Brazil&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;states &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Addressing&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Subdivision&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;all([&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;BR&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;])&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;# User selected Ceará? Get municipalities&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;municipalities &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Addressing&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Subdivision&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;all([&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;BR&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;CE&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;])&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;# Traverse the hierarchy&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;states&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;each &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;state&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;|&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  state&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;children&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;each &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;municipality&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;|&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;puts&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;#{state&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;name}&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt; - &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;#{municipality&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;name}&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;The data is hierarchical and translated, so the form can follow the selected country.&lt;/p&gt;
&lt;h2&gt;Why I would not roll this by hand&lt;/h2&gt;
&lt;p&gt;The gem builds on Google&#39;s Address Data Service and the CLDR project. One &lt;code&gt;bundle install&lt;/code&gt; gives you the country data, labels, formats, and subdivisions without turning your checkout into a postal research project.&lt;/p&gt;
&lt;p&gt;It also keeps the odd cases close to the code. Singapore uses six-digit postal codes. Russia uses Cyrillic addresses. Some places have no postal codes at all. Those are not details I want scattered through view helpers.&lt;/p&gt;
&lt;p&gt;Address formats change over time too. Countries split, subdivisions get renamed, and postal rules move. Tracking CLDR updates in one library is much easier than rediscovering those changes in every app.&lt;/p&gt;
&lt;p&gt;This Ruby version ports the PHP &lt;a href=&quot;https://github.com/commerceguys/addressing&quot;&gt;addressing library&lt;/a&gt; from CommerceGuys, which has already done this work for major e-commerce platforms.&lt;/p&gt;
&lt;h2&gt;When to use this&lt;/h2&gt;
&lt;p&gt;Use it when addresses are more than a text box:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;checkout flows where wrong addresses mean failed deliveries&lt;/li&gt;
&lt;li&gt;booking systems that collect international customer details&lt;/li&gt;
&lt;li&gt;multi-tenant SaaS where customers operate in different countries&lt;/li&gt;
&lt;li&gt;shipping tools that need postal labels to follow local rules&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Getting started&lt;/h2&gt;
&lt;p&gt;Check out &lt;a href=&quot;https://github.com/robinvdvleuten/addressing&quot;&gt;Addressing on GitHub&lt;/a&gt; for installation and complete documentation.&lt;/p&gt;
&lt;p&gt;Add it to your Gemfile, then wire up the validators and formatters where addresses enter or leave your application.&lt;/p&gt;
&lt;p&gt;Wrong addresses cost real money through failed shipments and frustrated customers. This gem turns a lot of postal research into an API you can call directly.&lt;/p&gt;
&lt;p&gt;Next time you are about to add a &amp;quot;State&amp;quot; field to an international form, check whether the selected country even has one.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>The tiny JSON parser that fixes your CSP headaches</title>
    <link href="https://robinvdvleuten.nl/post/tiny-json-parser-that-fixes-your-csp-headaches/"/>
    <updated>2025-09-18T00:00:00Z</updated>
    <id>https://robinvdvleuten.nl/post/tiny-json-parser-that-fixes-your-csp-headaches/</id>
    <summary>Pass server data to React without CSP violations by using safe JSON script tags instead of risky inline JavaScript.</summary>
    <content type="html">&lt;p&gt;You&#39;re building a React app with server-side rendering, and the client needs a little data before it can do anything useful: user preferences, API endpoints, feature flags, that sort of thing.&lt;/p&gt;
&lt;p&gt;The usual move is to put it on &lt;code&gt;window&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;html&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;window&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;APP_CONFIG&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    userId&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;12345&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    theme&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;dark&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    features&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;new-dashboard&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;beta-search&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;]&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  };&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;It works, until the security headers arrive.&lt;/p&gt;
&lt;h2&gt;The CSP problem nobody talks about&lt;/h2&gt;
&lt;p&gt;Ship that code with a strict &lt;a href=&quot;https://developers.google.com/web/fundamentals/security/csp/&quot;&gt;Content Security Policy&lt;/a&gt; and the browser refuses to run it:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: undefined&quot;&gt;Refused to execute inline script because it violates the following &lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: undefined&quot;&gt;Content Security Policy directive: &quot;script-src &#39;self&#39;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;The app boots without the data it expected. That is not a great failure mode.&lt;/p&gt;
&lt;p&gt;You could add &lt;code&gt;&#39;unsafe-inline&#39;&lt;/code&gt; to your CSP, but that gives up a lot of the protection you added the policy for. You could generate nonces for every request, but now caching gets harder and the setup gets more complicated.&lt;/p&gt;
&lt;h2&gt;Meet json-from-script&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/robinvdvleuten/json-from-script&quot;&gt;json-from-script&lt;/a&gt; keeps that data in JSON script tags instead of executable inline JavaScript. The browser does not run those tags as code, so a strict CSP can stay strict.&lt;/p&gt;
&lt;h2&gt;How to use it with plain HTML&lt;/h2&gt;
&lt;p&gt;Instead of executable JavaScript that triggers CSP violations, use &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type&quot;&gt;&lt;code&gt;&amp;lt;script type=&amp;quot;application/json&amp;quot;&amp;gt;&lt;/code&gt;&lt;/a&gt; tags. They do not execute code:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;html&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;!&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;DOCTYPE&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;html&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;html&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;head&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;title&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;My App&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;title&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;head&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;app&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;&amp;lt;!-- This is CSP-friendly because it doesn&#39;t execute --&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;application/json&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;data&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;data-attr&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;config&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    {&quot;userId&quot;:12345,&quot;theme&quot;:&quot;dark&quot;,&quot;features&quot;:[&quot;new-dashboard&quot;,&quot;beta-search&quot;]}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;application/json&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;data&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;data-attr&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;user&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    {&quot;name&quot;:&quot;Alice&quot;,&quot;email&quot;:&quot;alice@example.com&quot;,&quot;verified&quot;:true}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;src&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;/bundle.js&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;html&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;data-attr&lt;/code&gt; attribute names each JSON block. The library uses those names as keys in the final object, so &lt;code&gt;data-attr=&amp;quot;config&amp;quot;&lt;/code&gt; becomes the &lt;code&gt;config&lt;/code&gt; property.&lt;/p&gt;
&lt;h2&gt;Reading the data in React&lt;/h2&gt;
&lt;p&gt;In React, read the data once when the component mounts:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;jsx&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; React&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; { useState&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; useEffect } &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;react&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; jsonFromScript &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;json-from-script&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;App&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;appData&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;setAppData&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;useState&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;useEffect&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// This parses all JSON script tags and returns a single object&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;jsonFromScript&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;();&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;setAppData&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(data);&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  }&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; []);&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;appData) {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;Loading...&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;className&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;`app theme-&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;appData&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;config&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.theme&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;header&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;h1&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;Welcome, {&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;appData&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.name}!&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;h1&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        {&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;appData&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.verified &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;&amp;&amp;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;span&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;className&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;verified&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;✓ Verified&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;span&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;header&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;main&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        {&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;appData&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;config&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;features&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.includes&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;new-dashboard&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;          &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;NewDashboard&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;userId&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;appData&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;config&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.userId} /&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        ) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;          &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;LegacyDashboard&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;userId&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;appData&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;config&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.userId} /&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        )}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;main&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  );&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;jsonFromScript()&lt;/code&gt; call scans the DOM for all &lt;code&gt;&amp;lt;script class=&amp;quot;data&amp;quot;&amp;gt;&lt;/code&gt; tags, reads their JSON content, and creates a single object where each property name comes from the &lt;code&gt;data-attr&lt;/code&gt; value:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;js&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  config&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; { userId&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;12345&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; theme&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;dark&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; features&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;] }&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// from data-attr=&quot;config&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  user&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; { name&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;Alice&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; email&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;alice@example.com&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; verified&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; }  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// from data-attr=&quot;user&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h2&gt;Why this works&lt;/h2&gt;
&lt;p&gt;Those &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type&quot;&gt;&lt;code&gt;&amp;lt;script type=&amp;quot;application/json&amp;quot;&amp;gt;&lt;/code&gt;&lt;/a&gt; tags do not execute, so &lt;a href=&quot;https://developers.google.com/web/fundamentals/security/csp/#policy_applies_to_inline_code_too&quot;&gt;strict CSP policies&lt;/a&gt; leave them alone. You keep the data in the document without turning it into inline JavaScript.&lt;/p&gt;
&lt;p&gt;The caching story is calmer too. App logic can live in a normal bundled script, while server-rendered data stays with the HTML that needed it. You are not mixing configuration with executable code just to get it across the hydration boundary.&lt;/p&gt;
&lt;p&gt;When the JSON is wrong, you get the same kind of error you would get from &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse&quot;&gt;&lt;code&gt;JSON.parse()&lt;/code&gt;&lt;/a&gt;. That makes bad server output easy to catch during development:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;jsx&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;useEffect&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;try&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;jsonFromScript&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;();&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;setAppData&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(data);&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  } &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;catch&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (error) {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;console&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.error&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;Invalid JSON in script tag:&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; error);&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// Handle the error appropriately&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; []);&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h2&gt;Customizing the behavior&lt;/h2&gt;
&lt;p&gt;The default behavior looks for &lt;code&gt;script.data&lt;/code&gt; elements and uses &lt;code&gt;data-attr&lt;/code&gt; for property names. If your markup already has different conventions, pass a selector and attribute name:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;jsx&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// Look for different script tags with different attributes&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;jsonFromScript&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;script.app-config&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;data-key&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;This would parse HTML like:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;html&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;application/json&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;app-config&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;data-key&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;settings&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  {&quot;darkMode&quot;: true, &quot;language&quot;: &quot;en&quot;}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;And return:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;js&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  settings&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; { darkMode&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; language&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;en&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; }  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// &quot;settings&quot; comes from data-key=&quot;settings&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h2&gt;Next.js-style data loading&lt;/h2&gt;
&lt;p&gt;Here is how you might use this pattern in a blog application. First, the server renders data as JSON script tags:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;html&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;&amp;lt;!-- Server renders this --&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;application/json&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;data&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;data-attr&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;pageProps&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  {&amp;quot;posts&amp;quot;:[{&amp;quot;id&amp;quot;:1,&amp;quot;title&amp;quot;:&amp;quot;Hello World&amp;quot;,&amp;quot;excerpt&amp;quot;:&amp;quot;My first post&amp;quot;}],&amp;quot;currentUser&amp;quot;:{&amp;quot;id&amp;quot;:42,&amp;quot;name&amp;quot;:&amp;quot;Alice&amp;quot;}}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;application/json&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;data&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;data-attr&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;appConfig&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  {&amp;quot;apiUrl&amp;quot;:&amp;quot;https://api.example.com&amp;quot;,&amp;quot;version&amp;quot;:&amp;quot;2.1.0&amp;quot;,&amp;quot;features&amp;quot;:[&amp;quot;comments&amp;quot;,&amp;quot;sharing&amp;quot;]}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Then the React component parses all the data at once:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;jsx&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; React&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; { useMemo } &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;react&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; jsonFromScript &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;json-from-script&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;BlogPage&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;pageProps&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;appConfig&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;useMemo&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;jsonFromScript&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;();&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  }&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; []);&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;header&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;h1&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;My Blog&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;h1&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;v{&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;appConfig&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.version} | Welcome, {&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;pageProps&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;currentUser&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.name}!&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;header&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;main&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        {&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;pageProps&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;posts&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.map&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(post &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;          &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;article&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;post&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.id}&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;h2&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;{&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;post&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.title}&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;h2&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;{&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;post&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.excerpt}&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            {&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;appConfig&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;features&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.includes&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;sharing&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;&amp;&amp;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;              &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;Share this post&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            )}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;          &amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;article&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        ))}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;main&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  );&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h2&gt;When this approach shines&lt;/h2&gt;
&lt;p&gt;This pattern is useful anywhere the page already has the data and the client only needs to pick it up.&lt;/p&gt;
&lt;p&gt;For a widget embed, the host page can provide configuration next to the mount point:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;html&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;my-widget&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;application/json&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;data&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;data-attr&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;widget&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  {&quot;color&quot;:&quot;blue&quot;,&quot;size&quot;:&quot;large&quot;,&quot;showBorder&quot;:true}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;For progressive enhancement, the server-rendered page can stay in charge of the initial data:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;html&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;&amp;lt;!-- Existing server-rendered content --&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;legacy-content&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;...&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;&amp;lt;!-- New React component with data --&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;react-component&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;application/json&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;data&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;data-attr&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;componentData&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  {&quot;items&quot;:[...],&quot;settings&quot;:{...}}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;It also works well for pages with multiple small frontends that need to share the same server-provided configuration.&lt;/p&gt;
&lt;h2&gt;Getting started&lt;/h2&gt;
&lt;p&gt;Check out &lt;a href=&quot;https://github.com/robinvdvleuten/json-from-script&quot;&gt;json-from-script on GitHub&lt;/a&gt; for installation instructions and documentation.&lt;/p&gt;
&lt;p&gt;Then replace executable inline scripts with JSON script tags where the server hands data to the client.&lt;/p&gt;
&lt;p&gt;It is a focused tool for one small job.&lt;/p&gt;
&lt;h2&gt;Closing thoughts&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;json-from-script&lt;/code&gt; approach is simple on purpose. It uses existing browser behavior and keeps strict &lt;a href=&quot;https://developers.google.com/web/fundamentals/security/csp/&quot;&gt;CSP policies&lt;/a&gt; intact.&lt;/p&gt;
&lt;p&gt;Next time inline scripts run into CSP, JSON script tags are worth reaching for.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>A CSS-only carousel slider that still feels native</title>
    <link href="https://robinvdvleuten.nl/post/css-only-carousel-slider/"/>
    <updated>2024-10-09T00:00:00Z</updated>
    <id>https://robinvdvleuten.nl/post/css-only-carousel-slider/</id>
    <summary>Build a small carousel with scroll snap, anchor links, and no JavaScript state machine.</summary>
    <content type="html">&lt;p&gt;You&#39;re building a product page, and somebody asks for a carousel.&lt;/p&gt;
&lt;p&gt;The request sounds harmless. A few cards. Some dots. Maybe previous and next buttons if there is time. Then the small details arrive: it should swipe on touch screens, work with a keyboard, land cleanly on each slide, and not drag a dependency into a page that otherwise does not need JavaScript.&lt;/p&gt;
&lt;p&gt;That is where carousel code tends to get silly.&lt;/p&gt;
&lt;p&gt;I have written the version with hidden radio inputs. I have written the version that keeps an &lt;code&gt;activeIndex&lt;/code&gt; in JavaScript and nudges a strip of slides around with transforms. The worst version had the dots, buttons, swipe handling, resize handling, and animation timing all disagreeing with each other in tiny ways.&lt;/p&gt;
&lt;p&gt;Most of that work was me fighting the browser.&lt;/p&gt;
&lt;h2&gt;Let the browser do the scrolling&lt;/h2&gt;
&lt;p&gt;A carousel is mostly a horizontal scroll area with strong opinions about where scrolling should stop. &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_scroll_snap&quot;&gt;CSS scroll snap&lt;/a&gt; is built for exactly that kind of thing.&lt;/p&gt;
&lt;p&gt;The browser already knows how to handle trackpads, touch screens, keyboard scrolling, reduced motion settings, and the odd input device you forgot existed. Use that.&lt;/p&gt;
&lt;p&gt;Start with the markup:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;html&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;section&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;carousel&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;aria-labelledby&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;carousel-title&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;h2&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;carousel-title&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;Featured projects&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;h2&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;carousel__track&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;aria-label&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;Project slides&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;tabindex&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;0&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;article&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;carousel__slide&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;project-1&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;h3&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;Dashboard cleanup&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;h3&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;Small layout changes that made a busy admin page easier to scan.&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;article&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;article&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;carousel__slide&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;project-2&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;h3&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;Checkout recovery&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;h3&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;A quieter payment flow with fewer dead ends when a card fails.&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;article&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;article&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;carousel__slide&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;project-3&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;h3&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;Search tuning&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;h3&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;Better defaults for people who do not know the exact term yet.&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;article&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;nav&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;carousel__nav&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;aria-label&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;Choose a project&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;href&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;#project-1&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;aria-label&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;Show project 1&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;href&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;#project-2&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;aria-label&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;Show project 2&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;href&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;#project-3&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;aria-label&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;Show project 3&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;nav&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;section&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Nothing clever yet. The slides are real content. The dots are normal links. Each link points at the &lt;code&gt;id&lt;/code&gt; of a slide, which means the browser can move that slide into view without a click handler.&lt;/p&gt;
&lt;p&gt;That last bit is doing more work than it looks like. This is ordinary document behavior, not a fake navigation system painted on top of a widget.&lt;/p&gt;
&lt;h2&gt;The CSS that turns it into a carousel&lt;/h2&gt;
&lt;p&gt;Now make the track scroll horizontally and tell each slide where it should settle.&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;css&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.carousel&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;max-width&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;48&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;rem&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.carousel__track&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;display&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;flex&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;gap&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;rem&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;overflow-x&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;auto&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;overscroll-behavior-x&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;contain&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;scroll-behavior&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;smooth&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;scroll-snap-type&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;x mandatory&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;scrollbar-width&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;none&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.carousel__track::-webkit-scrollbar&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;display&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;none&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.carousel__slide&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;flex&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;0 0 100&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;min-height&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;16&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;rem&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;scroll-snap-align&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;start&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.carousel__nav&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;display&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;flex&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;gap&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;0.5&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;rem&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;justify-content&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;center&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;margin-block-start&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;rem&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.carousel__nav&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;block-size&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;0.75&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;rem&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;border&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;px&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt; solid currentColor&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;border-radius&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;999&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;px&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;inline-size&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;0.75&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;rem&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;@media&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (prefers-reduced-motion&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; reduce) {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.carousel__track&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;scroll-behavior&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;auto&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;scroll-snap-type: x mandatory&lt;/code&gt; tells the track to snap along the horizontal axis. Each slide opts in with &lt;code&gt;scroll-snap-align: start&lt;/code&gt;, so the left edge of the slide becomes the resting point.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;scroll-behavior&lt;/code&gt; property only affects scrolling triggered by navigation or CSSOM APIs, which is exactly what happens when you click one of those fragment links. User scrolling still feels like user scrolling.&lt;/p&gt;
&lt;p&gt;The track is focusable with &lt;code&gt;tabindex=&amp;quot;0&amp;quot;&lt;/code&gt;, so keyboard users can put focus on it and scroll through the slides with the keys their browser already supports.&lt;/p&gt;
&lt;h2&gt;Making peeked slides&lt;/h2&gt;
&lt;p&gt;Full-width slides are fine for a hero carousel. Card carousels often feel better when the next item peeks in from the side.&lt;/p&gt;
&lt;p&gt;Change the slide basis and add some padding to the track:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;css&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.carousel__track&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;display&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;flex&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;gap&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;rem&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;overflow-x&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;auto&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;padding-inline&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;rem&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;scroll-padding-inline&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;rem&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;scroll-snap-type&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;x mandatory&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.carousel__slide&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;flex&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;0 0 min(80&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt; 28&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;rem&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;scroll-snap-align&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;start&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;scroll-padding-inline&lt;/code&gt; is the easy-to-miss part. It tells the snap container that the useful viewing area starts after the padding, so the first slide does not snap awkwardly underneath the edge.&lt;/p&gt;
&lt;p&gt;That gives you the familiar &amp;quot;one card and a bit of the next one&amp;quot; layout without calculating transforms.&lt;/p&gt;
&lt;h2&gt;What about previous and next buttons?&lt;/h2&gt;
&lt;p&gt;This is where the CSS-only promise gets a bit uncomfortable.&lt;/p&gt;
&lt;p&gt;Dots are easy because every dot can point at a known slide. Previous and next buttons need to know where you are now. CSS can style the &lt;code&gt;:target&lt;/code&gt; slide after a link click, but it does not know the current scroll position after a swipe, a trackpad gesture, or keyboard scrolling.&lt;/p&gt;
&lt;p&gt;So you have two reasonable options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Keep it CSS-only and use direct slide links.&lt;/strong&gt; This works well for small carousels where jumping to slide 1, 2, or 3 is clear enough.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Add a tiny layer of JavaScript for relative controls.&lt;/strong&gt; Let CSS handle the layout and snapping. Use JavaScript for the parts that actually need state, like &amp;quot;go to the next slide&amp;quot; or &amp;quot;mark the current dot.&amp;quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That second option is not cheating. It keeps the awkward cross-device scrolling work in the browser, and the state you own stays small.&lt;/p&gt;
&lt;h2&gt;A complete version&lt;/h2&gt;
&lt;p&gt;Here is the whole thing together, with just enough styling to make the example readable:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;html&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;section&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;carousel&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;aria-labelledby&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;carousel-title&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;h2&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;carousel-title&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;Featured projects&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;h2&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;carousel__track&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;aria-label&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;Project slides&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;tabindex&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;0&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;article&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;carousel__slide&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;project-1&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;carousel__eyebrow&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;01&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;h3&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;Dashboard cleanup&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;h3&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;Small layout changes that made a busy admin page easier to scan.&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;article&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;article&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;carousel__slide&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;project-2&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;carousel__eyebrow&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;02&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;h3&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;Checkout recovery&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;h3&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;A quieter payment flow with fewer dead ends when a card fails.&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;article&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;article&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;carousel__slide&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;project-3&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;carousel__eyebrow&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;03&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;h3&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;Search tuning&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;h3&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;Better defaults for people who do not know the exact term yet.&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;article&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;nav&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;carousel__nav&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;aria-label&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;Choose a project&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;href&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;#project-1&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;aria-label&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;Show project 1&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;href&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;#project-2&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;aria-label&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;Show project 2&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;href&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;#project-3&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;aria-label&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;Show project 3&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;nav&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;section&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;css&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.carousel&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;color&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;#1f2937&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;max-width&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;48&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;rem&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.carousel__track&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;display&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;flex&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;gap&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;rem&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;overflow-x&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;auto&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;overscroll-behavior-x&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;contain&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;padding-inline&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;rem&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;scroll-behavior&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;smooth&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;scroll-padding-inline&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;rem&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;scroll-snap-type&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;x mandatory&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;scrollbar-width&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;none&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.carousel__track:focus-visible&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;outline&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;px&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt; solid #2563eb&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;outline-offset&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;0.25&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;rem&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.carousel__track::-webkit-scrollbar&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;display&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;none&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.carousel__slide&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;background&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;#f8fafc&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;border&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;px&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt; solid #cbd5e1&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;border-radius&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;0.75&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;rem&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;flex&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;0 0 min(80&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt; 28&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;rem&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;min-height&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;16&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;rem&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;padding&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;1.5&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;rem&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;scroll-snap-align&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;start&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.carousel__eyebrow&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;color&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;#64748b&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;font-size&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;0.875&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;rem&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;letter-spacing&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;0.08&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;em&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;text-transform&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;uppercase&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.carousel__nav&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;display&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;flex&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;gap&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;0.5&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;rem&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;justify-content&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;center&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;margin-block-start&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;rem&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.carousel__nav&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;block-size&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;0.75&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;rem&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;border&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;px&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt; solid currentColor&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;border-radius&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;999&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;px&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;color&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;inherit&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;inline-size&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;0.75&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;rem&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.carousel__nav&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;:hover&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.carousel__nav&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;:focus-visible&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;background&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;currentColor&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;@media&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (prefers-reduced-motion&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; reduce) {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.carousel__track&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;scroll-behavior&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;auto&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Notice what is missing. There are no cloned slides, no transform calculations, and no timer trying to move the carousel while somebody is halfway through reading the card.&lt;/p&gt;
&lt;p&gt;It is just a scroll container with snap points.&lt;/p&gt;
&lt;h2&gt;When this is enough&lt;/h2&gt;
&lt;p&gt;Use this when the carousel supports the page rather than becoming the page:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Product cards.&lt;/strong&gt; A row of related products can scroll, snap, and link directly to each item.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Testimonials.&lt;/strong&gt; Direct dots are usually enough because the reader is browsing, not completing a task.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Small media galleries.&lt;/strong&gt; A handful of images works well as long as each slide still has useful alt text and surrounding context.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Editorial teasers.&lt;/strong&gt; Cards that remain readable as ordinary HTML age better than a custom widget.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I would not use this for a complex gallery with thumbnails, zooming, lazy video playback, analytics, and synchronized controls. At that point you have application state, and pretending otherwise only makes the CSS stranger.&lt;/p&gt;
&lt;h2&gt;The practical lesson&lt;/h2&gt;
&lt;p&gt;The best CSS-only carousel is not a trick. It is a scroll area with enough CSS to make the browser&#39;s native behavior line up with the design.&lt;/p&gt;
&lt;p&gt;That is a much smaller thing to keep in your head six months later.&lt;/p&gt;
&lt;p&gt;If you need more, add JavaScript where the need appears. But start with &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_scroll_snap&quot;&gt;scroll snap&lt;/a&gt;, fragment links through stable &lt;code&gt;id&lt;/code&gt; attributes, and a normal overflow container. You get farther than you might expect.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>My Obsidian setup</title>
    <link href="https://robinvdvleuten.nl/post/my-obsidian-setup/"/>
    <updated>2024-07-28T00:00:00Z</updated>
    <id>https://robinvdvleuten.nl/post/my-obsidian-setup/</id>
    <summary>A plain Markdown note setup that keeps work, writing, and loose thoughts findable without turning note-taking into its own project.</summary>
    <content type="html">&lt;p&gt;I&#39;ve rebuilt my notes too many times.&lt;/p&gt;
&lt;p&gt;The pattern was always the same. I would find a new app, move a few files around, invent a tidy set of categories, and tell myself that this time the system would stick. A week later I was spending more time deciding where a note belonged than writing the note.&lt;/p&gt;
&lt;p&gt;That is a very silly way to avoid thinking.&lt;/p&gt;
&lt;p&gt;So my current &lt;a href=&quot;https://obsidian.md/&quot;&gt;Obsidian&lt;/a&gt; setup has one goal: make it easy to leave useful traces while I&#39;m working. Notes don&#39;t need to be beautiful. The graph doesn&#39;t need to impress anyone. I just want enough structure that yesterday&#39;s thinking is still there when I need it.&lt;/p&gt;
&lt;p&gt;Everything else can wait.&lt;/p&gt;
&lt;h2&gt;The rule: write first, classify later&lt;/h2&gt;
&lt;p&gt;The biggest change wasn&#39;t switching to Obsidian. It was giving up on sorting things perfectly the moment they appear.&lt;/p&gt;
&lt;p&gt;When I&#39;m in the middle of debugging something, I don&#39;t want to choose between &amp;quot;client&amp;quot;, &amp;quot;Elixir&amp;quot;, &amp;quot;architecture&amp;quot;, &amp;quot;bug&amp;quot;, or &amp;quot;future blog post.&amp;quot; I want to write down the error, the thing I tried, and the odd little clue that might matter tomorrow.&lt;/p&gt;
&lt;p&gt;Classification can wait. Capturing can&#39;t.&lt;/p&gt;
&lt;p&gt;That&#39;s why the busiest place in my vault is intentionally temporary. New notes, pasted links, rough outlines, and half-formed thoughts land in one holding area first. Later, when I have a quieter minute, I move them somewhere useful, merge them into an existing note, or delete them because they were only useful for ten minutes.&lt;/p&gt;
&lt;p&gt;That last option is underrated. Not every note deserves a future.&lt;/p&gt;
&lt;h2&gt;Notes as workbench, not museum&lt;/h2&gt;
&lt;p&gt;The notes I use most don&#39;t look polished. They look like a desk while work is happening.&lt;/p&gt;
&lt;p&gt;Here&#39;s a made-up but representative one:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;markdown&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;# Rails customer portal&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;Need:&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;- invite flow for new users&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;- billing page with recent invoices&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;- cleaner authorization checks around account access&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;Questions:&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;- Should invites expire after 7 or 14 days?&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;- Can account owners resend an invite?&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;- Where do we show failed payment state?&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;Links:&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;- Stripe customer portal docs&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;- Existing &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string)&quot;&gt;`Account`&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; permissions&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;- &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string)&quot;&gt;`InviteMailer`&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; preview&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;Decisions:&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;- Keep billing redirects server-side&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;- Store invite state on the membership record&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;- Use a mailer instead of adding another service object for now&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;2024-07-28&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;- Sketched the invite states&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;- Added an authorization test for suspended accounts&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;- Noted that invoice downloads should stay behind signed redirects&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;There are tasks in there, but it isn&#39;t a task manager. There are links, but it isn&#39;t a bookmark archive. There are decisions, but it isn&#39;t formal documentation.&lt;/p&gt;
&lt;p&gt;It&#39;s a working surface.&lt;/p&gt;
&lt;p&gt;That distinction keeps the pressure low. I can make a mess, change my mind, paste a command, leave myself a warning, or write a clumsy paragraph that later becomes a cleaner one. When the work is done, the note is still useful because it explains how I got there.&lt;/p&gt;
&lt;p&gt;Future me rarely needs a perfect record. He does appreciate a few breadcrumbs.&lt;/p&gt;
&lt;h2&gt;Daily writing is my release valve&lt;/h2&gt;
&lt;p&gt;Some thoughts don&#39;t belong to a project yet.&lt;/p&gt;
&lt;p&gt;They might turn into a blog post. They might be a reaction to something I read. They might just be me trying to figure out why a particular implementation feels wrong. If I force those into a named project too early, they either get shaped too soon or disappear.&lt;/p&gt;
&lt;p&gt;So I keep a simple dated file for loose writing:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;text&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: undefined&quot;&gt;2024/&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: undefined&quot;&gt;  07/&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: undefined&quot;&gt;    2024-07-28.md&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;The format is deliberately boring. A heading, a few paragraphs, maybe a tag if I know I&#39;ll want to find it again.&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;markdown&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;## Boring tools age better #writing&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;Every time I pick a tool now, I ask what happens if I stop using it. A folder of&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;Markdown files is not exciting, but it is wonderfully untrapped.&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Most entries never leave the vault. That&#39;s fine. The point is to keep the threshold low enough that I actually write. Published writing usually starts badly and privately, and Obsidian gives those bad first sentences somewhere to exist.&lt;/p&gt;
&lt;h2&gt;My vault has very few doors&lt;/h2&gt;
&lt;p&gt;I don&#39;t want a deep filing cabinet. I want a few obvious places to put things.&lt;/p&gt;
&lt;p&gt;The broad buckets are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Capture&lt;/strong&gt; for anything that hasn&#39;t earned a home yet.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Work&lt;/strong&gt; for active client, product, and open source notes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Writing&lt;/strong&gt; for drafts, outlines, and ideas that may become posts.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reference&lt;/strong&gt; for commands, snippets, checklists, and things I look up more than once.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Archive&lt;/strong&gt; for work that is done but still worth being able to search.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That&#39;s it.&lt;/p&gt;
&lt;p&gt;The names are plain on purpose because plain names survive mood swings. If I need to think for more than a few seconds about where something goes, the buckets are too clever.&lt;/p&gt;
&lt;p&gt;I rely on search for the rest. Search is better at finding an old phrase than I am at predicting the perfect category six months in advance.&lt;/p&gt;
&lt;h2&gt;The app almost disappears&lt;/h2&gt;
&lt;p&gt;Obsidian can get very busy. I try hard not to let it.&lt;/p&gt;
&lt;p&gt;I keep the interface quiet: file list when I need it, search when I need it, editor most of the time. I don&#39;t use the graph as a dashboard, I don&#39;t build a homepage for my vault, and I don&#39;t want a plugin to turn notes into a second operating system.&lt;/p&gt;
&lt;p&gt;The useful parts are small:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Quick switcher&lt;/strong&gt; to jump to a note without touching the mouse.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Search&lt;/strong&gt; because names are never as reliable as remembered phrases.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Templates&lt;/strong&gt; for the few notes I create repeatedly.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;File recovery&lt;/strong&gt; because losing text is a uniquely annoying kind of failure.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A minimal theme&lt;/strong&gt; with most visual noise removed.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That&#39;s about the shape of it. I have tried more, and some of it was genuinely clever. The problem is that clever plugins make me want to maintain the setup. I already have enough things to maintain.&lt;/p&gt;
&lt;p&gt;When Obsidian feels like a folder of Markdown files with a good editor on top, I&#39;m happy.&lt;/p&gt;
&lt;h2&gt;What stays out&lt;/h2&gt;
&lt;p&gt;Keeping things out of Obsidian matters as much as deciding what goes in.&lt;/p&gt;
&lt;p&gt;I don&#39;t run my calendar from it. I don&#39;t run my task system from it. I don&#39;t track every habit, every book, every person, or every idea that crosses my desk.&lt;/p&gt;
&lt;p&gt;Tasks in particular need a different kind of trust. They need due dates, review, recurring schedules, and a clear answer to &amp;quot;what should I do now?&amp;quot; Notes are allowed to be incomplete. Tasks aren&#39;t.&lt;/p&gt;
&lt;p&gt;I also don&#39;t treat the vault as the final home for everything I write. Once a blog post starts to look real, it moves into this site&#39;s repository. That&#39;s where it gets edited, built, and published. Obsidian is where the clay starts to take shape, not where the finished piece has to live forever.&lt;/p&gt;
&lt;p&gt;That boundary keeps the system from becoming sticky in the wrong way.&lt;/p&gt;
&lt;h2&gt;Why Obsidian stuck&lt;/h2&gt;
&lt;p&gt;The boring answer is portability.&lt;/p&gt;
&lt;p&gt;My notes are files. Real files. I can open them with Obsidian, Vim, VS Code, Finder previews, or whatever replaces all of this later. If the app disappears, the notes remain exactly as dull and readable as they were before.&lt;/p&gt;
&lt;p&gt;That changes how the tool feels. I don&#39;t have to trust Obsidian with my entire thinking life. I only have to trust it to be a good interface over a folder I already control.&lt;/p&gt;
&lt;p&gt;And it is.&lt;/p&gt;
&lt;p&gt;It is fast enough, flexible enough, local by default, and Markdown underneath. A little ugly if you let it be, pleasantly quiet if you remove enough furniture.&lt;/p&gt;
&lt;p&gt;That&#39;s all I wanted: a place where work-in-progress thoughts can land, useful scraps can be found, and writing can start before it knows what it is.&lt;/p&gt;
&lt;p&gt;No grand theory or perfect system.&lt;/p&gt;
&lt;p&gt;Just a boring setup I actually use.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Lazy loading images is just HTML now</title>
    <link href="https://robinvdvleuten.nl/post/lazy-loading-images-is-just-html-now/"/>
    <updated>2023-09-18T00:00:00Z</updated>
    <id>https://robinvdvleuten.nl/post/lazy-loading-images-is-just-html-now/</id>
    <summary>Use the native loading attribute for offscreen images, and save the JavaScript for the cases where you really need control.</summary>
    <content type="html">&lt;p&gt;You&#39;re building a page with a long scroll.&lt;/p&gt;
&lt;p&gt;The top matters. The hero image, the first product photo, maybe a screenshot right under the heading. The browser should fetch those right away.&lt;/p&gt;
&lt;p&gt;Then the page keeps going: related articles, customer logos, screenshots, gallery items, avatars in comments.&lt;/p&gt;
&lt;p&gt;None of those images need to compete with the first screen.&lt;/p&gt;
&lt;p&gt;For years, I reached for some combination of &lt;code&gt;data-src&lt;/code&gt;, an &lt;code&gt;IntersectionObserver&lt;/code&gt;, and a tiny script that became less tiny the moment the design changed. It worked. It also meant I owned one more bit of browser behavior.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/img#loading&quot;&gt;&lt;code&gt;loading=&amp;quot;lazy&amp;quot;&lt;/code&gt;&lt;/a&gt; is the boring version I wanted all along. You tell the browser which images can wait, and the browser decides when they are close enough to fetch.&lt;/p&gt;
&lt;h2&gt;The version most of us wrote first&lt;/h2&gt;
&lt;p&gt;A JavaScript lazy loader usually starts out as a neat little trick:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;html&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;img&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;data-src&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;/images/cabin.jpg&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;alt&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;A small wooden cabin between tall pine trees&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;width&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;1200&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;height&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;800&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;The script finds every image with &lt;code&gt;data-src&lt;/code&gt;, watches it, swaps the real URL into &lt;code&gt;src&lt;/code&gt;, and removes the observer when the image has loaded.&lt;/p&gt;
&lt;p&gt;That is not a terrible solution. &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API&quot;&gt;&lt;code&gt;IntersectionObserver&lt;/code&gt;&lt;/a&gt; is a good API, and sometimes you really do need the control.&lt;/p&gt;
&lt;p&gt;But most image lazy loading does not need a lifecycle. It needs a hint.&lt;/p&gt;
&lt;h2&gt;Let the browser handle the boring case&lt;/h2&gt;
&lt;p&gt;Native lazy loading is just an attribute on the real image:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;html&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;img&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;src&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;/images/cabin.jpg&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;alt&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;A small wooden cabin between tall pine trees&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;width&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;1200&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;height&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;800&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;loading&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;lazy&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;There is no placeholder source and no observer to clean up. The image still has its actual &lt;code&gt;src&lt;/code&gt;, so browsers that ignore the attribute load it like a normal image.&lt;/p&gt;
&lt;p&gt;The browser does not wait until the image is one pixel away from the viewport. It uses its own distance from the viewport, connection information, and implementation details to start the request before the user gets there.&lt;/p&gt;
&lt;p&gt;A lazy loader that is too strict saves bytes and then shows the user a blank rectangle. I have written that one. The native version is less fussy, which is exactly the point.&lt;/p&gt;
&lt;h2&gt;Keep the first viewport eager&lt;/h2&gt;
&lt;p&gt;The temptation is to add &lt;code&gt;loading=&amp;quot;lazy&amp;quot;&lt;/code&gt; to every image and call it a performance pass.&lt;/p&gt;
&lt;p&gt;Don&#39;t do that.&lt;/p&gt;
&lt;p&gt;Images that are visible on first load should stay eager. The same goes for the image that is likely to become your &lt;a href=&quot;https://web.dev/articles/lcp&quot;&gt;Largest Contentful Paint&lt;/a&gt;. Lazy loading that image asks the browser to delay the thing the user came to see.&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;html&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;&amp;lt;!-- First viewport: let the browser load these normally. --&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;img&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;src&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;/images/product-hero.jpg&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;alt&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;The walnut writing desk from the front&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;width&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;1600&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;height&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;1000&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;&amp;lt;!-- Further down the page: this can wait. --&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;img&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;src&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;/images/product-detail.jpg&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;alt&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;Close-up of the brass drawer handle&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;width&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;1200&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;height&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;800&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;loading&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;lazy&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;I usually draw the line there: eager for the first screen, lazy once the image is clearly part of the scroll. You can be more exact on a page that needs it, but that rule catches the common case without turning every template into a performance policy document.&lt;/p&gt;
&lt;h2&gt;Dimensions are not optional&lt;/h2&gt;
&lt;p&gt;Lazy loading changes when an image arrives. It should not change how much room the page keeps for it.&lt;/p&gt;
&lt;p&gt;Give the browser &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt;, even when CSS controls the rendered size:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;html&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;img&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;src&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;/images/gallery-04.jpg&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;alt&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;A workbench covered with hand tools&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;width&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;1200&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;height&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;800&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;loading&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;lazy&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;css&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;img&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;display&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;block&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;height&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;auto&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;max-width&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;100&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Those attributes let the browser calculate the image&#39;s aspect ratio before the file has loaded.&lt;/p&gt;
&lt;p&gt;Without dimensions, unloaded images can start life as &lt;code&gt;0x0&lt;/code&gt;. That can turn a performance fix into layout shift: the reader gets halfway through a paragraph, the image arrives, and the text jumps under their eyes.&lt;/p&gt;
&lt;h2&gt;It works with picture too&lt;/h2&gt;
&lt;p&gt;Responsive images do not change much here. Put the loading attribute on the fallback &lt;code&gt;img&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;html&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;picture&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;source&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;srcset&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;/images/cabin-wide.avif&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;image/avif&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;media&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;(min-width: 48rem)&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;source&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;srcset&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;/images/cabin-wide.webp&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;image/webp&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;media&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;(min-width: 48rem)&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;img&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;src&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;/images/cabin.jpg&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;alt&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;A small wooden cabin between tall pine trees&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;width&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;1200&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;height&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;800&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;loading&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;lazy&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;picture&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;The browser still picks the right source. The &lt;code&gt;img&lt;/code&gt; carries the loading hint because it is the element that represents the image in the document.&lt;/p&gt;
&lt;p&gt;Small detail. Easy to forget.&lt;/p&gt;
&lt;h2&gt;When JavaScript still earns its place&lt;/h2&gt;
&lt;p&gt;Native lazy loading is a good default, not a ban on custom code. I would still write the JavaScript when the page needs something more specific:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Tune the threshold yourself.&lt;/strong&gt; The native distance from the viewport is browser-defined. If a page needs a precise trigger point, use an observer.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lazy load something that is not an image.&lt;/strong&gt; Background images, videos, embeds, and expensive widgets need their own strategy.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Coordinate animations or analytics.&lt;/strong&gt; If loading an asset is part of a larger interaction, keep that logic explicit.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Support an older browser with the same behavior.&lt;/strong&gt; Browsers that do not understand the attribute ignore it, which is a fine fallback for many sites. It is not the same as polyfilled lazy loading.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For ordinary offscreen images, though, the boring solution has become the better one.&lt;/p&gt;
&lt;h2&gt;The version I use now&lt;/h2&gt;
&lt;p&gt;Most of the time, my image markup now looks like this:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;html&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;img&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;src&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;/images/workshop.jpg&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;alt&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;A narrow workshop with shelves of tools on both walls&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;width&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;1200&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;height&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;800&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;loading&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;lazy&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;That is for images below the initial viewport. If the image is visible on load, especially if it may become the LCP image, I leave the attribute off. If I need a precise threshold or I am lazy loading something other than an &lt;code&gt;img&lt;/code&gt;, I still reach for JavaScript.&lt;/p&gt;
&lt;p&gt;A real &lt;code&gt;src&lt;/code&gt;, useful &lt;code&gt;alt&lt;/code&gt; text, stable dimensions, and one attribute for images that can wait. That is a nice amount of ceremony.&lt;/p&gt;
&lt;p&gt;Sometimes HTML catches up with the thing we kept solving around it. This is one of those times.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Your Ecto changeset is allowed to be boring</title>
    <link href="https://robinvdvleuten.nl/post/your-ecto-changeset-is-allowed-to-be-boring/"/>
    <updated>2023-05-22T00:00:00Z</updated>
    <id>https://robinvdvleuten.nl/post/your-ecto-changeset-is-allowed-to-be-boring/</id>
    <summary>Ecto changesets do not need a private form framework to be useful. Casting, validating, and naming errors clearly gets you a long way.</summary>
    <content type="html">&lt;p&gt;I&#39;ve written changesets that were trying very hard to be impressive.&lt;/p&gt;
&lt;p&gt;You know the type. A schema has a &lt;code&gt;changeset/2&lt;/code&gt;, then a &lt;code&gt;registration_changeset/2&lt;/code&gt;, then an &lt;code&gt;admin_changeset/2&lt;/code&gt;. After that comes a little module that builds errors for the frontend, another module that knows which fields are visible on which screen, and one more helper because the embedded form is &amp;quot;a bit special.&amp;quot;&lt;/p&gt;
&lt;p&gt;At some point the code stops feeling like validation and starts feeling like a tiny form framework that nobody remembers designing.&lt;/p&gt;
&lt;p&gt;Annoyingly, the boring version was usually enough.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://hexdocs.pm/ecto/Ecto.Changeset.html&quot;&gt;Ecto changesets&lt;/a&gt; already have a clear job. They take data you do not trust yet, cast the fields you are willing to accept, validate the result, and keep the errors close to the data. That is enough for most forms.&lt;/p&gt;
&lt;h2&gt;The temptation to build a form system&lt;/h2&gt;
&lt;p&gt;The slippery slope rarely starts with a grand architecture plan. It starts with a reasonable request.&lt;/p&gt;
&lt;p&gt;&amp;quot;This field is required on signup, but not when an admin edits the user.&amp;quot;&lt;/p&gt;
&lt;p&gt;Fine. Add a second changeset.&lt;/p&gt;
&lt;p&gt;Then another one appears.&lt;/p&gt;
&lt;p&gt;&amp;quot;This API endpoint needs a slightly different error shape.&amp;quot;&lt;/p&gt;
&lt;p&gt;Still reasonable. Add a helper.&lt;/p&gt;
&lt;p&gt;Then a third.&lt;/p&gt;
&lt;p&gt;&amp;quot;This embedded form needs to validate three fields before we create the actual record.&amp;quot;&lt;/p&gt;
&lt;p&gt;Before long, the changeset is no longer the source of truth. It is one stop in a pipeline of mapping, translating, grouping, decorating, and massaging.&lt;/p&gt;
&lt;p&gt;The code still works, which is part of the problem. Every new field has to be added in four places. Every error message has a little journey before anyone sees it.&lt;/p&gt;
&lt;p&gt;That is usually when I try to ask the dull question again: what does Ecto already give me?&lt;/p&gt;
&lt;p&gt;More than I usually remember when I&#39;m halfway through inventing a tiny framework.&lt;/p&gt;
&lt;h2&gt;Start with the fields you accept&lt;/h2&gt;
&lt;p&gt;The most important line in many changesets is the least exciting one:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;elixir&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;changeset&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(user&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; attrs) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;do&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  user&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;|&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;cast&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(attrs&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; [:name&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; :email&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; :timezone])&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;|&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;validate_required&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;([:name&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; :email])&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;|&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;validate_format&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(:email&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;~r/^[^\s]+@[^\s]+$/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://hexdocs.pm/ecto/Ecto.Changeset.html#cast/4&quot;&gt;&lt;code&gt;cast/4&lt;/code&gt;&lt;/a&gt; is the boundary. It says: these are the fields this operation accepts from the outside world.&lt;/p&gt;
&lt;p&gt;That line is worth keeping boring and explicit.&lt;/p&gt;
&lt;p&gt;If a form posts &lt;code&gt;role: &amp;quot;admin&amp;quot;&lt;/code&gt; and &lt;code&gt;:role&lt;/code&gt; is not in the cast list, it does not become a change. You do not need a custom sanitizer for that. The changeset already has the list of fields you meant to allow.&lt;/p&gt;
&lt;p&gt;There is a nice side effect too: the code reads like the form.&lt;/p&gt;
&lt;p&gt;Name, email, timezone. Name and email required. Email shaped like an email.&lt;/p&gt;
&lt;p&gt;Nothing clever is hiding behind a private DSL with vocabulary you have to relearn six months later.&lt;/p&gt;
&lt;h2&gt;Different operation, different changeset&lt;/h2&gt;
&lt;p&gt;Not every operation has the same rules, and that is fine. The mistake is pretending one changeset has to cover every path.&lt;/p&gt;
&lt;p&gt;User registration is not the same as an account settings update:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;elixir&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;registration_changeset&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(user&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; attrs) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;do&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  user&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;|&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;cast&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(attrs&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; [:name&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; :email&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; :password&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; :timezone])&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;|&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;validate_required&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;([:name&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; :email&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; :password])&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;|&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;validate_format&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(:email&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;~r/^[^\s]+@[^\s]+$/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;|&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;validate_length&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(:password&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; min: &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;12&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;|&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;unique_constraint&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(:email)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;profile_changeset&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(user&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; attrs) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;do&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  user&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;|&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;cast&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(attrs&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; [:name&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; :timezone])&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;|&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;validate_required&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;([:name])&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Two changesets. Two names. Two clear reasons to exist.&lt;/p&gt;
&lt;p&gt;That beats one generic &lt;code&gt;changeset/3&lt;/code&gt; with a &lt;code&gt;context&lt;/code&gt; option that slowly grows a small command language:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;elixir&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;# This starts neat, then somehow becomes the new framework.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;User&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;changeset&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(user&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; attrs&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; mode: :registration&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; validate_email: &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; require_password: &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Options are not bad. But if the option changes what the operation &lt;em&gt;is&lt;/em&gt;, a separate function is usually easier to read, test, and delete later.&lt;/p&gt;
&lt;h2&gt;Put internal changes where they happen&lt;/h2&gt;
&lt;p&gt;Some data should not come from the user at all.&lt;/p&gt;
&lt;p&gt;Maybe you normalize an email address. Maybe you store who invited the user. Maybe you stamp a derived value the form should never be allowed to submit.&lt;/p&gt;
&lt;p&gt;That is where &lt;a href=&quot;https://hexdocs.pm/ecto/Ecto.Changeset.html#put_change/3&quot;&gt;&lt;code&gt;put_change/3&lt;/code&gt;&lt;/a&gt; is useful:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;elixir&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;registration_changeset&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(user&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; attrs&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; inviter) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;do&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  user&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;|&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;cast&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(attrs&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; [:name&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; :email&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; :password])&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;|&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;validate_required&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;([:name&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; :email&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; :password])&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;|&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;update_change&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(:email&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &amp;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;downcase&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;|&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;put_change&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(:invited_by_id&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; inviter&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;id)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;The split matters:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;cast/4&lt;/code&gt; handles data from outside.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;put_change/3&lt;/code&gt; handles data your application decided.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Keeping that difference visible makes the changeset easier to trust. The cast list tells you what the user controlled. The later steps tell you what the system added.&lt;/p&gt;
&lt;h2&gt;Constraints belong next to validations&lt;/h2&gt;
&lt;p&gt;Some rules cannot be checked honestly until the database gets involved.&lt;/p&gt;
&lt;p&gt;Email uniqueness is the classic example. You can query first, but another request can create the same email between your query and your insert. The database constraint is the thing that actually protects you.&lt;/p&gt;
&lt;p&gt;Ecto lets the changeset know about that:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;elixir&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;registration_changeset&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(user&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; attrs) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;do&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  user&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;|&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;cast&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(attrs&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; [:name&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; :email&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; :password])&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;|&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;validate_required&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;([:name&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; :email&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; :password])&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;|&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;validate_format&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(:email&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;~r/^[^\s]+@[^\s]+$/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;|&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;unique_constraint&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(:email)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;The important part is that &lt;a href=&quot;https://hexdocs.pm/ecto/Ecto.Changeset.html#unique_constraint/3&quot;&gt;&lt;code&gt;unique_constraint/3&lt;/code&gt;&lt;/a&gt; does not replace a database index. It turns a database error into a changeset error.&lt;/p&gt;
&lt;p&gt;That is a good trade: the database keeps the data safe, and the changeset keeps the feedback useful.&lt;/p&gt;
&lt;h2&gt;Error messages do not need a separate universe&lt;/h2&gt;
&lt;p&gt;Frontend code often wants errors as a map. Fair enough.&lt;/p&gt;
&lt;p&gt;That does not mean the application needs a whole error formatting layer before it has earned one.&lt;/p&gt;
&lt;p&gt;Ecto gives you &lt;a href=&quot;https://hexdocs.pm/ecto/Ecto.Changeset.html#traverse_errors/2&quot;&gt;&lt;code&gt;traverse_errors/2&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;elixir&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;translate_errors&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(changeset) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;do&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;traverse_errors&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(changeset&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;fn&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {message&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; opts} &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;Enum&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;reduce&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(opts&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; message&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;fn&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {key&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; value}&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; acc &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;replace&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(acc&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;%{&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;#{key}&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;}&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;to_string&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(value))&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Given a changeset with a missing email and a short password, you get a boring map back:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;elixir&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;%{&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  email: [&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;can&#39;t be blank&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  password: [&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;should be at least 12 character(s)&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;]&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;That is not the final answer for every product. You may need translations, error codes, nested structures, or field-specific copy later.&lt;/p&gt;
&lt;p&gt;But start here.&lt;/p&gt;
&lt;p&gt;The plain map is enough for most forms, tests, and JSON responses. More importantly, it keeps the validation language close to Ecto instead of inventing another error system beside it on day one.&lt;/p&gt;
&lt;h2&gt;Keep the boring parts boring&lt;/h2&gt;
&lt;p&gt;The changesets I like working with have a few things in common:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The cast list is explicit.&lt;/strong&gt; You can see what outside data is allowed to change.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The function name describes the operation.&lt;/strong&gt; &lt;code&gt;registration_changeset/2&lt;/code&gt; tells you more than &lt;code&gt;changeset/3&lt;/code&gt; with a bag of options.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Application-owned values are added after casting.&lt;/strong&gt; User input and system decisions do not get mixed together.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Database constraints stay in the database.&lt;/strong&gt; The changeset names them so errors come back in the right shape.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Errors are formatted late.&lt;/strong&gt; Keep the changeset useful for as long as possible before turning it into whatever the controller or API needs.&lt;/p&gt;
&lt;p&gt;None of this is advanced. That is the point.&lt;/p&gt;
&lt;p&gt;When a changeset is boring, you can change the form without spelunking through a private abstraction. You can add a field and see where it belongs. You can test one operation without setting up the entire product in miniature.&lt;/p&gt;
&lt;p&gt;And when the rules really do get complicated, the boring version gives you a better place to start. You will know which part became painful because there was not already a framework-shaped blanket over the whole thing.&lt;/p&gt;
&lt;p&gt;So yes, extract when the duplication becomes real. Add helpers when they remove noise. Build a richer error layer when the product asks for it.&lt;/p&gt;
&lt;p&gt;But let the changeset be boring first.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Never turn JSON keys into atoms in Elixir</title>
    <link href="https://robinvdvleuten.nl/post/never-turn-json-keys-into-atoms-in-elixir/"/>
    <updated>2022-11-07T00:00:00Z</updated>
    <id>https://robinvdvleuten.nl/post/never-turn-json-keys-into-atoms-in-elixir/</id>
    <summary>Decoding JSON into atoms feels nice until untrusted keys start filling the VM atom table. Keep external data as strings until you own the shape.</summary>
    <content type="html">&lt;p&gt;You&#39;re building a small JSON API client in Elixir. The endpoint returns a tidy response, &lt;a href=&quot;https://hexdocs.pm/jason/Jason.html#decode!/2&quot;&gt;Jason&lt;/a&gt; decodes it without complaint, and then you add the option that makes the rest of the code feel nicer:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;elixir&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;Jason&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;decode!&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(body&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; keys: :atoms)&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Now you can write &lt;code&gt;user.name&lt;/code&gt; instead of &lt;code&gt;user[&amp;quot;name&amp;quot;]&lt;/code&gt;. Small upgrade.&lt;/p&gt;
&lt;p&gt;A remote response can now decide which atoms exist in your VM.&lt;/p&gt;
&lt;p&gt;Slow down there.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://hexdocs.pm/elixir/Atom.html&quot;&gt;Atoms&lt;/a&gt; in Elixir are not normal strings with a colon in front. They live in the Erlang VM&#39;s &lt;a href=&quot;https://www.erlang.org/doc/system/system_limits.html&quot;&gt;atom table&lt;/a&gt;, shared by the whole running system. Once an atom exists, you should treat it as something the VM keeps around.&lt;/p&gt;
&lt;p&gt;When you created the atom yourself, fine. When the key came from the internet, different deal.&lt;/p&gt;
&lt;h2&gt;The convenience trap&lt;/h2&gt;
&lt;p&gt;The default behaviour in Jason is exactly what you want for arbitrary JSON:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;elixir&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;Jason&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;decode!&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;~s({&quot;name&quot;:&quot;Robin&quot;,&quot;role&quot;:&quot;developer&quot;})&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;%{&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;Robin&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;role&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;developer&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;String keys are a little noisier to access, but they stay ordinary data. Send a key the application has never heard of and nothing special happens:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;elixir&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;Jason&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;decode!&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;~s({&quot;whatever_the_client_sent&quot;:true})&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;%{&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;whatever_the_client_sent&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;With &lt;a href=&quot;https://hexdocs.pm/jason/Jason.html#module-decoding-keys-to-atoms&quot;&gt;&lt;code&gt;keys: :atoms&lt;/code&gt;&lt;/a&gt;, those keys become atoms:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;elixir&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;Jason&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;decode!&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;~s({&quot;name&quot;:&quot;Robin&quot;,&quot;role&quot;:&quot;developer&quot;})&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; keys: :atoms)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;%{name: &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;Robin&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; role: &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;developer&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;This is the tempting version. The map looks like the maps you write by hand, especially once it moves deeper into application code.&lt;/p&gt;
&lt;p&gt;The example above is harmless. The problem is the stream of payloads after it, especially the ones sent by somebody who does not care about your atom table.&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;elixir&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;Jason&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;decode!&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;~s({&quot;field_1&quot;:true})&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; keys: :atoms)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;Jason&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;decode!&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;~s({&quot;field_2&quot;:true})&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; keys: :atoms)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;Jason&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;decode!&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;~s({&quot;field_3&quot;:true})&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; keys: :atoms)&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Each new key asks the VM to create a new atom.&lt;/p&gt;
&lt;p&gt;On your laptop, nothing explodes. In production, the problem can stay quiet for a long time too.&lt;/p&gt;
&lt;p&gt;That is what makes the option dangerous. It sits at the edge of the system and turns untrusted input into something global.&lt;/p&gt;
&lt;h2&gt;Existing atoms are different&lt;/h2&gt;
&lt;p&gt;Jason gives you a stricter option:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;elixir&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;Jason&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;decode!&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(body&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; keys: :atoms!)&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;The exclamation mark is not decoration. Instead of creating atoms for every key it sees, Jason only accepts atoms that already exist.&lt;/p&gt;
&lt;p&gt;So this works:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;elixir&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; :name&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;:name&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;Jason&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;decode!&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;~s({&quot;name&quot;:&quot;Robin&quot;})&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; keys: :atoms!)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;%{name: &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;Robin&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;But this fails if the atom is unknown:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;elixir&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;Jason&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;decode!&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;~s({&quot;made_up_key&quot;:true})&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; keys: :atoms!)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;ArgumentError&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;) errors were found at the given arguments:&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; 1st argument: &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;not&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; an already existing atom&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;That failure mode is useful. It is loud, early, and easier to understand than &amp;quot;the VM fell over because every request invented a few more atoms.&amp;quot;&lt;/p&gt;
&lt;p&gt;Still, I don&#39;t reach for it as the default.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;keys: :atoms!&lt;/code&gt; is useful when the payload is internal, versioned, and genuinely has a fixed shape. For external API responses, webhooks, request bodies, imported files, or anything users can influence, string keys are the safer tradeoff.&lt;/p&gt;
&lt;h2&gt;Pattern match at the boundary&lt;/h2&gt;
&lt;p&gt;The annoying part is usually not decoding. It is the code after decoding.&lt;/p&gt;
&lt;p&gt;String keys feel clumsy:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;elixir&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;%{&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; name&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;email&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; email} &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; payload&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;I don&#39;t think that is a bad thing.&lt;/p&gt;
&lt;p&gt;The ugliness is a signal. You&#39;re still at the boundary, dealing with external data as external data.&lt;/p&gt;
&lt;p&gt;Convert it deliberately when it enters your system:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;elixir&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;defmodule&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;SignupParams&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;do&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;from_json&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(%{&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; name&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;email&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; email}) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;do&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    {:ok&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; %{name: name&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; email: email}}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;from_json&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;_payload&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;do&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    {:error&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; :invalid_signup_payload}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Now the conversion is explicit: you choose the atoms, and you decide which keys matter. Unknown data can be ignored, logged, validated, or rejected without being promoted into the VM&#39;s global vocabulary.&lt;/p&gt;
&lt;p&gt;The boundary I try to keep is boring:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;outside the application, JSON keys are strings&lt;/li&gt;
&lt;li&gt;inside the application, atom keys are fine&lt;/li&gt;
&lt;li&gt;at the boundary, convert only the fields you understand&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A little pattern matching at the edge beats a convenient decoder option that changes the cost of every unknown key.&lt;/p&gt;
&lt;h2&gt;When atom keys are okay&lt;/h2&gt;
&lt;p&gt;I still use atom keys. They are fine when the input is not really input anymore:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You decode a static fixture committed to the repository.&lt;/li&gt;
&lt;li&gt;You parse configuration that only your application authors can edit.&lt;/li&gt;
&lt;li&gt;You receive internal messages with a versioned schema.&lt;/li&gt;
&lt;li&gt;You use &lt;code&gt;keys: :atoms!&lt;/code&gt; and you want unknown keys to crash immediately.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Even then, I like making the choice visible near the code that knows why it is safe:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;elixir&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;load_fixture!&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(path) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;do&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  path&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;|&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;File&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;read!&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;|&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;Jason&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;decode!&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(keys: :atoms!)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;The option tells the next person something important: this is not arbitrary input.&lt;/p&gt;
&lt;p&gt;If that stops being true later, the decoder option should change too.&lt;/p&gt;
&lt;h2&gt;The boring rule&lt;/h2&gt;
&lt;p&gt;JSON keys should stay strings until you have a specific reason to turn them into something else.&lt;/p&gt;
&lt;p&gt;Atom keys are not evil. They are one of the nicest parts of writing Elixir data structures.&lt;/p&gt;
&lt;p&gt;But JSON is not an Elixir data structure yet, and pretending it is can make the edge of your system too soft.&lt;/p&gt;
&lt;p&gt;So my default is simple:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;elixir&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;Jason&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;decode!&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(body)&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Then pattern match, validate, and convert the fields I actually understand.&lt;/p&gt;
&lt;p&gt;It costs a few extra characters, and it keeps untrusted data from teaching the VM new words.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>AbortController is how fetch learned to clean up after itself</title>
    <link href="https://robinvdvleuten.nl/post/abortcontroller-is-how-fetch-learned-to-clean-up-after-itself/"/>
    <updated>2022-02-14T00:00:00Z</updated>
    <id>https://robinvdvleuten.nl/post/abortcontroller-is-how-fetch-learned-to-clean-up-after-itself/</id>
    <summary>React effects that fetch data can outlive the component that started them. AbortController gives us a small hook-friendly way to cancel that work.</summary>
    <content type="html">&lt;p&gt;Fetching data in a React component looks harmless at first.&lt;/p&gt;
&lt;p&gt;You render a component, call an API in an effect, store the result in state, and move on with your day. It feels too small to deserve much ceremony.&lt;/p&gt;
&lt;p&gt;Then the user navigates away before the request finishes.&lt;/p&gt;
&lt;p&gt;Or they type quickly and your component starts fetching search results for &lt;code&gt;r&lt;/code&gt;, &lt;code&gt;re&lt;/code&gt;, &lt;code&gt;rea&lt;/code&gt;, and &lt;code&gt;react&lt;/code&gt; at almost the same time.&lt;/p&gt;
&lt;p&gt;Or a slow response arrives after a newer one and quietly replaces the data with something stale.&lt;/p&gt;
&lt;p&gt;The fetch worked. The component moved on. Those two facts do not always happen in the same order.&lt;/p&gt;
&lt;p&gt;That is where &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/AbortController&quot;&gt;&lt;code&gt;AbortController&lt;/code&gt;&lt;/a&gt; earns its keep. It gives browser APIs like &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API&quot;&gt;&lt;code&gt;fetch()&lt;/code&gt;&lt;/a&gt; a cancellation signal, and React&#39;s &lt;a href=&quot;https://react.dev/reference/react/useEffect&quot;&gt;&lt;code&gt;useEffect&lt;/code&gt;&lt;/a&gt; gives us exactly one place to clean it up.&lt;/p&gt;
&lt;p&gt;The fit is small enough that it is easy to miss.&lt;/p&gt;
&lt;h2&gt;The request that outlives the component&lt;/h2&gt;
&lt;p&gt;Start with the version most of us write first:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;jsx&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; { useEffect&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; useState } &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;react&#39;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;UserProfile&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;({ userId }) {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;setUser&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;useState&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;useEffect&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;fetch&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;`/api/users/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;userId&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.then&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;((response) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;response&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.json&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;())&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.then&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;((data) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;setUser&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(data))&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  }&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; [userId])&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;user) {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;Loading...&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;h1&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;{&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.name}&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;h1&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;There is nothing exotic here. The component receives a &lt;code&gt;userId&lt;/code&gt;, fetches the matching user, and renders the name.&lt;/p&gt;
&lt;p&gt;The fast path is fine. The awkward bit starts when the component stops caring before the network does.&lt;/p&gt;
&lt;p&gt;Maybe the user clicks back. Maybe &lt;code&gt;userId&lt;/code&gt; changes. Maybe the request hangs for a few seconds on a train connection and resolves after a completely different screen has rendered.&lt;/p&gt;
&lt;p&gt;React effects already have a cleanup mechanism for exactly this kind of thing. If an effect returns a function, React runs that function before the effect runs again and when the component unmounts.&lt;/p&gt;
&lt;p&gt;Most of the time we use that for event listeners or timers. A network request deserves the same treatment.&lt;/p&gt;
&lt;h2&gt;Give fetch a signal&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;AbortController&lt;/code&gt; has two moving parts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the controller, which can cancel the work&lt;/li&gt;
&lt;li&gt;the signal, which gets passed to the thing doing the work&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With &lt;code&gt;fetch&lt;/code&gt;, that signal goes in the options object:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;jsx&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;useEffect&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;controller&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;AbortController&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;fetch&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;`/api/users/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;userId&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    signal&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;controller&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.signal&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  })&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.then&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;((response) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;response&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.json&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;())&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.then&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;((data) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;setUser&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(data))&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;controller&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.abort&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; [userId])&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Now every render that starts a request also knows how to stop it.&lt;/p&gt;
&lt;p&gt;When &lt;code&gt;userId&lt;/code&gt; changes, React runs the cleanup for the previous effect. When the component unmounts, same thing.&lt;/p&gt;
&lt;p&gt;There is one rough edge: aborting a fetch rejects the promise. If we do not handle that rejection, the console gets noisy.&lt;/p&gt;
&lt;p&gt;So we catch the abort and ignore that specific error:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;jsx&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;useEffect&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;controller&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;AbortController&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;fetch&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;`/api/users/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;userId&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    signal&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;controller&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.signal&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  })&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.then&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;((response) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;response&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.json&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;())&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.then&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;((data) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;setUser&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(data))&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.catch&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;((error) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.name &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;AbortError&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;throw&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; error&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    })&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;controller&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.abort&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; [userId])&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;The useful shape is the same every time: create the controller inside the effect, pass its signal to &lt;code&gt;fetch&lt;/code&gt;, abort it in the cleanup.&lt;/p&gt;
&lt;h2&gt;The hook is the point&lt;/h2&gt;
&lt;p&gt;If you only need this once, the effect above is fine. Ship it.&lt;/p&gt;
&lt;p&gt;The second copy is where the noise starts to show:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;loading state&lt;/li&gt;
&lt;li&gt;response state&lt;/li&gt;
&lt;li&gt;error state&lt;/li&gt;
&lt;li&gt;abort handling&lt;/li&gt;
&lt;li&gt;dependency changes&lt;/li&gt;
&lt;li&gt;cleanup&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That is a lot of plumbing to leave scattered through components.&lt;/p&gt;
&lt;p&gt;React hooks are good at this kind of extraction. Every three lines of code do not deserve a custom hook, but repeated lifecycle code has a way of growing little differences in every file.&lt;/p&gt;
&lt;p&gt;Let&#39;s move the fetch lifecycle into &lt;code&gt;useAbortableFetch&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;jsx&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; { useEffect&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; useState } &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;react&#39;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;useAbortableFetch&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(url) {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;setData&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;useState&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;setError&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;useState&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;loading&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;setLoading&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;useState&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;useEffect&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;controller&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;AbortController&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;setLoading&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;setError&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;fetch&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(url&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; { signal&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;controller&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.signal })&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.then&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;((response) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;response&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.ok) {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;          &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;throw&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Error&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;`Request failed with &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;response&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.status&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;response&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.json&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      })&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.then&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;((json) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;setData&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(json)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      })&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.catch&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;((error) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.name &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;AbortError&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;          &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;setError&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(error)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      })&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.finally&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;controller&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;signal&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.aborted) {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;          &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;setLoading&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      })&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;controller&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.abort&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  }&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; [url])&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; { data&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; error&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; loading }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Then the component gets boring again:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;jsx&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;UserProfile&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;({ userId }) {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; { data: &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;loading&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;useAbortableFetch&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;`/api/users/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;userId&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (loading) {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;Loading...&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (error) {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;Could not load the user.&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;user) {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;null&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;h1&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;{&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.name}&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;h1&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;That trade works well.&lt;/p&gt;
&lt;p&gt;The hook owns the awkward lifecycle. The component describes the screen.&lt;/p&gt;
&lt;p&gt;It also gives the cleanup behavior a name. &lt;code&gt;useAbortableFetch&lt;/code&gt; tells the next developer that cancellation is part of the contract. They do not have to inspect a chain of promises to discover whether the request cleans up after itself.&lt;/p&gt;
&lt;h2&gt;What happens when the URL changes quickly?&lt;/h2&gt;
&lt;p&gt;The hook gets more useful when the dependency changes often.&lt;/p&gt;
&lt;p&gt;Think about search:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;jsx&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;UserSearch&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;({ query }) {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; { data: &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;users&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;loading&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;useAbortableFetch&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;`/api/users?query=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;encodeURIComponent&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(query)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;`&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  )&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (loading) {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;Searching...&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;ul&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      {&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;users&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;?.map&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;((user) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;li&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.id}&amp;gt;{&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.name}&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;li&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      ))}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;ul&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  )&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Every time &lt;code&gt;query&lt;/code&gt; changes, the old request gets aborted before the new request starts.&lt;/p&gt;
&lt;p&gt;That does not make the server do less work in every possible situation. The request may already have reached it.&lt;/p&gt;
&lt;p&gt;It does stop the browser from continuing to care about a response the UI no longer needs.&lt;/p&gt;
&lt;p&gt;This pairs nicely with debouncing. I wrote separately about &lt;a href=&quot;https://robinvdvleuten.nl/post/debouncing-a-react-input-should-not-require-a-dependency/&quot;&gt;debouncing a React input without adding a dependency&lt;/a&gt;, and the two ideas solve different halves of the same problem:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;debounce before starting work you probably do not need&lt;/li&gt;
&lt;li&gt;abort work that has already become irrelevant&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;They are not replacements for each other. They solve different timing problems.&lt;/p&gt;
&lt;h2&gt;A small note about stale state&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;AbortController&lt;/code&gt; cancels a request, but it does not magically solve every stale state problem.&lt;/p&gt;
&lt;p&gt;If your effect does more work after the fetch, or if some other async function does not support abort signals, you still need to think about what happens when old work finishes late. Sometimes the right answer is a local &lt;code&gt;ignore&lt;/code&gt; flag in the effect cleanup. The React docs show that pattern in their &lt;a href=&quot;https://react.dev/reference/react/useEffect&quot;&gt;&lt;code&gt;useEffect&lt;/code&gt; examples&lt;/a&gt; for data fetching.&lt;/p&gt;
&lt;p&gt;For browser fetches, though, aborting is usually nicer than pretending the result did not arrive. We are telling the underlying operation that we are done with it instead of merely guarding the &lt;code&gt;setState&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The contract is easier to reason about.&lt;/p&gt;
&lt;h2&gt;Why this belongs in a hook&lt;/h2&gt;
&lt;p&gt;This is the part I care about more than the API itself.&lt;/p&gt;
&lt;p&gt;The browser gives us &lt;code&gt;AbortController&lt;/code&gt;. React gives us effects and cleanup. The useful application code is the small hook that makes those two ideas meet in one place.&lt;/p&gt;
&lt;p&gt;That is the same reason I like tiny hooks such as &lt;a href=&quot;https://robinvdvleuten.nl/post/use-previous-value-through-a-react-hook/&quot;&gt;&lt;code&gt;usePrevious&lt;/code&gt;&lt;/a&gt;. A ref and an effect are not complicated by themselves, but the hook turns the pattern into a reusable sentence: &amp;quot;give me the previous value.&amp;quot;&lt;/p&gt;
&lt;p&gt;This one says: &amp;quot;fetch this, and stop caring when the component does.&amp;quot;&lt;/p&gt;
&lt;p&gt;That beats copy-pasting another effect and hoping every cleanup branch survives the next refactor.&lt;/p&gt;
&lt;h2&gt;When I reach for it&lt;/h2&gt;
&lt;p&gt;I do not wrap every request in a custom hook. Sometimes a framework, router, or data library already owns the lifecycle, cache, retries, and invalidation. In that case, use the tool that is already solving the bigger problem.&lt;/p&gt;
&lt;p&gt;But when a component does its own browser fetch, especially in smaller React codebases, &lt;code&gt;AbortController&lt;/code&gt; is a good default.&lt;/p&gt;
&lt;p&gt;Use it when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the component can unmount before the request finishes&lt;/li&gt;
&lt;li&gt;the request depends on props or state that change often&lt;/li&gt;
&lt;li&gt;a search, filter, or tab switch can make old results irrelevant&lt;/li&gt;
&lt;li&gt;you want loading and error state to be handled consistently&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The hook is small enough to understand and boring enough to trust. That is exactly where I want this kind of code to live.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Use previous value through a React hook</title>
    <link href="https://robinvdvleuten.nl/post/use-previous-value-through-a-react-hook/"/>
    <updated>2022-01-13T00:00:00Z</updated>
    <id>https://robinvdvleuten.nl/post/use-previous-value-through-a-react-hook/</id>
    <summary>A small usePrevious hook can store the previous render&#39;s value by combining React&#39;s useRef and useEffect hooks.</summary>
    <content type="html">&lt;p&gt;React Hooks took me a little while to get used to after they were &lt;a href=&quot;https://reactjs.org/blog/2019/02/06/react-v16.8.0.html&quot;&gt;introduced in React v16.8.0&lt;/a&gt;. After using them for a while, the value became clear: custom hooks let you move repeated component logic into small functions.&lt;/p&gt;
&lt;p&gt;After building several applications with hooks, I ended up with a small library of patterns I reuse often.&lt;/p&gt;
&lt;p&gt;One of those patterns is reading the previous value of state or props. It only takes a few lines and uses &lt;a href=&quot;https://react.dev/reference/react/useRef&quot;&gt;&lt;code&gt;useRef()&lt;/code&gt;&lt;/a&gt;. A ref can point at an HTML element, but it can also store any value you assign to it.&lt;/p&gt;
&lt;p&gt;Combined with &lt;a href=&quot;https://react.dev/reference/react/useEffect&quot;&gt;&lt;code&gt;useEffect()&lt;/code&gt;&lt;/a&gt;, that gives us a small &lt;code&gt;usePrevious()&lt;/code&gt; hook:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;tsx&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; React &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;react&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;usePrevious&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;T&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;(value&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;T&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;T&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// Create a reference to hold the previous version of the value,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// as it is basically a generic object whose `current` property can hold any value.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;ref&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;React&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.useRef&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;T&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;();&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// Use the `useEffect` hook to run a callback...&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;React&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.useEffect&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// ...to store the passed value on the ref&#39;s current property...&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;ref&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.current &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; value;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  }&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; [value]); &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// ...whenever the value changes.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// And return the currently stored value,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// as this will run before the `useEffect` callback runs.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;ref&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.current;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h2&gt;What to do with this hook?&lt;/h2&gt;
&lt;p&gt;When you combine &lt;code&gt;usePrevious()&lt;/code&gt; with state, you can run logic when a value differs from the previous render. I often use it to compare boolean values, like a loading state:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;tsx&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; React &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;react&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;Application&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// Call some external API through a custom hook.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;loading&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;useAsyncApi&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// Pass its loading indicator to our created hook.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;wasLoading&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;usePrevious&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;boolean&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;(loading);&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// Use the `useEffect` hook to run a callback...&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;React&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.useEffect&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// ...to check if the API was loading but now it is completed...&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;loading &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;&amp;&amp;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; wasLoading) {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// ...and run some arbitary code...&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  }&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; [loading&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; wasLoading]); &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// ...whenever one of these changes.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      {&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;/* ... */&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  );&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;It is a small hook, but it is useful whenever the current render needs to know what changed since the previous one.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Rails credentials are not a replacement for configuration</title>
    <link href="https://robinvdvleuten.nl/post/rails-credentials-are-not-a-replacement-for-configuration/"/>
    <updated>2021-08-23T00:00:00Z</updated>
    <id>https://robinvdvleuten.nl/post/rails-credentials-are-not-a-replacement-for-configuration/</id>
    <summary>Rails credentials are great for secrets, but terrible as a junk drawer. Keep secrets encrypted, and keep ordinary configuration visible.</summary>
    <content type="html">&lt;p&gt;You&#39;re adding Stripe to a Rails app. Nothing fancy, just a few settings:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;yaml&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;stripe&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;publishable_key&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;pk_test_...&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;secret_key&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;sk_test_...&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;webhook_secret&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;whsec_...&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;checkout_success_path&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;/billing/success&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;checkout_cancel_path&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;/billing&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;The first three values are secrets. The last two are not.&lt;/p&gt;
&lt;p&gt;Once you&#39;ve opened &lt;code&gt;bin/rails credentials:edit&lt;/code&gt;, though, it&#39;s very tempting to put the whole block in there and move on.&lt;/p&gt;
&lt;p&gt;The app boots. The file is encrypted. The pull request doesn&#39;t expose anything awkward.&lt;/p&gt;
&lt;p&gt;That&#39;s where the trouble starts: the encrypted file no longer contains only secrets. It also contains product behaviour, routes, public keys, and little operational choices that someone may need to review later.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://guides.rubyonrails.org/security.html#custom-credentials&quot;&gt;Rails credentials&lt;/a&gt; solve one problem: keeping secrets out of plain text. They don&#39;t solve the broader problem of making application configuration understandable, reviewable, and easy to change.&lt;/p&gt;
&lt;p&gt;Those are different jobs.&lt;/p&gt;
&lt;h2&gt;The credentials junk drawer&lt;/h2&gt;
&lt;p&gt;I like Rails credentials. They&#39;re a huge improvement over copying API keys between wiki pages, password managers, and forgotten &lt;code&gt;.env&lt;/code&gt; files. Rails gives you an encrypted &lt;code&gt;config/credentials.yml.enc&lt;/code&gt;, keeps the master key out of Git, and exposes the values through &lt;a href=&quot;https://api.rubyonrails.org/classes/Rails/Application.html#method-i-credentials&quot;&gt;&lt;code&gt;Rails.application.credentials&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Good machinery.&lt;/p&gt;
&lt;p&gt;The trouble starts when every setting begins to look secret-shaped:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;yaml&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;stripe&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;secret_key&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;sk_live_...&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;webhook_secret&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;whsec_...&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;statement_descriptor&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;ACME&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;trial_days&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;14&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;plans&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;starter&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;price_123&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;business&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;price_456&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Some of that belongs there. Some of it doesn&#39;t.&lt;/p&gt;
&lt;p&gt;The trial length is product behaviour. The plan mapping might be business configuration. The statement descriptor is visible to customers. If all of that lives inside an encrypted file, the interesting change disappears from the review:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;diff&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;- trial_days: 14&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;+ trial_days: 30&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;That is not a secret. That&#39;s a product decision.&lt;/p&gt;
&lt;p&gt;When it lives in credentials, nobody sees it in a pull request. Nobody comments on it. Staging and production can drift apart for weeks, and the first person to notice might be a customer.&lt;/p&gt;
&lt;p&gt;That&#39;s how a security feature turns into a junk drawer.&lt;/p&gt;
&lt;h2&gt;Secrets have a smell&lt;/h2&gt;
&lt;p&gt;These days I ask a simpler question first:&lt;/p&gt;
&lt;p&gt;If a value would hurt you when it leaks, it belongs in credentials or another secret store. If a value only changes how the application behaves, it probably belongs somewhere visible.&lt;/p&gt;
&lt;p&gt;That makes these good candidates for credentials:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;API secret keys&lt;/li&gt;
&lt;li&gt;webhook signing secrets&lt;/li&gt;
&lt;li&gt;OAuth client secrets&lt;/li&gt;
&lt;li&gt;encryption keys&lt;/li&gt;
&lt;li&gt;passwords for external services&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And these usually deserve a different home:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;feature flags&lt;/li&gt;
&lt;li&gt;retry counts&lt;/li&gt;
&lt;li&gt;public API keys&lt;/li&gt;
&lt;li&gt;price identifiers&lt;/li&gt;
&lt;li&gt;route names&lt;/li&gt;
&lt;li&gt;copy, labels, and email addresses shown to users&lt;/li&gt;
&lt;li&gt;timeouts that explain product behaviour&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are fuzzy cases. A Stripe price ID isn&#39;t a password, but maybe you don&#39;t want it casually exposed. A public key is called a key, but the whole point is that it gets sent to the browser.&lt;/p&gt;
&lt;p&gt;So I don&#39;t start with &amp;quot;can Rails credentials store this?&amp;quot;&lt;/p&gt;
&lt;p&gt;They can. The better question is &amp;quot;should this change be reviewable?&amp;quot;&lt;/p&gt;
&lt;h2&gt;Use plain configuration for plain facts&lt;/h2&gt;
&lt;p&gt;Rails already gives us a few places for ordinary configuration.&lt;/p&gt;
&lt;p&gt;For small, code-shaped values, &lt;a href=&quot;https://guides.rubyonrails.org/configuring.html#custom-configuration&quot;&gt;&lt;code&gt;config.x&lt;/code&gt;&lt;/a&gt; is often enough:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;ruby&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;# config/application.rb&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;config&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;x&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;billing&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;trial_days &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;14&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;config&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;x&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;billing&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;success_path &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;/billing/success&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;config&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;x&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;billing&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;cancel_path &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;/billing&quot;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Then your code reads the setting without pretending it&#39;s secret:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;ruby&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;redirect_to &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Rails&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;configuration&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;x&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;billing&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;success_path&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;For larger configuration, a YAML file can be clearer:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;yaml&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;# config/billing.yml&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;shared&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;trial_days&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;14&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;development&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;stripe_publishable_key&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;pk_test_...&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;success_path&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;/billing/success&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;production&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;stripe_publishable_key&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;pk_live_...&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;success_path&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;/billing/success&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Load it with &lt;a href=&quot;https://api.rubyonrails.org/classes/Rails/Application.html#method-i-config_for&quot;&gt;&lt;code&gt;config_for&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;ruby&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;# config/application.rb&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;config&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;x&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;billing &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; config_for(:billing)&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Now the ordinary settings show up as ordinary code review material:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;ruby&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Rails&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;configuration&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;x&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;billing[&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;trial_days&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;The actual secret can stay in credentials:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;ruby&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Stripe&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;api_key &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Rails&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;application&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;credentials&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;dig(:stripe&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; :secret_key)&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;The split looks boring, which is exactly the point. Secrets go where secrets go. Configuration stays where humans can see it.&lt;/p&gt;
&lt;h2&gt;Environment variables still have a place&lt;/h2&gt;
&lt;p&gt;There&#39;s another trap here: turning the credentials file into the only source of runtime truth.&lt;/p&gt;
&lt;p&gt;Credentials are great for values you want to deploy with the app. Environment variables are still useful for values owned by the environment:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;ruby&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;config&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;x&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;app&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;host &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;ENV&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;fetch(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;APP_HOST&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;config&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;x&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;worker&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;concurrency &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;ENV&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;fetch(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;WORKER_CONCURRENCY&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;to_i&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;The hostname depends on where the app runs. Worker concurrency may depend on the machine size. A one-off review app might need a different callback URL.&lt;/p&gt;
&lt;p&gt;Encryption doesn&#39;t make those values better. It makes them harder to operate.&lt;/p&gt;
&lt;p&gt;I don&#39;t want to redeploy encrypted credentials because production moved to a larger instance. I want the environment to tell the app what the environment is.&lt;/p&gt;
&lt;h2&gt;Make missing configuration loud&lt;/h2&gt;
&lt;p&gt;The worst configuration bug is the one that quietly falls back to something plausible.&lt;/p&gt;
&lt;p&gt;This is especially true when secrets and non-secrets meet:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;ruby&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Stripe&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;api_key &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Rails&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;application&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;credentials&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;dig(:stripe&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; :secret_key)&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;If that returns &lt;code&gt;nil&lt;/code&gt;, the app may boot happily and fail later, somewhere far away from the mistake.&lt;/p&gt;
&lt;p&gt;Make required values explicit:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;ruby&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Stripe&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;api_key &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Rails&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;application&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;credentials&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;dig(:stripe&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; :secret_key) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;||&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;raise&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;Missing stripe.secret_key in Rails credentials&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;For environment variables, &lt;code&gt;ENV.fetch&lt;/code&gt; does the same kind of work:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;ruby&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Rails&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;application&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;routes&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;default_url_options[:host] &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;ENV&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;fetch(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;APP_HOST&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;A boot-time crash is annoying for about thirty seconds. Finding the same mistake through a broken checkout flow is worse.&lt;/p&gt;
&lt;h2&gt;A small decision table&lt;/h2&gt;
&lt;p&gt;When I&#39;m unsure where something belongs, I run through this:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Would leaking this value create a security problem?&lt;/strong&gt; Put it in credentials or a secret manager.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Would I want to review a change to this value in a pull request?&lt;/strong&gt; Keep it in code or a plain config file.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Does the hosting environment own this value?&lt;/strong&gt; Read it from &lt;code&gt;ENV&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Is it really product behaviour?&lt;/strong&gt; Give it a name in the codebase where the team can find it.&lt;/p&gt;
&lt;p&gt;That last one is the easy one to miss. Product behaviour loves disguising itself as configuration:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;trial length&lt;/li&gt;
&lt;li&gt;retry limits&lt;/li&gt;
&lt;li&gt;allowed file sizes&lt;/li&gt;
&lt;li&gt;billing plan mappings&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;They look like knobs, but they often encode decisions. I want those changes in a diff where someone can ask why.&lt;/p&gt;
&lt;h2&gt;The boring split&lt;/h2&gt;
&lt;p&gt;My Rails apps are calmer when the split looks roughly like this:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;ruby&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;# Secret&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Rails&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;application&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;credentials&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;dig(:stripe&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; :secret_key)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;# Environment-owned&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;ENV&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;fetch(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;APP_HOST&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;# Application-owned&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Rails&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;configuration&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;x&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;billing&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;trial_days&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Credentials are not bad. They&#39;re easy to overuse because they make awkward things disappear.&lt;/p&gt;
&lt;p&gt;But the awkwardness is often the point.&lt;/p&gt;
&lt;p&gt;If a value changes behaviour, I want the team to see it when it changes.&lt;/p&gt;
&lt;p&gt;Rails gives us enough places to put each kind of value. The trick is not using encrypted credentials for everything.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>URL state is still state</title>
    <link href="https://robinvdvleuten.nl/post/url-state-is-still-state/"/>
    <updated>2021-03-15T00:00:00Z</updated>
    <id>https://robinvdvleuten.nl/post/url-state-is-still-state/</id>
    <summary>Filters, tabs, and pagination often belong in the URL, not hidden inside React state where reloads and shared links lose them.</summary>
    <content type="html">&lt;p&gt;I&#39;ve built this screen more times than I can count: a table of orders, a search field, a status filter, maybe a date range, a few tabs across the top, pagination at the bottom.&lt;/p&gt;
&lt;p&gt;Nothing dramatic.&lt;/p&gt;
&lt;p&gt;You wire it up with React state because that is the obvious place for state to go:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;jsx&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;status&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;setStatus&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;useState&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;open&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;page&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;setPage&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;useState&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;setQuery&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;useState&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;It works, right up to the moment someone refreshes the page and loses the filter they were looking at. Or sends a link to a colleague and the colleague lands on the default view. Or clicks the back button expecting to undo the last filter change and leaves the screen entirely.&lt;/p&gt;
&lt;p&gt;The problem isn&#39;t React state. Some state just belongs in the URL.&lt;/p&gt;
&lt;h2&gt;The state you can point at&lt;/h2&gt;
&lt;p&gt;Not every bit of interface state deserves a URL. Whether a dropdown is open? Probably not. The current value of an unfinished form field? Usually no. The hover state of a button? Please don&#39;t.&lt;/p&gt;
&lt;p&gt;But if the state changes what the page &lt;em&gt;means&lt;/em&gt;, the URL should at least be considered.&lt;/p&gt;
&lt;p&gt;Things like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the active tab&lt;/li&gt;
&lt;li&gt;search queries&lt;/li&gt;
&lt;li&gt;filters&lt;/li&gt;
&lt;li&gt;sorting&lt;/li&gt;
&lt;li&gt;pagination&lt;/li&gt;
&lt;li&gt;selected date ranges&lt;/li&gt;
&lt;li&gt;compact vs detailed views&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Those are more than UI details. They are part of the resource the user is looking at. If I&#39;m looking at paid invoices from March on page three, that should be something I can bookmark, reload, or send to someone else.&lt;/p&gt;
&lt;p&gt;The URL is how the page makes that state portable.&lt;/p&gt;
&lt;h2&gt;The hidden cost of keeping it local&lt;/h2&gt;
&lt;p&gt;Here is the kind of component I used to write without thinking much about it:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;jsx&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;OrdersPage&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;status&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;setStatus&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;useState&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;open&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &amp;lt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;select&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{status} &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;onChange&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{(event) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;setStatus&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;target&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.value)}&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;option&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;open&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;Open&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;option&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;option&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;paid&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;Paid&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;option&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;option&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;cancelled&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;Cancelled&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;option&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;select&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;OrdersTable&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;status&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{status} /&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &amp;lt;/&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  )&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;There is nothing wrong with this in isolation. The problem is where the memory lives.&lt;/p&gt;
&lt;p&gt;The state only exists as long as the component does. Refresh the page and you&#39;re back to &lt;code&gt;open&lt;/code&gt;. Copy the URL and the current filter disappears. Open the same screen in a new tab and you start over.&lt;/p&gt;
&lt;p&gt;The interface remembers. The page doesn&#39;t.&lt;/p&gt;
&lt;h2&gt;Let the URL carry the boring parts&lt;/h2&gt;
&lt;p&gt;The browser already has a place for this kind of state: query parameters.&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;text&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: undefined&quot;&gt;/orders?status=paid&amp;page=3&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;It is not glamorous, but it is useful. You can read it on load, update it when filters change, and let the browser do what browsers are good at: preserve location.&lt;/p&gt;
&lt;p&gt;A tiny version can look like this with &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams&quot;&gt;&lt;code&gt;URLSearchParams&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;jsx&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;readFilters&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;params&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;URLSearchParams&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;window&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;location&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.search)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    status&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;params&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.get&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;status&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;open&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    page&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Number&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;params&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.get&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;page&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;1&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Now the component starts from the URL instead of inventing its own private truth:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;jsx&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;OrdersPage&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;filters&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;setFilters&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;useState&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(readFilters)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;updateFilters&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(nextFilters) {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;params&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;URLSearchParams&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;params&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.set&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;status&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;nextFilters&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.status)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;params&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.set&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;page&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;nextFilters&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.page))&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;window&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;history&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.replaceState&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;`?&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;params&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.toString&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;setFilters&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(nextFilters)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &amp;lt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;select&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;filters&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.status}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;onChange&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{(event) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;          &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;updateFilters&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;({ status&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;target&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.value&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; page&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; })&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;option&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;open&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;Open&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;option&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;option&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;paid&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;Paid&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;option&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;option&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;cancelled&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;Cancelled&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;option&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;select&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;OrdersTable&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;status&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;filters&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.status} &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;page&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;filters&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.page} /&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &amp;lt;/&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  )&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Notice the small detail when the status changes: the page goes back to &lt;code&gt;1&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Changing the status invalidates the current pagination, so reset it. That rule is easier to see when filters are treated as one piece of state instead of scattered across a component.&lt;/p&gt;
&lt;h2&gt;Push or replace?&lt;/h2&gt;
&lt;p&gt;There are two browser APIs you will usually reach for:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;js&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;window&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;history&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.pushState&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; url)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;window&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;history&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.replaceState&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; url)&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;The difference is whether you want a new browser history entry. For pagination, &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/History/pushState&quot;&gt;&lt;code&gt;pushState&lt;/code&gt;&lt;/a&gt; often feels right. If a user clicks from page one to page two, the back button taking them back to page one makes sense.&lt;/p&gt;
&lt;p&gt;For every keystroke in a search field, &lt;code&gt;pushState&lt;/code&gt; is usually awful. Nobody wants to press back fifteen times because they typed &amp;quot;invoice&amp;quot;. That&#39;s where &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/History/replaceState&quot;&gt;&lt;code&gt;replaceState&lt;/code&gt;&lt;/a&gt; is useful: it updates the URL without turning every tiny change into a history event.&lt;/p&gt;
&lt;p&gt;A decent rule of thumb:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use &lt;code&gt;pushState&lt;/code&gt; for deliberate navigation.&lt;/strong&gt; Tabs, pagination, saved views, anything that feels like moving to another version of the page.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use &lt;code&gt;replaceState&lt;/code&gt; for refinement.&lt;/strong&gt; Search text, sliders, fast filters, and anything that changes too often to deserve its own back-button stop.&lt;/p&gt;
&lt;p&gt;You will still make judgement calls. That&#39;s fine. Just make them deliberately.&lt;/p&gt;
&lt;h2&gt;URL state is shareable state&lt;/h2&gt;
&lt;p&gt;Once filters live in the URL, a few things get better without adding much code.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Reloads stop being destructive.&lt;/strong&gt; The user can refresh the page and stay exactly where they were.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Links become useful again.&lt;/strong&gt; Support can ask for a URL and see the same filtered view. A teammate can send &amp;quot;the cancelled orders from last week&amp;quot; without writing instructions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Browser history starts making sense.&lt;/strong&gt; The back button can move through meaningful states instead of leaving the screen unexpectedly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Debugging gets easier.&lt;/strong&gt; You can paste a URL into a fresh tab and reproduce the same state without clicking through the UI again.&lt;/p&gt;
&lt;p&gt;That&#39;s a lot of value for a query string.&lt;/p&gt;
&lt;h2&gt;Don&#39;t put everything there&lt;/h2&gt;
&lt;p&gt;The URL is not a dumping ground. I have seen people push entire form drafts into query parameters, encode blobs of JSON, or store state that only makes sense for a single render.&lt;/p&gt;
&lt;p&gt;That way lies sadness.&lt;/p&gt;
&lt;p&gt;A URL should be readable enough that a human can squint at it and understand the shape of the page:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;text&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: undefined&quot;&gt;/orders?status=paid&amp;page=3&amp;sort=created_at&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Readable. Useful.&lt;/p&gt;
&lt;p&gt;This is not:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;text&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: undefined&quot;&gt;/orders?state=eyJ0YWJzIjpbeyJpZCI6...&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;At that point you are not using the URL so much as smuggling an application store through it.&lt;/p&gt;
&lt;p&gt;Use the URL for state that describes the view. Keep short-lived interaction state in React. Keep private or sensitive state out of it entirely.&lt;/p&gt;
&lt;h2&gt;The useful question&lt;/h2&gt;
&lt;p&gt;When I&#39;m not sure whether something belongs in the URL, I ask one question: would it be useful if this survived a refresh?&lt;/p&gt;
&lt;p&gt;If the answer is yes, it probably should not live only in component state:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Filters? Yes.&lt;/li&gt;
&lt;li&gt;Current page? Usually.&lt;/li&gt;
&lt;li&gt;Open modal? Maybe.&lt;/li&gt;
&lt;li&gt;Hovered row? Absolutely not.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That question has saved me from a surprising amount of awkward UI. URL state is not fancy. It just makes the page explain itself.&lt;/p&gt;
&lt;p&gt;Good URLs are for people using the thing, not for routers and crawlers alone. Filters survive reloads. Shared links open the right view. The page keeps enough context to be useful when you come back to it.&lt;/p&gt;
&lt;p&gt;Just a page you can point at.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Making Tailwind stick in existing stylesheets</title>
    <link href="https://robinvdvleuten.nl/post/making-tailwind-stick-in-existing-stylesheets/"/>
    <updated>2021-02-26T00:00:00Z</updated>
    <id>https://robinvdvleuten.nl/post/making-tailwind-stick-in-existing-stylesheets/</id>
    <summary>One Tailwind configuration option can make gradual adoption calmer when old CSS is still part of the project.</summary>
    <content type="html">&lt;p&gt;A few years ago I wrote about &lt;a href=&quot;https://robinvdvleuten.nl/post/how-i-started-thinking-about-my-styles/&quot;&gt;how I started thinking about my styles&lt;/a&gt;. That post was mostly about the mental shift: stop treating every bit of styling as something that needs a beautiful component name first.&lt;/p&gt;
&lt;p&gt;That still feels right to me.&lt;/p&gt;
&lt;p&gt;Tailwind makes the idea practical in a way the older atomic CSS tools never quite did. You can build a screen with small decisions, right there in the markup, and the result is usually easier to change than a pile of hopeful class names.&lt;/p&gt;
&lt;p&gt;Most projects do not start with an empty stylesheet, though.&lt;/p&gt;
&lt;p&gt;They start with a few years of Sass, a CMS theme, a typography wrapper, button classes from three redesigns ago, and one selector that nobody wants to touch because it is probably load-bearing.&lt;/p&gt;
&lt;p&gt;That is where Tailwind can feel surprisingly fragile.&lt;/p&gt;
&lt;h2&gt;The awkward middle&lt;/h2&gt;
&lt;p&gt;The awkward middle is the phase where Tailwind is already useful, but old CSS is still everywhere.&lt;/p&gt;
&lt;p&gt;You add a utility because you want the next change to be local:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;html&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;button bg-gray-900 text-white px-4 py-2&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  Continue&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Then the browser keeps the button blue.&lt;/p&gt;
&lt;p&gt;Somewhere else, a selector like this is waiting:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;css&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.checkout&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.button&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;background&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;#1d4ed8&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;color&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;white&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;The utility did nothing wrong. The old selector is just heavier.&lt;/p&gt;
&lt;p&gt;This is the part I dislike most about gradual adoption. Not the old CSS itself. Old CSS is allowed to exist. The annoying part is that every utility becomes a question instead of an answer.&lt;/p&gt;
&lt;p&gt;Will this class apply here? Does the parent have a special wrapper? Is there a more specific rule in a file I forgot about? Why did changing a background color turn into archaeology?&lt;/p&gt;
&lt;h2&gt;One boring configuration line&lt;/h2&gt;
&lt;p&gt;Tailwind has an &lt;a href=&quot;https://v3.tailwindcss.com/docs/configuration#important&quot;&gt;&lt;code&gt;important&lt;/code&gt; configuration option&lt;/a&gt; that marks every utility declaration as &lt;code&gt;!important&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;js&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// tailwind.config.js&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;module&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;exports&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  important&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;It is not subtle. That is the point.&lt;/p&gt;
&lt;p&gt;With that option enabled, Tailwind emits utilities that are much harder for old component CSS to override by accident.&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;css&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.bg-gray-900&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;background-color&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;#111827 &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;!important&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;The markup starts behaving like the markup again:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;html&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;button bg-gray-900 text-white px-4 py-2&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  Continue&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Now &lt;code&gt;bg-gray-900&lt;/code&gt; is not a suggestion. It is the decision for this element.&lt;/p&gt;
&lt;p&gt;That sounds dangerous if you&#39;ve spent years learning to avoid &lt;code&gt;!important&lt;/code&gt;. I have too. Most random &lt;code&gt;!important&lt;/code&gt; declarations are a smell because they are local patches without a larger rule behind them.&lt;/p&gt;
&lt;p&gt;This is different. It is one rule for one layer.&lt;/p&gt;
&lt;p&gt;Tailwind utilities become the place where small overrides live. Existing CSS can keep handling the broad strokes while you move the project over piece by piece.&lt;/p&gt;
&lt;h2&gt;Why I like it before everything is Tailwind&lt;/h2&gt;
&lt;p&gt;When a project is already fully written with Tailwind, this option matters less. There are fewer old selectors trying to compete with your utilities.&lt;/p&gt;
&lt;p&gt;The benefit shows up when you are not there yet.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It makes migration incremental.&lt;/strong&gt; You can improve one template without first flattening the entire CSS cascade around it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It keeps changes close to the markup.&lt;/strong&gt; If a link needs a different size, the change is visible on the link instead of hidden in another partial.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It reduces tiny naming problems.&lt;/strong&gt; You do not need &lt;code&gt;.button--checkout-but-slightly-darker&lt;/code&gt; just because one button is special.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It avoids selector escalation.&lt;/strong&gt; Without it, the easy fix is often writing a more specific selector. That works once. Then the next person has to beat yours.&lt;/p&gt;
&lt;p&gt;That last point is the trap. Specificity rarely grows because someone planned it. It grows because every small change needs to survive the selectors that came before it.&lt;/p&gt;
&lt;h2&gt;Where it can bite&lt;/h2&gt;
&lt;p&gt;There is one trade-off I always check before enabling it: JavaScript that writes inline styles.&lt;/p&gt;
&lt;p&gt;Some libraries control elements by setting styles directly. Think maps, sliders, animations, or widgets that bring their own opinions. If those inline styles need to beat Tailwind classes, global important utilities can get in the way.&lt;/p&gt;
&lt;p&gt;For most application markup, I prefer class changes anyway:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;html&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;hidden md:block&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  ...&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;That is easier to reason about than mixing classes with &lt;code&gt;style=&amp;quot;display: block&amp;quot;&lt;/code&gt;. But third-party code does not always ask what I prefer, which is rude but common.&lt;/p&gt;
&lt;p&gt;So I would not enable this blindly in a project with lots of DOM-manipulating widgets. I would test those first.&lt;/p&gt;
&lt;h2&gt;Closing thoughts&lt;/h2&gt;
&lt;p&gt;I still do not like seeing &lt;code&gt;!important&lt;/code&gt; scattered through a stylesheet. It usually means nobody knows who is supposed to win anymore.&lt;/p&gt;
&lt;p&gt;But enabling it for Tailwind utilities is not scattering. It is drawing a boundary.&lt;/p&gt;
&lt;p&gt;Old CSS can keep doing what it does while you move forward. New markup gets a calmer way to express small styling decisions. You avoid the selector arms race, the mystery modifier names, and the fantasy that the old cascade is simpler than it is.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>How to use Vim on Linux and macOS</title>
    <link href="https://robinvdvleuten.nl/post/how-to-use-vim-on-linux-and-macos/"/>
    <updated>2020-12-07T00:00:00Z</updated>
    <id>https://robinvdvleuten.nl/post/how-to-use-vim-on-linux-and-macos/</id>
    <summary>A tiny Vim survival guide for opening a file, making a change, saving it, and getting back out again.</summary>
    <content type="html">&lt;p&gt;If you have ever tried to edit files on Linux through the terminal, you have probably run into Vim. It is a text editor that ships with macOS and many Linux distributions.&lt;/p&gt;
&lt;p&gt;It is handy, but confusing the first time you open it by accident. Exiting Vim is still one of &lt;a href=&quot;https://stackoverflow.com/questions/11828270/how-do-i-exit-the-vim-editor&quot;&gt;the most viewed questions&lt;/a&gt; on Stack Overflow for a reason.&lt;/p&gt;
&lt;p&gt;Here are the few commands you need to open a file, make a change, and exit again.&lt;/p&gt;
&lt;h2&gt;How to open a file&lt;/h2&gt;
&lt;p&gt;Use &lt;code&gt;vim&lt;/code&gt; or &lt;code&gt;vi&lt;/code&gt; with the name of the file you want to open. If the file does not exist in the current directory, Vim creates it.&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;bash&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;vi file-to-open.txt&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h2&gt;How to edit a file&lt;/h2&gt;
&lt;p&gt;When a file opens, Vim starts in &lt;strong&gt;normal mode&lt;/strong&gt;. You can read the contents and run commands, but typing will not insert text yet. Press &lt;code&gt;i&lt;/code&gt; to enter &lt;strong&gt;insert mode&lt;/strong&gt;. In insert mode, your keystrokes change the text. Press &lt;code&gt;&amp;lt;ESC&amp;gt;&lt;/code&gt; to return to normal mode.&lt;/p&gt;
&lt;p&gt;Other Vim commands start with &lt;code&gt;:&lt;/code&gt; and can only be entered from normal mode.&lt;/p&gt;
&lt;p&gt;A good overview is provided by the following reference diagram.&lt;/p&gt;
&lt;figure class=&quot;sm:col-start-2 sm:col-span-9&quot;&gt;
    &lt;img class=&quot;object-contain h-56 w-full&quot; src=&quot;https://robinvdvleuten.nl/images/vim-modes.png&quot; alt=&quot;An overview of all available Vim modes&quot;&gt;
    &lt;figcaption class=&quot;text-xs text-center mt-4&quot;&gt;
        Source: &lt;a href=&quot;https://github.com/nilp0inter/Vim-for-Muggles&quot;&gt;https://github.com/nilp0inter/Vim-for-Muggles&lt;/a&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2&gt;How to exit Vim&lt;/h2&gt;
&lt;p&gt;When you are done, press &lt;code&gt;&amp;lt;ESC&amp;gt;&lt;/code&gt; to make sure you are in normal mode. Then use &lt;code&gt;:q&lt;/code&gt; or one of its siblings:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;vim&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;:q&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt; - &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;just quit when you don&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;t have any changes&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;:q&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;! - &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;quit and discard any changes you&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;ve made&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;:wq&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt; - &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;quit and save all changes &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;to&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; the file&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
</content>
  </entry>
  <entry>
    <title>How to capitalize strings in JavaScript</title>
    <link href="https://robinvdvleuten.nl/post/how-to-capitalize-strings-in-javascript/"/>
    <updated>2020-11-11T00:00:00Z</updated>
    <id>https://robinvdvleuten.nl/post/how-to-capitalize-strings-in-javascript/</id>
    <summary>Capitalizing a string does not need another npm dependency. Plain JavaScript is enough, and CSS may be better when it is only presentation.</summary>
    <content type="html">&lt;p&gt;To capitalize a JavaScript string, you do not need another npm dependency. Plain JavaScript is enough, and CSS may be the better choice if the change is only visual.&lt;/p&gt;
&lt;h2&gt;The short version&lt;/h2&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;js&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;chars&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;hello&#39;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;chars[&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.toUpperCase&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;chars&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.slice&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// &#39;Hello&#39;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h2&gt;Walk through all steps&lt;/h2&gt;
&lt;p&gt;Start with the basic idea: a string is a sequence of characters. You can think of this:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;js&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;chars&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;h&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;e&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;l&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;l&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;o&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.join&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// &#39;hello&#39;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h3&gt;Uppercase the first letter&lt;/h3&gt;
&lt;p&gt;You can access an array item by index. If we need the letter &lt;code&gt;e&lt;/code&gt;, we use index &lt;code&gt;1&lt;/code&gt;, because arrays start at &lt;code&gt;0&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Since ECMAScript 5, strings can be treated as array-like objects, so the same bracket access works on a string:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;js&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// We get the first letter by accessing the character at index zero.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;chars&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;h&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;e&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;l&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;l&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;o&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;]&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;chars[&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// &#39;h&#39;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// We get the first letter by using the `charAt()` method with index zero.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;chars&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;hello&#39;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;chars[&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// &#39;h&#39;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Now that we have the first letter, call &lt;code&gt;String.prototype.toUpperCase()&lt;/code&gt; on it. The method returns a new uppercase string instead of changing the original one. The &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/toUpperCase&quot;&gt;MDN docs&lt;/a&gt; have more detail.&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;js&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;chars&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;hello&#39;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;chars[&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.toUpperCase&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// &#39;H&#39;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h3&gt;Slice the rest of the letters&lt;/h3&gt;
&lt;p&gt;Next, get the rest of the string after the first character. For arrays, &lt;code&gt;Array.prototype.slice&lt;/code&gt; returns a portion between a start and end index. The &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice&quot;&gt;MDN docs&lt;/a&gt; explain the method in more detail.&lt;/p&gt;
&lt;p&gt;We do not want the first character, so the slice starts at &lt;code&gt;1&lt;/code&gt;. Our example word has &lt;code&gt;5&lt;/code&gt; characters, and because arrays start at &lt;code&gt;0&lt;/code&gt;, the last index is &lt;code&gt;4&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;js&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;chars&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;h&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;e&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;l&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;l&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;o&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;]&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;chars&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.slice&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;4&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// [&#39;e&#39;, &#39;l&#39;, &#39;l&#39;, &#39;o&#39;]&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;That is not useful when you do not know the length upfront. Use the &lt;code&gt;length&lt;/code&gt; property instead:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;js&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;chars&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;h&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;e&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;l&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;l&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;o&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;]&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;chars&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.slice&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;chars&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// [&#39;e&#39;, &#39;l&#39;, &#39;l&#39;, &#39;o&#39;]&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Because slicing to the end is common, you can skip the end index:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;js&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;chars&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;h&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;e&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;l&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;l&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;o&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;]&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;chars&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.slice&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// [&#39;e&#39;, &#39;l&#39;, &#39;l&#39;, &#39;o&#39;]&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;For strings, use &lt;code&gt;String.prototype.slice&lt;/code&gt;. It works the same way. The &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/slice&quot;&gt;MDN docs&lt;/a&gt; cover the string version.&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;js&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;chars&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;hello&#39;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;chars&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.slice&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// &#39;ello&#39;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Now combine the uppercased first character with the rest of the string:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;js&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;chars&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;hello&#39;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;chars&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.charAt&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.toUpperCase&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;chars&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.slice&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// &#39;Hello&#39;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;That gives us a capitalized string.&lt;/p&gt;
&lt;h2&gt;Just use CSS&lt;/h2&gt;
&lt;p&gt;If you only need the text to look capitalized on a web page, use CSS instead:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;html&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;style&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.capitalize&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;text-transform&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;capitalize&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;style&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;span&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;capitalize&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt; hello &amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;span&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
</content>
  </entry>
  <entry>
    <title>How to use React Context</title>
    <link href="https://robinvdvleuten.nl/post/how-to-use-react-context/"/>
    <updated>2020-10-23T00:00:00Z</updated>
    <id>https://robinvdvleuten.nl/post/how-to-use-react-context/</id>
    <summary>React Context lets components share values without passing every prop through every layer. Here is the small counter example version.</summary>
    <content type="html">&lt;p&gt;React Context, &lt;a href=&quot;https://reactjs.org/blog/2018/03/29/react-v-16-3.html&quot;&gt;introduced in React v16.3.0&lt;/a&gt;, lets components read values from a shared context instead of receiving everything through props. It is useful when several components need the same value and prop drilling starts to get noisy.&lt;/p&gt;
&lt;p&gt;This assumes you already know the basics of React: JSX, components, state, and props. If those are still new, start with the &lt;a href=&quot;https://reactjs.org/docs/getting-started.html&quot;&gt;React docs&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;What is React Context?&lt;/h2&gt;
&lt;p&gt;React Context lets you share data across multiple components. The common use case is state that several child components need. Start with a prop-based version:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;jsx&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; React &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;react&#39;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;ParentComponent&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;counter&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;setCounter&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;React&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.useState&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;ChildComponent&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;counter&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{counter}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;onIncrement&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{() &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;setCounter&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(counter &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        /&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    )&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;ChildComponent&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; ({ counter&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; onIncrement }) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            {counter}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;onClick&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{onIncrement}&amp;gt;increment&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    )&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Here, &lt;code&gt;&amp;lt;ParentComponent&amp;gt;&lt;/code&gt; owns the counter state. It passes the current value and an increment callback to &lt;code&gt;&amp;lt;ChildComponent /&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;That is fine for a small example. It gets noisy when more components need the same state, because every component in the middle has to keep passing props along.&lt;/p&gt;
&lt;p&gt;Instead of passing &lt;code&gt;counter&lt;/code&gt; into &lt;code&gt;ChildComponent&lt;/code&gt;, share it through context:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;jsx&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; React &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;react&#39;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;CounterContext&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;React&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.createContext&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;ParentComponent&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;counter&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;setCounter&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;React&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.useState&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;increment&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;setCounter&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(counter &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;CounterContext.Provider&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{{ counter&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; increment }}&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;ChildComponent&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; /&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;CounterContext.Provider&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    )&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;ChildComponent&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;counter&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;increment&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;React&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.useContext&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(CounterContext)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            {counter}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;onClick&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{() &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;increment&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;()}&amp;gt;increment&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    )&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;ChildComponent&lt;/code&gt; now reads the counter state and increment action from context instead of props. It no longer needs &lt;code&gt;ParentComponent&lt;/code&gt; to pass them directly.&lt;/p&gt;
&lt;h2&gt;How is this different from &lt;code&gt;useState()&lt;/code&gt;?&lt;/h2&gt;
&lt;p&gt;The main difference is scope. &lt;code&gt;useState()&lt;/code&gt; stores state inside one component. Context makes a value available to multiple components below a provider.&lt;/p&gt;
&lt;p&gt;Context can also share more than state. It can share functions, configuration, a current user object, or anything else React can pass as a value.&lt;/p&gt;
&lt;h2&gt;How does the context API work?&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;Context&lt;/code&gt; object has two main pieces: &lt;code&gt;Provider&lt;/code&gt; and &lt;code&gt;Consumer&lt;/code&gt;. In modern function components, you will often use the provider plus the &lt;code&gt;useContext()&lt;/code&gt; hook instead of rendering a consumer component directly.&lt;/p&gt;
&lt;p&gt;A single provider can serve multiple components:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;jsx&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; React &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;react&#39;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;CounterContext&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;React&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.createContext&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;ParentComponent&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;counter&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;setCounter&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;React&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.useState&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;increment&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;setCounter&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(counter &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;CounterContext.Provider&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{{ counter&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; increment }}&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;DisplayCounterComponent&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; /&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;IncrementCounterComponent&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; /&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;CounterContext.Provider&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    )&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;DisplayCounterComponent&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;counter&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;React&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.useContext&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(CounterContext)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;{counter}&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;IncrementCounterComponent&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;increment&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;React&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.useContext&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(CounterContext)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;onClick&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{() &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;increment&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;()}&amp;gt;increment&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    )&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Here, &lt;code&gt;DisplayCounterComponent&lt;/code&gt; reads the &lt;code&gt;counter&lt;/code&gt; value, while &lt;code&gt;IncrementCounterComponent&lt;/code&gt; reads the &lt;code&gt;increment&lt;/code&gt; function. Both come from the same provider.&lt;/p&gt;
&lt;h2&gt;Create a custom context provider&lt;/h2&gt;
&lt;p&gt;The previous examples used &lt;code&gt;CounterContext.Provider&lt;/code&gt; directly. You can wrap that in a &lt;code&gt;CounterProvider&lt;/code&gt; component to make the context easier to reuse.&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;jsx&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; React &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;react&#39;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;CounterContext&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;React&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.createContext&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;CounterProvider&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; ({ children&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; initialCount &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; }) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;counter&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;setCounter&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;React&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.useState&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(initialCount)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;increment&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;setCounter&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(counter &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;CounterContext.Provider&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{{ counter&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; increment }}&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            {children}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;CounterContext.Provider&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    )&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;CounterProvider&lt;/code&gt; receives an initial count from the parent and uses it as the initial state. It also renders &lt;code&gt;children&lt;/code&gt;, so anything inside the provider can read the context.&lt;/p&gt;
&lt;p&gt;Use it like this:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;jsx&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; React &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;react&#39;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;ParentComponent&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;CounterProvider&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;initialCount&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;ChildComponent&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; /&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;CounterProvider&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    )&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;ChildComponent&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;counter&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;increment&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;React&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.useContext&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(CounterContext)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            {counter}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;onClick&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{() &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;increment&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;()}&amp;gt;increment&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    )&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;The child still uses &lt;code&gt;useContext&lt;/code&gt; to read the counter context. If you use the same context in several places, a tiny custom hook keeps that call in one place:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;jsx&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;useCounterContext&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;React&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.useContext&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(CounterContext)&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Then components can use &lt;code&gt;useCounterContext()&lt;/code&gt; directly:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;jsx&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;ChildComponent&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;counter&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;increment&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;useCounterContext&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            {counter}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;onClick&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{() &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;increment&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;()}&amp;gt;increment&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    )&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h2&gt;Closing thoughts&lt;/h2&gt;
&lt;p&gt;Context is useful when several components need the same value and passing props through every layer starts to get in the way. Keep it small, name the provider clearly, and avoid turning it into a dumping ground for unrelated state.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://reactjs.org/docs/context.html&quot;&gt;React docs&lt;/a&gt; cover the API in more detail.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Trap focus in a React component</title>
    <link href="https://robinvdvleuten.nl/post/trap-focus-in-a-react-component/"/>
    <updated>2020-10-12T00:00:00Z</updated>
    <id>https://robinvdvleuten.nl/post/trap-focus-in-a-react-component/</id>
    <summary>Accessible dialogs need to keep keyboard focus inside the active element. The focus-trap package handles that part for plain JavaScript and React.</summary>
    <content type="html">&lt;p&gt;Some interactions need to move focus into a specific part of the page and keep it there. A modal dialog is the usual example. We can use &lt;a href=&quot;https://github.com/focus-trap/focus-trap&quot;&gt;focus-trap&lt;/a&gt; to trap focus inside a DOM node or React component.&lt;/p&gt;
&lt;p&gt;Interactive applications often need overlays. When an overlay is active, the rest of the page is usually hidden visually and the user should only interact with the overlay. ARIA attributes help assistive technologies understand that, but they do not stop keyboard users from tabbing out of the active element.&lt;/p&gt;
&lt;p&gt;For example, &lt;code&gt;aria-modal=&amp;quot;true&amp;quot;&lt;/code&gt; tells assistive technologies that the elements behind the current dialog are not available&lt;a href=&quot;https://www.w3.org/TR/wai-aria-practices/examples/dialog-modal/dialog.html&quot;&gt;^1&lt;/a&gt;. It does not, by itself, keep keyboard focus inside the modal. For that, we need JavaScript.&lt;/p&gt;
&lt;h2&gt;Trap focus with vanilla JavaScript&lt;/h2&gt;
&lt;p&gt;Trapping focus while staying accessible is fragile work&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/dialog_role#Required_JavaScript_features&quot;&gt;^2&lt;/a&gt;. The &lt;a href=&quot;https://github.com/focus-trap/focus-trap&quot;&gt;focus-trap&lt;/a&gt; package handles the tricky parts for us.&lt;/p&gt;
&lt;p&gt;Suppose we have this modal markup:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;html&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;demo&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;show&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;show modal&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;modal&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        Modal with &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;href&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;#&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;with&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt; &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;href&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;#&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;some&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;href&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;#&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;focusable&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt; elements.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;hide&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;hide modal&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;With a script on the page, activate the focus trap when the modal is shown:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;js&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; { createFocusTrap } &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;focus-trap&#39;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;modal&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;document&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.getElementById&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;modal&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;focusTrap&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;createFocusTrap&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;#modal&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;onActivate&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; () {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;modal&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.className &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;trap is-visible&#39;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    }&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;onDeactivate&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; () {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;modal&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.className &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;trap&#39;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    }&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;})&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;document&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.getElementById&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;show&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.addEventListener&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;click&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; () {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;focusTrap&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.activate&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;})&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;document&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.getElementById&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;hide&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.addEventListener&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;click&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; () {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;focusTrap&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.deactivate&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;})&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;focus-trap&lt;/code&gt; package has more options, including deactivating when the user clicks outside the component or presses &lt;code&gt;ESC&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Trap focus with React&lt;/h2&gt;
&lt;p&gt;In React, use &lt;a href=&quot;https://github.com/focus-trap/focus-trap-react&quot;&gt;focus-trap-react&lt;/a&gt;. It is a thin wrapper around the original package.&lt;/p&gt;
&lt;p&gt;Here is the same example as a React component:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;jsx&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; React &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;react&#39;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; ReactDOM &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;react-dom&#39;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; FocusTrap &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;focus-trap-react&#39;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;Demo&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;showModal&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;setShowModal&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;React&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.useState&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;onClick&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{() &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;setShowModal&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)}&amp;gt;show modal&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;FocusTrap&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;active&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{showModal}&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;modal&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                    Modal with &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;href&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;#&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;with&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt; &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;href&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;#&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;some&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;{&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39; &#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                    &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;href&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;#&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;focusable&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt; elements.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                    &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;onClick&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{() &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;setShowModal&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)}&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                        hide modal&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                    &amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                &amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            &amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;FocusTrap&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    )&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;ReactDOM&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.render&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Demo&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; /&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;document&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.getElementById&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;demo&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;The modal is wrapped with &lt;code&gt;&amp;lt;FocusTrap /&amp;gt;&lt;/code&gt;. When &lt;code&gt;showModal&lt;/code&gt; is true, focus stays inside the trap&#39;s children.&lt;/p&gt;
&lt;h2&gt;Closing thoughts&lt;/h2&gt;
&lt;p&gt;Accessible modals are easy to get wrong, especially for people who navigate with a keyboard. The &lt;a href=&quot;https://github.com/focus-trap/focus-trap&quot;&gt;focus-trap&lt;/a&gt; package gives you a tested way to keep keyboard focus where it belongs.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Debouncing a React input should not require a dependency</title>
    <link href="https://robinvdvleuten.nl/post/debouncing-a-react-input-should-not-require-a-dependency/"/>
    <updated>2020-09-21T00:00:00Z</updated>
    <id>https://robinvdvleuten.nl/post/debouncing-a-react-input-should-not-require-a-dependency/</id>
    <summary>Search boxes, filters, and autosave fields all need the same tiny delay. A small hook is enough to keep components calm without pulling in a utility library.</summary>
    <content type="html">&lt;p&gt;Search inputs have a funny way of looking finished before they touch the network.&lt;/p&gt;
&lt;p&gt;You start with a controlled input, store the value in &lt;a href=&quot;https://react.dev/reference/react/useState&quot;&gt;state&lt;/a&gt;, and filter a list while the user types. Nice and simple.&lt;/p&gt;
&lt;p&gt;Then the list moves behind an API.&lt;/p&gt;
&lt;p&gt;Suddenly every keypress does work. Type &lt;code&gt;r&lt;/code&gt;, request. Type &lt;code&gt;re&lt;/code&gt;, request. Type &lt;code&gt;rea&lt;/code&gt;, request. Before you&#39;ve finished typing &lt;code&gt;react&lt;/code&gt;, the server has already answered four questions nobody meant to ask.&lt;/p&gt;
&lt;p&gt;My first instinct used to be: fine, install a debounce helper and move on.&lt;/p&gt;
&lt;p&gt;That isn&#39;t a terrible instinct. Libraries like lodash have solved this problem well. But for the common React version of it, &amp;quot;wait a moment before using this value&amp;quot;, a dependency can feel like a lot for one timeout and one cleanup function.&lt;/p&gt;
&lt;p&gt;React already gives us the pieces.&lt;/p&gt;
&lt;p&gt;The input should stay fast. The decision can wait.&lt;/p&gt;
&lt;h2&gt;The noisy version&lt;/h2&gt;
&lt;p&gt;Here&#39;s the straightforward implementation:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;jsx&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; { useState } &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;react&#39;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;UserSearch&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;({ onSearch }) {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;setQuery&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;useState&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;handleChange&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(event) {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;nextQuery&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;target&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.value&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;setQuery&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(nextQuery)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;onSearch&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(nextQuery)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;input&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;search&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{query}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;onChange&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{handleChange}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;placeholder&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;Search users...&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    /&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  )&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;There is nothing wrong with this code. If &lt;code&gt;onSearch&lt;/code&gt; filters a small array in memory, ship it.&lt;/p&gt;
&lt;p&gt;If &lt;code&gt;onSearch&lt;/code&gt; calls an API, updates the URL, writes to storage, or recalculates something expensive, the application now cares about every half-typed value. &lt;code&gt;rea&lt;/code&gt; probably isn&#39;t a real search query. It&#39;s just &lt;code&gt;react&lt;/code&gt; on the way past.&lt;/p&gt;
&lt;p&gt;The useful move is to separate two things that are easy to mix together:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;what the user is typing right now&lt;/li&gt;
&lt;li&gt;what the application should react to&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Those values are related, but they don&#39;t need to move at the same speed.&lt;/p&gt;
&lt;h2&gt;The almost-fix&lt;/h2&gt;
&lt;p&gt;The tempting version is to put a timeout inside the change handler:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;jsx&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;handleChange&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(event) {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;nextQuery&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;target&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.value&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;setQuery&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(nextQuery)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;setTimeout&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;onSearch&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(nextQuery)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  }&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;300&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;This looks right for about thirty seconds.&lt;/p&gt;
&lt;p&gt;Then you type quickly and realize nothing got cancelled. We scheduled every search. The request for &lt;code&gt;r&lt;/code&gt; still happens. So does &lt;code&gt;re&lt;/code&gt;. So does &lt;code&gt;rea&lt;/code&gt;. They just happen late, which is harder to reason about.&lt;/p&gt;
&lt;p&gt;We need to clear the previous timeout whenever the value changes again.&lt;/p&gt;
&lt;p&gt;And in React, &amp;quot;do something after a value changes, then clean it up before doing it again&amp;quot; is exactly what an &lt;a href=&quot;https://react.dev/reference/react/useEffect&quot;&gt;effect&lt;/a&gt; is for.&lt;/p&gt;
&lt;h2&gt;A small debounce hook&lt;/h2&gt;
&lt;p&gt;Here&#39;s the whole thing:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;jsx&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; { useEffect&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; useState } &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;react&#39;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;useDebouncedValue&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(value&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; delay) {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;debouncedValue&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;setDebouncedValue&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;useState&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(value)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;useEffect&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;timeout&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;setTimeout&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;setDebouncedValue&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(value)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    }&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; delay)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;clearTimeout&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(timeout)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  }&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; [value&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; delay])&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; debouncedValue&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Every time &lt;code&gt;value&lt;/code&gt; changes, the hook starts a timer.&lt;/p&gt;
&lt;p&gt;If &lt;code&gt;value&lt;/code&gt; changes again before the timer finishes, React runs the cleanup function and clears the old timeout. Only the latest value survives long enough to become &lt;code&gt;debouncedValue&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;That&#39;s the whole trick. A value, a delay, and a cleanup function.&lt;/p&gt;
&lt;h2&gt;Using it in a search input&lt;/h2&gt;
&lt;p&gt;Now the component has two values:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;jsx&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;setQuery&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;useState&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;debouncedQuery&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;useDebouncedValue&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(query&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;300&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;query&lt;/code&gt; belongs to the input. It should update on every keystroke.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;debouncedQuery&lt;/code&gt; belongs to the work we don&#39;t want to repeat too eagerly.&lt;/p&gt;
&lt;p&gt;Put together, that looks like this:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;jsx&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; { useEffect&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; useState } &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;react&#39;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;UserSearch&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;({ onSearch }) {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;setQuery&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;useState&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;debouncedQuery&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;useDebouncedValue&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(query&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;300&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;useEffect&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;onSearch&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(debouncedQuery)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  }&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; [debouncedQuery&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; onSearch])&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;input&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;search&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{query}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;onChange&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{(event) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;setQuery&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;target&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.value)}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;placeholder&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;Search users...&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    /&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  )&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Notice what didn&#39;t change: the input itself isn&#39;t debounced.&lt;/p&gt;
&lt;p&gt;Typing still feels immediate because the input uses &lt;code&gt;query&lt;/code&gt;. The expensive part uses &lt;code&gt;debouncedQuery&lt;/code&gt;, which only changes after 300 milliseconds of quiet.&lt;/p&gt;
&lt;p&gt;That separation is the point.&lt;/p&gt;
&lt;h2&gt;A more realistic example&lt;/h2&gt;
&lt;p&gt;Let&#39;s wire it to a small API search:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;jsx&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;UserSearch&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;setQuery&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;useState&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;users&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;setUsers&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;useState&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;([])&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;debouncedQuery&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;useDebouncedValue&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(query&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;300&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;useEffect&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (debouncedQuery &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;setUsers&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;([])&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;searchUsers&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;params&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;URLSearchParams&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;({ q&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; debouncedQuery })&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;response&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;fetch&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;`/api/users?&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;params&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;response&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.json&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;setUsers&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(data)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;searchUsers&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  }&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; [debouncedQuery])&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &amp;lt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;input&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;search&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{query}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;onChange&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{(event) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;setQuery&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;target&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.value)}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;placeholder&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;Search users...&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      /&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;ul&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        {&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;users&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.map&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;((user) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;          &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;li&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.id}&amp;gt;{&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.name}&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;li&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        ))}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;ul&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &amp;lt;/&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  )&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Now the user can type freely, and the API only hears from us when the query settles. We get the behavior without pulling in a package or hiding the timing inside a helper someone has to debug later.&lt;/p&gt;
&lt;h2&gt;One annoying edge case&lt;/h2&gt;
&lt;p&gt;Debouncing reduces the number of requests, but it doesn&#39;t guarantee that responses arrive in the same order they were sent.&lt;/p&gt;
&lt;p&gt;On a fast connection, you may never notice. On a slow one, an older request can finish after a newer request and overwrite the results. You type the right thing, see the right results, and then watch the wrong results replace them a moment later.&lt;/p&gt;
&lt;p&gt;We can guard against that with the same cleanup idea:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;jsx&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;useEffect&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (debouncedQuery &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;setUsers&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;([])&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; ignore &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;false&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;searchUsers&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;params&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;URLSearchParams&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;({ q&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; debouncedQuery })&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;response&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;fetch&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;`/api/users?&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;params&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;response&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.json&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;ignore) {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;setUsers&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(data)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;searchUsers&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    ignore &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;true&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; [debouncedQuery])&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;When &lt;code&gt;debouncedQuery&lt;/code&gt; changes, React cleans up the previous effect. If the previous request finishes later, it no longer gets to update state.&lt;/p&gt;
&lt;p&gt;This doesn&#39;t cancel the request itself. For that, use &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/AbortController&quot;&gt;&lt;code&gt;AbortController&lt;/code&gt;&lt;/a&gt;. But for many small search fields, ignoring stale responses is enough to keep the UI honest.&lt;/p&gt;
&lt;h2&gt;When to use this&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Search boxes.&lt;/strong&gt; Wait until the user pauses before asking the server for results.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Filter panels.&lt;/strong&gt; Let someone change a few controls before recalculating an expensive list.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Autosave fields.&lt;/strong&gt; Save after typing settles instead of storing every half-written sentence.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;URL updates.&lt;/strong&gt; Sync query parameters without filling browser history with every intermediate keystroke.&lt;/p&gt;
&lt;p&gt;The pattern is always the same: keep the interface immediate, delay the side effect.&lt;/p&gt;
&lt;h2&gt;When not to use this&lt;/h2&gt;
&lt;p&gt;Don&#39;t debounce every input by default.&lt;/p&gt;
&lt;p&gt;If the work is cheap and local, keep it instant. A checkbox that toggles a small section doesn&#39;t need a timeout. Neither does a plain controlled input.&lt;/p&gt;
&lt;p&gt;Debouncing helps when the next step is noisy, expensive, or external. API calls. Large filters. Autosave. Analytics. Things where a short pause makes the application calmer.&lt;/p&gt;
&lt;p&gt;And if you need leading calls, trailing calls, cancellation, flushing, or a maximum wait time, use a real debounce utility. Libraries exist for a reason.&lt;/p&gt;
&lt;p&gt;Most inputs don&#39;t need all of that. Most inputs just need: &amp;quot;please wait 300 milliseconds before treating this as real.&amp;quot;&lt;/p&gt;
&lt;p&gt;For that, this hook is enough.&lt;/p&gt;
&lt;h2&gt;The useful part&lt;/h2&gt;
&lt;p&gt;A debounce is usually explained as a timing trick. In React, I find it more useful as a modeling trick.&lt;/p&gt;
&lt;p&gt;There is the value the user is editing.&lt;/p&gt;
&lt;p&gt;And there is the value the application believes.&lt;/p&gt;
&lt;p&gt;Those two values are usually the same. When the next step is expensive, external, or noisy, giving them a little distance makes the code easier to reason about.&lt;/p&gt;
&lt;p&gt;No dependency, no function identity puzzle.&lt;/p&gt;
&lt;p&gt;Just a value that waits a moment before the rest of the app believes it.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Scroll a React component into view</title>
    <link href="https://robinvdvleuten.nl/post/scroll-a-react-component-into-view/"/>
    <updated>2020-07-03T00:00:00Z</updated>
    <id>https://robinvdvleuten.nl/post/scroll-a-react-component-into-view/</id>
    <summary>React refs let you reach DOM APIs when you need them. Here is how to use a ref to scroll an element or component into view.</summary>
    <content type="html">&lt;p&gt;React refs give you access to the underlying DOM element when you need a browser API directly. One common example is scrolling an element into view after a button click.&lt;/p&gt;
&lt;p&gt;People got much more comfortable scrolling long pages over time. We are now used to websites where not everything is visible at first glance.&lt;/p&gt;
&lt;p&gt;So how do you move someone to content that is currently outside the viewport? The browser already has an API for that: &lt;code&gt;Element.scrollIntoView()&lt;/code&gt;. It does what the name says, with a few useful options for changing the behavior.&lt;/p&gt;
&lt;h2&gt;Scroll to element with plain HTML&lt;/h2&gt;
&lt;p&gt;Before using it in React, start with the plain HTML version.&lt;/p&gt;
&lt;p&gt;Suppose we have an article with a long body:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;html&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;article&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;h1&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;title&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;An interesting article for Latin readers&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;h1&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam sit&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        amet luctus neque. Etiam eu quam lacinia, placerat sem ut, interdum&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        risus. Quisque ut feugiat mauris. Aenean euismod fermentum facilisis.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        Donec ultricies maximus elit, sit amet convallis urna rhoncus vitae.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        Aliquam bibendum turpis et felis blandit commodo. Donec egestas neque&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        non elit laoreet, faucibus tempor ante gravida.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        Duis in ante turpis. Phasellus dignissim tellus et nunc lacinia&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        elementum. Sed venenatis tincidunt justo. Praesent sed purus facilisis,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        porttitor ligula in, mattis velit. Curabitur non pellentesque nunc. Duis&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        elit urna, bibendum et purus nec, maximus imperdiet mauris. Cras&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        euismod, leo id vehicula vulputate, nibh massa tincidunt justo, sit amet&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        fringilla quam orci pellentesque enim.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;...&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;article&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;At the end of the article, we can add a link that points back to the &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; by id:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;html&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;article&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    ...&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;href&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;#title&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt; Back to the top &amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;article&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;When the user clicks the link, the browser jumps back to the title. That is the simplest way to scroll an element into view without JavaScript.&lt;/p&gt;
&lt;h2&gt;Scroll to element with vanilla JavaScript&lt;/h2&gt;
&lt;p&gt;To do the same thing with JavaScript, create a button that scrolls back to the top when clicked:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;html&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;article&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    ...&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;onclick&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;document&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.getElementById&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;(&#39;title&#39;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.scrollIntoView&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;()&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        Back to the top&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;article&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;The button finds the heading by its &lt;code&gt;title&lt;/code&gt; id and tells the browser to scroll it into the viewport.&lt;/p&gt;
&lt;p&gt;For many cases, that is enough. If you want a smooth animation, pass an options object:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;js&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;titleElement&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;document&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.getElementById&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;title&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;titleElement&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.scrollIntoView&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;({ behavior&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;smooth&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; })&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;With &lt;code&gt;behavior&lt;/code&gt; set to &lt;code&gt;smooth&lt;/code&gt;, the browser scrolls to the element instead of jumping there instantly.&lt;/p&gt;
&lt;h2&gt;Scroll to a React element&lt;/h2&gt;
&lt;p&gt;In React, we can still use &lt;code&gt;Element.scrollIntoView()&lt;/code&gt;. We just need access to the underlying HTML element.&lt;/p&gt;
&lt;p&gt;First, convert the example to a function component:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;jsx&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; React &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;react&#39;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;Article&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;article&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;h1&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;A React article for Latin readers&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;h1&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;...&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;...&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;Back to the top&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;article&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    )&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;We could keep the &lt;code&gt;id&lt;/code&gt;, but a ref gives React direct access to the element. Use &lt;code&gt;useRef&lt;/code&gt; for that. You can read more about &lt;code&gt;useRef()&lt;/code&gt; in &lt;a href=&quot;https://reactjs.org/docs/hooks-reference.html#useref&quot;&gt;the React documentation&lt;/a&gt;.&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;jsx&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; React&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; { useRef } &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;react&#39;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;Article&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;titleRef&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;useRef&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;article&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;h1&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;ref&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{titleRef}&amp;gt;A React article for Latin readers&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;h1&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            // Rest of the article&#39;s content...&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;Back to the top&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;article&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    )&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Next, handle the button click with an &lt;code&gt;onClick&lt;/code&gt; event handler. You can read more about event handling in &lt;a href=&quot;https://reactjs.org/docs/handling-events.html&quot;&gt;the React documentation&lt;/a&gt;.&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;jsx&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; React&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; { useRef } &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;react&#39;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;Article&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;titleRef&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;useRef&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;handleBackClick&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// Scroll back to the title element...&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;article&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;h1&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;ref&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{titleRef}&amp;gt;A React article for Latin readers&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;h1&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            // Rest of the article&#39;s content...&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;onClick&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{handleBackClick}&amp;gt;Back to the top&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;article&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    )&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Inside the event handler, the title element is available through the ref. Then we can call &lt;code&gt;scrollIntoView()&lt;/code&gt; just like before.&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;jsx&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;titleRef&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;useRef&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;handleBackClick&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;titleRef&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;current&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.scrollIntoView&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;({ behavior&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;smooth&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; })&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;useRef()&lt;/code&gt; gives the component access to the underlying HTML element, including the DOM APIs on that element.&lt;/p&gt;
&lt;h2&gt;Scroll to a React component&lt;/h2&gt;
&lt;p&gt;The same idea works when the element lives inside another React component. Forward the ref to the component&#39;s root element, and the parent can still call the DOM API.&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;jsx&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; React&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; { forwardRef&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; useRef } &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;react&#39;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Article&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;forwardRef&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(({ onBackClick }&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; ref) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;article&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;h1&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;ref&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{ref}&amp;gt;A React article for Latin readers&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;h1&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            // Rest of the article&#39;s content...&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;onClick&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{onBackClick}&amp;gt;Back to the top&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;article&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    )&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;})&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// ...&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;AnotherComponent&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;articleRef&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;useRef&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;handleBackClick&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;articleRef&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;current&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.scrollIntoView&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;({ behavior&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;smooth&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; })&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Article&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;ref&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{articleRef} &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;onBackClick&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{handleBackClick} /&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;forwardRef()&lt;/code&gt; call lets another component access the &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; inside &lt;code&gt;Article&lt;/code&gt; by ref. You can read more about &lt;code&gt;forwardRef()&lt;/code&gt; in &lt;a href=&quot;https://reactjs.org/docs/forwarding-refs.html&quot;&gt;the React documentation&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Bonus: scroll to the first error in a Formik form&lt;/h2&gt;
&lt;p&gt;For a more realistic use case, imagine a large React form using &lt;a href=&quot;https://formik.org/&quot;&gt;Formik&lt;/a&gt; for submission and validation. Here is a small newsletter signup form:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;jsx&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; React &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;react&#39;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; { Formik } &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;formik&#39;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;SignupForm&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Formik&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;initialValues&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{{ email&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; }}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;validate&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{(values) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;errors&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;values&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.email) {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;errors&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.email &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;Required&#39;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; errors&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            }}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;onSubmit&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{(values) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// ...&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            }}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            {(formik) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;form&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;onSubmit&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;formik&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.handleSubmit}&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                    &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;label&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;htmlFor&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;email&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;Email Address&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;label&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                    &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;input&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;email&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;email&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;email&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;onChange&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;formik&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.handleChange}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;formik&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;values&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.email}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                    /&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                    {&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;formik&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;errors&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.email &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                        &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;{&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;formik&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;errors&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.email}&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                    ) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                    &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;submit&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;Submit&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                &amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;form&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            )}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Formik&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    )&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;When the user submits the empty form, it shows an error for the email field. In this tiny form, that is easy to spot. In a larger form, it helps to scroll to the first field with an error.&lt;/p&gt;
&lt;p&gt;Add a small helper component inside the form:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;jsx&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; React&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; { useEffect } &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;react&#39;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; { useFormikContext } &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;formik&#39;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;ErrorFocus&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// Get the context for the Formik form this component is rendered into.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;isSubmitting&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;isValidating&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;errors&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;useFormikContext&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;useEffect&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// Get all keys of the error messages.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;keys&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Object&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.keys&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(errors)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// Whenever there are errors and the form is submitting but finished validating.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;keys&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;&amp;&amp;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; isSubmitting &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;&amp;&amp;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;isValidating) {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// We grab the first input element that error by its name.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;errorElement&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;document&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.querySelector&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;`input[name=&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;keys[&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;]`&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            )&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (errorElement) {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// When there is an input, scroll this input into view.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;errorElement&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.scrollIntoView&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;({ behavior&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;smooth&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; })&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    }&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; [isSubmitting&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; isValidating&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; errors])&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// This component does not render anything by itself.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;null&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Now add &lt;code&gt;&amp;lt;ErrorFocus&amp;gt;&lt;/code&gt; to the Formik form. When validation fails, the user is scrolled to the first input with an error.&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;jsx&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; React &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;react&#39;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; { Formik } &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;formik&#39;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; ErrorFocus &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;./error-focus&#39;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;SignupForm&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Formik&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;initialValues&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{{ email&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; }}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;validate&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{(values) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;errors&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;values&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.email) {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;errors&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.email &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;Required&#39;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; errors&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            }}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;onSubmit&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{(values) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// ...&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            }}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            {(formik) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;form&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;onSubmit&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;formik&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.handleSubmit}&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                    &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;label&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;htmlFor&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;email&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;Email Address&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;label&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                    &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;input&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;email&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;email&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;email&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;onChange&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;formik&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.handleChange}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;formik&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;values&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.email}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                    /&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                    {&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;formik&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;errors&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.email &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                        &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;{&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;formik&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;errors&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.email}&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                    ) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                    &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;submit&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;Submit&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                    {&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;/* The component itself does not render anything, but needs to be within the Formik context */&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                    &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;ErrorFocus&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; /&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                &amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;form&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            )}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Formik&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    )&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h2&gt;Closing thoughts&lt;/h2&gt;
&lt;p&gt;Refs are useful when React code needs to call a browser API directly. This post used &lt;code&gt;Element.scrollIntoView()&lt;/code&gt;, but the same pattern applies to other element methods too.&lt;/p&gt;
&lt;p&gt;For example, elements can be animated through JavaScript with &lt;code&gt;Element.animate()&lt;/code&gt;. The &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Element/animate&quot;&gt;MDN documentation&lt;/a&gt; covers that API in more detail.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>A package for presenting person names</title>
    <link href="https://robinvdvleuten.nl/post/package-for-presenting-person-names/"/>
    <updated>2020-04-16T00:00:00Z</updated>
    <id>https://robinvdvleuten.nl/post/package-for-presenting-person-names/</id>
    <summary>When an application needs to show a person&#39;s name in several formats, php-person-name keeps that formatting in one small object.</summary>
    <content type="html">&lt;p&gt;Applications often need to show a person&#39;s name in more than one format: full name, first name, initials, mention handle, sorted name, and so on. I created a package called &lt;a href=&quot;https://github.com/webstronauts/php-person-name&quot;&gt;php-person-name&lt;/a&gt; to keep that logic in one place.&lt;/p&gt;
&lt;p&gt;It is based on the &lt;a href=&quot;https://github.com/basecamp/name_of_person&quot;&gt;name_of_person&lt;/a&gt; gem from &lt;a href=&quot;https://basecamp.com/&quot;&gt;Basecamp&lt;/a&gt;. They had the same kind of problem: how do you present a person&#39;s name in different formats when you only store first and last name?&lt;/p&gt;
&lt;p&gt;The public API is small:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;php&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;&amp;lt;?&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;php&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;$name &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;PersonName&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;make&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;David Heinemeier Hansson&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;echo&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; $name&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;full        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// &quot;David Heinemeier Hansson&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;echo&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; $name&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;first       &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// &quot;David&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;echo&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; $name&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;last        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// &quot;Heinemeier Hansson&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;echo&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; $name&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;initials    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// &quot;DHH&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;echo&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; $name&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;familiar    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// &quot;David H.&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;echo&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; $name&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;abbreviated &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// &quot;D. Heinemeier Hansson&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;echo&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; $name&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;sorted      &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// &quot;Heinemeier Hansson, David&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;echo&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; $name&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;mentionable &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// &quot;davidh&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;echo&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; $name&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;possessive  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// &quot;David Heinemeier Hansson&#39;s&quot;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;For Laravel, you can add a &lt;code&gt;name&lt;/code&gt; attribute to your model:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;php&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;&amp;lt;?&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;php&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Webstronauts&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;\&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;PersonName&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;\&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;PersonName&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;User&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;extends&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;Model&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;/**&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;     * The attributes that are mass assignable.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;     *&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;     * &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;@var&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;array&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;     */&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;protected&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; $fillable &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; [&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;name&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;first_name&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;last_name&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    ];&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;/**&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;     * Return a PersonName instance composed from the `first_name` and `last_name` attributes.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;     *&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;     * &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;@return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;PersonName&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;     */&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;getNameAttribute&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;PersonName&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;$this&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;first_name&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;$this&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;last_name);&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;/**&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;     * Sets the `first_name` and `last_name` attributes from a full name.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;     *&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;     * &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;@param&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt; $name&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;     * &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;@return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;void&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;     */&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;setNameAttribute&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;($name)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        $fullName &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;PersonName&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;make&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;$name&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        [&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;$this&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;first_name&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;$this&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;last_name] &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; $fullName &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; [$fullName&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;first&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; $fullName&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;last] &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;];&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Now the model stores &lt;code&gt;first_name&lt;/code&gt; and &lt;code&gt;last_name&lt;/code&gt;, while &lt;code&gt;name&lt;/code&gt; gives you the formatted versions:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;php&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;&amp;lt;?&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;php&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;$user &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;User&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;([&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;first_name&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;Robin&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;last_name&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;van der Vleuten&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;])&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;echo&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; $user&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;full &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// Robin van der Vleuten&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;It is a small helper, but I end up needing this kind of formatting in many applications. The package is available &lt;a href=&quot;https://github.com/webstronauts/php-person-name&quot;&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Keep personal files out of your project .gitignore</title>
    <link href="https://robinvdvleuten.nl/post/keep-personal-files-out-of-gitignore/"/>
    <updated>2019-11-18T00:00:00Z</updated>
    <id>https://robinvdvleuten.nl/post/keep-personal-files-out-of-gitignore/</id>
    <summary>Git has a global ignore file for editor, operating system, and local tooling clutter. Use it, and keep project .gitignore files focused.</summary>
    <content type="html">&lt;p&gt;You&#39;re reviewing a pull request and everything looks normal until one small line turns up in the diff:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;diff&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; node_modules/&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; public/build/&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;+ .idea/&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Harmless enough, maybe.&lt;/p&gt;
&lt;p&gt;But it also says something about the boundary between project configuration and personal configuration. The project probably does need to ignore &lt;code&gt;node_modules/&lt;/code&gt; or generated build output. It probably doesn&#39;t need to know which editor I use.&lt;/p&gt;
&lt;p&gt;That belongs on my machine, not in everybody&#39;s repository.&lt;/p&gt;
&lt;h2&gt;Two kinds of ignored files&lt;/h2&gt;
&lt;p&gt;I used to treat &lt;code&gt;.gitignore&lt;/code&gt; as the place for everything Git shouldn&#39;t track. That is technically true, but it hides an important distinction.&lt;/p&gt;
&lt;p&gt;There are two different lists:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Files the project creates.&lt;/li&gt;
&lt;li&gt;Files my computer creates.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Those are not the same thing.&lt;/p&gt;
&lt;p&gt;Project files are things every contributor will run into: dependency folders, compiled assets, temporary cache directories, test output, logs, uploads, local build artifacts. They belong in the repository&#39;s &lt;code&gt;.gitignore&lt;/code&gt; because they describe how the project behaves.&lt;/p&gt;
&lt;p&gt;Personal files are different. macOS leaves &lt;code&gt;.DS_Store&lt;/code&gt; behind. Editors create &lt;code&gt;.vscode/&lt;/code&gt;, &lt;code&gt;.idea/&lt;/code&gt;, swap files, workspace metadata, and local history folders. Maybe you use &lt;code&gt;.envrc&lt;/code&gt; for direnv. Maybe nobody else on the team does.&lt;/p&gt;
&lt;p&gt;The repository doesn&#39;t need to care.&lt;/p&gt;
&lt;h2&gt;The better place for personal clutter&lt;/h2&gt;
&lt;p&gt;Git has a setting for this: &lt;code&gt;core.excludesfile&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;It points Git at a global ignore file on your machine. Anything you always want Git to ignore can live there once, instead of getting added to every project you touch.&lt;/p&gt;
&lt;p&gt;I keep mine in my home directory:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;sh&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;touch &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;~&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;/.gitignore&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;git config --global core.excludesfile &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;~&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;/.gitignore&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Then fill it with files that are about your environment, not the project:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;# macOS
.DS_Store

# Editors
.idea/
.vscode/
*.swp

# Local tools
.envrc&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;&lt;!-- Note from shiki-twoslash: the language gitignore was not set up for Shiki to use, and so there is no code highlighting --&gt;
&lt;p&gt;That&#39;s it.&lt;/p&gt;
&lt;p&gt;From then on, those files disappear from &lt;code&gt;git status&lt;/code&gt; in every repository. You stop adding &lt;code&gt;.DS_Store&lt;/code&gt; to random projects. Editor folders stop showing up in pull requests. Your local workspace metadata stays local.&lt;/p&gt;
&lt;h2&gt;Keep project ignores boring&lt;/h2&gt;
&lt;p&gt;A good project &lt;code&gt;.gitignore&lt;/code&gt; should be boring in a specific way: another developer should be able to read it and understand what the project itself produces.&lt;/p&gt;
&lt;p&gt;For a Rails app, that might mean entries like these:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;/log/*
/tmp/*
/storage/*
/public/assets
.byebug_history&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;&lt;!-- Note from shiki-twoslash: the language gitignore was not set up for Shiki to use, and so there is no code highlighting --&gt;
&lt;p&gt;Those entries make sense because Rails creates them, or because the app expects them to be local runtime state.&lt;/p&gt;
&lt;p&gt;Your editor choice doesn&#39;t have that property. Neither does the operating system metadata your laptop leaves around. Once those personal rules land in the repository, everybody inherits them, even if they don&#39;t use the same tools.&lt;/p&gt;
&lt;p&gt;That is how project ignores turn into little records of every contributor&#39;s setup.&lt;/p&gt;
&lt;h2&gt;Check what Git is using&lt;/h2&gt;
&lt;p&gt;If you forget where your global ignore file lives, ask Git:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;sh&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;git config --global core.excludesfile&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;And if a file is being ignored but you don&#39;t know why, &lt;code&gt;git check-ignore&lt;/code&gt; can show the matching rule:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;sh&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;git check-ignore -v .vscode/settings.json&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;That command is especially useful when a file vanishes from &lt;code&gt;git status&lt;/code&gt; and you can&#39;t remember whether the rule came from the project, your global file, or somewhere else.&lt;/p&gt;
&lt;h2&gt;What belongs where&lt;/h2&gt;
&lt;p&gt;The rule I try to follow is simple:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If the project creates it, ignore it in the project.&lt;/strong&gt; Build output, logs, caches, uploaded files, generated assets, local databases. These rules help every contributor.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If your machine creates it, ignore it globally.&lt;/strong&gt; Editor folders, OS metadata, swap files, local shell tooling. These rules follow you around without leaking into shared history.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If it&#39;s secret, don&#39;t rely on ignore rules alone.&lt;/strong&gt; &lt;code&gt;.env&lt;/code&gt; files often belong in &lt;code&gt;.gitignore&lt;/code&gt;, but the real fix is still to keep secrets out of Git from the start. Ignore files prevent accidents. They don&#39;t erase history.&lt;/p&gt;
&lt;p&gt;It is a small setup step, but it removes a surprising amount of noise. Your commits stay narrower, pull requests stop carrying personal workspace details, and project &lt;code&gt;.gitignore&lt;/code&gt; files stay about the project.&lt;/p&gt;
&lt;p&gt;That is the whole point: shared config should describe the project, not everybody&#39;s laptop.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Going epic with Redux Observable tests</title>
    <link href="https://robinvdvleuten.nl/post/going-epic-with-redux-observable-tests/"/>
    <updated>2017-11-10T00:00:00Z</updated>
    <id>https://robinvdvleuten.nl/post/going-epic-with-redux-observable-tests/</id>
    <summary>Test Redux Observable epics by injecting a fake client, passing an action stream into the epic, and asserting the emitted actions.</summary>
    <content type="html">&lt;p&gt;The last few React projects I worked on used &lt;strong&gt;Redux Observable&lt;/strong&gt; heavily. It is a good way to keep business logic out of components, but testing epics took me a while to get comfortable with.&lt;/p&gt;
&lt;h2&gt;So what is this Redux Observable?&lt;/h2&gt;
&lt;p&gt;If Redux Observable is new to you, start with &lt;a href=&quot;https://www.youtube.com/watch?v=AslncyG8whg&quot; title=&quot;Watch Jay Phelps explaining Redux Observable&quot;&gt;RxJS + Redux + React = Amazing!&lt;/a&gt; by &lt;a href=&quot;https://twitter.com/_jayphelps&quot; title=&quot;Visit Jay Phelps&#39; Twitter profile&quot;&gt;Jay Phelps&lt;/a&gt;. It explains how Netflix used common JavaScript patterns with &lt;a href=&quot;https://github.com/Reactive-Extensions/RxJS&quot; title=&quot;Visit the official RxJS documentation&quot;&gt;RxJS&lt;/a&gt; to manage business logic in React applications. The core idea became the &lt;a href=&quot;https://github.com/redux-observable/redux-observable/&quot; title=&quot;Go to the Redux Observable repository&quot;&gt;Redux Observable&lt;/a&gt; library.&lt;/p&gt;
&lt;picture&gt;
    &lt;source srcset=&quot;https://robinvdvleuten.nl/images/redux-observable.webp&quot; type=&quot;image/webp&quot; media=&quot;(prefers-reduced-motion: no-preference)&quot;&gt;
    &lt;source srcset=&quot;https://robinvdvleuten.nl/images/redux-observable.gif&quot; type=&quot;image/gif&quot; media=&quot;(prefers-reduced-motion: no-preference)&quot;&gt;
    &lt;img class=&quot;object-contain h-56 w-full&quot; src=&quot;https://robinvdvleuten.nl/images/redux-observable.png&quot; alt=&quot;Redux Observable Logo&quot;&gt;
&lt;/picture&gt;
&lt;p&gt;The &lt;a href=&quot;https://redux-observable.js.org/&quot;&gt;documentation&lt;/a&gt; has plenty of small examples to get you started. Testing is the part that felt less obvious to me. The docs even say they were still learning the best way to test epics:&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
    Testing async code that creates side effects isn&#39;t easy. We&#39;re still learning the best way to test Epics. If you have found the perfect way, do share!
  &lt;/p&gt;
  &lt;footer&gt;
    &lt;cite&gt;
      &lt;a href=&quot;https://redux-observable.js.org/docs/recipes/WritingTests.html&quot;&gt;Redux Observable docs&lt;/a&gt;
    &lt;/cite&gt;
  &lt;/footer&gt;
&lt;/blockquote&gt;
&lt;p&gt;After fighting with observable tests on a few projects, this is the approach that started to make sense.&lt;/p&gt;
&lt;h2&gt;What epic are we going to test?&lt;/h2&gt;
&lt;p&gt;Here is a small epic we can use for testing asynchronous business logic:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;js&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;authenticateUserEpic&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (action$&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; store&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; { client }) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// Only act on actions of a given type,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// in this case &quot;USER_AUTHENTICATE_REQUEST&quot;.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        action$&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.ofType&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;USER_AUTHENTICATE_REQUEST&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// Map the received action to a new action &quot;observable&quot;.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.switchMap&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;((action) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// Authenticate with the dispatched credentials in the action,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// using the injected client instance.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; client&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.authenticate&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;action&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.username&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;action&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.password)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.then&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;((response) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;response&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.isSuccessful) {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                            &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// Map the response to a &quot;failed&quot; action with the error.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                            &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                                type&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;USER_AUTHENTICATE_FAILURE&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                                error&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                                    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;Something went wrong while authenticating&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                            }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                        }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                            &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// Map the response to a &quot;successful&quot; action with a JWT token.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                            type&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;USER_AUTHENTICATE_SUCCESS&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                            idToken&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;response&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.idToken&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                        }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                    })&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            })&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    )&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;This epic authenticates a user with credentials from the dispatched action. The action might look like this:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;js&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;authenticate&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (username&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; password) {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  return { type&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;USER_AUTHENTICATE_REQUEST&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; username&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; password };&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;dispatch&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;authenticate&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;johndoe&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;mysupersecretpassword&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;The client dependency is &lt;a href=&quot;https://redux-observable.js.org/docs/recipes/InjectingDependenciesIntoEpics.html&quot; title=&quot;Read more about injecting in the Redux Observable docs&quot;&gt;injected into the epic&lt;/a&gt;. You could import the client directly, but injection makes it much easier to mock in tests.&lt;/p&gt;
&lt;h2&gt;Creating the tests with Jest&lt;/h2&gt;
&lt;p&gt;Most React projects I saw around that time used &lt;a href=&quot;https://jestjs.io/&quot; title=&quot;Visit the official Jest documentation&quot;&gt;Jest&lt;/a&gt;, so I will use it for the examples.&lt;/p&gt;
&lt;p&gt;The test passes a dispatched action into the epic and checks the action that comes out. Looking at the epic, we need two tests: one for &lt;code&gt;USER_AUTHENTICATE_SUCCESS&lt;/code&gt; with a JWT token and one for &lt;code&gt;USER_AUTHENTICATE_FAILURE&lt;/code&gt; with an error.&lt;/p&gt;
&lt;p&gt;In Jest, that starts like this:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;js&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;describe&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;authenticateUserEpic&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;it&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;should dispatch a JWT token when authenticating is successful&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// ...&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    })&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;it&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;should dispatch an error when authenticating has failed&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// ...&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    })&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;})&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Focus on the successful case first. We pass the epic a dispatched action and wait for the RxJS observable to complete. There are several ways to do this, but this version worked well for me:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;js&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; { ActionsObservable } &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;redux-observable&#39;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; authenticateUserEpic &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;./epics&#39;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// ...&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;it&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;should dispatch a JWT token when authenticating is successful&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// The response object we expect to receive from the server.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;response&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        isSuccessful&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        idToken&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;a-random-generated-jwt&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// Create a fake client instance that returns the response.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;client&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; { authenticate&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;jest&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.fn&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;() }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;client&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;authenticate&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.mockReturnValue&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Promise&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.resolve&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(response))&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// Create an Observable stream of the dispatching action.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;action$&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;ActionsObservable&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.of&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;({&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        type&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;USER_AUTHENTICATE_REQUEST&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        username&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;johndoe&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        password&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;mysupersecretpassword&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    })&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// Pass the Observable action to our epic and inject the&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// mocked client instance.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;epic$&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;authenticateUserEpic&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(action$&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; store&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; { client })&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// Get the resulting actions by using async/await.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;result&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;epic$&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.toArray&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.toPromise&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// Test if we&#39;ve received the expected action as result.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;expect&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(result)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.toEqual&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;([&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            type&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;USER_AUTHENTICATE_SUCCESS&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            idToken&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;a-random-generated-jwt&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        }&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    ])&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;})&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;The RxJS part takes some getting used to, but the test ends up being direct: put an action in, wait for the epic, assert the resulting action.&lt;/p&gt;
&lt;p&gt;The failed response test follows the same shape:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;js&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;it&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;should dispatch an error when authenticating has failed&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// The response object we expect to receive from the server.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;response&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        isSuccessful&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// Create a fake client instance that returns the response.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;client&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; { authenticate&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;jest&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.fn&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;() }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;client&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;authenticate&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.mockReturnValue&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Promise&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.resolve&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(response))&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// Create an Observable stream of the dispatching action.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;action$&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;ActionsObservable&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.of&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;({&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        type&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;USER_AUTHENTICATE_REQUEST&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        username&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;johndoe&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        password&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;mysupersecretpassword&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    })&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// Pass the Observable action to our epic and inject the&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// mocked client instance.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;epic$&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;authenticateUserEpic&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(action$&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; store&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; { client })&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// Get the resulting actions by using async/await.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;result&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;epic$&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.toArray&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.toPromise&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// Test if we&#39;ve received the expected action as result.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;expect&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(result)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.toEqual&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;([&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            type&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;USER_AUTHENTICATE_FAILURE&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            error&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;Something went wrong while authenticating&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        }&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    ])&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;})&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Did I get some headaches along the way? Definitely. I had &lt;a href=&quot;https://stackoverflow.com/questions/42276419/invoking-epics-from-within-other-epics&quot;&gt;a few&lt;/a&gt; &lt;a href=&quot;https://stackoverflow.com/questions/47037119/testing-observable-epic-which-invokes-other-epic&quot;&gt;questions&lt;/a&gt; before RxJS started to click. The Redux Observable community helped a lot, and the pattern gave me a useful way to structure React applications with less logic inside components.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>How I started thinking about my styles</title>
    <link href="https://robinvdvleuten.nl/post/how-i-started-thinking-about-my-styles/"/>
    <updated>2017-01-16T00:00:00Z</updated>
    <id>https://robinvdvleuten.nl/post/how-i-started-thinking-about-my-styles/</id>
    <summary>A reflection on moving from carefully named CSS components toward small, explicit helper classes when the project calls for them.</summary>
    <content type="html">&lt;p&gt;If I had received a dollar for every time I promised myself I was about to write the most maintainable CSS I had ever seen, I could have bought a lot of coffee by now.&lt;/p&gt;
&lt;p&gt;When I started programming as a kid, somewhere in the table-layout era, things were simple. I made one website with one stylesheet. If it worked for me, it probably worked for everybody, mostly because my friends and I were the only ones visiting it anyway. It was fun.&lt;/p&gt;
&lt;picture&gt;
    &lt;source srcset=&quot;https://robinvdvleuten.nl/images/brent-rambo.webp&quot; type=&quot;image/webp&quot; media=&quot;(prefers-reduced-motion: no-preference)&quot;&gt;
    &lt;source srcset=&quot;https://robinvdvleuten.nl/images/brent-rambo.gif&quot; type=&quot;image/gif&quot; media=&quot;(prefers-reduced-motion: no-preference)&quot;&gt;
    &lt;img class=&quot;object-contain h-56 w-full&quot; src=&quot;https://robinvdvleuten.nl/images/brent-rambo.png&quot; alt=&quot;Brent Rambo approves&quot;&gt;
&lt;/picture&gt;
&lt;p&gt;Years and plenty of larger projects later, CSS started to feel like one of the harder parts of web work. Sure, &lt;a href=&quot;https://martinfowler.com/bliki/TwoHardThings.html&quot; title=&quot;Go to the Two Hard Things article of Martin Fowler&quot;&gt;naming things is hard and cache invalidation is harder&lt;/a&gt;. But keeping a CSS codebase understandable gets worse with every extra developer. It gets even worse when someone drops a few &lt;strong&gt;!important&lt;/strong&gt; fixes into the stylesheet because it worked on their machine.&lt;/p&gt;
&lt;p&gt;I got used to it. That seemed to be part of the job, and at least we did not have to deal with IE6 anymore. Then one day, while going through podcast episodes on my commute, I listened to &lt;a href=&quot;https://fullstackradio.com/&quot; title=&quot;Go to the website of the Full Stack Radio podcast&quot;&gt;Full Stack Radio&lt;/a&gt; interview &lt;a href=&quot;https://twitter.com/davidhemphill&quot; title=&quot;Go to the Twitter profile of David Hemphill&quot;&gt;David Hemphill&lt;/a&gt; about &lt;a href=&quot;http://buildwithbeard.com/&quot; title=&quot;Go to the website of the Beard framework&quot;&gt;Beard&lt;/a&gt;. It is an atomic CSS framework, or as David called it, &amp;quot;a framework for people with better things to do&amp;quot;.&lt;/p&gt;
&lt;p&gt;That clicked for me. We all know about &lt;a href=&quot;http://getbem.com/&quot; title=&quot;Read more about BEM on its official website&quot;&gt;BEM&lt;/a&gt;, &lt;a href=&quot;http://oocss.org/&quot; title=&quot;Read more about OOCSS on its official website&quot;&gt;OOCSS&lt;/a&gt;, and &lt;a href=&quot;https://smacss.com/&quot; title=&quot;Read more about SMACSS on its official website&quot;&gt;SMACSS&lt;/a&gt;. Those approaches can work well, but they often feel better suited to large projects with bigger budgets than to small freelance projects with tight deadlines.&lt;/p&gt;
&lt;p&gt;That is where a framework like Beard can fit. It may feel strange at first because we have all learned to avoid inline styles. But instead of inline styles, you get helper classes that can be customized with a preprocessor like &lt;a href=&quot;https://sass-lang.com/&quot; title=&quot;Read more about SASS on its official website&quot;&gt;Sass&lt;/a&gt; or &lt;a href=&quot;http://lesscss.org/&quot; title=&quot;Read more about LESS on its official website&quot;&gt;Less&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;With a preprocessor, you can even use helper classes as extends. That seems to power the &lt;a href=&quot;https://marvelapp.com/styleguide/&quot; title=&quot;Go to the Marvel styleguide&quot;&gt;styleguides&lt;/a&gt; behind &lt;a href=&quot;https://marvelapp.com/&quot; title=&quot;Go to the Marvel website&quot;&gt;Marvel&lt;/a&gt;. I like their fully written helpers more than Beard&#39;s abbreviations:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;css&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.fdcr&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; // Used by Beard&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.flexDirection-column-reverse&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; // Used by Marvel;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;For new developers on a project, the longer version is easier to pick up than overlapping abbreviations and exceptions.&lt;/p&gt;
&lt;p&gt;It is also not a coincidence that people in the React world are exploring &lt;a href=&quot;https://speakerdeck.com/vjeux/react-css-in-js&quot; title=&quot;View the React CSS-in-JS presentation of Vjeux&quot;&gt;similar techniques&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Should I use this for every future project? Probably not. But it did change how I think about styling: sometimes a small, explicit helper is better than another class name pretending to be a component.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>JavaScript rendering in PHP with Lambda</title>
    <link href="https://robinvdvleuten.nl/post/javascript-rendering-in-php-with-lambda/"/>
    <updated>2016-04-11T00:00:00Z</updated>
    <id>https://robinvdvleuten.nl/post/javascript-rendering-in-php-with-lambda/</id>
    <summary>Render React from a PHP application by invoking AWS Lambda through the PHP SDK and reusing the same payload on the client.</summary>
    <content type="html">&lt;p&gt;As a web developer, you have probably run into &lt;a href=&quot;http://blog.nodejitsu.com/scaling-isomorphic-javascript-code&quot;&gt;polymorphic JavaScript&lt;/a&gt;, or &lt;a href=&quot;https://cdb.reacttraining.com/universal-javascript-4761051b7ae9&quot;&gt;universal JavaScript&lt;/a&gt;. Whatever name you prefer, the idea is the same: render the same JavaScript application in more than one environment.&lt;/p&gt;
&lt;p&gt;For PHP applications, that usually means finding a way to run JavaScript from PHP. One option is AWS Lambda.&lt;/p&gt;
&lt;p&gt;If you are not familiar with &lt;a href=&quot;https://aws.amazon.com/lambda/&quot;&gt;Lambda&lt;/a&gt;, it is a serverless compute service. You upload code as functions, and AWS handles scaling and availability. Those functions can then be invoked from web or mobile applications.&lt;/p&gt;
&lt;p&gt;At the time, there were two common ways to run JavaScript from PHP: install the &lt;a href=&quot;https://github.com/phpv8/v8js&quot;&gt;V8js extension&lt;/a&gt;, which embeds the V8 engine in your PHP app, or let PHP and Node.js talk through the &lt;a href=&quot;http://ericescalante.com/2015/06/07/isomorphic/&quot;&gt;DNode RPC protocol&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;With Lambda, you can upload JavaScript to AWS and call it through the AWS API. Start with a small &amp;quot;Hello, World!&amp;quot; function:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;js&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;/**&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt; * Very simple Lambda which just returns a string on invocation.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt; */&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;exports&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;handler&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (event&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; context) {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;context&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.succeed&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;Hello, World!&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Upload that code to your AWS account. &lt;a href=&quot;https://medium.com/@tombray&quot;&gt;Tom Bray&lt;/a&gt; wrote &lt;a href=&quot;https://medium.com/@tombray/getting-started-with-aws-lambda-node-js&quot;&gt;a useful tutorial&lt;/a&gt; that uses the same kind of example.&lt;/p&gt;
&lt;p&gt;Once the function runs in the AWS console, invoke it through the &lt;a href=&quot;https://aws.amazon.com/sdk-for-php/&quot;&gt;AWS PHP SDK&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;php&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;&amp;lt;?&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;php&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;require_once&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;__DIR__&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;/vendor/autoload.php&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Aws&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;\&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Lambda&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;\&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;LambdaClient&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;$client &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;LambdaClient&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;factory&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;[&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;version&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;latest&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// The region where you have created your Lambda&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;region&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;eu-west-1&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;$result &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; $client&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;invoke&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;[&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// The name of your created Lambda function&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;FunctionName&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;hello-world&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;echo&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;json_decode&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;) $result&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;Payload&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;))&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// You&#39;ll now see &quot;Hello, World! in your output&quot;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;The result comes back as a JSON encoded payload. The example is basic, but it is enough to show the shape of the integration. Next, render a small &lt;a href=&quot;https://reactjs.org/&quot;&gt;React&lt;/a&gt; application inside a simple &lt;a href=&quot;https://silex.symfony.com/&quot;&gt;Silex&lt;/a&gt; application.&lt;/p&gt;
&lt;p&gt;Suppose we have a React component that renders a simple string:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;js&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; React &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;react&#39;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;Application&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (props) &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;Hello, {&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;props&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.name}!&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; Application&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;That component can be rendered on the client side like this:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;html&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;html&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;head&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;meta&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;charset&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;utf-8&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; /&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;title&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;php-react-lamda-example&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;title&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;head&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;root&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;window&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.__INITIAL_PAYLOAD__ &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; { name&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;World&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;src&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;/bundle.js&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;html&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;js&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; React &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;react&#39;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; { render } &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;react-dom&#39;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; Application &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;./app&#39;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;payload&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;window&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.__INITIAL_PAYLOAD__&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;render&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Application&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;payload&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.name} /&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;document&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.getElementById&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;root&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;The client reads the &lt;code&gt;name&lt;/code&gt; property from the payload and renders the component into the &lt;strong&gt;#root&lt;/strong&gt; element.&lt;/p&gt;
&lt;p&gt;To render the component on Lambda, upload the component code with this handler:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;js&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; React &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;react&#39;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; { renderToString } &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;react-dom/server&#39;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; Application &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;../client/app&#39;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;handler&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(event&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; context) {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;markup&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;renderToString&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Application&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.name} /&amp;gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;context&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.succeed&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(markup)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Instead of rendering into an HTML element, the handler passes the resulting string to the Lambda function&#39;s &lt;em&gt;succeed&lt;/em&gt; callback.&lt;/p&gt;
&lt;p&gt;Then invoke the Lambda function from a small PHP application:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;php&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;&amp;lt;?&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;php&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;$filename &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;__DIR__&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;/../dist/&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;preg_replace&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;#(\?.*)$#&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt; $_SERVER[&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;REQUEST_URI&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;php_sapi_name&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;cli-server&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;&amp;&amp;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;is_file&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;$filename&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;require_once&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;__DIR__&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;/../vendor/autoload.php&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Aws&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;\&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Lambda&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;\&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;LambdaClient&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Silex&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;\&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Application&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;$app &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Application&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;();&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;$app[&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;debug&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;$app&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;/&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; ($app) {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    $client &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;LambdaClient&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;factory&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;[&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;version&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;latest&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;region&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;eu-west-1&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    ]&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    $payload &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;json_encode&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;name&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;Server&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    $result &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; $client&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;invoke&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;[&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;FunctionName&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;php-react-lamda-example&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;Payload&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; $payload&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    ]&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    $markup &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;json_decode&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;) $result&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;Payload&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;))&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string)&quot;&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;EOF&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-string)&quot;&gt;&amp;lt;html&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-string)&quot;&gt;    &amp;lt;head&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-string)&quot;&gt;        &amp;lt;meta charset=&quot;utf-8&quot;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-string)&quot;&gt;        &amp;lt;title&amp;gt;php-react-lamda-example&amp;lt;/title&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-string)&quot;&gt;    &amp;lt;/head&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-string)&quot;&gt;    &amp;lt;body&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-string)&quot;&gt;        &amp;lt;div id=&quot;root&quot;&amp;gt;$markup&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-string)&quot;&gt;        &amp;lt;script&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-string)&quot;&gt;            window.__INITIAL_PAYLOAD__ = $payload;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-string)&quot;&gt;        &amp;lt;/script&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-string)&quot;&gt;        &amp;lt;script src=&quot;/bundle.js&quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-string)&quot;&gt;    &amp;lt;body&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-string)&quot;&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;EOF&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;$app&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;run&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;When someone opens the root route, the PHP app invokes the Lambda function and passes a JSON encoded payload. Lambda returns a React markup string, which the PHP app uses for the server-rendered HTML. The same payload is also passed to the client.&lt;/p&gt;
&lt;p&gt;This is a basic example, but the same shape can be extended for larger PHP and JavaScript applications. The full source, including the webpack build, is available &lt;a href=&quot;https://github.com/webstronauts/php-react-lambda-example&quot;&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Handle authenticated users in Behat/Mink</title>
    <link href="https://robinvdvleuten.nl/post/handle-authenticated-users-in-behat-mink/"/>
    <updated>2015-12-12T00:00:00Z</updated>
    <id>https://robinvdvleuten.nl/post/handle-authenticated-users-in-behat-mink/</id>
    <summary>Skip repeated login steps in Behat scenarios by creating an authenticated Symfony session through Mink&#39;s BrowserKit driver.</summary>
    <content type="html">&lt;p&gt;One useful addition to PHP&#39;s testing tools is &lt;a href=&quot;https://docs.behat.org/en/latest/&quot;&gt;Behat&lt;/a&gt;. It focuses on business expectations through behavior driven development, while &lt;a href=&quot;https://phpunit.de/&quot;&gt;PHPUnit&lt;/a&gt; is usually used for lower-level test driven development. With BDD, you describe how the feature should behave from the user&#39;s point of view.&lt;/p&gt;
&lt;p&gt;If you use Behat with &lt;a href=&quot;https://mink.behat.org/en/latest/&quot;&gt;Mink&lt;/a&gt;, you can test browser behavior. But when your application has authentication, you probably do not want to fill in the login form in every scenario. You do not have to.&lt;/p&gt;
&lt;p&gt;First, look at this Behat feature:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;gherkin&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;Feature&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string)&quot;&gt; Sign in to the website&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;In &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;order to access the administrative interface&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    As a visitor&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    I need to be able to log in to the website&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;Background&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;:&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;Given &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;there are following users:&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;            | username | email       | plain_password | enabled |&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;            | bar      | bar@foo.com | foo            | yes     |&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;Scenario&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string)&quot;&gt; Log in with username and password&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;Given &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;I am on &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;/admin/login&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;         &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;When &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;I fill in the following:&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;            | username | bar |&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;            | password | foo |&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;          &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;And &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;I press &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;Login&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;         &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;Then &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;I should be on &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;/admin/&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;          &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;And &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;I should see &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;Logout&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;Scenario&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string)&quot;&gt; Log in with bad credentials&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;Given &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;I am on &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;/admin/login&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;         &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;When &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;I fill in the following:&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;            | username | bar@foo.com |&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;            | password | bar         |&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;          &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;And &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;I press &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;Login&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;         &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;Then &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;I should be on &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;/admin/login&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;          &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;And &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;I should see &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;Invalid username or password&quot;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;This feature describes a login form. It creates a &lt;em&gt;bar&lt;/em&gt; user, logs in with valid credentials, then checks the failed login path. But what if you want to test an admin dashboard? You might start with something like this:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;gherkin&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;Feature&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string)&quot;&gt; Use the admin dashboard&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;In &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;order to see the current status of my application&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    As an administrative user&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    I need to be able to use the admin dashboard&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;Background&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;:&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;Given &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;there are following users:&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;            | username | email       | plain_password | enabled |&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;            | bar      | bar@foo.com | foo            | yes     |&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;Scenario&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string)&quot;&gt; Displaying the blog overview&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;Given &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;I am on &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;/admin/login&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;When &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;I fill in the following:&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;           | username | bar |&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;           | password | foo |&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;         &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;And &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;I press &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;Login&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;Then &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;I should be on &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;/admin/&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;         &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;And &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;I should see &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;Admin dashboard&quot;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;This scenario fills in the login form just to reach the admin dashboard. But the login behavior was already tested in the previous feature. You can quickly end up repeating those same steps in every scenario that needs an authenticated user.&lt;/p&gt;
&lt;p&gt;I wanted a reusable step instead of repeating the login process everywhere. After some searching, I found that &lt;a href=&quot;https://knplabs.com/en/blog/behat-like-a-boss-meta-steps&quot;&gt;KNP Labs&lt;/a&gt; had run into the same problem. Their solution grouped the login process into one step with so-called meta-steps. That reduces the number of lines in the feature, but the browser still goes through the login process every time.&lt;/p&gt;
&lt;p&gt;There had to be a better option. The Symfony cookbook explains &lt;a href=&quot;https://symfony.com/doc/current/testing/http_authentication.html&quot;&gt;how authentication can be simulated through tokens&lt;/a&gt;, and after some trial and error with Behat and Mink, I ended up with this code in my FeatureContext:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;php&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;&amp;lt;?&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;php&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Behat&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;\&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Mink&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;\&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Driver&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;\&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;BrowserKitDriver&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Symfony&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;\&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Component&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;\&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;BrowserKit&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;\&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Cookie&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Symfony&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;\&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Component&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;\&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Security&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;\&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Core&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;\&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Authentication&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;\&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Token&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;\&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;UsernamePasswordToken&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;/**&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt; * @Given /^I am authenticated as &quot;([^&quot;]*)&quot;$/&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt; */&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;iAmAuthenticatedAs&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;($username)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    $driver &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;$this&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;getSession&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;getDriver&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;$driver &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;instanceof&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;BrowserKitDriver&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;throw&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;UnsupportedDriverActionException&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;This step is only supported by the BrowserKitDriver&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    $client &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; $driver&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;getClient&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    $client&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;getCookieJar&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Cookie&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;session_name&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(),&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    $session &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; $client&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;getContainer&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;session&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    $user &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;$this&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;kernel&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;getContainer&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;fos_user.user_manager&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;findUserByUsername&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;$username&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    $providerKey &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;$this&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;kernel&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;getContainer&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;getParameter&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;fos_user.firewall_name&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    $token &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;UsernamePasswordToken&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;($user&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; $providerKey&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; $user&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;getRoles&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    $session&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;_security_&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;$providerKey&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;serialize&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;$token&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;))&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    $session&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;save&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    $cookie &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Cookie&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;($session&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;getName&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(),&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; $session&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;getId&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    $client&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;getCookieJar&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;$cookie&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Now a scenario that needs an authenticated user can start like this:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;gherkin&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;Scenario&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string)&quot;&gt; Displaying the blog overview&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;Given &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;I am authenticated as &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;bar&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;And &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;I am on &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;/admin/&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;     &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;Then &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;I should see &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;Admin dashboard&quot;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;That is much better than writing the login process over and over again.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Running Hapi with Passenger on Heroku</title>
    <link href="https://robinvdvleuten.nl/post/running-hapi-with-passenger-on-heroku/"/>
    <updated>2015-11-05T00:00:00Z</updated>
    <id>https://robinvdvleuten.nl/post/running-hapi-with-passenger-on-heroku/</id>
    <summary>Configure Passenger to run a Hapi.js application locally and on Heroku with the right startup file, socket binding, and buildpacks.</summary>
    <content type="html">&lt;p&gt;Before it changed its name to Passenger, the application server was called mod_rails and ran as a module inside &lt;a href=&quot;https://httpd.apache.org/&quot;&gt;Apache HTTPD&lt;/a&gt; for &lt;a href=&quot;https://rubyonrails.org/&quot;&gt;Ruby on Rails&lt;/a&gt; applications.&lt;/p&gt;
&lt;p&gt;Passenger tunes itself to make better use of the server: all available CPU cores by default, HTTP caching without extra configuration, and load balancing across application processes.
If you have run Node.js in production, you know how much tuning can depend on the server. A little help is welcome.&lt;/p&gt;
&lt;h2&gt;Run Node.js with Passenger&lt;/h2&gt;
&lt;p&gt;The first step is installing Passenger. It supports many Unix-based platforms, and the &lt;a href=&quot;https://www.phusionpassenger.com/library/install/standalone/install/oss/&quot;&gt;Passenger guides&lt;/a&gt; cover them in detail. For this example, assume you want to run Passenger on macOS and install it with &lt;a href=&quot;https://brew.sh/&quot;&gt;Homebrew&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Afterwards, check whether the install worked:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;bash&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;$ sudo passenger-config validate-install&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Passenger tells you whether all checks passed. If something fails, use the macOS-specific &lt;a href=&quot;https://www.phusionpassenger.com/library/install/standalone/install/oss/osx/&quot;&gt;guide&lt;/a&gt; to fix it.&lt;/p&gt;
&lt;p&gt;Now start a Node.js script from the command line. We will use Passenger&#39;s &lt;a href=&quot;https://github.com/phusion/passenger-nodejs-connect-demo&quot;&gt;Node.js demo app&lt;/a&gt;. Run this command inside the app directory to start Passenger in the foreground:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;bash&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;$ passenger start --app-type node --startup-file myapp.js&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;This tells Passenger to run a Node.js application with &lt;code&gt;--app-type node&lt;/code&gt; and use &lt;code&gt;myapp.js&lt;/code&gt; as the startup file with &lt;code&gt;--startup-file myapp.js&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Configure Passenger for Hapi.js&lt;/h2&gt;
&lt;p&gt;The demo app uses Node&#39;s built-in &lt;a href=&quot;https://nodejs.org/api/http.html&quot;&gt;HTTP module&lt;/a&gt;. To make Passenger work with a &lt;a href=&quot;https://hapi.dev/&quot;&gt;Hapi.js&lt;/a&gt; application, Hapi needs to listen a little differently. Instead of listening on a port number, it needs to listen on a Unix domain socket, as described in &lt;a href=&quot;https://www.phusionpassenger.com/library/indepth/nodejs/reverse_port_binding.html#hapi-js-solution-example&quot;&gt;Passenger&#39;s guide&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;js&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;typeof&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; PhusionPassenger &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;!==&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;undefined&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;PhusionPassenger&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.configure&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;({ autoInstall&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; })&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; Hapi &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;hapi&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; server&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;typeof&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; PhusionPassenger &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;!==&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;undefined&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// Requires Passenger &amp;gt;= 4.0.52!&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    server &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Hapi&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.Server&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;/passenger&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;} &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    server &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Hapi&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.Server&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;localhost&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;3000&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;server&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.route&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;({&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    method&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;GET&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    path&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;/hello&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;handler&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (request&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; reply) {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;reply&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;hello world&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    }&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;})&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;server&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.start&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h2&gt;Run Hapi.js with Passenger on Heroku&lt;/h2&gt;
&lt;p&gt;Now that the &lt;em&gt;Hello World&lt;/em&gt; example runs locally, we can run it on &lt;a href=&quot;https://www.heroku.com/&quot;&gt;Heroku&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Heroku processes can be managed with &lt;a href=&quot;https://github.com/ddollar/foreman&quot;&gt;Foreman&lt;/a&gt; and &lt;em&gt;Procfiles&lt;/em&gt;. A typical &lt;em&gt;Procfile&lt;/em&gt; for a Node.js Passenger application looks like this:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;bash&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;web: bundle &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;exec&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; passenger start --app-type node --startup-file myapp.js --port $PORT&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;em&gt;$PORT&lt;/em&gt; environment variable comes from Heroku. Your app must bind to it so Heroku can route HTTP traffic to the process.&lt;/p&gt;
&lt;p&gt;You may also notice &lt;code&gt;bundle exec&lt;/code&gt;. That is a Ruby command for running an application with the gems installed by Bundler.&lt;/p&gt;
&lt;p&gt;That sounds odd for a Node.js application, but Passenger is installed through the Ruby buildpack. Heroku detects the Node.js buildpack automatically, and it also supports &lt;a href=&quot;https://devcenter.heroku.com/articles/using-multiple-buildpacks-for-an-app&quot;&gt;multiple buildpacks&lt;/a&gt;. Create a &lt;em&gt;.buildpacks&lt;/em&gt; file with this content:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: undefined&quot;&gt;https://github.com/heroku/heroku-buildpack-nodejs.git https://github.com/heroku/heroku-buildpack-ruby.git&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Then add a &lt;em&gt;Gemfile&lt;/em&gt; so the Ruby buildpack installs Passenger:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: undefined&quot;&gt;source &#39;https://rubygems.org&#39; gem &#39;passenger&#39;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Now Passenger can run the Hapi application on Heroku.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>How I use environment variables with Node.js</title>
    <link href="https://robinvdvleuten.nl/post/how-i-use-environment-variables-with-nodejs/"/>
    <updated>2014-08-14T00:00:00Z</updated>
    <id>https://robinvdvleuten.nl/post/how-i-use-environment-variables-with-nodejs/</id>
    <summary>Use direnv and a local .envrc file to keep project-specific Node.js environment variables near the code without committing secrets.</summary>
    <content type="html">&lt;p&gt;After I tweeted about &lt;a href=&quot;https://github.com/zimbatm/direnv&quot;&gt;direnv&lt;/a&gt;, I noticed that a lot of people were dealing with the same problem in their &lt;a href=&quot;http://nodejs.org/&quot;&gt;Node.js&lt;/a&gt; projects: project-specific environment variables.&lt;/p&gt;
&lt;p&gt;This was the tweet:&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot; data-tweet=&quot;&quot;&gt;
    &lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Damn it looks like &lt;a href=&quot;https://t.co/pW1BSAlHRe&quot;&gt;https://t.co/pW1BSAlHRe&lt;/a&gt; makes my &lt;a href=&quot;https://twitter.com/hashtag/nodejs?src=hash&amp;amp;ref_src=twsrc%5Etfw&quot;&gt;#nodejs&lt;/a&gt; project-based env vars a whole lot easier!&lt;/p&gt;
    &lt;footer&gt;Robin van der Vleuten (@robinvdvleuten) &lt;a href=&quot;https://twitter.com/robinvdvleuten/status/491511945204621312?ref_src=twsrc%5Etfw&quot;&gt;July 22, 2014&lt;/a&gt;&lt;/footer&gt;
&lt;/blockquote&gt;
&lt;p&gt;The response made me write down how I use direnv in Node.js projects.&lt;/p&gt;
&lt;p&gt;Most of my private Node.js projects ran on &lt;a href=&quot;http://heroku.com/&quot;&gt;Heroku&lt;/a&gt;. Heroku add-ons usually expose configuration through environment variables like &lt;code&gt;MONGOHQ_URL&lt;/code&gt; or &lt;code&gt;REDISTOGO_URI&lt;/code&gt;. Those values are project-specific, so I do not want them in &lt;code&gt;~/.profile&lt;/code&gt;. I want them near the project, without committing them to the repository. That is where &lt;strong&gt;direnv&lt;/strong&gt; helps.&lt;/p&gt;
&lt;h2&gt;So what&#39;s direnv then?&lt;/h2&gt;
&lt;p&gt;Direnv calls itself an &lt;em&gt;environment variable manager&lt;/em&gt;, which is exactly how I use it. Instead of putting variables in your global &lt;code&gt;~/.profile&lt;/code&gt;, or writing application code that checks whether it is running locally, you create an &lt;code&gt;.envrc&lt;/code&gt; file in the project root. It can look like this:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;bash&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; SIGNATURE_SECRET=ThisIsAVerySecretSecret&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;That is not something you want in the repository, so add &lt;code&gt;.envrc&lt;/code&gt; to &lt;code&gt;.gitignore&lt;/code&gt;. If direnv is installed correctly, it warns you when the file changes:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;bash&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;direnv: error .envrc is blocked. Run &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;`direnv allow`&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; to approve its content.&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;That warning is useful. Maybe you changed the file, or maybe someone else changed it and you should read it first. If you trust the change, run &lt;code&gt;direnv allow&lt;/code&gt; and direnv loads the variables.&lt;/p&gt;
&lt;h2&gt;How do I install direnv?&lt;/h2&gt;
&lt;p&gt;The direnv maintainers already document installation well. Check &lt;a href=&quot;https://github.com/zimbatm/direnv#install&quot;&gt;the GitHub project&lt;/a&gt; for current install instructions.&lt;/p&gt;
&lt;h2&gt;How do I use these env vars in Node.js?&lt;/h2&gt;
&lt;p&gt;Node.js exposes environment variables on &lt;code&gt;process.env&lt;/code&gt;. If your &lt;code&gt;.envrc&lt;/code&gt; contains the &lt;code&gt;SIGNATURE_SECRET&lt;/code&gt; value from above, you can read it like this:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;js&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;console&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.log&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;process&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;env&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;SIGNATURE_SECRET&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Run that code from the project root. That part matters, because otherwise direnv does not load the &lt;code&gt;.envrc&lt;/code&gt; file. You should see &lt;code&gt;ThisIsAVerySecretSecret&lt;/code&gt; as output.&lt;/p&gt;
&lt;p&gt;That is enough for most local project configuration.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Rendering templates from a Twig extension</title>
    <link href="https://robinvdvleuten.nl/post/rendering-templates-in-a-twig-extension/"/>
    <updated>2014-07-10T00:00:00Z</updated>
    <id>https://robinvdvleuten.nl/post/rendering-templates-in-a-twig-extension/</id>
    <summary>Let a Symfony Twig extension render a separate template by asking Twig to inject the current environment into your function.</summary>
    <content type="html">&lt;p&gt;Suppose you need to render something inside a Symfony template, like a toolbar. You could create a controller action and embed it. Symfony already explains embedded controllers in the &lt;a href=&quot;https://symfony.com/doc/2.7/templating.html#embedding-controllers&quot;&gt;docs&lt;/a&gt;, and that answer shows up often.&lt;/p&gt;
&lt;p&gt;There is another option: create a Twig extension with a function that renders the template.&lt;/p&gt;
&lt;p&gt;Start by creating the extension. Assume the usual &lt;em&gt;AcmeDemoBundle&lt;/em&gt; exists in the Symfony project. This Twig extension class, similar to the one described &lt;a href=&quot;https://symfony.com/doc/2.7/templating/twig_extension.html&quot;&gt;in the Symfony docs&lt;/a&gt;, renders the HTML for a toolbar:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;php&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;&amp;lt;?&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;php&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// src/Acme/DemoBundle/Twig/AcmeExtension.php&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;namespace&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;Acme&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;\&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;DemoBundle&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;\&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;Twig&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;AcmeExtension&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;extends&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;\&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;Twig_Extension&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;getFilters&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;\&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Twig_SimpleFunction&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;toolbar&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;array&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;$this&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;renderToolbar&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;array&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;is_safe&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;array&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;html&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        ))&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;renderToolbar&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        $toolbar &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string)&quot;&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;EOF&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-string)&quot;&gt;&amp;lt;ul class=&quot;toolbar&quot;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-string)&quot;&gt;    &amp;lt;li class=&quot;toolbar-item&quot;&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;Tool #1&amp;lt;/a&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-string)&quot;&gt;    &amp;lt;li class=&quot;toolbar-item&quot;&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;Tool #2&amp;lt;/a&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-string)&quot;&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;EOF&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; $toolbar;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;getName&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;acme_extension&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;The toolbar HTML is returned from &lt;code&gt;renderToolbar&lt;/code&gt;. It works, but the string in that method is not something I would want to maintain.&lt;/p&gt;
&lt;p&gt;Move the HTML into a separate template:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;twig&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;ul&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;toolbar&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;li&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;toolbar-item&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;href&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;#&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;Tool #1&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;li&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;toolbar-item&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;href&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;#&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;Tool #2&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;ul&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;This template can be referenced as &lt;strong&gt;AcmeDemoBundle::toolbar.html.twig&lt;/strong&gt; and rendered from the extension. You could inject the &lt;code&gt;templating&lt;/code&gt; or &lt;code&gt;twig&lt;/code&gt; service, but Twig can also pass the current environment into your method:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;php&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;&amp;lt;?&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;php&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// src/Acme/DemoBundle/Twig/AcmeExtension.php&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;namespace&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;Acme&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;\&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;DemoBundle&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;\&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;Twig&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;AcmeExtension&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;extends&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;\&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;Twig_Extension&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;getFilters&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;\&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Twig_SimpleFunction&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;toolbar&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;array&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;$this&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;renderToolbar&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;array&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;is_safe&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;array&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;html&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;needs_environment&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;true&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        ))&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;renderToolbar&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;\&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Twig_Environment&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; $twig)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; $twig&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;render&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;AcmeDemoBundle::toolbar.html.twig&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;getName&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;acme_extension&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;The important part is &lt;code&gt;&#39;needs_environment&#39; =&amp;gt; true&lt;/code&gt;. It tells Twig to inject the current environment. Because this is a Symfony environment, you can use the bundle template shortcut like usual.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>A spelling and grammar checker for Node</title>
    <link href="https://robinvdvleuten.nl/post/a-spelling-and-grammar-checker-for-node/"/>
    <updated>2014-01-03T00:00:00Z</updated>
    <id>https://robinvdvleuten.nl/post/a-spelling-and-grammar-checker-for-node/</id>
    <summary>Gingerbread wraps Ginger&#39;s spelling and grammar correction API for Node scripts and command line usage.</summary>
    <content type="html">&lt;p&gt;When you write in a foreign language, you probably reach for &lt;a href=&quot;https://translate.google.com/&quot;&gt;Google Translate&lt;/a&gt;. It works well for individual words, but sentence grammar is a different problem. For example, &lt;code&gt;Edwards will be sck yesterday&lt;/code&gt; becomes &lt;code&gt;Edwards will be sick yesterday&lt;/code&gt;, but it does not fix &lt;code&gt;will be&lt;/code&gt; to &lt;code&gt;was&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;There are other services that handle that kind of correction, so I made a Node wrapper for one of them: &lt;a href=&quot;https://github.com/robinvdvleuten/gingerbread&quot;&gt;Gingerbread&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Gingerbread uses the free API from &lt;a href=&quot;https://www.gingersoftware.com/&quot;&gt;Ginger&lt;/a&gt; and is based on the &lt;a href=&quot;https://github.com/subosito/gingerice&quot;&gt;gingerice&lt;/a&gt; gem. You can install it as a CLI executable or use it in your Node scripts. Install it through npm:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;bash&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;$ npm install gingerbread&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;To install it as a global executable, add &lt;code&gt;-g&lt;/code&gt; to that command.
In a script, use it like this:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;javascript&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; gingerbread &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;gingerbread&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;gingerbread&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;The smelt of fliwers bring back memories.&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;/** options to override **/&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    }&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (error&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; text&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; result&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; corrections) {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// result contains &#39;The smell of flowers brings back memories.&#39;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;See the &lt;a href=&quot;https://github.com/robinvdvleuten/gingerbread/blob/master/README.md&quot;&gt;README&lt;/a&gt; for more usage information, or report issues &lt;a href=&quot;https://github.com/robinvdvleuten/gingerbread/issues&quot;&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Running Symfony on Heroku</title>
    <link href="https://robinvdvleuten.nl/post/running-symfony-on-heroku/"/>
    <updated>2013-10-28T00:00:00Z</updated>
    <id>https://robinvdvleuten.nl/post/running-symfony-on-heroku/</id>
    <summary>Deploy a Symfony standard edition app to Heroku with a PHP buildpack, environment variables, and the right web document root.</summary>
    <content type="html">&lt;p&gt;If you mostly work with PHP, &lt;a href=&quot;https://www.heroku.com/&quot;&gt;Heroku&lt;/a&gt; may not be the first platform you reach for. It started with Ruby applications, but buildpacks made it possible to run many other stacks. At the time, Heroku did not have an official PHP buildpack, but &lt;a href=&quot;https://www.christophh.net/&quot;&gt;Christoph Hochstrasser&lt;/a&gt; had one that made Symfony deployments possible.&lt;/p&gt;
&lt;p&gt;First, install the &lt;a href=&quot;https://devcenter.heroku.com/articles/heroku-cli&quot;&gt;Heroku CLI&lt;/a&gt; so you can access your Heroku account from the command line. Make sure this works:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;bash&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;# Will output something like &#39;heroku/7.42.1 darwin-x64 node-v12.16.2&#39;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;$ heroku --version&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Now create a Symfony application with &lt;a href=&quot;https://getcomposer.org/&quot;&gt;Composer&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;bash&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;$ php composer.phar create-project symfony/framework-standard-edition myapp/ 2.3.6&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Composer creates the application in &lt;code&gt;myapp/&lt;/code&gt; and installs the dependencies. Move into that directory and edit &lt;em&gt;composer.json&lt;/em&gt; so the buildpack knows which &lt;em&gt;document-root&lt;/em&gt; to use and which PHP settings to apply. Add this to the &lt;em&gt;extras&lt;/em&gt; section:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;json&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;&quot;extra&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;&quot;heroku&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;&quot;document-root&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;web&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;&quot;php-config&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; [&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;date.timezone=Europe/Amsterdam&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;display_errors=off&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;                &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;short_open_tag=off&quot;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            ]&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Every Heroku application is backed by a Git repository. When you push a new commit to the Heroku remote, Heroku deploys that commit as a new version of the application. Before deploying, initialize a Git repository and commit the Symfony application files:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;bash&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;# Run these commands from your application directory.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;$ git init&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;$ git add &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;$ git commit -m &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;Initialized a base Symfony application&quot;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;The Symfony application is now ready to deploy. Create a Heroku app that uses Christoph&#39;s &lt;a href=&quot;https://github.com/CHH/heroku-buildpack-php&quot;&gt;PHP buildpack&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;bash&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;heroku create myapp --buildpack https://github.com/CHH/heroku-buildpack-php&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Heroku creates the application and adds a &lt;code&gt;heroku&lt;/code&gt; remote to the Git repository. Push the commit to that remote:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;bash&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;$ git push heroku master&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;When Heroku receives the commit, it deploys the application through the buildpack. After the deploy, opening the app may show a 404. That is expected with the Symfony standard edition because it only includes &lt;em&gt;AcmeDemoBundle&lt;/em&gt;, and that bundle is only visible in the dev environment through &lt;em&gt;app_dev.php&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;There are a few Heroku-specific things to watch. The first is &lt;em&gt;parameters.yml&lt;/em&gt;. In the Symfony standard edition, Composer generates it and asks for input the first time. During a Heroku deploy, you cannot provide that input. A better approach is to remove &lt;em&gt;parameters.yml&lt;/em&gt; from &lt;em&gt;config.yml&lt;/em&gt; and configure everything with environment variables. Symfony reads them when they are prefixed with &lt;code&gt;SYMFONY__&lt;/code&gt;. To set the &lt;em&gt;secret&lt;/em&gt; value on Heroku, run:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;bash&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;$ heroku config:set SYMFONY__SECRET=ThisTokenIsNotSoSecretChangeIt&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;You also need environment variables during the compile process. To make them available, enable the &lt;a href=&quot;https://devcenter.heroku.com/articles/labs-user-env-compile&quot;&gt;user-env-compile&lt;/a&gt; labs feature:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;bash&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;$ heroku labs:enable user-env-compile -a myapp&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;You can enable &lt;a href=&quot;https://newrelic.com/&quot;&gt;New Relic&lt;/a&gt; support by adding this to &lt;em&gt;composer.json&lt;/em&gt;:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;json&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;&quot;extra&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;&quot;heroku&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;&quot;newrelic&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;true&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;That is enough to get a Symfony application running on Heroku.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Install the intl PHP extension in MAMP</title>
    <link href="https://robinvdvleuten.nl/post/install-the-intl-php-extension-in-mamp/"/>
    <updated>2013-06-17T00:00:00Z</updated>
    <id>https://robinvdvleuten.nl/post/install-the-intl-php-extension-in-mamp/</id>
    <summary>Build ICU, install PHP&#39;s intl extension with PECL, and enable it for Symfony applications running through MAMP.</summary>
    <content type="html">&lt;p&gt;If you build Symfony applications, you will run into &lt;a href=&quot;https://symfony.com/doc/2.7/translation.html&quot;&gt;translations&lt;/a&gt; sooner or later. The translation component needs the PHP intl extension, which may require a little extra setup in MAMP.&lt;/p&gt;
&lt;p&gt;First, grab the &lt;a href=&quot;http://site.icu-project.org/download/48#ICU4C-Download&quot;&gt;ICU libraries&lt;/a&gt; and build them:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;bash&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;$ curl -O http://download.icu-project.org/files/icu4c/4.8.1.1/icu4c-4_8_1_1-src.tgz&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;$ tar xzvf icu4c-4_8_1_1-src.tgz&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;$ &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;cd&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; icu/source&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;$ ./runConfigureICU MacOSX&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;$ make &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;&amp;&amp;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; sudo make install&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Now install the PHP intl extension with PECL. Make sure the MAMP PHP binary directory is in your $PATH, then run:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;bash&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;$ pecl install intl&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Finally, add &lt;code&gt;extension=intl.so&lt;/code&gt; to your php.ini file. Restart your shell and MAMP so they pick up the change.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Mock Guzzle responses in Symfony tests</title>
    <link href="https://robinvdvleuten.nl/post/mock-guzzle-responses-in-symfony-tests/"/>
    <updated>2013-06-03T00:00:00Z</updated>
    <id>https://robinvdvleuten.nl/post/mock-guzzle-responses-in-symfony-tests/</id>
    <summary>Add Guzzle&#39;s MockPlugin to a Symfony service client so functional tests can replay raw HTTP responses instead of calling external APIs.</summary>
    <content type="html">&lt;p&gt;Most web applications talk to at least one external service. Maybe you pull in tweets, send email, call a payment provider, or fetch data from an API. If you have functional tests around that code, you want them to be fast and predictable.&lt;/p&gt;
&lt;p&gt;I had this problem with the latest-tweet bar on this site. The tweets were fetched with &lt;a href=&quot;http://guzzlephp.org/&quot;&gt;Guzzle&lt;/a&gt;, and Guzzle has built-in support for mocked responses. Here is how I used it in Symfony.&lt;/p&gt;
&lt;p&gt;Guzzle explains response mocking in its &lt;a href=&quot;http://guzzlephp.org/testing/unit-testing.html#queueing-mock-responses&quot;&gt;docs&lt;/a&gt;. You add &lt;code&gt;Guzzle\Plugin\Mock\MockPlugin&lt;/code&gt; as a subscriber to the client object. In Symfony, that client is probably registered as a service, so grab it from the container and add the plugin before running the request in your test:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;php&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;&amp;lt;?&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;php&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Guzzle&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;\&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Http&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;\&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Message&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;\&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Response&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Guzzle&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;\&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Plugin&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;\&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Mock&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;\&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;MockPlugin&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;HeaderControllerTest&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;extends&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;WebTestCase&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;testTweets&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        $client &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;static::&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;createClient&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        $twitterClient &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; $client&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;getContainer&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;rvdv_blog.guzzle.twitter_client&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        $plugin &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;MockPlugin&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;();&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        $plugin&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;addResponse&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;__DIR__&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;/twitter_200_response.txt&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        $twitterClient&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;addSubscriber&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;$plugin&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        $crawler &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; $client&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;request&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;GET&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;/_header_tweets&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        $response &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; $client&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;getResponse&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;$this&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;assertTrue&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;$response&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;isSuccessful&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;())&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;$this&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;assertTrue&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;$response&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;isCacheable&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;())&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;$this&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;assertEquals&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;3600&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; $response&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;getMaxAge&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;())&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;$this&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;assertNotNull&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;$response&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;getEtag&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;())&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;$this&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;assertGreaterThan&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; $crawler&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;filter&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;li&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;count&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;())&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;The client comes from the container, and the mock plugin is added as a subscriber. The text file contains the raw response you would receive from the external service. For example:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;bash&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;HTTP/1.1 200 OK&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;Date: Wed, 25 Nov 2009 12:00:00 GMT&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;Connection: close&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;Server: AmazonS3&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;Content-Type: application/xml&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;&amp;lt;?&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;xml version=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;1.0&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; encoding=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;UTF-8&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;?&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;LocationConstraint xmlns=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;http://s3.amazonaws.com/doc/2006-03-01/&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;EU&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;/LocationConstraint&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;You can create multiple raw responses for different scenarios. Maybe you want to test a 500 response, a 401 response, or a malformed payload, and then check whether your application handles each case correctly.&lt;/p&gt;
&lt;p&gt;To get those raw responses from the service, I used &lt;a href=&quot;https://www.charlesproxy.com/&quot;&gt;Charles&lt;/a&gt;. It is an HTTP proxy that sits between your local environment and the service. Turn Charles on and add these cURL options to your Guzzle client so you can inspect the traffic:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;php&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;&amp;lt;?&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;php&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Guzzle&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;\&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Http&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;\&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Client&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;$client &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;Client&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;https://api.twitter.com/{version}&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;array&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;version&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;v1.1&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;request.options&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;array&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// This should be the port you&#39;ve configured Charles to listen for.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;proxy&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;   &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;http://localhost:8888&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    )&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Once you have captured the responses, remove the proxy configuration and use the files in your tests.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Masking content in Drupal</title>
    <link href="https://robinvdvleuten.nl/post/masking-content-in-drupal/"/>
    <updated>2013-03-06T00:00:00Z</updated>
    <id>https://robinvdvleuten.nl/post/masking-content-in-drupal/</id>
    <summary>Content Mask adds shortcode-style visibility rules to Drupal content so specific users, roles, or groups can see protected sections.</summary>
    <content type="html">&lt;p&gt;Today I released a small &lt;a href=&quot;https://www.drupal.org/project/content_mask&quot;&gt;module&lt;/a&gt; for hiding parts of your content from everyone except specific roles, users, or organic groups. It is based on WordPress&#39;s &lt;a href=&quot;https://codex.wordpress.org/Shortcode_API&quot;&gt;shortcode API&lt;/a&gt; and works together with a Drupal text format.&lt;/p&gt;
&lt;p&gt;A simple example looks like this:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;bash&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla ac ligula&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;non augue scelerisque dapibus vel et ante. Pellentesque volutpat nibh ut&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;ipsum bibendum euismod. Ut nibh nisi, aliquam sed ornare eu, hendrerit&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;quis nunc. Nam vel nunc sed erat dapibus accumsan id eget nunc.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;[content_mask uid&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;1&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;]&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;Mauris ornare augue ac augue tempor auctor. Praesent justo ligula,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;convallis quis semper a, eleifend ut mi. Etiam eleifend aliquet&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;quam, accumsan eleifend erat vestibulum at. Pellentesque habitant&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;morbi tristique senectus et netus et malesuada fames ac turpis egestas.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;[/content_mask]&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;[content_mask role&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&quot;editor|administrator&quot;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;]&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;Praesent rutrum, ligula vitae porta faucibus, nunc ante consequat est,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; imperdiet eros odio vitae nunc.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;[/content_mask]&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;You can mask content based on a user&#39;s UID or role. The attribute on the shortcode describes who is allowed to view the content.&lt;/p&gt;
&lt;p&gt;The module&#39;s project page is &lt;a href=&quot;https://www.drupal.org/project/content_mask&quot;&gt;on Drupal.org&lt;/a&gt;, with the code and issue queue there too.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Using Drush in a MAMP environment</title>
    <link href="https://robinvdvleuten.nl/post/using-drush-in-a-mamp-environment/"/>
    <updated>2012-06-22T00:00:00Z</updated>
    <id>https://robinvdvleuten.nl/post/using-drush-in-a-mamp-environment/</id>
    <summary>Point your shell at MAMP&#39;s PHP binary, clean up PEAR configuration, and install Drush inside a local MAMP setup.</summary>
    <content type="html">&lt;p&gt;&lt;a href=&quot;http://drush.ws/&quot;&gt;Drush&lt;/a&gt; is one of those tools you want nearby when working on Drupal. Installing it inside a local MAMP setup can be a little awkward, though.&lt;/p&gt;
&lt;p&gt;I had to set it up again after a fresh MAMP Pro install, so here are the steps.&lt;/p&gt;
&lt;h2&gt;Make MAMP&#39;s PHP binary the default one&lt;/h2&gt;
&lt;p&gt;When you install MAMP on a Mac, you usually end up with more than one PHP binary. macOS ships with its own Apache/PHP setup, and that is the one your terminal may use by default. For Drush, you want the command line to use MAMP&#39;s PHP instead.&lt;/p&gt;
&lt;p&gt;You can do that by changing your &lt;code&gt;PATH&lt;/code&gt;. First, check the current value:
&lt;code&gt;echo $PATH&lt;/code&gt;
The output should look something like this:
&lt;code&gt;/sbin:/usr/local/bin:/usr/X11/bin&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;MAMP Pro came with PHP &lt;em&gt;5.2.17&lt;/em&gt; and &lt;em&gt;5.3.6&lt;/em&gt;. I wanted version &lt;em&gt;5.3.6&lt;/em&gt; to be the default one in the terminal. Edit &lt;em&gt;~/.bash_profile&lt;/em&gt;:
&lt;code&gt;sudo vi ~/.bash_profile&lt;/code&gt;
Make sure this line exists:
&lt;code&gt;PATH=/Applications/MAMP/Library/bin:/Applications/MAMP/bin/php/php5.3.6/bin:$PATH&lt;/code&gt;
This puts MAMP&#39;s PHP 5.3.6 binary before the other PHP binaries on the system.&lt;/p&gt;
&lt;p&gt;Now run &lt;code&gt;which php&lt;/code&gt; in the terminal. You should see something like this:
&lt;code&gt;/Applications/MAMP/Library/bin&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;Install Drush through PEAR&lt;/h2&gt;
&lt;p&gt;MAMP&#39;s PEAR includes a config file that can cause strange issues. I had already hit this &lt;a href=&quot;https://robinvdvleuten.nl/post/install-the-mongo-extension-in-mamp-with-pecl/&quot;&gt;earlier&lt;/a&gt; and fixed it by running:
&lt;code&gt;rm /Applications/MAMP/bin/php/php5.3.6/conf/pear.conf&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Next, install the &lt;em&gt;console_getopt&lt;/em&gt; PEAR module:
&lt;code&gt;pear install Console_Getopt&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Now install Drush:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;bash&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;$ pear upgrade --force Console_Getopt&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;$ pear upgrade --force pear&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;$ pear upgrade-all&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;$ pear channel-discover pear.drush.org&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;$ pear install drush/drush&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;To check if everything works, run:
&lt;code&gt;drush version&lt;/code&gt;
If the install worked, you should see &lt;em&gt;drush version 5.4&lt;/em&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Install Mongo extension in MAMP with PECL</title>
    <link href="https://robinvdvleuten.nl/post/install-the-mongo-extension-in-mamp-with-pecl/"/>
    <updated>2012-03-26T00:00:00Z</updated>
    <id>https://robinvdvleuten.nl/post/install-the-mongo-extension-in-mamp-with-pecl/</id>
    <summary>Install the legacy Mongo PHP extension in MAMP by matching the PHP source, fixing PEAR configuration, and enabling mongo.so.</summary>
    <content type="html">&lt;p&gt;As you may have guessed from &lt;a href=&quot;https://robinvdvleuten.nl/post/upgrade-mamps-phpmyadmin/&quot;&gt;my previous post&lt;/a&gt;, I was using MAMP on a Mac. I liked the Pro interface more than tools like XAMPP, but installing PEAR or PECL extensions was not obvious.&lt;/p&gt;
&lt;p&gt;Here is how I installed the &lt;a href=&quot;https://www.php.net/manual/en/class.mongodb.php&quot;&gt;MongoDB extension&lt;/a&gt;. The same approach should work for other extensions too.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Download the PHP source code that matches the version you use with MAMP. I was using 5.3.6, so I downloaded &lt;a href=&quot;https://www.php.net/get/php-5.3.6.tar.bz2/from/a/mirror&quot;&gt;this package&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Extract it to this folder: &lt;code&gt;/Applications/MAMP/bin/php/php5.3.6/include/php&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Run the following command in this folder:
&lt;code&gt;./configure&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The PEAR config file causes some strange issues in MAMP, so remove it:
&lt;code&gt;rm /Applications/MAMP/bin/php/php5.3.6/conf/pear.conf&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Install the MongoDB extension with PECL:
&lt;code&gt;cd /Applications/MAMP/bin/php/php5.3.6/bin&lt;/code&gt;
&lt;code&gt;./pecl install mongo&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Activate the installed extension in your php.ini file:
&lt;code&gt;extension=mongo.so&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That&#39;s it. You can now use MongoDB from PHP applications running through MAMP. Most of these steps came from &lt;a href=&quot;http://m-schmidt.eu/2011/11/06/develop-mongodb-web-apps-with-mamp-under-mac-os-x/&quot;&gt;Matthias Schmidt&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>From A to Z in PHP</title>
    <link href="https://robinvdvleuten.nl/post/from-a-till-z-in-php/"/>
    <updated>2012-03-06T00:00:00Z</updated>
    <id>https://robinvdvleuten.nl/post/from-a-till-z-in-php/</id>
    <summary>PHP&#39;s range function can generate the alphabet with one small call, which is easy to miss when you need letter arrays.</summary>
    <content type="html">&lt;p&gt;Today I found a small PHP helper I had somehow missed: &lt;code&gt;range()&lt;/code&gt; can give you an array with every letter in the alphabet.&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;php&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;&amp;lt;?&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;php&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;range&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;a&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;z&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;?&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
</content>
  </entry>
  <entry>
    <title>Upgrade MAMP&#39;s phpMyAdmin</title>
    <link href="https://robinvdvleuten.nl/post/upgrade-mamps-phpmyadmin/"/>
    <updated>2012-03-05T00:00:00Z</updated>
    <id>https://robinvdvleuten.nl/post/upgrade-mamps-phpmyadmin/</id>
    <summary>Replace MAMP&#39;s bundled phpMyAdmin with a newer version by backing up the old files and extracting the download into the right folder.</summary>
    <content type="html">&lt;p&gt;My &lt;a href=&quot;https://www.mamp.info/en/&quot;&gt;MAMP&lt;/a&gt; version shipped with phpMyAdmin 3.3.9.2, while phpMyAdmin 3.4.10.1 was already available. Upgrading the bundled version turned out to be simple: download it and extract it to the right folder.&lt;/p&gt;
&lt;p&gt;First, download the latest version from phpMyAdmin&#39;s &lt;a href=&quot;https://www.phpmyadmin.net/downloads/&quot;&gt;download page&lt;/a&gt;.
If you use MAMP, back up the old files and extract the new files here: &lt;code&gt;/Applications/MAMP/bin/phpMyAdmin&lt;/code&gt;.
Restart MAMP and open &lt;code&gt;http://localhost/phpMyAdmin&lt;/code&gt;. You should see the upgraded phpMyAdmin interface.&lt;/p&gt;
&lt;p&gt;For MAMP Pro, the phpMyAdmin files live here: &lt;code&gt;/Library/Application Support/appsolute/MAMP PRO/phpMyAdmin&lt;/code&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Remove Drupal stylesheets with hook_css_alter</title>
    <link href="https://robinvdvleuten.nl/post/remove-drupal-stylesheets-with-hook_css_alter/"/>
    <updated>2012-02-24T00:00:00Z</updated>
    <id>https://robinvdvleuten.nl/post/remove-drupal-stylesheets-with-hook_css_alter/</id>
    <summary>Use Drupal&#39;s hook_css_alter in template.php to remove core or module stylesheets before the page renders.</summary>
    <content type="html">&lt;p&gt;When you start building Drupal themes, you quickly find a lot of default stylesheets in the page. If you want to remove some of them from your theme, implement &lt;em&gt;hook_css_alter&lt;/em&gt; in &lt;em&gt;template.php&lt;/em&gt; and unset the files you do not want.&lt;/p&gt;
&lt;p&gt;Here is a small example:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;php&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;&amp;lt;?&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;php&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;/**&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt; * Remove Drupal&#39;s core system stylesheets.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt; */&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;THEMENAME_css_alter&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;&amp;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;$css)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    $path_system &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;drupal_get_path&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;module&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;system&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    $remove &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;array&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        $path_system &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;/system.base.css&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        $path_system &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;/system.menus.css&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        $path_system &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;/system.messages.css&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        $path_system &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;/system.theme.css&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    );&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// Remove stylesheets which match our remove array.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;foreach&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; ($css &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;as&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; $stylesheet &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; $options) {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;in_array&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;$stylesheet&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt; $remove&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;unset&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;$css[$stylesheet]&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;This removes the system module stylesheets before Drupal renders them. The same pattern works for any module stylesheet you want to remove.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Add your own styles to Drupal&#39;s WYSIWYG module</title>
    <link href="https://robinvdvleuten.nl/post/add-your-own-styles-to-drupals-wysiwyg-module/"/>
    <updated>2012-02-24T00:00:00Z</updated>
    <id>https://robinvdvleuten.nl/post/add-your-own-styles-to-drupals-wysiwyg-module/</id>
    <summary>Make CKEditor inside Drupal&#39;s WYSIWYG module load your theme styles by enabling the stylesheetparser plugin.</summary>
    <content type="html">&lt;p&gt;For a better editing experience on node forms, I installed the &lt;a href=&quot;https://www.drupal.org/project/wysiwyg&quot;&gt;WYSIWYG module&lt;/a&gt; with the &lt;a href=&quot;https://ckeditor.com/&quot;&gt;CKEditor plugin&lt;/a&gt;. It adds CKEditor to large text fields, but by default it loads CKEditor&#39;s predefined styles.&lt;/p&gt;
&lt;p&gt;That is fine at first, but I wanted the editor to show the styles from the theme. The WYSIWYG module supports loading stylesheets, but at the time it was buggy with CKEditor.&lt;/p&gt;
&lt;p&gt;After some digging, I found that CKEditor 3.6 needs &lt;a href=&quot;https://docs-old.ckeditor.com/CKEditor_3.x/Developers_Guide/Styles#Stylesheet_Parser_Plugin&quot;&gt;the stylesheetparser plugin&lt;/a&gt;, and the WYSIWYG module was not loading it. A tiny module was enough to fix that:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;php&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;&amp;lt;?&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;php&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;ckeditor_styles_wysiwyg_editor_settings_alter&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;&amp;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;$settings&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;&amp;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;$context)&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;{&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; ($context[&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;profile&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;editor &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;ckeditor&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        $settings[&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;extraPlugins&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;.=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;,stylesheetparser&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// Not necessary but removes all default styles from the CKEditor.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;        $settings[&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;stylesSet&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;array&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;();&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    }&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Now go to &lt;em&gt;admin/config/content/wysiwyg/profile/filtered_html/edit&lt;/em&gt; and select &lt;em&gt;Define CSS&lt;/em&gt; under &lt;em&gt;Editor CSS&lt;/em&gt;. Add the stylesheet URI there, and CKEditor will show your theme styles on every textarea that uses &lt;em&gt;filtered_html&lt;/em&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Configure multiple WordPress environments</title>
    <link href="https://robinvdvleuten.nl/post/configure-multiple-wordpress-environments/"/>
    <updated>2012-02-01T00:00:00Z</updated>
    <id>https://robinvdvleuten.nl/post/configure-multiple-wordpress-environments/</id>
    <summary>Load a different WordPress configuration file per environment so local, staging, and production can keep separate database settings.</summary>
    <content type="html">&lt;p&gt;Most web applications need more than one environment. You might have development, staging, and production, each with its own database settings. WordPress can handle that with a multi-site installation, but that did not fit the way I wanted to work.&lt;/p&gt;
&lt;p&gt;In my case, the WordPress site was hosted on &lt;a href=&quot;http://pagodabox.com/&quot;&gt;Pagoda Box&lt;/a&gt;. It was a good hosting option for PHP applications, but it was limited when it came to configuring WordPress. Plugins had to be installed locally and committed to the repository. I first followed &lt;a href=&quot;http://help.pagodabox.com/customer/portal/articles/175468-wordpress&quot;&gt;their documentation&lt;/a&gt; to get WordPress running, but I did not like how the database connection had to be configured both locally and on Pagoda Box.&lt;/p&gt;
&lt;p&gt;The version I ended up with checks an environment variable called &lt;code&gt;ENVIRONMENT&lt;/code&gt; and loads the matching configuration file. It defaults to &lt;code&gt;dev&lt;/code&gt;, so a local setup loads &lt;em&gt;wp-config/wp-config.dev.php&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;To make WordPress load those files, change &lt;em&gt;wp-config.php&lt;/em&gt; to this:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;php&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;&amp;lt;?&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;php&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;/** Absolute path to the WordPress directory. */&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; ( &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;defined&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;ABSPATH&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; )&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;define&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;ABSPATH&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt; dirname&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;__FILE__&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;/&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;/** Load configuration based on the server&#39;s environment **/&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;$env &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;isset&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;$_SERVER[&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;ENVIRONMENT&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; $_SERVER[&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;ENVIRONMENT&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;dev&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;require_once&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; ABSPATH &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;wp-config/wp-config.&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;strtolower&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;$env&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;.php&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;/** Sets up WordPress vars and included files. */&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;require_once&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(ABSPATH &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;wp-settings.php&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Now your environment configuration file can look like this, for example when connecting to a Pagoda Box database instance:&lt;/p&gt;
&lt;pre class=&quot;shiki css-variables&quot; style=&quot;background-color: var(--shiki-color-background); color: var(--shiki-color-text)&quot;&gt;&lt;div class=&quot;language-id&quot;&gt;php&lt;/div&gt;&lt;div class=&quot;code-container&quot;&gt;&lt;code&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;&amp;lt;?&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;php&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;/**&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt; * The base configurations of the WordPress.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt; *&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt; * This file has the following configurations: MySQL settings, Table Prefix,&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt; * Secret Keys, WordPress Language, and ABSPATH. You can find more information&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt; * by visiting {&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;@link&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt; http://codex.wordpress.org/Editing_wp-config.php Editing&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt; * wp-config.php} Codex page. You can get the MySQL settings from your web host.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt; *&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt; * This file is used by the wp-config.php creation script during the&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt; * installation. You don&#39;t have to use the web site, you can just copy this file&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt; * to &quot;wp-config.php&quot; and fill in the values.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt; *&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt; * &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;@package&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt; WordPress&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt; */&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;// ** MySQL settings - You can get this info from your web host ** //&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;/** The name of the database for WordPress */&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;define&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;DB_NAME&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;database_name_here&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;/** MySQL database username */&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;define&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;DB_USER&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;username_here&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;/** MySQL database password */&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;define&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;DB_PASSWORD&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;password_here&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;/** MySQL hostname */&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;define&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;DB_HOST&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;localhost&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;/** Database Charset to use in creating database tables. */&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;define&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;DB_CHARSET&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;utf8&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;/** The Database Collate type. Don&#39;t change this if in doubt. */&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;define&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;DB_COLLATE&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;/**&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt; * Authentication Unique Keys and Salts.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt; *&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt; * Change these to different unique phrases!&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt; * You can generate these using the {&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;@link&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt; https://api.wordpress.org/secret-key/1.1/salt/ WordPress.org secret-key service}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt; * You can change these at any point in time to invalidate all existing cookies. This will force all users to have to log in again.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt; *&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt; * &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;@since&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt; 2.6.0&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt; */&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;define&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;AUTH_KEY&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;         &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;put your unique phrase here&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;define&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;SECURE_AUTH_KEY&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;put your unique phrase here&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;define&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;LOGGED_IN_KEY&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;put your unique phrase here&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;define&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;NONCE_KEY&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;put your unique phrase here&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;define&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;AUTH_SALT&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;put your unique phrase here&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;define&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;SECURE_AUTH_SALT&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;put your unique phrase here&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;define&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;LOGGED_IN_SALT&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;   &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;put your unique phrase here&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;define&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;NONCE_SALT&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;       &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;put your unique phrase here&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;/**&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt; * WordPress Database Table prefix.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt; *&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt; * You can have multiple installations in one database if you give each a unique&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt; * prefix. Only numbers, letters, and underscores please!&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt; */&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;$table_prefix  &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;wp_&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;/**&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt; * WordPress Localized Language, defaults to English.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt; *&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt; * Change this to localize WordPress. A corresponding MO file for the chosen&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt; * language must be installed to wp-content/languages. For example, install&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt; * de_DE.mo to wp-content/languages and set WPLANG to &#39;de_DE&#39; to enable German&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt; * language support.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt; */&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;define&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;WPLANG&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;/**&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt; * For developers: WordPress debugging mode.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt; *&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt; * Change this to true to enable the display of notices during development.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt; * It is strongly recommended that plugin and theme developers use WP_DEBUG&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt; * in their development environments.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt; */&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt;define&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;WP_DEBUG&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-function)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-constant)&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-punctuation)&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-comment)&quot;&gt;/** Sets up WordPress vars and included files. */&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;line&quot;&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;require_once&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;(ABSPATH &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-keyword)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: var(--shiki-token-string-expression)&quot;&gt;&#39;wp-settings.php&#39;&lt;/span&gt;&lt;span style=&quot;color: var(--shiki-color-text)&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/div&gt;&lt;/pre&gt;
</content>
  </entry>
</feed>
