<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://erikras.com/</id>
    <title>erikras.com</title>
    <updated>2026-05-21T00:00:00.000Z</updated>
    <generator>https://github.com/jpmonette/feed</generator>
    <author>
        <name>Erik Rasmussen</name>
        <email>rasmussenerik@gmail.com</email>
        <uri>https://erikras.com</uri>
    </author>
    <link rel="alternate" href="https://erikras.com/"/>
    <link rel="self" href="https://erikras.com/atom.xml"/>
    <subtitle>Erik Rasmussen's personal website</subtitle>
    <logo>https://erikras.com/og-image.jpg</logo>
    <icon>https://erikras.com/favicon.ico</icon>
    <rights>© Erik Rasmussen</rights>
    <entry>
        <title type="html"><![CDATA[⏯️ AudioCard — The React audio player designed for podcasts and Twitter cards]]></title>
        <id>https://erikras.com/blog/audiocard</id>
        <link href="https://erikras.com/blog/audiocard"/>
        <updated>2019-04-16T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Engage your podcast audience on social media with an in-Twitter player!]]></summary>
        <content type="html"><![CDATA[
I recently launched two podcasts, neither of which are technical. The first is
[Seek Justice](https://seekjustice.fm/), in which I engage my friend who is an
expert in the criminal justice system — a thing I know almost nothing about — in
thoughtful discussion about justice reform. The second is
[Happy Hour with Dennis and Erik](https://happyhour.fm/), in which my same
friend and I have an evening beverage, tell stories, and make each other laugh.

I’ve learned a lot about podcasting so far. One thing I already knew was that
both podcasts needed to have decent websites that could quickly get someone
listening, and make it as frictionless as possible to subscribe. I really liked
the design of [The Undefined Podcast](https://undefined.fm/), by fellow React
nerds Jared Palmer and Ken Wheeler, so I more or less plagiarized their design
layout entirely.

I knew I wanted the website to be static html with React rehydration, which
currently very fashionable in 2019, and I went back and forth looking at
blogging boilerplates from [Gatsby](https://www.gatsbyjs.org/) and
[React Static](https://react-static.js.org/), but what really sealed the deal
was learning of Shawn Wang’s very recent work on a
[React Static Podcast hosting framework](https://github.com/sw-yx/react-static-podcast-hosting),
which does things like generating an RSS feed with show notes directly from the
same markdown files that make up your episode post, using his hilariously named
[Podcats](https://github.com/sw-yx/podcats) library. Awesome!

> But there was one final piece of the puzzle that I needed: an in-page audio
> player.

Surveying the React audio player landscape, I found that the two leading
contenders are [Cassette](https://benwiley4000.github.io/cassette/styleguide)
and [React Audio Player](https://github.com/justinmc/react-audio-player).
Cassette is quite sophisticated, and does video as well, but it was playing my
MP3 files using the `<video>` tag, and required a stylesheet import. And React
Audio Player is just a simple wrapper around the HTML5 `<audio>` tag. I wanted
to replicate the embeddable players of some of the nice podcast hosting
services, like Simplecast or Anchor, with “album” art.

And for bonus points, I’d love it if I could use the `twitter:player` meta tag
to get my podcast episodes to play
[_directly in Twitter_](https://developer.twitter.com/en/docs/tweets/optimize-with-cards/overview/player-card.html).
My favorite podcast consumption app, [Overcast](https://overcast.fm/), has this
functionality on its website.

I also knew, since it’s 2019, I wanted to use Typescript and React Hooks. I
wasn’t really sure how to create a library with Typescript, but I do know of a
well maintained React Typescript library! So I more or less cloned the repo for
[Formik](https://github.com/jaredpalmer/formik) and removed all the code, but
kept the scaffolding. One side-effect of this was that I ended up learning
[Storybook](https://storybook.js.org/) for testing my component.

> Without further ado… Introducing…

## ⏯️ AudioCard

AudioCard is a responsive audio player for React designed to be compatible with
[Twitter Player Cards](https://developer.twitter.com/en/docs/tweets/optimize-with-cards/overview/player-card.html).

```jsx
<AudioCard
  title="Ep 1. – What is Criminal Justice?"
  art="https://seekjustice.fm/art300.jpg"
  source="https://seekjustice.fm/media/001.mp3"
  skipBackSeconds={10}
  skipForwardSeconds={30}
/>
```

<caption>Example usage</caption>

This renders something that looks like:

<div style={{ textAlign: "center" }}>
  <img
    src="/images/audio-card-screenshot.jpg"
    alt="AudioCard screenshot"
    width={1000}
    height={313}
  />
</div>
<caption>It’s just a screenshot. Don’t click it! 😄</caption>

If you have a podcasting website that you build with React, and you either don’t
have an in-page player, or are dissatisfied with the one your podcast host has
given you to embed, or would like to add Twitter player card functionality, then
check it out.

### [Github Repo](https://github.com/erikras/audiocard)

### [Storybook Playground](https://erikras.github.io/audiocard/)

---

Here you can see it working with embedded tweets:

<s>1116441320636260352</s>

<s>1112983811401318401</s>

Thanks for reading. I hope this project can be of use to some people. ❤️

---

...and if you find either of my podcasts interesting, susbcribing, sharing and
⭐⭐⭐⭐⭐ iTunes ratings are the nicest gifts you can give a podcaster.
]]></content>
        <author>
            <name>Erik Rasmussen</name>
            <email>rasmussenerik@gmail.com</email>
            <uri>https://erikras.com</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Beatles in Code]]></title>
        <id>https://erikras.com/blog/beatles-in-code</id>
        <link href="https://erikras.com/blog/beatles-in-code"/>
        <updated>2021-11-09T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[A twitter thread I started back in May 2021]]></summary>
        <content type="html"><![CDATA[
<s>https://twitter.com/erikras/status/1394204791929196544</s>
]]></content>
        <author>
            <name>Erik Rasmussen</name>
            <email>rasmussenerik@gmail.com</email>
            <uri>https://erikras.com</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Introducing… 🔒 Blockchain Form]]></title>
        <id>https://erikras.com/blog/blockchain-form</id>
        <link href="https://erikras.com/blog/blockchain-form"/>
        <updated>2019-04-01T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[From the guy who thought putting form state in Redux was a good idea...]]></summary>
        <content type="html"><![CDATA[
I think we can all agree, the problem with all the current UI form libraries is
that the form state is controlled by a single entity, usually a corporation or
government, and there is very little accountability, and even less cryptographic
hashing. Wouldn’t it be better if the entire history of a user’s interaction
with a form were stored in a secure _decentralized_ place where both parties,
the user and the website, could trust was accurate and secured by strong one-way
cryptography?

> Now you can!

From [the guy](https://twitter.com/intent/follow?screen_name=erikras) that
thought putting form state in Redux was a good idea...

## 🔒 Blockchain Form

Now every single keypress, change of focus, and validation error of your form
fields can be stored on the Ethereum blockchain, which has an average
transaction speed of only 6 minutes per transaction. It’s the securest way ever
invented to store your form data. _**It’s 🔥 BLAZING 🔥 secure!**_

<div style={{ textAlign: "center" }}>
  <img
    src="/images/blockchain-animation.gif"
    alt="How Blockchain Form Works"
    width={629}
    height={329}
  />
</div>

<caption>Simple animation showing how 🔒 Blockchain Form works.</caption>

Want to modify field B when field A changes? No problem! Just write a quick and
easy [smart contract](https://en.wikipedia.org/wiki/Smart_contract) to modify
the other value! Just make sure you don’t make any mistakes in coding your smart
contract — and that your requirements will never change — because those
motherfuckers are **permanent**.

> But wait! There’s more!

<div style={{ textAlign: "center" }}>
  <img src="/images/but-wait.gif" alt="But wait!" width={391} height={295} />
</div>

Every time someone submits a form with 🔒 Blockchain Form, both the website and
the user will receive one free FormCoin™. _**OMGamification!!!**_ FormCoin™s are
a secure cryptocurrency that can be exchanged for goods and services if you can
convince someone that they’d rather have a virtual currency than whatever it is
they have that you want.

```bash
npm install blockchain-form
```

> [👉 Read the easy-to-follow, thorough documentation 👈](https://www.npmjs.com/package/blockchain-form)

Get started today! Because remember, kids: 🤔

> When your form data is distributed, no one is in control! ✊

<div style={{ textAlign: "center" }}>
  <img
    src="/images/spinning-coin.gif"
    alt="Spinning coin"
    width={240}
    height={240}
  />
</div>
]]></content>
        <author>
            <name>Erik Rasmussen</name>
            <email>rasmussenerik@gmail.com</email>
            <uri>https://erikras.com</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[🔔 CI Ding: Get Notified When CI Passes]]></title>
        <id>https://erikras.com/blog/ci-ding</id>
        <link href="https://erikras.com/blog/ci-ding"/>
        <updated>2026-04-11T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[CI Ding watches open GitHub pull requests in your browser and plays a real pass-or-fail sound when checks finally settle.]]></summary>
        <content type="html"><![CDATA[
Modern web development at companies with real products almost always involves a
large monorepo with a set of CI –
[Continuous Integration](https://en.wikipedia.org/wiki/Continuous_integration) –
tests to maintain code quality and make sure your PR hasn't broken something.
Even the leanest test suites I've seen take at least 10 minutes to run on most
PRs. This means that between the time when you feel "done" with your task, and
before you can merge the changes, and ideally before you request that someone
review your work, you have this 10 minutes of waiting. I often use this to
"self-review" my work, turning a critical eye on what I'm asking someone else to
evaluate, but sometimes I'm happy with the work, and some tiny linting thing
fails, and CI has to begin again.

So I wander off, ideally to start on my next task, but often to get coffee or,
let's be honest, to more unsavory places of the Internet in another tab.

> The biggest bottleneck in my workflow is remembering to check if my CI passed.

Sometimes I'll come back to a PR and CI will have passed or failed 30 minutes
earlier, but I hadn't noticed because I wasn't watching it constantly.

There's gotta be a better way!

> 💡 I want a browser extension (or first party implementation @github) that
> puts a "Notify me when CI finishes" checkbox on my PRs, and then sends an
> OS-level notification. v2 would buzz my Apple watch if I get up from my desk
> to stretch.
>
> – @erikras,
> [Aug 19, 2021](https://x.com/erikras/status/1428290614945329164?s=20)

I've been wanting a PR notiication for _almost five years_!

So I built one. It's called [CI Ding](https://ciding.app/). It does one thing:
it plays a happy sound when CI passes in an open, but backgrounded, tab, and it
plays a sad sound if it fails.

10-15 minute breaks between projects are healthy and promote focus and flow, but
it's important to remember to stop your break.

<div style={{ fontSize: "30px", textAlign: "center" }}>
  <a href="https://chromewebstore.google.com/detail/ci-ding/lojjpdolaokdkdkiidmcobbmhoombfjo">
    Get On Extension Store
  </a>
</div>

Let me know what you guys think.
[Download now](https://chromewebstore.google.com/detail/ci-ding/lojjpdolaokdkdkiidmcobbmhoombfjo)
while supplies last! It's free for your first 10 notifications.
]]></content>
        <author>
            <name>Erik Rasmussen</name>
            <email>rasmussenerik@gmail.com</email>
            <uri>https://erikras.com</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[The Colbert Questionert]]></title>
        <id>https://erikras.com/blog/colbert-questionert</id>
        <link href="https://erikras.com/blog/colbert-questionert"/>
        <updated>2026-05-21T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[In honor of the ending of The Late Show with Stephen Colbert, I decided to take his famous Questionert.]]></summary>
        <content type="html"><![CDATA[
Most weekday mornings during the past few years, between making coffee and
waiting for code to compile, I watch the previous night's
[_The Late Show with Stephen Colbert_](https://www.youtube.com/@ColbertLateShow)
on YouTube. In honor his final show tonight, I thought it might be fun to take
_The Colbert Questionert_, a series of questions that he likes to ask his
celebrity guests. Recently he gave it to
[Barack Obama](https://www.youtube.com/watch?v=XeQlFEkdQFo), and on his
penultimate show, Stephen
[took the Questionert himself](https://www.youtube.com/watch?v=iRvWlAGx7EM).
So...here we go!

---

## Best Sandwich?

My answer here is pure nostalgia. When I was a teen, I used to go to a sandwich
shop downtown in my home town, called "The Sub Club". I could walk there from my
mother's computer store, where I spent a _lot_ of time. My favorite order was a
"vegetable" (?) sandwich with a side of french onion soup. This sandwich had one
slice of cheese – I always ordered provolone – on a hoagie bun, and a whole
freakin' salad inside. The bizarre ingredient that I've never seen elsewhere
were bean sprouts. It was amazing, and will never be replicated, as the shop has
since shut down.

## What was your first concert?

[R.E.M.](https://en.wikipedia.org/wiki/R.E.M.) at the
[Dean Dome](https://en.wikipedia.org/wiki/Dean_Smith_Center). I was high school
at the North Carolina School of Science and Math, in Durham, North Carolina. It
must have been my junior year, because my senior year I went to many, including
a very memorable mosh pit with
[Alanis Morissette](https://en.wikipedia.org/wiki/Alanis_Morissette) during her
first ever
[Jagged Little Pill](https://en.wikipedia.org/wiki/Jagged_Little_Pill) tour, and
eventually
[Dave Matthews Band](https://en.wikipedia.org/wiki/Dave_Matthews_Band). Some of
my friends went to a
[Hootie and the Blowfish](https://en.wikipedia.org/wiki/Hootie_%26_the_Blowfish)
concert, of which I was extremely jealous.

## What is the scariest animal?

Some of the Questionert takers try to be clever and say, "Man", but that's too
easy.

Part of what makes pandemics so scary are the fact that you can't _see_ the
virus. I'm scared of things I can't see, so I'm gonna go with getting stung by a
jellyfish whilst swimming in the ocean...especially if it's far from shore.

## Apples or Oranges?

I love a good freshly squeezed orange juice and don't have time for apple juice.
However, nothing beats a crisp sweet apple.

And as Stephen always quips after the Questionertee answers "apples":

> You can't put peanut butter on an orange!

## Have you ever asked anyone else for their autograph?

Obviously this question makes more sense directed at celebrities, but I think my
only "standing in line to get an autograph" story is for
[Muggsy Bogues](https://en.wikipedia.org/wiki/Muggsy_Bogues), the famously
shortest player to ever play in the NBA. At the time, he played for my local
[Charlotte Hornets](https://en.wikipedia.org/wiki/Charlotte_Hornets), and I went
to a dozen or so games to see him play. It's not like I was a massive fan or
anything, but he must have been visiting my town for something and I got his
autograph.

## What do you think happens when we die?

This might seem like the hardest question, but to me it's the easiest: Nothing.

There is no afterlife. We just invented it because it's hard to reason about
"Where is [dead person]?" I've always appreciated the quote often attributed to
Mark Twain:

> I do not fear death. I had been dead for billions and billions of years before
> I was born, and had not suffered the slightest inconvenience from it.

That's perfection. The
[best answer](https://www.youtube.com/watch?v=8T3vwIXkTEc) ever on Colbert's
show was a question to Keanu Reeves that actually started the entire Questionert
series:

> I know that the ones who love us will miss us.

Even more perfect than Twain.

## What is your favorite action movie?

People like to say _Die Hard_ to this one, but I've gotta go with Sean Connery.
I'm not sure why, but
[_Indiana Jones and the Last Crusade_](https://en.wikipedia.org/wiki/Indiana_Jones_and_the_Last_Crusade)
hit me at the perfect age. The Nazi punching and the boobytrapped cave and the
whole Grail myth and the "cup of a carpenter" line. Just excellent.

## Window or Aisle?

This changed for me in adulthood. As a kid, I used to love to look out the
window, but I've seen all the clouds and tiny landscapes I need to by now, and
as a 6' (183cm) fellow, I've grown to appreciate the aisle, although it does
come with the risk of a food trolley banging your knee if you take too much
advantage of the aisle.

Stephen always jokes about the window involving bothering neighbors when you
need to go to the bathroom, but he never mentions one of my favorite lines from
_Fight Club_ where Brad Pitt muses about "Do I give you the ass or the crotch?"

## Favorite Smell?

Brewing coffee is lovely, but I've gotta go with _frying garlic_.

Which is a thing I do very often in my Mediterranean lifestyle.

## Least Favorite Smell?

I've thankfully yet to experience a decomposing corpse, and my son is just on
the cusp of becoming a stinky teenager (which is often an answer people give),
but my answer here is super basic. I'm gonna go with _poop_, even my own.

## Earliest Memory?

This is the hardest one of all for me because ever since reading
[_The Invisible Gorilla_](https://en.wikipedia.org/wiki/The_Invisible_Gorilla),
and similar books about how memory works, I have lost all faith in memory. If I
am ever called to be on a jury, I will probably get kicked off because I differ
from most people in that I give _zero_ credence to "eyewitness testimony".

Gun to my head, it's probably playing monochrome computer games with my best
childhood friend, Jacob. Especially hitting the ⬅️➡️ arrow keys repeatedly to
"run" in [Decathlon](https://en.wikipedia.org/wiki/Olympic_Decathlon). I was
probably five?

## Cats or Dogs?

I currently have a cat, and that decision was mostly because my apartment
lifestyle means that a dog is a _lot_ more work, because you've gotta walk them,
rain or shine, and also you can't leave them alone in the apartment for days at
a time to go on trips.

But dogs give so much more love back to you, so the answer is an unequivocal:
_dogs_!

## You get one song to listen to for the rest of your life: what is it?

He always specifies that it's not always playing, but any time you go to listen
to music, that's the song that plays.

This is a tough one. I don't think I want lyrics. The first one that comes to
mind is [Rhapsody in Blue](https://en.wikipedia.org/wiki/Rhapsody_in_Blue).

## What number am I thinking of?

This is always played as a joke when Stephen says, "Nope". He claims in his
self-Questionert that Meryl Streep and Ethan Hawke both correctly guessed 3.

But to be scientific about it, I'm gonna go with _37_, because that's the
[most common number](https://www.youtube.com/watch?v=d6iQrh2TK98) people think
of when you ask them for a "random" number.

## How would you describe the rest of your life in five words?

Proud parent. Happy traveling husband.

---

Thank you, Stephen Colbert. 🫶
]]></content>
        <author>
            <name>Erik Rasmussen</name>
            <email>rasmussenerik@gmail.com</email>
            <uri>https://erikras.com</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Declarative Form Rules]]></title>
        <id>https://erikras.com/blog/declarative-form-rules</id>
        <link href="https://erikras.com/blog/declarative-form-rules"/>
        <updated>2018-03-22T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[How can I update field B when field A changes from X to Y?]]></summary>
        <content type="html"><![CDATA[
The users of [🏁 React Final Form](https://react-final-form.org) continue to
suggest interesting use cases that challenge just what is possible from a form
library, and stimulate my imagination about possible solutions.

Today’s problem was:

> How can I update field B when field A changes from X to Y?

As the API stands currently, this could not be accomplished with
[🏁 Final Form Calculate](/blog/final-form-decorators), as it matters what the
_previous value_ was. We need a way to run some code to update another field
when one field changes, _without calling the update in `render()`_.

This requires a _**stateful**_ component to subscribe to changes to a field and
keep track of what the previous value was. It was pretty trivial to code up an
`OnChange` component that doesn’t render anything, but calls a callback when the
field value changes. This seemed so potentially useful that I went ahead and,
for completeness, created `OnBlur` and `OnFocus`, resulting in the library
[🏁 React Final Form Listeners](https://github.com/final-form/react-final-form-listeners#-react-final-form-listeners).

Having just discovered this idea, I have not yet decided how useful it is, or
whether or not it’s a good idea, but I’m sharing it to get feedback from others.
These components allow us to write “form rules” as non-rendering components,
like this:

```jsx
<WhenFieldChanges
  field="pickup"
  becomes={true}
  set="delivery.street"
  to={undefined}
/>
```

How do we write `WhenFieldChanges`? Well, we need a `Field` to get the
`onChange` function for the field we want to change, and then the OnChange
component to notify us when the field we want to listen to has changed value.

```jsx
const WhenFieldChanges = ({ field, becomes, set, to }) => (
  <Field name={set} subscription={{}}>
    {(
      // No subscription. We only use Field to get to the change function
      { input: { onChange } }
    ) => (
      <OnChange name={field}>
        {(value) => {
          if (value === becomes) {
            onChange(to);
          }
        }}
      </OnChange>
    )}
  </Field>
);
```

It’s important to notice that the function given to `OnChange` will _only_ be
called when the value changes, not on every render. Let’s see how it looks in
practice.

<iframe
  src="https://codesandbox.io/embed/52q597j2p?fontsize=14&hidenavigation=1&theme=dark"
  style={{
    width: "100%",
    height: 500,
    border: 0,
    borderRadius: 4,
    overflow: "hidden",
  }}
  title="🏁 React Final Form - Declarative Form Rules"
  allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
  sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>

## Conclusion

I’m still not convinced how useful this concept is, but it’s kind of fun to
explore the boundaries of what React allows us to do. Component composition, and
letting them keep state as the data cascades through them, is extremely
powerful.

As always, feedback welcome and encouraged! 🤔
]]></content>
        <author>
            <name>Erik Rasmussen</name>
            <email>rasmussenerik@gmail.com</email>
            <uri>https://erikras.com</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[🏁 Final Form – Announcement]]></title>
        <id>https://erikras.com/blog/final-form-announcement</id>
        <link href="https://erikras.com/blog/final-form-announcement"/>
        <updated>2017-11-27T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[The road to the checkered flag]]></summary>
        <content type="html"><![CDATA[
Two and a half years ago, about a month after
[Dan Abramov](https://overreacted.io)’s
[unforgettable launch](https://www.youtube.com/watch?v=xsSnOQynTHs) of Redux at
React Europe 2015, I released a humble little library that managed form state in
Redux, called [Redux-Form](https://redux-form.com), which, over the intervening
period, has grown quite substantially in popularity.

<div style={{ textAlign: "center" }}>
  <img
    src="/images/redux-form-logo.png"
    width={300}
    height={181}
    alt="Redux Form Logo"
  />
</div>

As I write this in late 2017, Redux-Form has well over 8k stars on Github and
800k monthly downloads on NPM. By my recent calculations, based entirely on NPM
download figures, about half of React projects use Redux, and about a quarter of
those use Redux-Form, so about 11% of the _7 million_ projects that download
React, also download Redux-Form.

With this popularity and usage has come a deluge of feedback. Everyone has had a
slightly different use case and requested a slightly different behavior out of
Redux-Form. I have done my best to accommodate these, and merge in pull requests
for features that never would have occurred to me. I’ve done a lot of thinking
about forms and form state management.

## Problems with Redux-Form

Over these years, I have heard four primary complaints about Redux-Form. For
some reason, unlike with most complaints, I have most heard these in person at
conferences than on Twitter or Github or Stackoverflow. They are as follows, in
no particular order, but with growing emoji angst:

1. **🙁 It’s only for React?**

“Redux works with Angular, _et. al._, it’s too bad that Redux-Form doesn’t.”

2. **😢 I have to use Redux?**

“I really need a form solution, but my project isn’t using Redux.” Also,
everyone high up in the React community,
[_including both inventors of Redux_](https://github.com/reactjs/redux/issues/1287#issuecomment-175351978),
say that Redux is **_not_** the best place to keep form data. Oops.

3. **😖 I can’t use inline render functions?**

“Why does my input lose focus on first keypress?” Well, it’s because Redux has
asynchronously updated both your input and the surrounding form component
(because now `dirty === true`), and because your inline render function is !==
the previous one, blah blah blah 😢

4. **😭 It’s [over 26k gzipped?](https://bundlephobia.com/result?p=redux-form)
   No way!**

“Bundle size, bro. I’m watching my weight.”

## Premature Optimization

As `v5` of Redux-Form became very popular, the biggest complaint I had was that
it was slow. The theory behind why was that it was rerendering the _entire_ form
on every single keypress, since the `values` were provided to the form
component.

In early 2016, I solved this, by completely rewriting it from scratch, in what
became `v6`, creating a `<Field/>` component, which only updated when _its_
state changed, but did not update when another field’s state changed. This
complicated matters greatly, because now the whole `<Form/>` only received a few
select form-wide props, so the people that needed more information had to
`connect()` with a
[suite of selectors](https://redux-form.com/8.3.0/docs/api/selectors.md/) to get
other form state that was no longer provided by default.

It is entirely possible that the fear of rerendering the entire form drummed
into me by the complaints that lead to Redux-Form `v6` is unfounded, but,
whether or not the user notices it, it must be true that rerendering parts of
your tree needlessly _is_ going to be slower than avoiding it. I decided that my
next form solution should have built-in “only rerender if you need to” logic,
which most people will never need, but that will allow fine tuning of complex
forms.

## Solution

Some time in early 2017, I had the idea to create a “form state management
engine” that was totally independent of React or Redux, just pure javascript. My
first attempt was using Immutable.js, leveraging its power for fast “has this
whole tree changed?” checks. I was about halfway done with it in May, when I
mentioned it to fellow form library author,
[David Khourshid 🎹](https://twitter.com/davidkpiano). His reaction to involving
Immutable.js (15.3k gzipped) was negative, and it made me rethink my approach;
could I do it without the help of _any_ third party libraries? In August, during
an offline vacation, I began coding a dependency-free form management library.

## Going with the Flow

Normally I would build a library from the inside out, from the core to the
public API, but this time, I defined the API first, even writing some tests,
before implementing the core functionality. By strongly typing my API with
[Flow](https://flow.org/), I was able to catch bugs and ensure that the
internals were satisfying the contracts of the API. It felt a little like pair
programming with an annoying partner that was constantly saying, “Well actually,
that’s supposed to return a string!”, _but in a good way!_ Flow is awesome. 😍

## Clogged Namespace

Before I get to the _finish line_ of this post, I’d like to talk about the
process of choosing library names. I probably tried a hundred different names
involving the word “form” before finding one that was not taken already on NPM.
So many of them are rotten unused/unfinished/undocumented libraries that have
not been updated in a long time, and others are just empty namespace squatters —
a tactic I was forced to resort to in reserving the name of my library until
launch. I guess it was inevitable that a good NPM library name would become as
hard to acquire as a good .com domain, but it’s still frustrating.

Speaking of namespace..._Emojispace!_ After seeing the emoji branding succcesses
of popular 2017 libraries
[Styled Components 💅](https://www.styled-components.com/),
[Glamorous 💄](https://github.com/paypal/glamorous), and
[Downshift 🏎️](https://github.com/paypal/downshift), I decided that my library
needed an emoji to go with it. I have been very nervous that someone might steal
the one I chose (months ago) before launch, which has been very motivating!

Okay, now on to the announcement…..

<div style={{ textAlign: "center" }}>
  <img
    src="/images/final-form-banner.png"
    width={700}
    height={178}
    alt="Final Form Banner"
  />
</div>

## 🏁 Final Form

Today I am launching a new form library, called
[🏁 Final Form](https://final-form.org/). It’s based on the
[Observer pattern](https://en.wikipedia.org/wiki/Observer_pattern), so observers
can subscribe to receive updates for either form or field state changes. Each
observer must explicitly define _which_ parts of the state they want to be
notified about, and they will _only_ be called when the parts they have
subscribed to have been changed.

It has **zero** dependencies, is framework agnostic, and weighs in at 💥
**3.5k** gzipped 💥.

<div style={{ textAlign: "center" }}>
  <img
    src="/images/react-final-form-banner.png"
    width={700}
    height={178}
    alt="React Final Form Banner"
  />
</div>

## 🏁 React Final Form

Of course it had to come with a companion library,
[🏁 React Final Form](https://final-form.org/react), which is a thin wrapper
around 🏁 Final Form that allows it to be used in React. And when I say thin… it
weighs in at 💥 **2.2k** gzipped 💥, for a combined weight of **5.7k** gzipped.
It has no dependencies, and only two peer dependencies: 🏁 Final Form and
React…..as the name would suggest 🙄.

## Problems with Redux-Form: ✅ Solved!

1. **🙂 Is it only for React?**

Nope! There’s nothing stopping anyone from implementing a 🏁 Final Form wrapper
in Angular, Ember, Vue, Preact, Elm, ReasonML, TheNextHotness™, etc.

2. **😊 Do I have to use Redux?**

Nope! State is canonically kept inside the 🏁 Final Form instance, and the React
components know when to rerender by calling `setState()` when they are notified
of a state change.

3. **😀 Can I use inline render functions?**

Of course you can! It’s almost 2018, for Pete Hunt’s sake! And to paraphrase a
prominant thoughtleader of the React community:

> “OMG!! Render functions! RENDER FUNCTION ALL THE THINGS!!!11one11"
>
> –– Michael Jackson

Both the `<Form/>` and `<Field/>` components use the three ways of rendering
that I have humbly stolen from the awesome rival library,
[Formik](https://github.com/jaredpalmer/formik#formik-render-methods), by
[Jared Palmer](https://twitter.com/jaredpalmer). Brilliant idea. You can either
pass a component, a render prop, or a render function to the children prop
([see docs](https://final-form.org/docs/react-final-form/types/FormProps#render)).

4. **😍 It’s only 5.7k gzipped? 21% the size of Redux-Form? AWESOME!**

Lean and mean. 😎

## What does it look like?

People familiar with the Redux-Form syntax should feel right at home, in fact
many custom component libraries that have been adapted for Redux-Form should
Just Work™ in 🏁 React Final Form. I will eventually write a Migration Guide,
but I haven’t gotten around to that yet.

August 2019 Update:
[Migration Guide!](https://final-form.org/docs/react-final-form/migration/redux-form)

<iframe
  src="https://codesandbox.io/embed/ww40y2m595?fontsize=14&hidenavigation=1&theme=dark"
  style={{
    width: "100%",
    height: "500px",
    border: 0,
    borderRadius: "4px",
    overflow: "hidden",
  }}
  title="🏁 React Final Form - Simple Example"
  allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
  sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>

<caption>Simple Example — No custom inputs</caption>

## Roadmap

🏁 Final Form is not yet up to feature parity with Redux-Form. For example, at
launch, it’s missing the concept of `<FieldArray/>` to edit an array of values
(**EDIT:** `<FieldArray/>` functionality
[released 7 days after this post](https://erikras.com/blog/final-form-arrays-and-mutators)
and `<Fields/>` to edit several fields in a single component. Nor does it have
some of the more esoteric features such as `autofill`. I plan on being a little
more willing to say, “No, I will not implement your specific feature,” with this
library, to minimize its complexity and surface area. I suspect that most edge
cases that previously required a new Redux action could be managed within React
state.

I very much look forward to working with others who want to wrap 🏁 Final Form
into another view framework, or even other implementations of 🏁React Final
Form. One suggestion: get in early if you want to be the dominant library (see
first paragraph).

## Conclusion

I cannot wait to get feedback from the community; I have learned so much from
you all in my years with Redux Form. ❤️

I do plan on continuing to maintain Redux Form. If you’ve built a huge
enterprise application on top of Redux Form, don’t worry; I’m not going
anywhere. But maybe you could try out 🏁 Final Form on the next form page you
build?

🙏 Thanks for reading. Go install it, star it, and give it a try! 🎉
]]></content>
        <author>
            <name>Erik Rasmussen</name>
            <email>rasmussenerik@gmail.com</email>
            <uri>https://erikras.com</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[🏁 Final Form – Field Arrays and Mutators]]></title>
        <id>https://erikras.com/blog/final-form-arrays-mutators</id>
        <link href="https://erikras.com/blog/final-form-arrays-mutators"/>
        <updated>2017-12-04T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Extending the core functionality of Final Form]]></summary>
        <content type="html"><![CDATA[
After the release of [🏁 Final Form](https://final-form.org) and
[🏁 React Final Form](https://react-final-form.org)
[seven days ago](/blog/final-form-announcement), the very first feature that
[people asked for](https://github.com/erikras/react-final-form/issues/2) was
array fields / arrays of fields. I could have pretty easily implemented the
ability to push, pop, insert, etc. into array values as part of 🏁 Final Form,
but the reason that Redux Form got so gargantuan was from exactly this sort of
feature bloat, where many individuals added on their own pet feature, and
everyone that uses the library has to download _all of these features_ whether
they use them or not. How could I avoid this with 🏁 Final Form?

🏁 Final Form is already more or less feature complete (famous last words) as
far as editing flat or arbitrarily deep form values. But how could it be made
more _extensible_? Well, here’s what I came up with:

## Mutators

Mutators are functions that can be provided to 🏁 Final Form that are allowed to
mutate any part of the form state. You provide an object of mutators when you
call `createForm()`, and then they are exposed (bound to the form state) on the
form instance you get back, under `form.mutators.whatever()`. A mutator function
is given: the arguments it was called with, the form state, and a collection of
utility functions, like `getIn` and `setIn` to read and mutate arbitrarily deep
values, as well as `changeValue`, which updates a field value in the form state.
They can mutate the state however they wish, and then the form and field
subscribers that need to be notified will be notified.

What does this look like? Well, here’s a somewhat simplistic example that I
created when writing the unit tests:

```js
/** Clears a form value */
const clear = ([name], state, { changeValue }) => {
  changeValue(state, name, () => undefined)
}

/** Converts a form value to uppercase **/
const upper = ([name], state, { changeValue }) => {
  changeValue(state, name, value => value && value.toUpperCase())
}

const form = createForm({
  onSubmit,
  mutators: { clear, upper }
})

...

form.mutators.upper('foo')

...

form.mutators.clear('foo')
```

These are dumb examples, but what else does this allow? Well, for one, it
allows…..

## Field Arrays

Everyone doesn’t need to include the code for field arrays in their bundle, but
for those that do…..

Introducing:

## [🏁 Final Form Arrays](https://final-form.org/arrays)

This library
([687 bytes gzipped](https://bundlephobia.com/result?p=final-form-arrays@1.0.0))
contains mutators for performing array operations on form values in 🏁 Final
Form.

```js
import { createForm } from "final-form";
import arrayMutators from "final-form-arrays";

// Create Form
const form = createForm({
  mutators: {
    // potentially other mutators here
    ...arrayMutators,
  },
  onSubmit,
});

// push
form.mutators.push("customers", { firstName: "", lastName: "" });

// pop
const customer = form.mutators.pop("customers");
```

And of course it needs a companion:

## [🏁 React Final Form Arrays](https://github.com/final-form/react-final-form-arrays)

This library
([1.7 kB gzipped](https://bundlephobia.com/result?p=react-final-form-arrays@1.0.0))
consists of a `<FieldArray/>` component that very closely mimics the
functionality of
[Redux Form's `<FieldArray/>` component](https://redux-form.com/7.2.0/examples/fieldarrays/),
providing a fields object that is very array-like, with methods like `forEach`,
`map`, `push`, `pop`, `shift`, `splice`, etc. When you iterate through it, it
gives you the array-syntax strings (e.g. `foo[0]`) for the array value in your
form, ready to be used with the `<Field/>` component.

An example is worth 1,024 words…

<iframe
  src="https://codesandbox.io/embed/distracted-bhaskara-kx8qv67nk5?fontsize=14&hidenavigation=1&theme=dark"
  style={{
    width: "100%",
    height: 500,
    border: 0,
    borderRadius: 4,
    overflow: "hidden",
  }}
  title="🏁 React Final Form - Field Arrays"
  allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
  sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>

## Going Forward

I have some other ideas of functionality that can be provided by mutators, and
I’m sure you do, too. They could provide some very powerful features to your
forms in the future, without imposing feature bloat on applications that just
need a simple login and account form.

As always, feedback welcome! Thanks for reading. ❤️
]]></content>
        <author>
            <name>Erik Rasmussen</name>
            <email>rasmussenerik@gmail.com</email>
            <uri>https://erikras.com</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[🏁 Final Form — Decorators, Calculated Fields, and Warnings]]></title>
        <id>https://erikras.com/blog/final-form-decorators</id>
        <link href="https://erikras.com/blog/final-form-decorators"/>
        <updated>2017-12-11T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Two examples of how powerful decorators and mutators can be, when combined with the subscription-based paradigm of 🏁 Final Form]]></summary>
        <content type="html"><![CDATA[
Last week, I introduced
[the concept of mutators and array fields](/blog/final-form-arrays-mutators) to
🏁 Final Form. This week, I’d like to introduce the concept of decorators, and
show two examples of how powerful decorators and mutators can be, when combined
with the subscription-based paradigm of 🏁 Final Form.

## Decorators

The term comes from
[ye olde ancient forgotten land of OOP](https://en.wikipedia.org/wiki/Decorator_pattern).

> In object-oriented programming, the **decorator pattern** is a design pattern
> that allows behavior to be added to an individual object, either statically or
> dynamically, without affecting the behavior of other objects from the same
> class.

In terms of 🏁 Final Form, a decorator is just a function that takes the form
object, subscribes to it somehow, and returns a function that can be called to
“undecorate” (undo all of its subscriptions). This allows a decorator to listen
for changes and enact side-effects.

This simple pattern allowed me to write a
[45 line](https://github.com/final-form/final-form-calculate/blob/master/src/decorator.js)
— and those are post-[Prettier](https://prettier.io/) lines! — library to
perform realtime calculations between fields, not unlike a spreadsheet.

## [🏁 Final Form Calculate](https://github.com/final-form/final-form-calculate)

This library creates a 🏁 Final Form decorator based on a set of calculation
rules you provide. Here’s what it looks like:

```js
import { createForm } from "final-form";
import createDecorator from "final-form-calculate";

// Create Form
const form = createForm({ onSubmit });

// Create Decorator
const decorator = createDecorator(
  // Calculations:
  {
    field: "foo", // when the value of foo changes...
    updates: [
      {
        // ...set field "doubleFoo" to twice the value of foo
        doubleFoo: (fooValue, allValues) => fooValue * 2,
      },
    ],
  },
  {
    field: /items\[\d+\]/, // when a field matching this pattern changes...
    updates: {
      // ...sets field "total" to the sum of all items
      total: (itemValue, allValues) =>
        (allValues.items || []).reduce((sum, value) => sum + value, 0),
    },
  }
);

// Decorate form
const undecorate = decorator(form);

// Use form as normal
```

Here is a live demo showing what this looks like.

<iframe
  src="https://codesandbox.io/embed/oq52p6v96y?fontsize=14&hidenavigation=1&theme=dark"
  style={{
    width: "100%",
    height: 500,
    border: 0,
    borderRadius: 4,
    overflow: "hidden",
  }}
  title="🏁  React Final Form - Calculated Fields"
  allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
  sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>

Kind of a silly example, but there are valid use cases for this feature.

## [🏁 Final Form Set Field Data](https://github.com/final-form/final-form-set-field-data)

This library is a trivial little thing that provides a mutator that set any
arbitrary metadata for a field. You use it like this:

```js
import { createForm } from "final-form";
import setFieldData from "final-form-set-field-data";

// Create Form
const form = createForm({
  mutators: { setFieldData },
  onSubmit,
});

form.mutators.setFieldData("firstName", { awesome: true });

form.registerField(
  "firstName",
  (fieldState) => {
    const { awesome } = fieldState.data; // true
  },
  {
    // ...other subscription items
    data: true,
  }
);
```

Why would you want this? Well, for one, it allows you to create…..

## ⚠️ Field Warnings ⚠️

Way back in September of 2016 — in the horrible Dark Times before Prettier–a
user and prolific contributor to Redux-Form submitted
[a pull request](https://github.com/erikras/redux-form/pull/1780) for a feature
that never would have occurred to me. He needed fields to have what he called
“warnings”, which are just like synchronous validation errors, but they don’t
prevent submission. The way it was implemented was the way new features are
often added to software: by taking the existing validation pipeline and
duplicating all the methods and data structures to create a warning pipeline. It
worked perfectly, of course, but as is often the case with code duplication, it
created problems in the future, where someone fixed a bug in the error-handling
code, but not in the mirror image _warning_-handling code. And, of course, it
wasn’t long before someone came along and said, “You know, it’d be cool if we
could set, just, like, any arbitrary metadata on a field that we want, maaaan.”

With the ability to set arbitrary metadata into fields provided by
`final-form-set-field-data`, coupled with the subscription-based form state
paradigm of 🏁 Final Form, it becomes trivial to implement your own warning
system. Behold:

```jsx
const WarningEngine = ({ mutators: { setFieldData } }) => (
  // FormSpy lets you listen to any part of the form state you want.
  // If you provide an onChange prop, FormSpy will not render to the DOM.
  <FormSpy
    subscription={{ values: true }}
    onChange={({ values }) => {
      setFieldData("firstName", {
        warning: values.firstName ? undefined : "Recommended",
      });
      setFieldData("lastName", {
        warning: values.lastName ? undefined : "Recommended",
      });
    }}
  />
);
```

This component subscribes to the form values, so its onChange function gets
called every time they change, and it sets or removes a warning value in the
fields. All you have to do is include this somewhere inside your form — after
all the fields, so that they are registered when onChange is called the first
time.

Here it is in action: (tab through the fields to see the warnings appear)

<iframe
  src="https://codesandbox.io/embed/react-final-form-warnings-m5qwxpr6o8?fontsize=14&hidenavigation=1&theme=dark"
  style={{
    width: "100%",
    height: 500,
    border: 0,
    borderRadius: 4,
    overflow: "hidden",
  }}
  title="🏁  React Final Form - Warnings"
  allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
  sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>

## Conclusion

My goals with 🏁 Final Form is to be as lean and flexible as possible. Rather
than implementing a feature like field arrays or field warnings or calculated
fields directly in the core library, the core library allows ways for them to be
implemented outside, so that each application can only use the tools and pieces
it needs, and unforeseen use cases can be implemented for their specific project
and not for all. What interesting form features can you imagine building?

Happy coding, y’all. ❤️👩‍💻👨‍💻
]]></content>
        <author>
            <name>Erik Rasmussen</name>
            <email>rasmussenerik@gmail.com</email>
            <uri>https://erikras.com</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[🏁 Final Form goes to TypeScript]]></title>
        <id>https://erikras.com/blog/final-form-to-typescript</id>
        <link href="https://erikras.com/blog/final-form-to-typescript"/>
        <updated>2025-06-07T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[All Final Form modules have been modernized to TypeScript]]></summary>
        <content type="html"><![CDATA[
When I first wrote 🏁 Final Form back in
[2017](https://erikras.com/blog/final-form-announcement),
[Flow](https://flow.org/) and [TypeScript](https://www.typescriptlang.org/) were
exactly tied in the race to strongly type JavaScript.

At the time, I was just coming off of a switch from Angular to React. It was
really telling to me at the time that Google wasn't really using Angular in
their core user facing products, but Facebook was putting _all_ their chips on
React, dogfooding the crap out of it with their entire business on the line. And
Facebook was doing an _amazing_ job stewarding React in the Open Source
community. Choosing Flow, from Facebook, to build Final Form was a no-brainer.

In retrospect, we now know that I put all my money on the wrong dog in the race.

Shortly thereafter, Facebook stopped supporting Flow externally. It's my
understanding that those on the inside of Meta still use and love it.

## Withdrawl from Open Source

After getting laid off in March 2020 – remember anything else going on then? –
my career switched to jobs that needed more of my attention and also scratched
my React and JavaScript itches. I know they say that "[Software] is never
finished, merely abandoned", but... I think

> Final Form is more or less feature complete.

It handles a huge portion (> 95%) of form use cases, and those use cases haven't
changed in the eight years since I wrote it.

Nor will they.

Form state management will still be the same general shape of problem even when
our great-grandkids are filling out their Space Academy applications. Values,
validation, dirty, touched, submission errors, etc. The UI frameworks will
change, but the underlying form state won't. As far as I know, Final Form
remains one of only two framework agnostic form state solutions (the other is
the brand new [TanStack Form](https://tanstack.com/form), which solves the same
problems in a similar way).

### Complaints

People have been contacting me lately asking me to mark the library as dead or
at least update it to modern, say, React 19, standards. For years my response
has been, "Volunteers welcome!", but it turns out no one _really_ wants to help,
[certainly not a corporation](https://erikras.com/blog/open-source-sustainability).
In a recent
[response on Github](https://github.com/final-form/react-final-form/issues/1045#issuecomment-2853580304),
I said:

> It is true that:
>
> - I am no longer _actively_ maintaining FF.
> - An unattended `package.json` _will_ develop cobwebs.
>
> However, it's also true that:
>
> - The code still works.
> - IMHO, RFF is still the most ergonomic way to manage form state in React.
> - For the first time ever, I'm actually consuming RFF at my day job.
>
> I would love to hand the keys to the chapel over to a responsible young monk
> who will come in and sweep the floors and clear the cobwebs every so often.
> Any volunteers?

As per usual, this response received only crickets chirping. But the more I
thought about it, the more I liked my cobwebs metaphor.

<div style={{ textAlign: "center" }}>
  <img
    src="/images/cobwebs-church.jpg"
    alt="Cobwebs in a Church"
    width={1021}
    height={1019}
  />
</div>

If only there were a way to find junior developers that would work for little or
no money and keep working until all the linting and tests pass! Rather than an
intern to sweep the floors, what if there were like, some sort of...

_robot_ vacuum?!

## LLMs to the rescue!

It's 2025! We've all got an army of junior devs at our fingertips!

<div style={{ textAlign: "center" }}>
  <img
    src="/images/robot-monks-sweeping.jpg"
    alt="Robot monks sweeping"
    width={1024}
    height={1024}
  />
</div>

So I set about asking LLMs to convert Final Form from Flow to TypeScript, and it
went better than I could have expected. Of course, it helped that long ago I had
put in all of the legwork to have near 100% test coverage, so I could be certain
that the migration did not introduce any bugs.

Now that the code is modernized, I feel less apathy towards maintaining it, so I
hope to be more attentive in the future, though I am not promising anything.

In total precaution, I am bumping all the versions as if this were a breaking
change, even though I'm 99.9% sure that it isn't, unless you were relying on the
flow types, and in that case, you work for Meta and can build your own 🤬
internal form library.

| Library                             | Version                                                                                         |
| ----------------------------------- | ----------------------------------------------------------------------------------------------- |
| `final-form`                        | [`v5.0.0`](https://github.com/final-form/final-form/releases/tag/v5.0.0)                        |
| `final-form-focus`                  | [`v2.0.0`](https://github.com/final-form/final-form-focus/releases/tag/v2.0.0)                  |
| `final-form-calculate`              | [`v2.0.0`](https://github.com/final-form/final-form-calculate/releases/tag/v2.0.0)              |
| `final-form-arrays`                 | [`v4.0.0`](https://github.com/final-form/final-form-arrays/releases/tag/v4.0.0)                 |
| `final-form-submit-listener`        | [`v2.0.0`](https://github.com/final-form/final-form-submit-listener/releases/tag/v2.0.0)        |
| `react-final-form`                  | [`v7.0.0`](https://github.com/final-form/react-final-form/releases/tag/v7.0.0)                  |
| `react-final-form-arrays`           | [`v4.0.0`](https://github.com/final-form/react-final-form-arrays/releases/tag/v4.0.0)           |
| `react-final-form-arrays`           | [`v2.0.0`](https://github.com/final-form/react-final-form-listeners/releases/tag/v3.0.0)        |
| `react-final-form-html5-validation` | [`v2.0.0`](https://github.com/final-form/react-final-form-html5-validation/releases/tag/v2.0.0) |

❤️ Thank you for reading, and thank you all for bearing with me during these
years. 🫶
]]></content>
        <author>
            <name>Erik Rasmussen</name>
            <email>rasmussenerik@gmail.com</email>
            <uri>https://erikras.com</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Finite State Machines in ReScript]]></title>
        <id>https://erikras.com/blog/finite-state-machines-in-rescript</id>
        <link href="https://erikras.com/blog/finite-state-machines-in-rescript"/>
        <updated>2021-03-22T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[ReScript's strong typing makes it especially well suited for defining Finite State Machines.]]></summary>
        <content type="html"><![CDATA[
After hearing my friend, [David 🎹](https://twitter.com/davidkpiano), go on and
on about [finite] state machines and the value they can provide to us as
frontend programmers, a few months ago, I was able to actually use his library,
[XState](https://xstate.js.org), in anger at work, and the result was nothing
short of glorious!

> It eliminates a whole class of bugs!

👆 This is a phrase that has come out of my mouth very often in the last couple
of years since being baptized into the Holy Church of TypeScript. I have spread
the Good Word far and wide, and even created a meme which I have used multiple
times on PRs with my team.

<s>1308736441753767937</s>

## ReScript

[ReScript](https://rescript-lang.org), the artist formerly known as BuckleScript
and Reason, takes "It eliminates a whole class of bugs" to the next level. Quoth
the docs:

> ReScript has **no** pitfalls, aka the type system is "sound" (the types will
> always be correct). e.g. If a type isn't marked as nullable, its value will
> never lie and let through some undefined value silently. **ReScript code has
> no null/undefined errors.**

Okay, you have my attention! I read through the docs
[one day](https://twitter.com/erikras/status/1370007991202758657) like it was a
_Jack Reacher_ novel, very excited to see the sort of tradeoffs ReScript was
making.

In my n00b experience so far, the interop edges where ReScript must consume –
moreso than provide – third party JavaScript libraries feels a bit awkward...at
least more awkward than modern 2021 TypeScript which has huge community support
(so the comparison is not really fair).

Since acquiring this itch, I've been looking for a simple example project to try
out my ReScript skills.

## Twitter's "Follow" Button

Last week, I noticed an interesting thing about the "Follow" button on Twitter's
webapp. Rather than having four states, `not-following`, `following`,
`not-following-hover`, and `following-hover`, it actually has _five_! There's a
special state when you've just clicked "Follow", but are still hovering over the
button.

<s>1372838015656136708</s>

**Perfect!** This is a job perfect for a state machine, and also tiny enough
that maybe I could implement it in ReScript!

At the time of this writing, there are virtually zero search results that come
back for "XState ReScript". There is one article about ReScript and state
machines called
[Modelling domain with state machines in ReasonML](https://dev.to/margaretkrutikova/modelling-domain-with-state-machines-in-reasonml-n29),
from almost a year ago by
[Margarita Krutikova](https://twitter.com/rita_krutikova), in which she mentions
XState, but codes in ~~ReasonML~~ ReScript. That is what I shall attempt to do,
as well. One could imagine a ReScript interop layer for XState existing
(ReXState? 🦖), but it doesn't seem to at the moment.

## "Follow" Button State Machine

I admit to usually rolling my eyes at the point of David 🎹's talks where he
recommends:

> Start with a sheet of paper or a whiteboard, and draw out the states you need.

"I can see the Matrix and visualize all the states just looking at the code", I
think smugly to myself. However, this time, darned if he wasn't right! I
struggled with considering all the interactions writing just code (plus, this
coding language is foreign to me), so I found great benefit in drawing up my
state machine by hand first. Of course I used
[Excalidraw](https://excalidraw.com/), not paper – I'm not a caveman!

<img
  src="/images/follow-button-state-chart.png"
  width={1480}
  height={801}
  alt="Follow Button Finite State Machine"
/>
<caption>
  The clicks without hovering are there for completeness (and maybe
  accessibility?).
</caption>

Notice that from `not-following-hover`, it's pretty hard to get to
`following-hover` (with red background and "Unfollow" text), when you would
think would be the next state; you have to:

1. Click
2. Mouse out
3. Mouse over again

## ReScript Implementation

### Types

Let's start with our types:

```reason
type state =
  | Following
  | FollowingHover
  | FollowingNeverMouseOut
  | NotFollowing
  | NotFollowingHover
type event = Click | MouseOver | MouseOut
type style = Highlight | Danger | Hover | None
```

The actual Twitter "Follow" button doesn't have a style for `NotFollowingHover`,
but I decided to improve on that and make a `Hover` style.

### `getText()`

What should the text of the button be in each state? Notice that you can "fall
through" just like you can with a `switch-case` statement in JavaScript if you
leave out the `break`.

```reason
@genType
let getText = (state: state) => {
  switch state {
  | NotFollowing
  | NotFollowingHover => "Follow"
  | FollowingHover => "Unfollow"
  | Following
  | FollowingNeverMouseOut => "Following"
  }
}
```

Notice the `@genType`. One fun fact that made me want to attempt adopting
ReScript is that: **_IT GENERATES TYPESCRIPT TYPES FOR YOU_!!!** 🤯

It's not the most beautiful TS ever, but it gives you the typings and then
defers the calls to the generated JavaScript. For example, here's an excerpt of
the TypeScript the code above generated:

```ts
// tslint:disable-next-line:no-var-requires
const followButtonBS = require("./followButton.bs");

// tslint:disable-next-line:interface-over-type-literal
export type state =
  | "Following"
  | "FollowingHover"
  | "NotFollowing"
  | "NotFollowingHover"
  | "FollowingNeverMouseOut";

// tslint:disable-next-line:interface-over-type-literal
export type event = "Click" | "MouseOver" | "MouseOut";

// tslint:disable-next-line:interface-over-type-literal
export type style = "Highlight" | "Danger" | "Hover" | "None";

export const getText: (state: state) => string = function (Arg1: any) {
  const result = followButtonBS.getText($$toRE161065015[Arg1]);
  return result;
};
```

However, more interesting, is the JavaScript that it generates. Check this out:

```js
function getText(state) {
  switch (state) {
    case /* FollowingHover */ 1:
      return "Unfollow";
    case /* NotFollowing */ 2:
    case /* NotFollowingHover */ 3:
      return "Follow";
    case /* Following */ 0:
    case /* FollowingNeverMouseOut */ 4:
      return "Following";
  }
}
```

All nicely documented so you can follow along.

### `getStyle()`

Now we declaratively define how the state relates to the style (think, "CSS
class") of the button.

```reason
@genType
let getStyle = (state: state) => {
  switch state {
  | FollowingHover => Danger
  | Following => Highlight
  | NotFollowing => None
  | NotFollowingHover
  | FollowingNeverMouseOut => Hover
  }
}
```

Pretty similar to `getText()`. Notice that the "fifth state", `FollowingHover`,
is special in both functions.

### The Machine

Here's where it gets fun! The machine is a simple mapping from state to state
via events. This is just the code that represents the arrows in the
drawing...nothing more.

```reason
@genType
let machine = (. state:state, event: event) =>
  switch state {
    | NotFollowing => switch (event) {
      | Click => Following
      | MouseOver => NotFollowingHover
      | MouseOut => state
    }
    | NotFollowingHover => switch event {
      | Click => FollowingNeverMouseOut
      | MouseOut => NotFollowing
      | MouseOver => state
    }
    | FollowingNeverMouseOut => switch event {
      | Click => NotFollowingHover
      | MouseOut => Following
      | MouseOver => state
    }
    | Following => switch event {
      | MouseOver => FollowingHover
      | Click => NotFollowing
      | MouseOut => state
    }
    | FollowingHover => switch event {
      | MouseOut => Following
      | Click => NotFollowingHover
      | MouseOver => state
    }
  }
```

...which compiles nicely – in milliseconds! – to this efficient JS code:

```js
function machine(state, $$event) {
  switch (state) {
    case /* Following */ 0:
      switch ($$event) {
        case /* Click */ 0:
          return /* NotFollowing */ 2;
        case /* MouseOver */ 1:
          return /* FollowingHover */ 1;
        case /* MouseOut */ 2:
          return state;
      }
    case /* FollowingHover */ 1:
      switch ($$event) {
        case /* Click */ 0:
          return /* NotFollowingHover */ 3;
        case /* MouseOver */ 1:
          return state;
        case /* MouseOut */ 2:
          return /* Following */ 0;
      }
    case /* NotFollowing */ 2:
      switch ($$event) {
        case /* Click */ 0:
          return /* Following */ 0;
        case /* MouseOver */ 1:
          return /* NotFollowingHover */ 3;
        case /* MouseOut */ 2:
          return state;
      }
    case /* NotFollowingHover */ 3:
      switch ($$event) {
        case /* Click */ 0:
          return /* FollowingNeverMouseOut */ 4;
        case /* MouseOver */ 1:
          return state;
        case /* MouseOut */ 2:
          return /* NotFollowing */ 2;
      }
    case /* FollowingNeverMouseOut */ 4:
      switch ($$event) {
        case /* Click */ 0:
          return /* NotFollowingHover */ 3;
        case /* MouseOver */ 1:
          return state;
        case /* MouseOut */ 2:
          return /* Following */ 0;
      }
  }
}
```

## Demo Time

The following button (pun intended) is written using the logic described above,
with the actual JavaScript compiled from ReScript.

<div style={{ textAlign: "center", marginTop: "1em" }}>
  <FsmFollowButton />
</div>

Notice how there are two states where it reads "Following", the one right after
you've clicked it, and the button is still gray, and the one after you've moused
away and the button is purple.

So concludes my first of what will hopefully be
[many posts about ReScript](/blog/categories/rescript). Feel free to follow me
on Twitter as I ask questions from people smarter than myself and get help
learning this promising language.
]]></content>
        <author>
            <name>Erik Rasmussen</name>
            <email>rasmussenerik@gmail.com</email>
            <uri>https://erikras.com</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Focus On Errors 🧐]]></title>
        <id>https://erikras.com/blog/focus-on-errors</id>
        <link href="https://erikras.com/blog/focus-on-errors"/>
        <updated>2018-03-19T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Without a doubt, one of the most common questions I hear from people using my forms libraries is, "How can I focus on the first field that has an error?"]]></summary>
        <content type="html"><![CDATA[
Without a doubt, one of the most common questions I hear from people using my
forms libraries is:

> “How can I focus on the first field that has an error?”

Merely asking this question demonstrates a perceptive level of UX intelligence.
Clearly it’s an order of magnitude better to actually take a user to the problem
spot rather than just tell them “There’s an error up there somewhere. Scroll up
and try to find it!”

Here’s the answer I usually give:

> Well, that’s a tough problem, because calling focus() on an input requires
> having direct access to the DOM instance, and [form library] does not have
> that; all [form library] does is provide a `value` and `onChange` (and other
> listeners) to you, the developer that actually renders the `<input>`, and
> there is no good way for you, who may have a DOM reference to the input, to
> give that back to [form library] to allow [form library] to call focus() on
> the DOM element.

🙄 Just typing that practically put me to sleep. Yawn! 😴

<div style={{ textAlign: "center" }}>
  <img
    src="/images/sleepy-girl.gif"
    alt="Sleepy...zzzz"
    width={400}
    height={282}
  />
</div>

## 🤔 Can we do better?

It ocurred to me that, at least in DOM-land (sorry React Native folks!), we have
this ancient structure which, like Seinfeld — _“What’s the deal with render
props?!”_ — dates back to the early 1990’s, that modern web developers might not
know about. Did you know that _document.forms[0][0]_ will give you a reference
to the first input on the first form on your page? Or, that if you give your
form a name, e.g. `<form name="foo">`, that you can iterate through an
`HTMLCollection` of its inputs at `document.forms.foo`? 🤯

So, maybe a higher level form library can gain access to the input DOM elements,
as long as they are given the name value that matches the field name the form
library knows about?

---

🥁 🥁 🥁 Introducing, the alliterative…

## 🏁 Final Form Focus 🧐

[🏁 Final Form Focus 🧐](https://github.com/final-form/final-form-focus) is a
decorator for 🏁 Final Form. That means that you just import it and plug it in,
and it does all the rest. There is only one possible change in configuration: in
the interest of future proofing it and maybe allowing non-DOM platforms, like
React Native, to take advantage of it, you may provide a `getInputs()` function
that will give a list of “focusable inputs” (i.e. objects with a `name` property
and a `focus()` method) that correspond to the fields in your form. For those of
us in DOM land, you can omit that parameter and it will default to searching
through your `document.forms` structure and will "just work".

Whenever your form is submitted and the submission fails due to synchronous,
asynchronous, or submission validation errors, the first input on the page that
has a `name` prop that matches the path to an error will have `focus()` called
on it.

What does it look like? Well, if you’re using 🏁 React Final Form, you just add
the decorator like this:

```jsx

import React from 'react'
import { Form, Field } from 'react-final-form
import createDecorator from 'final-form-focus'

const focusOnErrors = createDecorator()
...
<Form
  onSubmit={submit}
  decorators={[ focusOnErrors ]} // <--------- 😎
  validate={validate}
  render={({ handleSubmit }) =>
    <form onSubmit={handleSubmit}>

      ... inputs here ...

    </form>
  }
/>
```

<caption>
  Step 1: Add the decorator to your form. Step 2: There is no Step 2!
</caption>

Let’s see it in action:

<iframe
  src="https://codesandbox.io/embed/6174kqr403?fontsize=14&hidenavigation=1&theme=dark"
  style={{
    width: "100%",
    height: 500,
    border: 0,
    borderRadius: 4,
    overflow: "hidden",
  }}
  title="🏁 React Final Form - Focus On First Error"
  allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
  sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>

## Vanilla JS Code

For those of you that have arrived to this post by googling “how to focus on the
input with the first validation error” (_Look, ma! I’m SEOing!!_), or looking to
add this functionality to your own form solution, this is all it takes:

```js
function focusOnFirstError(formName, hasError) {
  const form = document.forms[formName];
  for (let i = 0; i < form.length; i++) {
    if (hasError(form[i].name)) {
      form[i].focus();
      break;
    }
  }
}
```

<caption>
  `hasError()` is a predicate that returns truthy if a field has an error
</caption>

## Conclusion

Almost none of the web forms — or native forms, for that matter — I use on a
daily basis are sophisticated enough to bring focus to the first field with an
error, and thus introduce friction and frustration into the user experience.
Implementing “focus on first error” manually on every form is quite difficult,
e.g. keeping track of all the DOM refs, sorting them by which is first on the
page, and examining the error structure at submission to determine which one to
focus on.

But now, with 🏁 Final Form, there is a pluggable way to get that functionality
_**on every form you write**_.

❤️ Thanks for reading, and I look forward to your feedback! ❤️
]]></content>
        <author>
            <name>Erik Rasmussen</name>
            <email>rasmussenerik@gmail.com</email>
            <uri>https://erikras.com</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Should we follow Observables?]]></title>
        <id>https://erikras.com/blog/follow-observables</id>
        <link href="https://erikras.com/blog/follow-observables"/>
        <updated>2021-03-19T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[If all the social media platforms are moving from "subscribe" to "follow", what do we do to Observables?]]></summary>
        <content type="html"><![CDATA[
With every new technology, we inevitably attempt to describe it with our
existing vocabulary that applies to the older technology. A viral video shot on
a mobile phone might be headlined with "[Phenomenon] caught on tape!",
podcasters talk about being "on the air", etc. etc. So it only made sense that
when the Blog (_née_ "Web Log") was introduced, if you wanted to get new
"articles" from it, you would "subscribe" to it, just like you would to a
newspaper or magazine. But when you "subscribe" to a newspaper or magazine, you
are paying money!

As of iOS 14.5, if you would like to subscribe to a podcast on Apple's Podcasts
app,
[you must tap "Follow"](https://9to5mac.com/2021/03/09/apple-adopting-the-word-follow-for-podcasts-as-users-associate-subscribe-with-paid-content/).
This seems reasonable. Over the course of the last decade, the social media
giants have more or less agreed that the verb for "I want to hear more from this
entity whenever it produces content" is "follow". While Facebook still
differentiates the mutually bidirectional relationship of "friend", for
unidirectional bondings, it agrees with Instagram, Twitter, TikTok, and now
Clubhouse, that the word is "follow". From what I can tell, YouTube is the only
remaining holdout on the Island of Subscribe.

The really interesting tidbit to come out of this news is that back in 2018,
[Tom Webster discovered](https://www.niemanlab.org/2018/08/podcastings-next-frontier-a-manifesto-for-growth-beyond-the-already-converted/)
that most people that don't listen to podcasts don't because they think they
aren't free!

<!-- prettier-ignore-start -->
> Now we are getting to one of the more dangerous perceptions — that podcasts
> cost money. This is a flat out misconception, but it’s one that _we_ created
> with our continual insistence that people “subscribe” to our show. With the
> exception of a YouTube channel, you pay for things you subscribe to —
> magazines, satellite radio, HBO, the fruit-of-the-month club. The listeners we
> have may understand that podcasts are largely free. But many of The [millions
> of Americans who do not listen to podcasts] do not.
<!-- prettier-ignore-end -->

How interesting!

This immediately resonated with me because a year or two ago, I wrote a talk to
discuss my JavaScript library [🏁 Final Form](https://final-form.org) at a
conference. I mentioned how what really set 🏁 Final Form apart from the
competition was the use of the
[Observer pattern](https://en.wikipedia.org/wiki/Observer_pattern), which allows
the library consumer fine grained control over rerenders via _subscriptions_.
You can _subscribe_ to exactly which parts of form state you'd like to be
notified about when they change.

I sent my talk proposal off to be reviewed by a non-technical person who was
offering to broker a deal with the conference organizers to get my talk
accepted. She wrote me back and said, "I don't think they will accept your talk
if you're going to be hocking a paid subscription service." 🤯 The twGo meanings
of the word had never once occurred to me! In the same way that we took the old
magazine/newspaper "tell me when something is new" nomenclature for blogs and
podcasts, we had done the same thing with `Observable`s. I used the word
"subscribe" because
[RxJS did](https://rxjs.dev/api/index/class/Observable#subscribe-), and the
["pub-sub" pattern](https://en.wikipedia.org/wiki/Publish–subscribe_pattern)
long predates Rx.

This got me thinking... perhaps it's time to deprecate the word "subscribe" in
our code as well as in our social networks? I don't really love `follow()`, but
perhaps that's so ubiquitous in other realms that it's the way to go. The other
obvious words that occur to me are `listen()` and `observe()`, evoking our
senses of hearing and vision.

What do you think? Hit me up on Twitter to discuss... I'll be
\_\_\_\_\_\_\_\_ing [to] your tweets.
]]></content>
        <author>
            <name>Erik Rasmussen</name>
            <email>rasmussenerik@gmail.com</email>
            <uri>https://erikras.com</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Guide to the Internet]]></title>
        <id>https://erikras.com/blog/guide-to-the-internet</id>
        <link href="https://erikras.com/blog/guide-to-the-internet"/>
        <updated>2023-08-01T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[A paper I wrote about the emerging Internet in 1991.]]></summary>
        <content type="html"><![CDATA[
When I finished 6th grade in June 1991, my father had the idea to assign me a
summer term paper to write. He was curious about this newfangled "Internet"
thing that was clearly about to take over the world, and he wanted to know more
about it. In exchange for my efforts, he agreed to pay for a "local" ISP, called
VNet, in Charlotte, North Carolina, the nearest big city. It was still a long
distance call, which was not free in those days (ask your grandparents).

What is most interesting is that this was done before the actual game changing
protocol that would become what most of us think of when we say "Internet": the
World Wide Web, which was released to the public on August 23, 1991. I am also
amused by just how much of my life on the internet still revolves around the
terminal command line prompt.

What follows is that report. Enjoy!

---

## What is the Internet?

The Internet is a complex information highway composed of thousands of computers
around the world. The amount of information contained in the Internet system is
staggering. There are billions and billions of bytes of information on hundreds
of thousands of subjects.

## How do I get on the Internet?

There are only a few things you need to get on the Internet. First, you need a
computer. Second, you need a modem. Your modem can be of any speed, but usually
you want a modem that is at least 9600 baud. The faster your modem, the less
time you have to spend on the Internet waiting for the computer to display
information. For some people a call to an Internet access is a long distance
call. The faster you get the information, the less money you have to spend. Of
course, your modem is no good unless you have a telephone line. Finally, you
might need a little money. The Internet has gotten so big that most relatively
big cities have an Internet Access. If you live in an urban area, the call to an
Internet access computer is usually a local call. For many others, it is still a
long distance call to get to an Internet access. There are no free 800 accesses
to the Internet. If there were, all the others would be out of business. Another
reason for having money is to pay a membership fee. Most Internet access
services make members pay a joining fee and usually a monthly rate. Some access
services charge callers a certain amount per minute of their online time. The
Internet is not free, but the amount of information you can get is usually worth
the expenses.

There are almost as many Internet access services as there are networks on the
Internet. Everyone must search around until he finds the right one for him or
her. It is somewhat like finding your favorite route to work. You want the one
that is the quickest to get there but you also want to choose the one that has
the best roads, most scenery, fewest toll booths, and the least traffic.

In San Francisco there is an Internet Access service that is run by the Whole
Earth catalog company. It is called _The WELL_ ("The Whole Earth 'Lectronic
Link"). There are members of _The WELL_ from all over the world. _The WELL_ has
something called a conference. A conference is when people that are on _The
WELL_ at a certain time all converse about a topic at one time. _The WELL_ has
conferences on many topics from baseball to abortion. There are many other
services on _The WELL_ too, but _The WELL_ is famous for its conferences. To
become a member on _The WELL_ you must pay a monthly fee of $15 plus an online
charge of $2 an hour, which is billed by the minute. The first five hours of
your membership is absolutely free, so you have a chance to poke around a little
bit and get yourself oriented. _The WELL_ is a scenic way to work with lots of
traffic, but the other motorists are kind. To become a member on _The WELL_,
call, by modem, (415) 332-6106. At the login prompt type "newuser" (see Figure
1).

<p>
  <img
    src="/images/the-internet-figure-1.jpg"
    alt="Figure 1"
    width={1000}
    height={567}
  />

<caption>
  Figure 1
</caption>
</p>

There is an Internet access service in Charlotte, North Carolina called VNet
Internet Access. VNet provides a UNIX interface. The people that are members
there are very kind and willing to help on any subject. VNet has wonderful
menu-driven text and mail editors (see E-mail). VNet is a fast and user-friendly
system. If you aren't familiar with UNIX, you can find a book on UNIX commands
in most libraries and bookstores. For an individual account the cost is $25 per
month. Another account in the same family costs only $10 per month. This
includes two free hours every day during "peak" times. "Peak" times are from
2:00 PM to midnight. All other times are free. The monthly payment entitles you
to up to 2 megabytes of storage space in your area on the network. Any more than
2 megabytes and you will be charged 25¢ per megabyte per day. To become a member
on VNet Internet Access Service, call, by modem, (704) 347-8839. Type "new" at
the login prompt (see Figure 2). To set up an account by voice, call (704)
374-0779.

<p>
  <img
    src="/images/the-internet-figure-2.jpg"
    alt="Figure 2"
    width={1000}
    height={570}
  />

<caption>
  Figure 2
</caption>
</p>

## How do I find the information?

There are many ways of finding information on the Internet. Two of the best and
easiest ways are Gopher and Archie.

### GOPHER

Gopher really has nothing to do with rodents. Gopher is a coined phrase that is
short for go-fer or go-for, because it goes for things. Gopher is a huge web of
menus. Most computers attached to the Internet have a Gopher system. The menus
start out at very broad topics and get more and more detailed as you choose
items that better represent the topic you are interested in. If the information
that you requests gets too detailed for the computer Gopher system that you are
on to handle, then you get transferred to another Gopher system that specializes
in that topic.

#### SYNTAX

On most computers, you can just type "gopher" at your prompt. This will run the
Gopher system that is on the computer that you are using. Another helpful syntax
of Gopher is like the Telnet command (see Telnet). Unlike Telnet, you don't need
to have a password or a login name at that certain computer. If a computer has a
Gopher system, and you know its telnet address, you can go ot that computer's
Gopher system by typing "gopher &lt;telnet address&gt;". This command is the
equivalent of logging in to that machine and typing "gopher".

### ARCHIE

Archie is a command that is used to find the location of information. The Archie
command searches over 600 computers to find out if each one has any information
on the topic. Archie is the quickest and easiest way to find a file on a certain
subiect. Archie is easy to understand too. If you just type "archie" at your
command prompt, the computer will display a list of other commands and switches
that can be used with the archie command (see Figure 3).

<p>
  <img
    src="/images/the-internet-figure-3.jpg"
    alt="Figure 3"
    width={1000}
    height={619}
  />

<caption>
  Figure 3
</caption>
</p>

To search for a specific string, type "archie &lt;strings&gt;". For example, to
search for any information on baseball and have the computer display each
location of information on its own line, you would type "archie -l baseball"
(see Figure 4).

<p>
  <img
    src="/images/the-internet-figure-4.jpg"
    alt="Figure 4"
    width={1000}
    height={533}
  />

<caption>
  Figure 4
</caption>
</p>

Once you find out the location of the information, you are ready to go get it.
Let's go get it!

## How do I get the information?

There are two main ways to go places on the Internet. You can either go by
Telnet or FTP. There are four things you need to be able to "telnet" or "FTP" to
another computer. You need an Internet access. You have to know the Telnet
address or IP address of a host computer. Most of the time you need a valid
login name and password for Telnet, and sometimes for FTP.

### TELNET

Telnet is a way of moving your control from one computer to another on the
Internet. Telnet is easy to use once you understand the Telnet address system.

#### TELNET ADDRESSES

The telnet address for the internet access called _The WELL_ that we talked
about earlier is well.sf.ca.us. To the average person this is just a bunch of
letters and periods, but to the experienced Internet user, this means a lot.
Let's decipher this thing.

> `well.sf.ca.us`

Okay. From the first part, _well_, we can tell what the name of the computer or
organization that this is the address is for: _The WELL_. From the last part,
_us_, we can tell what country the computer is located: the United States. From
the third part, _ca_, we can tell what state, the computer is in: California.
Finally, from the second part, _sf_, we can guess that it is located in San
Francisco. Not all Internet addresses are this simple. Most, in fact, are
nothing but letters and periods, unless you have some idea of what or where they
are. For example, can you tell what this is the address to?

> `locis.loc.gov`

Let's take it apart. The _gov_ at the end must mean that it is a government
organization. _Locis_ might be the name of the computer, but that doesn't help
us much. Hmmm. What could _loc_ mean? Could it mean Library of Congress?
**Yes!** This is the address of the Library of Congress in Washington, DC!

The address of the VNet Internet Access that we have talked about earlier is
_char.vnet.net_. The _net_ at the end stands for network. The vet stands for the
name of the organization. And the _char_ stands for Charlotte, because the
location of the network is in Charlotte, NC. Now, we have gone from a bunch of
letters and periods to a detailed description of the computer. _char.vnet.net_
is a network named VNet in Charlotte, North Carolina.

Some other standard Telnet address suffixes are:

<table>
  <tbody>
    <tr>
      <th>com</th>
      <td>Commercial</td>
      <td>A company or other commercial organization.</td>
    </tr>
    <tr>
      <th>edu</th>
      <td>Education</td>
      <td>A college, university, or other educational institution.</td>
    </tr>
    <tr>
      <th>gov</th>
      <td>Government</td>
      <td>A federal, state, or local government site.</td>
    </tr>
    <tr>
      <th>net</th>
      <td>Military</td>
      <td>A military site such as the U.S. Army.</td>
    </tr>
    <tr>
      <th>mil</th>
      <td>Network</td>
      <td>Gateways or other hosts in a network.</td>
    </tr>
    <tr>
      <th>org</th>
      <td>Organization</td>
      <td>
        A private organization such as a non-profit group, a foundation, or
        other association.
      </td>
    </tr>
  </tbody>
</table>

Some Telnet addresses have suffixes that tell what country the computer is
located in. Some example suffixes are:

<table>
  <tbody>
    <tr>
      <th>au</th>
      <td>Australia</td>
      <th>il</th>
      <td>Israel</td>
    </tr>
    <tr>
      <th>ca</th>
      <td>Canada</td>
      <th>nl</th>
      <td>Netherlands</td>
    </tr>
    <tr>
      <th>de</th>
      <td>Germany</td>
      <th>fi</th>
      <td>New Zealand</td>
    </tr>
    <tr>
      <th>fi</th>
      <td>Finland</td>
      <th>se</th>
      <td>Sweden</td>
    </tr>
    <tr>
      <th>fr</th>
      <td>France</td>
      <th>tw</th>
      <td>Taiwan</td>
    </tr>
    <tr>
      <th>jp</th>
      <td>Japan</td>
      <th>uk</th>
      <td>United Kingdom</td>
    </tr>
    <tr>
      <th>kr</th>
      <td>Korea</td>
      <th>us</th>
      <td>United States</td>
    </tr>
  </tbody>
</table>

#### SYNTAX

To actually go to and control a computer using the Telnet command, type "telnet
&lt;telnet address&gt;". To "telnet" to _The WELL_, for example, from another
computer on the Internet, type "telnet well.sf.ca.us" (see Figure 5).

<p>
  <img
    src="/images/the-internet-figure-5.jpg"
    alt="Figure 5"
    width={1000}
    height={570}
  />

<caption>
  Figure 5
</caption>
</p>

## IP ADDRESSES

Sometimes you are only given a set of numbers to contact a system. Then what?
The computer doesn't actually use the Telnet address. It uses something called
an IP address. The IP address really is a set of numbers and periods. Each
computer on the Internet is numbered. Its number is its IP address. To make the
addresses easier for humans to understand and remember, the computer translates
it to what we know as the Telnet address. If you know the IP address of a
computer and you don't know the Telnet address, you can "telnet" to a computer
by typing "telnet &lt;IP address&gt;". For example, "telnet 198.93.4.10". The
WELL's IP Address is 198.93.4.10. So typing "telnet 198.93.4.10" is the same as
typing "telnet well.sf.ca.us". Another example of an IP address is
140.147.254.3. This is the IP address of Library of Congress. As we mentioned
earlier, the Telnet address of the Library of Congress is _locis.loc.gov_. The
IP address and Telnet address of a computer can usually be used interchangeably.

## FTP

FTP stands for File Transfer Protocol. FTP is a way of transferring files from
one computer to another. Telnet is to FTP what a sports car is to a U-Haul
truck. Every once and a while, while using Telnet, a computer will offer to let
you download a file. With FTP, you go to a computer and can download almost any
file.

#### SYNTAX

FTP is used like Telnet. To go to a computer via FTP, simply type "ftp
&lt;telnet address&gt;" (see Figure 6). If you have an account on the computer
that you are "FTP"ing to then you can get rights to more files at that system.
If you don't have an account there then you have to use a technique called
Anonymous FTPS. With most FTP hosts, you can type "?" at the FTP prompt and get
a list of commands (see Figure 6). For more information on a certain command,
type "? &lt;command&gt;" (i.e. "? get").

<p>
  <img
    src="/images/the-internet-figure-6.jpg"
    alt="Figure 6"
    width={1000}
    height={557}
  />

<caption>
  Figure 6
</caption>
</p>

The _dir_ command tells you what is in the directory that you are in. The
command _cd_ changes directories. The _get_ command is used to get files. For
example, if you wanted a file called _b.clinton_, you would type "get
b.clinton". To upload a file, or send a file to the host computer, you would
have to go to the directory with the file on the remote computer, before using
the FTP command. Once you had logged on to the host computer, you would use the
_send_ command. For example, you could type "send b.clinton". To disconnect from
a host computer, type "close". To disconnect and leave the FTP prompt type
"quit" or "bye".

## ANONYMOUS FTP

There are several things that you need to have before you can use the technique
called Anonymous FTP. You need an Internet access with FTP, a valid E-mail
address (see E-mail) and a host computer that will let you use Anonymous FTP.
With Anonymous FTP, you don't need a login or password at a host FTP computer.
The syntax for the FTP command is the same ("ftp &lt;telnet address&gt;"). When
you have connected to the host computer, and it asks you for a login name, type
"anonymous". When it asks you for a password, type in your E-mail address. When
you type in your E-mail address, it wil not echo on the screen, so you have to
be careful when typing in in. This technique lets you download public files. The
computer wants to know your E-mail address so that if there is a problem with a
download or something, then it can send you E-mail about it. Usually there is no
use for any more rights than the basic "anonymous" rights. Anonymous FTP is
probably the most popular form of downloading files.

## How do I communicate with others on the Internet?

Communication is an important feature of the Internet. Not only can you get
information from computers around the world, but you can send information to
people around the world. Many companies that do business internationally and
nationally use the Internet system to send information to their clients. Others
use the Internet as a huge social structure. Some people have dozens of friends
that they write little short messages to every day. You can even send mail to
the President or the Vice President of the United States. The system that is
used to send mail around the Internet is called E-mail.

### E-MAIL (ELECTRONIC MAIL)

Imagine having the U.S. Mail service, UPS, or Federal Express being able to send
things at the speed of light. You could do all of your Christmas shopping on the
23rd! Using a technology called fiber optics, the Internet system can send
E-mail at the speed of light. One computer sends a set code of quick, little
flashes of light through a piece of glass thinner than a human hair, and another
computer on another continent reads those flashes of light and translates the
flashes into letters and numbers for a human to read. Fifty pages of information
or a long letter could be sent in about a second using this technology.

#### SYNTAX

There is no syntax of E-mail that will work on every system. You really have to
learn the mail system on the computer that you will be mailing from. Most mail
systems are compatible. For example, if I use my mail system and send a message
via Internet to you at your mailing system, you should receive the message
exactly as I sent it even if your mail system is completely different from mine.
Most mail programs ask you for an E-mail address (see E-mail Addresses), and a
subject of the message. Some ask you more (i.e. carbon copies, etc.). Messages
can be written in any form; sometimes a business style is more appropriate, and
for other purposes a more personal style may be better.

There are several mail-reading techniques that can be used. A quick way, and one
that saves time, is to copy each message to a file and then download the file to
your own computer. After reading or printing the message, you can write your
reply in a word processor or simple text editor. Then you can upload the message
to your Internet access computer and read the file into your reply through the
mail system. Some mail services won't let users use this method, but most will.
This is an easy and money-saving technique that is highly recommended. Of
course, you can also just read and reply to your messages while you are on-line.

### E-MAIL ADDRESSES

E-mail addresses are really very simple to understand. An E-mail address is a
person's login name combined with the Telnet address of the computer that they
are a member on. The login name and the Telnet address are separated by an _at
sign_ ("@"). For example, say you know someone at _The WELL_ named George (this
name is not real). His E-mail address would be:

> `george@well.sf.ca.us`

This system is used with all Internet addresses all around the world. There are
several ways to find out someone's Internet address. The easiest and most
obvious way to find the address of someone you know is to call them by voice or
mail them and ask them. Unfortunately we can't do that for everyone. Most Gopher
systems have some kind of Address Finder. With these systems, you give the
computer all the information you know about a person, and it tries to locate the
person's E-mail address.

The Internet is a vast network of networks of information. The Internet can be
used in many ways, from finding information on past micro-biology experiments to
writing a letter to your best friend in Australia. Almost anyone can use the
Internet. It all comes down to Telnet Addresses. If you understand Telnet
addresses, then you can understand almost all parts of the Internet.

---

<div style={{ textAlign: "center" }}>
  <a href="/guide-to-the-internet.pdf" target="_blank">
    View Original PDF
  </a>
</div>
]]></content>
        <author>
            <name>Erik Rasmussen</name>
            <email>rasmussenerik@gmail.com</email>
            <uri>https://erikras.com</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[The HOC Drill Pattern]]></title>
        <id>https://erikras.com/blog/hoc-drill-pattern</id>
        <link href="https://erikras.com/blog/hoc-drill-pattern"/>
        <updated>2016-04-27T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Drilling props through HOC-wrapped components]]></summary>
        <content type="html"><![CDATA[
With the rise of Higher Order Component composition in the React community,
there are a plethora of libraries, such as my own
[`redux-form`](https://redux-form.com), that will add functionality to your
application by decorating your component and wrapping it in a HOC.

What you end up with is a component structure like this, where you control the
yellow components, `MyContainer` and `MyWrappedComponent`, but you have no
control over the red code, LibraryHOC.

<img
  src="/images/wrapped-hoc.png"
  alt="Wrapped Components Diagram"
  width={1000}
  height={496}
/>

Most such libraries are polite enough to pass through any props that they do not
use as parameters down to your wrapped component, but sometimes they will not
provide a way, e.g. getWrappedInstance, to access instance methods on your
wrapped component.

What can you do? How can you get through this barrier?

## Concrete Example

Let’s lay out a concrete example. Your MyWrappedComponent has instance methods
of load(), save(), and clear(), and you need buttons in your MyContainer to be
able to call these.

```jsx
// MyContainer.js
import React, { Component } from "react";
import MyWrappedComponent from "./MyWrappedComponent";

export default class MyContainer extends Component {
  constructor() {
    // bind handlers
    this.handleLoad = this.handleLoad.bind(this);
    this.handleSave = this.handleSave.bind(this);
    this.handleClear = this.handleClear.bind(this);
  }

  handleLoad() {
    // somehow call load() on MyWrappedComponent
  }

  handleSave() {
    // somehow call save() on MyWrappedComponent
  }

  handleClear() {
    // somehow call clear() on MyWrappedComponent
  }

  render() {
    return (
      <div>
        <button onClick={this.handleLoad}>Load</button>
        <button onClick={this.handleSave}>Save</button>
        <button onClick={this.handleClear}>Clear</button>
        <MyWrappedComponent />
      </div>
    );
  }
}
```

```jsx
// MyWrappedComponent.js
import React, { Component } from "react";
import { hocLibrary } from "some-hoc-library";

@hocLibrary({ config: "parameters" })
export default class MyWrappedComponent extends Component {
  load() {
    // load something
  }

  save() {
    // save something
  }

  clear() {
    // clear something
  }

  render() {
    return <div>Gorgeous UI</div>;
  }
}
```

How can MyContainer drill through the HOC to call methods on
`MyWrappedComponent`?

<div style={{ textAlign: "center" }}>
  <img
    src="/images/drill.gif"
    alt="Wrapped Components Diagram"
    width={362}
    height={360}
  />
</div>

<caption>This HOC is tough!</caption>

## Callback Drill

Because our HOC allows us to pass props down through it, we can pass a callback
that will give our container component references to the wrapped component’s
instance methods.

Let’s see that in action. When the inner `MyWrappedComponent` is going to be
mounted, it sends up the API that it wants to expose to `MyContainer`.

```jsx
// MyContainer.js
import React, { Component } from "react";
import MyWrappedComponent from "./MyWrappedComponent";

export default class MyContainer extends Component {
  constructor() {
    // bind handlers
    this.handleCallback = this.handleCallback.bind(this);
  }

  handleCallback(load, save, clear) {
    // save instance methods from MyWrappedComponent to this
    this.handleLoad = load;
    this.handleSave = save;
    this.handleClear = clear;
  }

  render() {
    return (
      <div>
        <button onClick={this.handleLoad}>Load</button>
        <button onClick={this.handleSave}>Save</button>
        <button onClick={this.handleClear}>Clear</button>
        <MyWrappedComponent methodCallback={this.handleCallback} />
      </div>
    );
  }
}
```

```jsx
// MyWrappedComponent.js
import React, { Component } from "react";
import { hocLibrary } from "some-hoc-library";

@hocLibrary({ config: "parameters" })
export default class MyWrappedComponent extends Component {
  componentWillMount() {
    this.props.methodCallback(
      this.load.bind(this),
      this.save.bind(this),
      this.clear.bind(this)
    );
  }

  load() {
    // load something
  }

  save() {
    // save something
  }

  clear() {
    // clear something
  }

  render() {
    return <div>Gorgeous UI</div>;
  }
}
```

## Conclusion

It is a little messy, but breaking through barriers often is. I have used this
pattern on several occasions, and it works well. One important side note: If
your wrapped component might be unmounted while your container remains, to
prevent a memory leak, you should “unregister” the instance methods via another
callback in `componentWillUnmount()`.

Happy coding!
]]></content>
        <author>
            <name>Erik Rasmussen</name>
            <email>rasmussenerik@gmail.com</email>
            <uri>https://erikras.com</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[🏁 Final Form: Form state management via Observers]]></title>
        <id>https://erikras.com/blog/holy-js-moscow-2018</id>
        <link href="https://erikras.com/blog/holy-js-moscow-2018"/>
        <updated>2019-03-19T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Holy JS – Moscow – 2018]]></summary>
        <content type="html"><![CDATA[
<video>fxEW4jgoX-4</video>
]]></content>
        <author>
            <name>Erik Rasmussen</name>
            <email>rasmussenerik@gmail.com</email>
            <uri>https://erikras.com</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[You're a Wizard Form, Harry.]]></title>
        <id>https://erikras.com/blog/infobip-shift-2021</id>
        <link href="https://erikras.com/blog/infobip-shift-2021"/>
        <updated>2021-09-09T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Infobip Shift – Zadar, Croatia – September 2021]]></summary>
        <content type="html"><![CDATA[
It was _so_ nice to go back to an in-person conference! The
[Infobip Shift](https://shift.infobip.com) conference organizers did an amazing
job of making it as safe as possible. Everyone entering the conference venue was
required to show a European COVID vaccination certificate (which I have), or a
negative test from that morning. Then, once inside... _WE COULD REMOVE OUR
MASKS_!!! I've never been so happy to see so many noses and mouths!

It was also great to finally meet in person some folks I've known online for
years, like [Shawn Wang](https://twitter.com/swyx),
[Nader Dabit](https://twitter.com/dabit3),
[Ives van Hoorne](https://twitter.com/CompuIves), and
[Kent C. Dodds](https://twitter.com/kentcdodds). And it was also great to hang
out with some other folks whom I already consider friends, like
[David 🎹](https://twitter.com/DavidKPiano),
[Tomasz Łakomy](https://twitter.com/tlakomy), and
[Kitze](https://twitter.com/thekitze), as well meeting many new friends that I
hope to see again. I'd like to say a special _obrigada_ to
[Atila Fassina](https://twitter.com/atilafassina); we were complete strangers
when we met up at the hotel lobby, but we decided to spend a day together
walking around the historical district of Zadar. We had a great time, talking
not about tech, but about being expat fathers.

The venue for this particular talk was pretty wild. It was in a
[UFO-shaped basketball stadium](https://www.youtube.com/watch?v=yWqQoovu93M),
where I was in the center of the circle, and the audience was all around me at
360º. Because I'm such a big fan of live coding talks, both seeing and giving, I
was a little restricted in the time where I could be facing those behind me, but
I did my best.

## The Talk

<video>0bY9_DasGEg</video>

<caption>
  The slides can be found{" "}
  <a
    href="https://speakerdeck.com/erikras/youre-a-wizard-form-harry"
    target="_blank"
  >
    here
  </a>
  , and the code <a
    href="https://github.com/erikras/potionocean"
    target="_blank"
  >
    here
  </a>, if you so desire.
</caption>

## Photos

<p>
<img
  src="/images/infobip-shift-2021-1.jpg"
  alt="Pre-talk Selfie"
  width={4032}
  height={3024}
/>

<caption>Obligatory Pre-talk Selfie</caption>
</p>

<p>
<img
  src="/images/infobip-shift-2021-2.jpg"
  alt="Front Row Groupies"
  width={4032}
  height={3024}
/>

<caption>
  My front-row groupies: <a href="https://twitter.com/tlakomy">Tomasz Łakomy</a>, <a href="https://twitter.com/jen_ayy_">Jenny Truong</a>, <a href="https://twitter.com/jacobmparis">Jacob Paris</a>, <a href="https://twitter.com/DavidKPiano">David 🎹</a>, and <a href="https://twitter.com/kentcdodds">Kent C. Dodds</a>.
</caption>
</p>

<p>
<img
  src="/images/infobip-shift-2021-3.jpg"
  alt="360º Crowd"
  width={4032}
  height={3024}
/>

<caption>
  360º Crowd. Photo by <a href="https://twitter.com/swyx">Shawn Wang</a>.
</caption>
</p>

<p>
<img
  src="/images/infobip-shift-2021-4.jpg"
  alt="Panorama"
  width={13056}
  height={3918}
/>

<caption>
  It was a <em>huge</em> venue! Photo by <a href="https://twitter.com/swyx">Shawn Wang</a>.
</caption>
</p>

<p>
<img
  src="/images/infobip-shift-2021-5.jpg"
  alt="Flow"
  width={3024}
  height={4032}
/>

<caption>
  I ended with a pitch for my new employer, <a href="https://www.centered.app/">Centered</a>.
  Photo by <a href="https://twitter.com/swyx">Shawn Wang</a>.
</caption>
</p>

<p>
<img
  src="/images/infobip-shift-2021-6.jpg"
  alt="Speaker's Dinner"
  width={4032}
  height={3024}
/>

<caption>
  We had a speakers dinner in an abandoned church, where I enjoyed the company of <a href="https://twitter.com/atilafassina">Atila Fassina</a>, <a href="https://twitter.com/mhevery">Misko Hevery</a>, <a href="https://twitter.com/kentcdodds">Kent C. Dodds</a>, and <a href="https://twitter.com/CompuIves">Ives van Hoorne</a>.
</caption>
</p>

<p>
<img
  src="/images/infobip-shift-2021-7.jpg"
  alt="Ives and Kitze"
  width={4032}
  height={3024}
/>

<caption>
  Me with the legendary <a href="https://twitter.com/CompuIves">Ives van Hoorne</a> and <a href="https://twitter.com/thekitze">Kitze</a>.
</caption>
</p>
]]></content>
        <author>
            <name>Erik Rasmussen</name>
            <email>rasmussenerik@gmail.com</email>
            <uri>https://erikras.com</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[💡 IDEA: JavaScript Compatibility as a Service]]></title>
        <id>https://erikras.com/blog/javascript-compatibility-as-a-service</id>
        <link href="https://erikras.com/blog/javascript-compatibility-as-a-service"/>
        <updated>2018-04-16T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[What if javascript bundling happened at the CDN?]]></summary>
        <content type="html"><![CDATA[
---

Sometimes I wonder how many hours of frustration and billions of dollars has
been spent over the history of web development on making code that works in one
browser support another browser. With tools like Babel to transpile modern
language features down to an older language that works on ancient browsers,
young devs these days have no idea how good they have it.

It occurred to me that it kind of sucks that even the newer browsers have to
download the larger, older javascript versions of the bundles, laden with
polyfills. What if they didn't? What if you could build bundles for many
different browsers, and then had some Express middleware or Nginx rule that
would serve the right bundle depending on the client's User-Agent header? That
would be awesome!

I am a happily paying customer of [CloudFlare](https://www.cloudflare.com). I
love that all my javascript bundles are automatically cached on servers around
the world to make them load faster for my users.

## What if? 🤔

What if there was a service that would allow me to put a bundle of the most
bleeding edge, unminified, [Prettier](https://prettier.io)-ified, modern
JavaScript on my server, and have my brower's request be rerouted to a service
that would read my `User-Agent` header, and serve a minified, uglified,
transpiled bundle specifically tailored to my browser version, cached at my
local CDN node?

---

<div fontSize="7em" textAlign="center">
  🔥🔥🤯🔥🔥
</div>

---

## Introducing...

Nah, I'm just kidding. I haven't built this. I have too many other startup irons
already in the fire. I'm just throwing it out there as a "Wouldn't it be cool?"
product idea, but I promise I'll be a customer if _you_ build it.

It seems like it would be easiest for someone with the infrastructure in place
already, like _CloudFlare_ or _DigitalOcean_, to implement, but I bet a leaner
company could handle it with a moderate cloud storage plan.

## A few things to consider

- You'd need to have to be _very_ confident in your
  User-Agent-to-browser-feature mapping. You can't make many mistakes there.
- Ideally you would automatically detect what esoteric features, e.g. `Symbol`
  or `for...of`, are being used in the code to know if you need a polyfill or
  not, but you could also do feature checkboxes on a settings screen.
- You might also consider pulling the code from a Github branch. 🤷‍♀️
- Doing all bundle versions lazily (on first request) might not be the optimal
  solution; it would require some thinking and testing.
- You could maybe provide source maps to people that had an auth cookie for your
  website.
- Might be a good idea to have a "dev mode" that puts in some error tracking
  code, like _Rollbar_ or _New Relic_ do, to detect edge cases when your
  `User-Agent`-to-browser-feature mapping wasn't up to snuff.

## Conclusion

The minimum step in this direction would be an open source library for the
Express middleware with a detailed description of how to tailor existing build
tools to output browser-specific bundles - like
[this](https://gist.github.com/newyankeecodeshop/79f3e1348a09583faf62ed55b58d09d9),
but as a published lib. I'd also be grateful to learn of something like that. If
you know of one, or have just finished building it after being inspired by this
article,
[@ me on Twitter](https://twitter.com/intent/follow?screen_name=erikras).

And if you don't want to build this and get filthy rich, but think it might make
everyone's experience better on the web, maybe share this until it finds someone
who _does_ want to build it.

### Updates

- [@Raathigesh](https://twitter.com/Raathigesh) on Twitter
  [pointed me](https://twitter.com/Raathigesh/status/985827397030129664) towards
  [Polyfill.io](https://polyfill.io), a service from _The Financial Times_, that
  "reads the User-Agent header of each request and returns polyfills that are
  suitable for the requesting browser." And it's
  [open source](https://github.com/Financial-Times/polyfill-service), too! 👍
]]></content>
        <author>
            <name>Erik Rasmussen</name>
            <email>rasmussenerik@gmail.com</email>
            <uri>https://erikras.com</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Abstracting Form State with Redux Form]]></title>
        <id>https://erikras.com/blog/js-channel-2016</id>
        <link href="https://erikras.com/blog/js-channel-2016"/>
        <updated>2016-08-02T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[JS Channel – Bangalore, India – 2016]]></summary>
        <content type="html"><![CDATA[
<video>eDTi7lYR1VU</video>
]]></content>
        <author>
            <name>Erik Rasmussen</name>
            <email>rasmussenerik@gmail.com</email>
            <uri>https://erikras.com</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Modern Forms in React]]></title>
        <id>https://erikras.com/blog/jsdc-2019</id>
        <link href="https://erikras.com/blog/jsdc-2019"/>
        <updated>2020-02-07T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[JSDC – Taipei, Taiwan – 2017]]></summary>
        <content type="html"><![CDATA[
<video>dIhODcA1Tv4</video>
]]></content>
        <author>
            <name>Erik Rasmussen</name>
            <email>rasmussenerik@gmail.com</email>
            <uri>https://erikras.com</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Model Based E2E Testing with State Machines]]></title>
        <id>https://erikras.com/blog/model-based-e2e-testing</id>
        <link href="https://erikras.com/blog/model-based-e2e-testing"/>
        <updated>2023-06-05T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[with Playwright and @xstate/test – Tenerife, Spain – May 2023]]></summary>
        <content type="html"><![CDATA[
This was my third time on the Canary Islands, second time on the island of
Tenerife. I really love the 17º – 24º C (63º – 75º F) weather every day of the
year is pretty amazing. If the terrain weren't so taxing – sooo hilly!! – I
might consider moving there. I had been winking at JS Day Canarias over Twitter
("twinking"?) for several years, and I finally got to meet the organizers at
[React Alicante](https://reactalicante.es) last year, so I was delighted to be
invited this year.

Ever since I saw
[David K. 🎹's talk from React Rally 2019](https://www.youtube.com/watch?v=tpNmPKjPSFQ),
I've been curious about model-based testing. As he is want to do, his demo was a
little, let's say, "set in the future", so it took a few years for Reality to
catch up, but last year at the
[first ever Remix Conf](https://erikras.com/blog/remixconf-2022), I learned more
about [Stately](https://stately.ai)'s
[`@xstate/test`](https://xstate.js.org/docs/packages/xstate-test/) from
[Matt Pocock](https://twitter.com/mattpocockuk), who was working on the alpha
docs at the time.

This was the first year where I submitted talks before actually having any
content behind the proposals, so when I had two _different_ talks accepted
back-to-back in the same week of May, it was a little stressful. By March, I had
actually built the project that confirmed that `@xstate/test` was a way to build
tests. My friendship from Croatia the previous year with
[Debbie O'Brien](https://debbie.codes) made [Playwright](https://playwright.dev)
the obvious choice for my demo.

## Talk

Sadly, this talk was not recorded...or if it was, the video was never published.
The last video on their YouTube channel is
[the promo for the event](https://www.youtube.com/watch?v=6VHUmy-6x-g).

## Food

The day before the conference began, I went on a little excursion with my
friends from [Lemon Code](https://lemoncode.net) to experience the most
authentic food on the islands, namely those provided by a
[_guachinche_](https://en.wikipedia.org/wiki/Guachinche). Five of us piled into
a small rental car, and we did a bit of tourism on our way to the _guachinche_,
including stopping at a town where I had been before when I traveled to the
island for the first time with my family two years earlier. We ended up at
Guachinche Los Gomez. There was no menu to order from. The sexagenarian waiter
came to our table and read out a list of things they had, including such Canary
staples as _ropa vieja_ and _queso asado_. We said, "Yeah, some of all that."
Then we began to order the main course, one person asking for the _solomillo_,
but the waiter stopped us, warning that it might be too much food and we should
wait to order additional dishes until we'd had our appetizers. We acquiesed.

<p>
<img
  src="/images/guachinche-ropa-vieja-queso-asado.jpg"
  alt="Ropa Vieja and Queso Asado"
  width={3024}
  height={4032}
/>

<caption>
  <em>Ropa Vieja</em> and <em>Queso Asado</em>
</caption>
</p>

Afterwards, we actually did want to try the meat, so we ordered one _solomillo_.
In Spanish, the word "_solomillo_" is used for both beef sirloin steak and pork
tenderloin, the beef usually being way more expensive. This meat was _so_ tasty
and juicy, we were genuinely unclear as to whether we were eating beef or pork.
We confirmed with the waiter that it was pork, and someone at the table
exclaimed, "_¡Menudo cerdo, tío!_" ("Man, what a pig!")

<p>
<img
  src="/images/guachinche-solomillo.jpg"
  alt="Pork Tenderloin"
  width={3024}
  height={4032}
/>

<caption>
  <em>Solomillo de Cerdo</em> (Pork Tenderloin)
</caption>
</p>

Rather than present us with a dessert menu, the waiter just brought us three of
their homemade desserts.

When we couldn't eat or drink any more (we'd had 6 liters of local red wine), we
went to pay. There had never been any mention of prices, of course (no menus
🙄). When the waiter told us 66€, our reaction was, "That's actually not a bad
price for what we ate!", assuming it was 66€ _each_. I mean, just the wine
should cost that much! But when he clarified that it was 66€ _in total_, we were
astonished!

I've placed a digital pin on the map, so I can find that place every time I go
back to Tenerife. Wow!
]]></content>
        <author>
            <name>Erik Rasmussen</name>
            <email>rasmussenerik@gmail.com</email>
            <uri>https://erikras.com</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Modeling React in XState]]></title>
        <id>https://erikras.com/blog/modeling-react-in-xstate</id>
        <link href="https://erikras.com/blog/modeling-react-in-xstate"/>
        <updated>2022-06-24T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Can XState state charts improve your understanding of the React render cycle?]]></summary>
        <content type="html"><![CDATA[
The chart above is from
[a famous tweet](https://twitter.com/dan_abramov/status/981712092611989509) by
Dan Abramov in 2018. Dan, as usual, does an amazing job of communicating his
deep technical knowledge in a way that is easy to comprehend.

Since the release of
[Stately's Visual Editor](https://stately.ai/registry/new?source=erikras), I've
been using it more and more at my dayjob at
[Centered](https://centered.app/erik) to construct new, and comprehend existing,
state machines in our codebase. The visual editor is just so _fun_ to use!

The other day, I thought to myself,

> "I wonder if I could test my knowledge of React and state charts, and model
> the React lifecycle methods in a state chart?" 🤔

So I gave it a try!

## React Lifecycle State Chart

<p>
<img
  src="/images/react-lifecycle-state-chart.png"
  alt="React Lifecycle State Chart"
  width={2590}
  height={2084}
/>

<caption>
  Elegant, ain't it?
</caption>
</p>

One thing that immediately jumped out at me was how all the lifecycle methods –
and `render()` itself! – ended up being side effects, or "actions" in XState
parlance. This makes total sense, as they really are _where you put side
effects_!

> Rendering is a side effect!

### The Code

This is the XState machine that the visual editor generated:

```js
import { createMachine } from "xstate";

export default /** @xstate-layout N4IgpgJg5mDOIC5QCUwEMDGAXABAYQHsBbABwIDsxysA6AWQIFdrIBiAVXKKesVDNgBLLIIp8QAD0QBaAIwAGAGw0ALAE4AHPICsigMxrFi+RqMAaEAE8ZAJhsaaAdm0qVsmyb12bamwF8-C1RMXEJSCipaBmYsNgA5MAB3HAAFACcCEngkEAFhUXJxKQRpNxsaeVk9FUc1Xz09DTttC2sS-WUG9wbtbUdXWUcAoPRsfGIySmp6HliIVgBlMFwFrDRY8TyRMRzi2SqaRTVHE3t+tS7HVplFDxofBUUVDQ1ZC5tZYZBgsbDJyJmMTYADECGkMGAcOwSBB1mBNgQhNtCrsZAp5DRZC4zvI9I4DPtriVtC8aGoVDZtLImoZHI4ngFAiByAQIHBxD9QhMItNoiwIDRUOQ2WlIAikQUijJapj8U19iSVLpZCoidI9L0KvJ5EqdQTNEovpzxuEplFZmKcltJaiSio9MpKtVavVGs01S8MV07I4fJoLiojaMuaaAZxuECIOL8jtQHs8WT5fI7KY3eYrLYsU4XColIZFNofP4mca-jysNHkVK7cYKlUanUbA0mpS1XUvXp3DZatVc7JFIy-EA */
createMachine({
  description: "made by @erikras",
  initial: "Mounted",
  states: {
    Mounted: {
      entry: [
        "constructor",
        "getDerivedStateFromProps",
        "render",
        "componentDidMount",
      ],
      initial: "Rendered",
      states: {
        Rendered: {},
      },
      on: {
        Unmount: {
          actions: "componentWillUnmount",
          target: "Unmounted",
        },
        "New Props": {
          actions: ["getDerivedStateFromProps", "render", "componentDidUpdate"],
          cond: "shouldComponentUpdate",
          target: ".Rendered",
        },
        "Set State": {
          actions: ["render", "componentDidUpdate"],
          cond: "shouldComponentUpdate",
          target: ".Rendered",
        },
        "Force Update": {
          actions: ["render", "componentDidUpdate"],
          target: ".Rendered",
        },
      },
    },
    Unmounted: {
      type: "final",
    },
  },
  id: "React Component",
});
```

The crazy comment at the top is how the visual editor encodes the location of
all the boxes and arrows and whatnot, so if you open this exact file in the
editor, the layout will be the same. Clever, huh?

Of course, because
[I tweeted it](https://twitter.com/erikras/status/1539984775523475457), I got
[a clever response/challenge](https://twitter.com/donavon/status/1540112177515970567),
from my friend, Donovan:

> Nice. Now do this one.

with a link to [this chart](https://github.com/donavon/hook-flow) he, his son,
and Dan put together three years ago:

<p>
<img
  src="/images/react-hooks-lifecycle.png"
  alt="React Hooks Lifecycle"
  width={761}
  height={969}
/>

<caption>
  React Hooks Lifecycle
</caption>
</p>

I couldn't turn down such a challenge, now could I?

## React Hooks State Chart

It actually didn't take me long at all to whip up this one. Although React
hooks, as an API, is full of footguns with which to shoot yourself, the actual
mental model of what is happening is simpler than the lifecycle methods.

<p>
<img
  src="/images/react-hooks-lifecycle-state-chart.png"
  alt="React Hooks State Chart"
  width={2226}
  height={1700}
/>

<caption>
  React Hooks State Chart
</caption>
</p>

With this one, I took the additional step of imagining we were actually
implementing it as an XState state machine, where we could have to save the
props, state, and context into the machine's context (that's the XState term for
the internal storage of the machine).

### The Code

```js
import { assign, createMachine } from "xstate";

export default /** @xstate-layout N4IgpgJg5mDOIC5QCUwEMDGAXABAYQHsBbABwIDsxzdBkAhwAkCCBrWHAMQBsCB3AOgCyBAK7VIfVOQhgATpADEABRkESbPAAs05GIlBlYASyyGKekAA9EAWgAsAdgAMfewGYAnK9e3XARgBs7v6OrgA0IACeNvYATPZ8tgAcMX7BjgH+9r4AvtnhqJi4hKQUVLQMTKwc3PxColjiktJyEPIAylhoDfhaOmDmBsam5OZWCHb+iXzuvgCsvr6J7rY+7u724VHj-r7u0zGJib5uwa7+B7n56Nj4xGSU1Dh0jCxsXLyCImIQElTNCoQxBYir1dEgQIMTGZwWM7PYpnEVolzjFbLNXEdNjZ5lMfPYso5-LZHGtZjFLiACjdivcyk8Kq9qh86t95ABVchEL5YAYEIxQkYwxD2WZ8SaxWKuRwxYJJXxY7Zk6azFU7HZJFX2XJ5EDkAjSeDgqlFO6lR7PSpvGqfeqNP6ySC8-nDUY2A6uaZHEW+Ry+1zo1wbSLRGIxBIHI7pUNrGUU423EoPcovKrvWrcx3gyEuoXjWbSviIpIotEY+XB8aJeYJZKpX0ZLJx64mxN0i2MtN8Dlc20QJ1DaGgMa2fx8ZFSvy+Ym+QOzWwK6yzI58RxkxyJJKuGJT3ZNwoJ2nmhmpmr9gWuvMywv2JEl9GYivWIK+GspUMi-xz-za7JAA */
createMachine(
  {
    description: "made by @erikras",
    initial: "Mounted",
    context: {
      props: {},
      state: {},
      context: {},
    },
    states: {
      Mounted: {
        entry: "Run Lazy Initializers",
        initial: "Rendered",
        states: {
          Rendered: {
            entry: [
              "Render",
              "Cleanup Layout Effects",
              "Run Layout Effects",
              "Browser Paints Screen",
              "Cleanup Effects",
              "Run Effects",
            ],
            on: {
              "Props Change": {
                actions: "setProps",
              },
              "State Change": {
                actions: "setState",
              },
              "Context Change": {
                actions: "setContext",
              },
            },
          },
        },
        on: {
          Unmount: {
            target: "Unmounted",
          },
        },
      },
      Unmounted: {
        type: "final",
      },
    },
    id: "React Component – Hooks Flow",
  },
  {
    actions: {
      setContext: assign({ props: (_context, event) => event.props }),
      setProps: assign({ props: (_context, event) => event.props }),
      setState: assign({ props: (_context, event) => event.props }),
    },
  }
);
```

## Conclusion

Am I trying to reimplement React using state machines? Hell no!

But this exercise _has_ served to benefit my understanding – and maybe yours
too? – of how React works, how to model flows in state charts using Stately's
visual editor, and how to read and understand state charts.
]]></content>
        <author>
            <name>Erik Rasmussen</name>
            <email>rasmussenerik@gmail.com</email>
            <uri>https://erikras.com</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Creating an Observable Subject in ReScript]]></title>
        <id>https://erikras.com/blog/observable-subject-in-rescript</id>
        <link href="https://erikras.com/blog/observable-subject-in-rescript"/>
        <updated>2021-03-30T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Creating a Subject is a good way to test your skills in a new language.]]></summary>
        <content type="html"><![CDATA[
The [Observer pattern](https://en.wikipedia.org/wiki/Observer_pattern) is very
powerful, and those familiar with my work on
[Final Form](https://final-form.org) will know that I'm a fan. The RxJs docs
[define Observables](https://rxjs.dev/guide/observable) as:

> Observables are lazy Push collections of multiple values. They fill the
> missing spot in the following table:
>
> |      |   Single   |   Multiple   |
> | ---- | :--------: | :----------: |
> | Pull | `Function` |  `Iterator`  |
> | Push | `Promise`  | `Observable` |

They can represent streams of data or a series of events over time. I like to
use them to notify interested parties to changes of values (a.k.a. "state") over
time. My favorite kind of observable is called a Subject. Back to the
[RxJs docs](https://rxjs.dev/guide/subject):

> A Subject is like an Observable, but can multicast to many Observers. Subjects
> are like EventEmitters: they maintain a registry of many listeners.

I like to think of a Subject sort of like a physical object on a table.
Vizualize a Rubik's Cube on the desk in front of you. You can look at it and see
its position. If I go and move it, as soon as I do, _if you're still looking at
it_, you will immediately know its new position. But if you look away, I can
change it a lot and you won't know until you look back at it.

## The Perfect Exercise

Creating a Subject is a nice exercise to try when learning a new programming
language, as it requires:

- Passing functions as arguments
- Adding to a data structure of items
- Iterating through a data structure of items
- Removing items from a data structure

So what a great task to tackle in my online learning of ReScript!

---

Due to [recent musings](/blog/follow-observables), I'm going to use the verb
`listen()` rather than `subscribe()`.

---

## Types

The most important contract with observables is that the `listen()` function
returns a function to "unlisten", to unregister the listener. We're going to
define our `unlisten` type, our `listener` type, and a record type that keeps a
unique `id` along with our `listener`. This is to tell them apart for
unlistening.

```reason
type unlisten = () => unit
type listener<'value> = (. 'value) => unit
type listenerId<'value> = {id: int, listener: listener<'value>}
```

<caption>Remember that `unit` is like `void` in TypeScript</caption>

Now let's define the shape of our Subject...

Sometimes you can have a Subject and you want to know what its value is without
subscribing to all future updates – a "glance" at the Rubik's Cube, if you will.
One way to do this, following the strict Observer pattern, is to quickly listen
and unlisten. In JavaScript, that would look like:

```js
let value;
subject.listen((v) => (value = v))();
//                                ^^-- immediately unlisten
```

But that's gross, and the internal implementation always has the value anyway,
so I like to provide a simple `get()` function on my Subjects.

```reason
type subject<'value> = {
  listen: listener<'value> => unlisten,
  get: unit => 'value,
  set: 'value => unit,
}
```

Notice our Subject is strongly typed with a
[Type Parameter](https://rescript-lang.org/docs/manual/latest/type#type-parameter-aka-generic),
also known as a "Generic" in other languages. All type parameters are prefixed
with a `'`, which I learned the hard way,
[_is not a backtick_](https://twitter.com/erikras/status/1372857612442726402)!
`unit` is like `void` in TypeScript.

Okay, I think we're ready to attempt the...

## Implementation

A few things to notice for non-ReScript folks:

- `let` bindings, by default, are immutable (like `const` in JS/TS), but you can
  [make them mutable](https://rescript-lang.org/docs/manual/latest/mutation#mutate-let-binding)
  by surrounding them with `ref()`, and then access the value with
  `myRef.contents`. Any React devs will find this nearly equivalent to the
  `useRef()` and `myRef.current` API that React provides. There is some syntax
  sugar that means that `myRef.contents = 7` can be written as `myRef := 7`.

- The `open Js.Array2` line "opens that module" into my current scope so that I
  can use `forEach` rather than `Js.Array2.forEach`. It's a bit like
  `import com.initech.frontend.js.array.*;` in Java.

  - Why are there
    [`Js.Array`](https://rescript-lang.org/docs/manual/latest/api/js/array)
    _and_
    [`Js.Array2`](https://rescript-lang.org/docs/manual/latest/api/js/array-2)
    modules, with identical docs? ReScript is still a growing language that
    hasn't quite figured out what their API dogma is yet. If that's a
    dealbreaker for you, then avoid it.
    [Quoth the docs](https://rescript-lang.org/docs/manual/latest/api/js#jsxxx2-modules):

  - > ### Js.Xxx2 Modules
    >
    > Prefer `Js.Array2` over `Js.Array`, `Js.String2` over `Js.String`, etc.
    > The latters are old modules.

- `->ignore` is required because ReScript doesn't like you calling functions
  that return a value and you not doing anything with it, so you have to
  explicitly ignore the value.

```reason
export createSubject = (initial: 'value) => {
  open Js.Array2
  let value = ref(initial)
  let nextId = ref(0)
  let listeners: array<listenerId<'value>> = []
  {
    get: () => value.contents,
    set: (v: 'value) => {
      value := v
      // notify all listeners
      listeners->forEach(({listener}) => listener(. v))
    },
    listen: (listener: listener<'value>) => {
      let id = nextId.contents
      nextId := id + 1
      listeners->push({id: id, listener: listener})->ignore
      // notify listener of value on listen
      listener(. value.contents)
      () => {
        let index =
          listeners->reducei(
            (foundIndex, listener, index) =>
              listener.id === id ? index : foundIndex,
            -1,
          )
        if index >= 0 {
          listeners->removeFromInPlace(~pos=index)->ignore
        }
      }
    },
  }
}
```

When I implement this in TypeScript or JavaScript, I will often store the
listeners in a `Record<number, Listener>` rather than an array, to allow for
easier removal, but it doesn't look like ReScript supports that syntax. And I
wasn't paying enough attention on
[Big O Day](https://en.wikipedia.org/wiki/Big_O_notation) in Data Structures 201
to know which is better. ReScript also supports linked lists, which pleases
someone who still remembers what
[CAR and CDR](https://en.wikipedia.org/wiki/CAR_and_CDR) are, but they aren't
useful for this random access deletion needed for my Subject.

I couldn't find a "remove the item for the first time this predicate is true and
stop iterating" function, so rather than `filter`ing through the array (and
making the reference mutable), I chose to search for the `index` (which still
goes through the entire array) and then delete (mutating) with that.

Thanks for reading! Comments and suggestions via Twitter are welcome!
]]></content>
        <author>
            <name>Erik Rasmussen</name>
            <email>rasmussenerik@gmail.com</email>
            <uri>https://erikras.com</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Open Source Sustainability]]></title>
        <id>https://erikras.com/blog/open-source-sustainability</id>
        <link href="https://erikras.com/blog/open-source-sustainability"/>
        <updated>2020-07-01T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[The status quo in open source is broken. Corporations profit hugely from open source libraries, and give almost nothing back, which leads to burnout which is bad for everyone.]]></summary>
        <content type="html"><![CDATA[
As we round the bend into the second half of this most exceptional year, 2020,
two things happened that affected me and my open source work:
[CodeFund](https://codefund.io) announced they were shutting down, and social
pressure forced [Tanner Linsley](https://github.com/tannerlinsley) and myself to
remove the [Scarf](https://scarf.sh) analytics service from our open source
libraries.

## Advertising

CodeFund has meant a lot to me over the years. I was an early adopter, and the
monthly revenue it generated – which has never been more than roughly equivalent
to what my dayjob pays me for a single day of work – was really motivating. When
I was 85% done with the MVP of [Final Form](https://final-form.org), I was
stalled for motivation, and seeing that I could potentially get some income from
having another popular library gave me the stamina to make it to the finish
line.

(That metaphor was not intentionally related to Final Form's logo, I swear.)

I still think that radio, television, the internet, social media, YouTube, and
podcasts have shown us that making something that people want, giving it to them
for free, but with ads, _is_ a viable strategy for a producer-consumer
relationship that works with how our brains make decisions. The eyes that peruse
the documentation pages for open source libraries are very valuable to
advertisers. Developers not only can influence decisions about which SaaS
solutions their employers adopt, but are also, generally, pretty well paid and
have disposable income to spend on gadgets and things.

In the future, I hope more companies like CodeFund spring up to offer themselves
as middlemen between OSS devs and advertisers; I think there's still money to be
conjured from this space. I think Microsoft, as the owner of both GitHub and
NPM, is uniquely positioned to make a difference in open source sustainability
via advertising.

## Analytics

Installing an analytics tracker in my open source library had never occurred to
me before Avi, from Scarf, approached me to talk about it. The way it works is
simple. Much like how this very webpage, totally unbeknownst to you and without
your consent, probably caused your computer to download half a dozen (or more)
little 1x1px gifs, allowing some unknown-to-you corporations to triangulate,
using only the HTTP headers of those requests, where you are, what device you're
on, and sometimes all the way down to your actually identity, including Scarf as
a dependency in my library caused a single HTTP request to fire off whenever
anyone ran `npm install` on a project that included my library as a dependency.
Scarf would then determine the location of the request, only to the resolution
of the country, and attempt to deduce if the IP address belonged to a
corporation, and then record the name of the corporation and the country,
_discarding the IP address itself_.

My initial reaction to this idea was similar to that of most developers I have
spoken to about it. Eww! Gross! That's creepy to have a library reporting to
some third party every time I run `npm install`. The implicit contract when I
run `npm install` is that I expect my computer to go fetch some metadata and
possibly published library code from NPM, and NPM only. But then Avi explained
that the long term goal of Scarf was to then approach these corporations to
request financial support, and it was a novel idea to the seemingly intractable
problem that this post is about, so I consented to give it a try.

The results that rolled in over the next few weeks were stunning. Microsoft,
Google, Amazon, and Apple were
[all using my library](https://final-form.org/users)! It was hugely validating
and motivating. ...but it also made more salient the fact that, between
[Github Sponsors](https://github.com/sponsors/erikras) and
[Open Collective](https://opencollective.com/final-form), the library was
receiving a grand total of just under $30/month in sponsorship.

In the end, I'm glad that briefly had Scarf enabled as a test of open source
analytics and get a feel for that avenue of attack on this problem, but, unlike
all the Silicon Valley behemouths that are gaining value from my library, I
cannot, in good faith, continue to force my consumers to give up any information
about themselves in exchange for using my library.

NPM _has_ all of these analytics, but does not share them. I think Microsoft, as
the owner of NPM, is uniquely positioned to make a difference in open source
sustainability via analytics.

## What is the value of Open Source?

For the purposes of this discussion, I'm going to ignore that, on most
platforms, most of the code all the way down to the kernel is open source. Let's
start at the web app level. You want to build a web application that can receive
Hypertext Transfer Protocol requests and return Hypertext Markup Language.
Personally, I'm old enough to remember when web applications were actual
compiled executables (or scripts) in the `/cgi-bin` directory that did exactly
that: listened to a port for HTTP requests and squirted out HTML. If you had to
start at that level every single time, double-checking the
[HTTP specs](https://www.w3.org/Protocols/), to design your web application,
you'd never get anywhere. The reason the web even exists is that it has been
collaborative from the beginning, with people sharing their code and having
others improve upon it, like Science in Academia.

If I want to build a single page app to track COVID-19 data, and building that
included designing a charting library from scratch, it'd never get done. But
there are a plethora of charting libraries out there that people have spent
many, many hours building and perfecting, just given to me...no strings
attached. And so, I can whip up my COVID-19 tracking app in a day or two! Am I
_stealing_ from the chart devs? No, of course not. They posted their code to the
public square and let anyone have it. Were I a thoughtful individual, I might be
so grateful for their effort that I toss them a $20 bill in thanks for saving me
200 hours of work.

Where it gets a bit more morally murky is if my virus tracking app starts making
$1M in monthly recurring revenue, and if the chart devs stopped maintaining
their library, I could lose $100K in revenue, and then another $50K in
development time to migrate to another library or try to build one in-house. The
incentives are out of whack. It should be in ViralCorp's best interest to make
sure that the chart devs are well compensated and not going to burn out or lose
interest.

In no other industry would such large corporations depend so heavily on single
individuals for no pay. Can you imagine a car manufacturer that gets an
important cog in the engine from a guy down the street who just really likes
making these cogs and leaving them out on this front porch in a "FREE, take as
many as you want!" box, and has no backup plan if the guy keels over or decides
to start making another thing? The accountants and shareholders would be
shouting "_OMFG RISK!!_" from the rooftops. And yet that is how the software
industry operates.

To me, the value of open source is in the time that it saves all of us to get an
app off the ground and working. That profitable corporations should give back is
a bit like corporations paying taxes because they built their business on the
roads the taxes pay for... oops, bad analogy...they don't pay taxes either!

## Donations are NOT the answer

This is the current model, and I don't even think this obvious fact warrants an
argument.

## The Spotify Model is NOT the answer

Some people suggest some system where you can pay $X every month to "some
arbitrator?", and then every library in your `package.json` with 75 dependencies
recieves $X/75 every month. This "by usage" model is how Spotify pays artists,
and
[how Egghead pays its content producers](https://howtoegghead.com/instructor/royalties/).
The problem is that this model incentivizes the tiny small utility, like the
infamous `leftpad`, and disincentives complex logic that actually saves devs
time.

Don't get me wrong; I _love_ that I will never have to think about how to parse
or format a query string ever again. I _could_ write that function in a few
minutes, but to write it properly with tests would be closer to an hour, and
even then I'm certain I'd miss some edge case that I know for sure that
`query-string` or `qs` has faced and addressed.

That said, a function that takes a string and converts it into an object does
not deserve the same compensation as a complex calendar widget. I challenge you
to find a website that doesn't contain some code from Sindre Sorhus. He deserves
$1M in annual recurring royalties, but not $1B.

So if we can't divvy it up evenly, how?

There's the rub. I guess it would have to be by which libraries the devs that
use them appreciate most. What I'm suggesting is that companies should provide
their devs with "gift vouchers" or some sort of credits for them to distribute
to open source libraries. Open Collective already supports vouchers, but I don't
see why the Github Sponsors program couldn't create "GitBucks" or something. I
think Microsoft, as the owner of NPM and GitHub, is uniquely positioned to make
a difference in open source sustainability via gift vouchers.

## Licensing is NOT the answer

A startup approached me recentlly, offering to handle all the recurring billing
and access restriction for me to have two versions of my library, one free and
open source, and a second one with more features that required a license to use.
I listened to their spiel intently and then pointed directly to the Emperor's
exposed genitals: "Who enforces this?" Their answer was, "Yeeeahhh, well....
it's up to the library consumer's internal legal team to remember that they have
to pay you, which is why we only expect about 2% of your so-called 'paying'
customers to _actually_ pay." Wow! Roll. On. The. Floor. Laughing!

If I switched from MIT to GPL tomorrow, 80% of people wouldn't notice, 19%
wouldn't care, and the remaining 1% would dump my library and migrate to another
free one with a slightly worse API and larger bundle size, and yet, _100% would
hate me_. Open Source Licensing is a joke, at least as it currently stands. I
_wonder_ if Microsoft, as the owner of NPM and GitHub, is not uniquely
positioned to make a difference in open source sustainability via licensing?

## Conclusion

It doesn't take
[a report from the Ford Foundation](https://www.fordfoundation.org/work/learning/research-reports/roads-and-bridges-the-unseen-labor-behind-our-digital-infrastructure/)
to see that the status quo is broken; it sucks for the little guy, and it sucks
for the corporate capitalist that's taking advantage of him even if they don't
know it yet. I don't really have a solid course of action to suggest. I guess
it's about awareness raising and asking your boss for some funding to go to
libraries you use; you can frame it like insurance: you hope there's never a
disaster, but you pay a little every month so that, if/when the disaster
happens, it's not so bad. If anyone can think of a corporation uniquely
positioned to help fix this problem, let me know.
]]></content>
        <author>
            <name>Erik Rasmussen</name>
            <email>rasmussenerik@gmail.com</email>
            <uri>https://erikras.com</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Getting a Promise from Redux Actions]]></title>
        <id>https://erikras.com/blog/promise-from-redux-actions</id>
        <link href="https://erikras.com/blog/promise-from-redux-actions"/>
        <updated>2018-03-30T00:00:00.000Z</updated>
        <summary type="html"><![CDATA["Great!", you’re thinking...just what the world needs: another blog post about Promises in Redux! Don’t leave yet; this one’s different.]]></summary>
        <content type="html"><![CDATA[
Let’s start off with the problem. I manage a few popular form libraries, and I
often get the question:

> How can I use `redux-saga` or `redux-observable` to submit my form via a
> side-effect?

I have to answer, “Well… the form library is expecting the `onSubmit` function
to return a `Promise` to know when it is submitting, so...” At least with
[Redux Form](https://redux-form.com/), because all of its state was managed in
Redux, including whether or not the form was currently being submitted, it was
possible to let users _dispatch_ the `START_SUBMIT` and `STOP_SUBMIT` actions
themselves, rather than allowing Redux Form to manage that for them via the
`Promise`. But, if you
[take your form state out of Redux](/blog/final-form-announcement), you’re just
sort of screwed…

<div style={{ textAlign: "center" }}>
  <img
    src="/images/cry-emoji.png"
    alt="Crying emoji"
    width={120}
    height={120}
  />
</div>

### Until now.

<div style={{ textAlign: "center" }}>
  <img
    src="/images/sweat-smile-emoji.png"
    alt="Sweat-Smile emoji"
    width={120}
    height={120}
  />
</div>

---

What if you had a way to create an asynchronous function that dispatched a START
action when it was called, and resolved or rejected the returned `Promise` when
either a `RESOLVE` or `REJECT` action was dispatched? That would totally solve
this problem. Right?

## 💥 React Redux Promise Listener 💥

The name is not the catchiest on NPM, but... I’ve just released
[React Redux Promise Listener](https://github.com/erikras/react-redux-promise-listener#react-redux-promise-listener),
a library that solves just this problem. The API looks something like this:

```jsx
<MakeAsyncFunction
  listener={promiseListener}
  start="START_ACTION_TYPE" // the type of action to dispatch when this function is called
  resolve="RESOLVE_ACTION_TYPE" // the type of action that will resolve the promise
  reject="REJECT_ACTION_TYPE" // the type of action that will reject the promise
>
  {(asyncFunc) => (
    <SomeFormLibrary onSubmit={asyncFunc}>
      ...
      <button type="submit">Submit</button>
    </SomeFormLibrary>
  )}
</MakeAsyncFunction>
```

The `promiseListener` comes from a piece of Redux middleware that must be
installed. By default, the values passed around are kept in `action.payload`,
but there are config options that let you read and write those from anywhere in
the actions.

## Conclusion

It should be a lot easier now to use async side-effect Redux libraries with
other async components, like routers, that were built on the assumption of
Promises being “how async is done” in Javascript.

❤️ Enjoy! ❤️
]]></content>
        <author>
            <name>Erik Rasmussen</name>
            <email>rasmussenerik@gmail.com</email>
            <uri>https://erikras.com</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[A practical guide to Redux Form]]></title>
        <id>https://erikras.com/blog/react-alicante-2017</id>
        <link href="https://erikras.com/blog/react-alicante-2017"/>
        <updated>2017-11-08T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[React Alicante – Alicante, Spain – 2017]]></summary>
        <content type="html"><![CDATA[
<video>ey7H8h4ERHg</video>
]]></content>
        <author>
            <name>Erik Rasmussen</name>
            <email>rasmussenerik@gmail.com</email>
            <uri>https://erikras.com</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Next Generation Forms with React Final Form]]></title>
        <id>https://erikras.com/blog/react-alicante-2018</id>
        <link href="https://erikras.com/blog/react-alicante-2018"/>
        <updated>2018-09-19T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[React Alicante – Alicante, Spain – 2018]]></summary>
        <content type="html"><![CDATA[
<video>WoSzy-4mviQ</video>
]]></content>
        <author>
            <name>Erik Rasmussen</name>
            <email>rasmussenerik@gmail.com</email>
            <uri>https://erikras.com</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Modern Forms in React]]></title>
        <id>https://erikras.com/blog/react-au-2020</id>
        <link href="https://erikras.com/blog/react-au-2020"/>
        <updated>2020-03-04T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[ReactConf AU – Sydney, Australia – 2020]]></summary>
        <content type="html"><![CDATA[
<video>v1JAUiqskiw</video>
]]></content>
        <author>
            <name>Erik Rasmussen</name>
            <email>rasmussenerik@gmail.com</email>
            <uri>https://erikras.com</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[React Beyond the DOM]]></title>
        <id>https://erikras.com/blog/react-beyond-the-dom-2025</id>
        <link href="https://erikras.com/blog/react-beyond-the-dom-2025"/>
        <updated>2025-12-31T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Using React custom renderers to do interesting things]]></summary>
        <content type="html"><![CDATA[
In 2024, I started a new job at [Attio](https://attio.com/), on the Ecosystem
Team, helping to build out an extensions SDK to allow third parties to build
[apps](https://attio.com/apps) to add functionality to the base CRM product.
When I joined the two coolest parts of our app infrastructure were just nacent
proofs of concept:

1. Executing third party code in a custom JavaScript runtime built with C and
   Rust in a WASM runtime, in an iframe, that could communicate back out of the
   runtime, out of the iframe, with the webapp, and then to the server, which
   executed third party server code in the same – but with slightly different
   capabilities – custom JavaScript runtime in The Cloud.
2. A custom React reconciler and renderer that would allow third party client
   code to "render UI" in a way that could be passed back out of the runtime,
   out of the iframe, and to the webapp for us to render our own beautifully
   designed components.

This combination makes for an incredibly _secure_ and ergonomic app development
experience. DX is my passion!

When deciding on a talk idea for 2025, I was certain that I wanted to talk about
custom React renderers, because, once I saw the inner workings of one, something
clicked in my head about the immense power it allowed, and I felt the need to
let the world know. ChatGPT and I brainstormed dozens of ideas, most of which
were utter crap, but when we got to the right one, my brain immediately
understood what good stagecraft it allowed; it was gonna be a show to delight
audiences!

I set about figuring out how to build it. This is the fun part of talk prep!

I've seen enough talks tank for relying on conference wifi, so I knew that wifi
was out of the question. I settled on a way to control smart lights with
[MQTT.js](https://www.npmjs.com/package/mqtt) and the
[Zigbee protocol](https://en.wikipedia.org/wiki/Zigbee) using
[Zigbee2MQTT](https://www.zigbee2mqtt.io/). It was not as hard as I expected it
to be what with my LLM sidekick reading docs for me.

The result was one of the best talks I've ever given, in my opinion. In 2025, it
took me to [London](https://www.meetup.com/london-js/events/306424233/),
[Rome](https://conferences.codemotion.com/rome2025/),
[Madrid](https://conferences.codemotion.com/madrid2025/),
[Greece](https://greece.cityjsconf.org/),
[Oslo](https://vintage.reactnorway.com/), [Skopje](https://wts.sh/),
[Alicante](https://reactalicante.es/), [New York City](https://reactsummit.us/),
and finally back to [London](https://reactadvanced.com/) to round out the year.
I have so many great memories of time spent with old friends and new blossoming
friendships; I am forever grateful that I have the privilege to travel like
this.

## The Talk

<video>UKdhU4S216Y</video>

You can also watch the
[Alicante](https://vimeo.com/reviews/87d87007-8c91-4daf-b0a5-ec96feb8d5f4/videos/1128472877),
[New York City](https://gitnation.com/contents/react-beyond-the-dom-3054),
[London](https://gitnation.com/contents/react-beyond-the-dom) versions, or watch
a [20 minute podcast interview](https://www.youtube.com/watch?v=U1uVgm8PzQI&pp)
I did about it.

I'm embarrassed to say that it took me halfway through the year before I came up
with the line, "And it wouldn't be a React talk with _props_!!" line as I pulled
out the light bulbs.

As I have often been asked after giving it, all the code from talk, both
projects, can be found
[here](https://github.com/erikras/react-html-to-markdown).

Thank you for reading, and I hope to see you in 2026!
]]></content>
        <author>
            <name>Erik Rasmussen</name>
            <email>rasmussenerik@gmail.com</email>
            <uri>https://erikras.com</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Render Props are Not Dead]]></title>
        <id>https://erikras.com/blog/react-europe-2020</id>
        <link href="https://erikras.com/blog/react-europe-2020"/>
        <updated>2020-05-16T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[React Europe – Not Paris 😭 – 2020]]></summary>
        <content type="html"><![CDATA[
<video>pn0pIgdQvhU</video>
]]></content>
        <author>
            <name>Erik Rasmussen</name>
            <email>rasmussenerik@gmail.com</email>
            <uri>https://erikras.com</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[🏁 React Final Form HTML5 Validation]]></title>
        <id>https://erikras.com/blog/react-final-form-html5-validation</id>
        <link href="https://erikras.com/blog/react-final-form-html5-validation"/>
        <updated>2018-03-27T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[You can now get native HTML5 validation functionality by substituting your Field component with one from react-final-form-html5-validation.]]></summary>
        <content type="html"><![CDATA[
Validation is probably the most complicated part of maintaining form state:
deciding _when_ to validate values, and _when_ and _where_ to display error
messages are important design decisions for making your forms as useable as
possible by the widest range of users.

Over a decade ago, the W3C set out some guidelines for what they thought the
next version of HTML should look like, and they called it HTML5. Part of that
standard included a system for validating form fields, both with native browser
rules, and also the ability to set a custom validation error on a field. These
days, the HTML5 validation standard is
[more-or-less supported by all web browsers](https://caniuse.com/#search=setCustomValidity).

## Why use HTML5 Validation?

I see three primary reasons why HTML5 form validation is a good thing to
incorporate into your application:

### Consistency of Design

By using the browser’s built-in mechanism for displaying form errors, your users
will be given a consistent UI for form errors across the entire internet (or at
least those very few sites that are advanced as yours will be when you start
using HTML5 form validation).

### Accessibility

This is the big one. Screen readers and other accessibility tools need a lot of
help understanding what exactly went wrong when a form submission fails. Some
screen readers will also let the user know a field is required when they enter
the field for the first time.

### Built-In Focus On Errors

As of last week, 🏁 Final Form supports [a plugin](/blog/focus-on-errors) to
focus on the first form field with an error. But with HTML5 Form Validation, it
comes for free from the browser! The browser stops the form submission and
focuses on the first error even before React is given the `onSubmit` event.

---

Introducing...

## [🏁 React Final Form HTML5 Validation](https://github.com/final-form/react-final-form-html5-validation)

I’ve written a drop-in replacement for the `Field` component in 🏁 React Final
Form that wraps the official `Field` component and adds the additional HTML5
validation onto it by doing three things:

- Deducing which DOM input corresponds to the field
- Injecting a special field-level validation function that reads from the
  `input.validity` DOM structure.
- Setting the HTML5 validation error with `input.setCustomValidity()` if there
  is an error from a specified field-level validation function.

The result is a two-way binding between the browser native HTML5 Validation, and
any field-level validation function specified to the component. The bindings are
two-way because any
[HTML5 contraint validation errors](https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#the-constraint-validation-api)
will be added to the 🏁 Final Form state, and any _field-level_ validation
errors from 🏁 Final Form will be set into the HTML5 `validity.customError`
state. Unfortunately, this functionality is not compatible with 🏁 React Final
Form _record-level_ validation, so the two should not be mixed.

> “Why not add this functionality directly into the officially bundled `Field`
> component?”

Good question. The reason is that not everyone needs this functionality, and not
everyone is using 🏁 React Final Form with the DOM (e.g. some people use it with
React Native). Therefore it makes sense to make this a separate package. This
version of `Field` is a thin wrapper over the official `Field` component, and
the only `Field` API that this library uses/overrides is the field-level
validate prop, so even if you are using this library’s `Field` component, you
will still get improvements as features are added to the 🏁 React Final Form
library in the future.

> "How to use it?"

Well, first you change this:

```js
import { Field } from "react-final-form";
```

to this:

```js
import { Field } from "react-final-form-html5-validation";
```

All the regular API of `Field` stays the same, except that now you can pass in
props like `maxLength={25}` or `required` and, optionally, messages to display
when those rules are broken, e.g. `tooLong="Be more concise!"` or
`valueMissing="You need to answer this"` respectively. All of these prop names
are taken from the
[HTML5 Specification](https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#the-constraint-validation-api),
so if you have staff already trained to the standards, they should pick it up
right away.

## What does it look like?

Well, the error UI look slightly different in every browser. No doubt the users
of each of these browsers will recognize this format of displaying error
messages.

<div style={{ textAlign: "center" }}>
  <img
    src="/images/html5-validation-safari.gif"
    alt="HTML5 Validation in Safari"
    width={500}
    height={368}
  />
</div>

<caption>Safari</caption>

<div style={{ textAlign: "center" }}>
  <img
    src="/images/html5-validation-chrome.gif"
    alt="HTML5 Validation in Chrome"
    width={500}
    height={355}
  />
</div>

<caption>Chrome</caption>

<div style={{ textAlign: "center" }}>
  <img
    src="/images/html5-validation-firefox.gif"
    alt="HTML5 Validation in Firefox"
    width={500}
    height={357}
  />
</div>

<caption>Firefox</caption>

And here’s the obligatory sandbox embed for you to play with…

<iframe
  src="https://codesandbox.io/embed/14r018yjp4?fontsize=14&hidenavigation=1&theme=dark"
  style={{
    width: "100%",
    height: 500,
    border: 0,
    borderRadius: 4,
    overflow: "hidden",
  }}
  title="🏁 React Final Form - HTML5 Validation Example"
  allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
  sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>

<caption>Go ahead! Play around with it!</caption>

## Conclusion

HTML5 Form Validation is yet another step towards making 🏁 Final Form the most
versatile, extensible form state management framework for React and beyond. Just
think how pleased your boss will be when you inform her that you’ve made all
your forms much more accessible! 😜

❤️ Thanks for reading. ❤️
]]></content>
        <author>
            <name>Erik Rasmussen</name>
            <email>rasmussenerik@gmail.com</email>
            <uri>https://erikras.com</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[A reducer is a single-state state machine]]></title>
        <id>https://erikras.com/blog/reducer-single-state-machine</id>
        <link href="https://erikras.com/blog/reducer-single-state-machine"/>
        <updated>2021-03-23T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[If you write a state machine that only has a single state, but modifies its context, what you've created is a reducer.]]></summary>
        <content type="html"><![CDATA[
It's hard to state just how difficult it is to talk about state management; it
all depends on the context of the conversation. The more I have been thinking
about state machines, and learning about XState, the more I like the way XState
talks about "application state".

In the context (🙄) of state machines:

**state**: which of a finite number of states the machine is currently in. e.g.
`fetching`, `loaded`, `error`.

**context**: the environment in which events are taking place. This includes
which user is logged in, any data that has been loaded, and errors that have
been returned.

Notice how these are the same words that React uses for different things. The
state machine "context" is what you would put into a `useState()` hook. In
Redux, the state machine "context" would be in your store.

Let's build the simplest possible state machine, in my new favorite language,
[ReScript](https://rescript-lang.org):

## The Ubiquitous Counterexample

In ReScript, our states, represented by
[variants](https://rescript-lang.org/docs/manual/latest/variant), can have a
[payload](https://rescript-lang.org/docs/manual/latest/variant#constructor-arguments),
which is where we will place our context. In this case, our context is a simple
integer.

```reason
type state = Idle(int)
type event = Increment | Decrement | Reset

export let initial = Idle(0)

export let transition = (state:state, event:event) =>
  switch (state, event) {
    | (Idle(value), Increment) => Idle(value + 1)
    | (Idle(value), Decrement) => Idle(value - 1)
    | (Idle(_), Reset) => initial
  }
```

Notice that we are always returning to our only state, `Idle`, and our events
are only modifying our context.

<img
  src="/images/single-state-machine.png"
  width={888}
  height={730}
  alt="A single-state state machine"
/>
<caption>American Idle</caption>

What have we created here? A simple **_reducer_**. This is the equivalent, in
TypeScript, of:

```ts
export const initial = 0;
export function transition(
  state: number,
  event: "Increment" | "Decrement" | "Reset"
) {
  switch (event) {
    case "Increment":
      return state + 1;
    case "Decrement":
      return state - 1;
    case "Reset":
      return initial;
  }
}
```

It's the kind of thing that powers Redux or what you might give to a
`useReducer()` hook in React.

I can hear you rolling your eyes already.

### Yeah, so what?

Well, what if we wanted to temporarily disable our counter, so that the value
couldn't be changed? You could add another `disabled: boolean` to your reducer's
`state` value... But if you were implementing it as a state machine, you could
have your `Pause` event shift the machine into the `Paused` state, where it
would ignore the `Increment` and `Decrement` events.

```reason
type state = Idle(int) | Paused(int)
type event = Increment | Decrement | Reset | Pause | Unpause

export let initial = Idle(0)

export let transition = (. state:state, event:event) =>
  switch (state, event) {
    | (Idle(value), Increment) => Idle(value + 1)
    | (Idle(value), Decrement) => Idle(value - 1)
    | (Idle(_), Reset) => initial
    | (Idle(value), Pause) => Paused(value)
    | (Paused(value), Unpause) => Idle(value)
    | (Idle(_), Unpause)
    | (Paused(_), _) => state
  }
```

These are very, very trivially simple examples, but I think there may be a
significant difference "at scale" with a real application.
]]></content>
        <author>
            <name>Erik Rasmussen</name>
            <email>rasmussenerik@gmail.com</email>
            <uri>https://erikras.com</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Redux Dead Drop]]></title>
        <id>https://erikras.com/blog/redux-dead-drop</id>
        <link href="https://erikras.com/blog/redux-dead-drop"/>
        <updated>2016-11-05T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Asynchronous Communication Like A Spy]]></summary>
        <content type="html"><![CDATA[
There are times, when writing a large scale React application in which one
component needs to send a message to another component that it is not a direct
descendent or ancestor of, a cousin component, if you will. Two examples that
spring to mind are:

1. Needing to apply focus to a particular DOM node, which requires a ref to the
   actual DOM node.
2. Needing to make some AJAX call, e.g. submitting a form, using information the
   receiving component has in its props.

Luckily, if you are writing a large enough React application, you are probably
already using some sort of global state management, such as Redux. That’s great,
so you have a common state that both components can read from and write to, but
how does that help you pass a message? That’s where the world of international
espionage comes in…

Draw the curtain and take a seat, Mr. Bond.

To quote Wikipedia:

> A **dead drop** or **dead letter** box is a method of espionage tradecraft
> used to pass items or information between two individuals (e.g., a case
> officer and agent, or two agents) using a secret location, thus not requiring
> them to meet directly and thereby maintaining operational security. The method
> stands in contrast to the live drop, so-called because two persons meet to
> exchange items or information.

<div style={{ textAlign: "center" }}>
  <img src="/images/booksafe.jpg" width={590} height={443} alt="Book Safe" />
</div>

<caption>
  Image from{" "}
  <a href="http://chadchandler.com/diy-book-safe/" target="_blank">
    Chad Chandler
  </a>
</caption>

You already see where this is going, don’t you? The sending component needs to
dispatch an action that will set some slice of the state that the receiving
component is listening to. The flow goes like this:

1. User interacts with **Sender**
2. **Sender** dispatches an action to modifiy `state.deaddrop` to `true` or to
   any value that it wants to pass with the message.
3. **Receiver** has `mapStateToProps` with a selector for `state.deaddrop`, and
   thus gets rerendered with a new prop.
4. **Receiver** initiates whatever action is required.

It is important to note here that the flow does not _need_ to begin with a user
interaction. With the rise of asynchronous middleware like `redux-saga` and
`redux-observable`, the ability to trigger any code on a component anywhere is
very useful.

<div style={{ textAlign: "center" }}>
  <img
    src="/images/redux-dead-drop-logo.jpg"
    width={308}
    height={311}
    alt="Redux Dead Drop Logo"
  />
</div>

<caption>redux-dead-drop logo</caption>

## Implementation

I briefly considered writing a `redux-dead-drop` library, even going so far as
“designing” a logo, with its own higher order reducer to enable this
functionality on any reducer and a HOC to connect any component the dead drop
location on the reducer, and even have it call an instance method on its wrapped
component. I'm still reasonably certain that it would be possible to write, but
the design pattern is so simple that it takes hardly any effort to implement it
on any existing reducer, so I rejected abstraction for abstraction's sake.

If you would like to try and implement such a library, help yourself to the name
and logo.

As in the world of covert government agents, is important that the Receiver
component clear out the dead drop once the message has been received.

I have used this pattern in my [`redux-form`](https://redux-form.com) library to
enable the triggering of a form submission via the dispatch of an action. This
allows the form component to know about how it needs to be submitted and
validated, but also allows any actor in the entire application to trigger the
submission.

This post will self destruct in 5…. 4…. 3….
]]></content>
        <author>
            <name>Erik Rasmussen</name>
            <email>rasmussenerik@gmail.com</email>
            <uri>https://erikras.com</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[State Machines on the Edge]]></title>
        <id>https://erikras.com/blog/remixconf-2022</id>
        <link href="https://erikras.com/blog/remixconf-2022"/>
        <updated>2022-06-24T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Remix Conf – Salt Lake City, Utah, USA – May 2022]]></summary>
        <content type="html"><![CDATA[
When Remix was first announced, by the gentlemen behind React Router, I knew it
was gonna rock...eventually. I figured they would need some time to work out the
exact APIs. That it was released initially as a paid offering was all I needed
to ignore it for a bit to let it mature. When it became open source several
months later, my interest was piqued.

I've been using [Next.js](https://nextjs.org) since it was in version 1.0. I
fully understood all that it was doing for me, because I had extensive
experience cobbling together such a site using Webpack, React Router, Express,
Server Side Rendering, Hot Reloading, and all the hot new technologies in 2015.
Even after authoring open source libraries used by millions of companies around
the globe,
[React Redux Universal Hot Example](https://github.com/erikras/react-redux-universal-hot-example)
remains my repository with more Github stars than any other. People are still
starring it, despite all the cobwebs, as I have not touched it in six years.
Every once in a while, someone will come up to me a conference and thank me for
it, telling me that they built and launched and grew a startup using that
starter kit. So I've been starting every project with Next.js and recommending
that everyone do the same for years and years.

All that is to say that I was highly skeptical that any framework could come
along and unseat my beloved Next.js from its pedestal. Nevertheless, I went
through the [Remix Tutorial](https://remix.run/docs/en/v1/tutorials/blog) –
which is different (better) now than it was when I did it – to see what these
Router boys were up to.

Within an hour, I was hooked. I will never start a project with Next.js again.

I've been building websites since _all_ pages were static files. When it was
discovered that you could execute code on the server – I ❤️ U `/cgi-bin`! – to
_dynamically_ change the HTML that was downloaded we were off to the races!

So I also come to Remix with a fundamental understanding of the ...errr...
fundamentals of HTTP and how the web works. Seeing Remix embrace The Platform™
warms my heart. That said, I remember why we all moved away from The
Platform™... because browsers sucked! But now that Internet Explorer is rotting
in its grave, I think it might be just the right time to return to the
fundamentals.

When [Remix Conf 2022](https://remix.run/conf/2022) was announced, I immediately
began brainstorming ideas interesting enough to attract the attention of the
Remix team. In my dayjob at [Centered](https://centered.app/erik), I have been
introducing [XState](https://xstate.js.org) state machines at every opportunity,
and it invariably improves the codebase. There was even one particularly complex
bit of backend logic, accepting a webhook from Linear, where I chose to
implement it as a state machine, and, wouldn't you know it?, it improved the
readability and robustness of the code. Remix is so elegant about how it does
stuff on the backend, I began to wonder if I couldn't come up with some sort of
an app to use a state machine on the backend using Remix.
[My previous talk](./infobip-shift-2021) was about using XState to implement an
e-commerce checkout flow, so what if I could do that entirely on the backend?

Narrator: "He pulled it off."

I wrote the prototype entirely on a transatlantic flight in February, and shared
the code with both the Remix and XState teams. My talk was _sort of_ accepted at
RemixConf. I was chosen as a backup speaker. This was a new concept to me, but
it makes total sense. I was to have all the perks of a speaker, travel,
expenses, hotel, etc., but rather than give my talk in front of the conference
attendees, it would be to an empty auditorium the night before, and the talk
would be released on YouTube with all the others, so future people wouldn't even
know it was to an empty crowd. Great plan! I was just delighted to attend the
conference!

As luck would have it, one of the speakers had some visa issues, and I was
bumped up to give a talk in front of an audience. And it made all the
difference! I've never given a talk with such an enthusiastic audience!

So, without further ado...

## The Talk

<video>r4xI6LPf6iQ</video>

<caption>
  The slides can be found{" "}
  <a
    href="https://speakerdeck.com/erikras/state-machines-on-the-edge-remixconf-2022"
    target="_blank"
  >
    here
  </a>
  , and the code <a
    href="https://github.com/erikras/remix-conf-2022"
    target="_blank"
  >
    here
  </a>, if you so desire.
</caption>

## Photos

<p>
<img
  src="/images/remixconf-2022-2.jpg"
  alt="The audience during my talk"
  width={2500}
  height={1669}
/>

<caption>
  The audience during my talk. I had the perfect slot, right before the morning coffee break.
</caption>
</p>

<p>
<img
  src="/images/remixconf-2022-3.jpg"
  alt="View from the back"
  width={2500}
  height={1668}
/>

<caption>
  The view from the back. That's Kent C. Dodds' pink sleeve on the far left, furiously live tweeting.
</caption>
</p>

<p>
<img
  src="/images/remixconf-2022-4.jpg"
  alt="Fisheye from the stage"
  width={2500}
  height={1669}
/>

<caption>
  Fisheye from the stage.
</caption>
</p>

<p>
<img
  src="/images/remixconf-2022-5.jpg"
  alt="Heroes and friends"
  width={4032}
  height={3024}
/>

<caption>
  A few of my heroes that I now call friends, <a href="https://twitter.com/tannerlinsley">Tanner Linsley</a>, <a href="https://twitter.com/jen_ayy_">Jenny Truong</a>, <a href="https://twitter.com/mattpocockuk">Matt Pocock</a>, <a href="https://twitter.com/DavidKPiano">David Khourshid</a>, and <a href="https://twitter.com/chancethedev">Chance Strickland</a>.
</caption>
</p>

<p>
<img
  src="/images/remixconf-2022-6.jpg"
  alt="Michael Chan and Chance Strickland"
  width={4032}
  height={3024}
/>

<caption>
  Photogenic lads, <a href="https://twitter.com/chantastic">Michael Chan</a> and <a href="https://twitter.com/chancethedev">Chance Strickland</a>.
</caption>
</p>

<p>
<img
  src="/images/remixconf-2022-7.jpg"
  alt="Van Selfie"
  width={4032}
  height={3024}
/>

<caption>
  We morbidly joked about what a loss to the JavaScript community it would be if this van crashed.
</caption>
</p>

<p>
<img
  src="/images/remixconf-2022-8.jpg"
  alt="Scooter Crew"
  width={4032}
  height={3024}
/>

<caption>
  <a href="https://twitter.com/johnlindquist">John Lindquist</a> lent some scooters to <a href="https://twitter.com/jen_ayy_">Jenny Truong</a>, <a href="https://twitter.com/mattpocockuk">Matt Pocock</a>, <a href="https://twitter.com/DavidKPiano">David Khourshid</a>, and me to ride around his neighborhood.
</caption>
</p>

<p>
<img
  src="/images/remixconf-2022-9.jpg"
  alt="Smash Burgers"
  width={4032}
  height={3024}
/>

<caption>
  <a href="https://twitter.com/jlengstorf">Jason Lengstorf</a> taught me the meaning of "smash burgers", and my life will never be the same.
</caption>
</p>

## Salt Lake City – Aerial Shots

<p>
<img
  src="/images/slc-2022-1.jpg"
  alt="Flying into Salt Lake City"
  width={4032}
  height={3024}
/>

<caption>
  As an East Coast boy whose never been to the rocky mountains, the terrain around Salt Lake City was <em>gorgeous</em>!
</caption>
</p>

<p>
<img
  src="/images/slc-2022-2.jpg"
  alt="Salt Lake"
  width={4032}
  height={3024}
/>

<caption>
  Apparently they have a big – dare I say "great"? – lake of salt there.
</caption>
</p>
]]></content>
        <author>
            <name>Erik Rasmussen</name>
            <email>rasmussenerik@gmail.com</email>
            <uri>https://erikras.com</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Coding in Flow]]></title>
        <id>https://erikras.com/blog/renderatl-2022</id>
        <link href="https://erikras.com/blog/renderatl-2022"/>
        <updated>2022-07-19T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Render ATL – Atlanta, Georgia, USA – June 2022]]></summary>
        <content type="html"><![CDATA[
## The Talk

I remember right where I was, walking the scorching June streets of Sevilla,
when I got the Twitter DM from Justin, organizer of the blockbuster debut Render
ATL conference in 2021, asking me if I'd be a speaker _11 months in the future_,
in June 2022. Having experienced extreme FOMO on Twitter during the 2021
conference, I was delighted to say yes! I'd never done a conference in the
Motherland before.

Usually, I have to propose a conference topic and get approved. At this point in
my career, it's rare to get a "come and speak about whatever!" Invitation, so I
decided to use it to talk about a thing that I'm passionate about, but not yet
really known for: the app that I'm building at work:
[Centered](https://centered.app/erik).

I've long been fascinated by psychology; both of my parents studied psychology
before going into computers, and I majored in computers and minord in psych in
college, so working on an app based on psychology theories is perfect for me.

## The Talk

<video>nyxI1RsF49c</video>

## Photos

<p>
<img
  src="/images/renderatl-2022-2.jpg"
  alt="High-Rise Selfie"
  width={4032}
  height={3024}
/>

<caption>
  Speaker and Sponsor Brunch was hosted on the umpteenth floor of a downtown high-rise
</caption>
</p>

<p>
<img
  src="/images/renderatl-2022-3.jpg"
  alt="Cocktail Ken"
  width={3024}
  height={4032}
/>

<caption>
  Some sort of spicy volcanic cocktail across from the inimatable <a href="https://twitter.com/ken_wheeler">Ken Wheeler</a>, one of the conference speakers I was most looking forward to finally meeting.
</caption>
</p>

<p>
<img
  src="/images/renderatl-2022-4.jpg"
  alt="Atlanta Sunset"
  width={2500}
  height={1669}
/>

<caption>
  Atlanta Sunset
</caption>
</p>

<p>
<img
  src="/images/renderatl-2022-5.jpg"
  alt="Ken, Ben, and Jay"
  width={4032}
  height={3024}
/>

<caption>
  Close runners up to Ken on my want-to-meet list were <a href="https://twitter.com/BenLesh">Ben Lesh</a> and <a href="https://twitter.com/_jayphelps">Jay Phelps</a>. We were instant friends.
</caption>
</p>

<p>
<img
  src="/images/renderatl-2022-6.jpg"
  alt="Outdoor Stage"
  width={4032}
  height={3024}
/>

<caption>
  The outdoor stage <em>seemed</em> like a good idea, but we were in Atlanta in June, and it was <em>hot</em>! No one could stand to be in the grassy area, so everyone was very far from the stage.
</caption>
</p>

<p>
<img
  src="/images/renderatl-2022-7.jpg"
  alt="Bar Smores with Joe and Facundo"
  width={4032}
  height={3024}
/>

<caption>
  New friends made: <a href="https://twitter.com/jsjoeio">Joe Previte</a> and <a href="https://twitter.com/facundozurdo">Facundo Giuliani</a>. Also: Bar Smores!!!
</caption>
</p>

<p>
<img
  src="/images/renderatl-2022-8.jpg"
  alt="Selfie with Fardeem"
  width={4032}
  height={3024}
/>

<caption>
  Also at the conference was my colleague at Centered, <a href="https://twitter.com/FardeemM">Fardeem Munir</a>, whom I had met in person once before.
</caption>
</p>

<p>
<img
  src="/images/renderatl-2022-9.jpg"
  alt="Smash Burgers"
  width={4032}
  height={3024}
/>

<caption>
  My new Polish friends, <a href="https://twitter.com/aleksandrasays">Aleksandra</a>, <a href="https://twitter.com/hasparus">Piotr</a>, their boss, <a href="https://twitter.com/flybayer">Brandon</a>, and some other people that agreed to have a shot of Polish vodka. These three are dear to my heart because they are fans of my <a href="https://erikras.com/blog/final-form-announcement">Final Form</a> library. 😍
</caption>
</p>
]]></content>
        <author>
            <name>Erik Rasmussen</name>
            <email>rasmussenerik@gmail.com</email>
            <uri>https://erikras.com</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[🏁 Ripple Final Form]]></title>
        <id>https://erikras.com/blog/ripple-final-form</id>
        <link href="https://erikras.com/blog/ripple-final-form"/>
        <updated>2025-10-04T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Implementing forms in future UI frameworks]]></summary>
        <content type="html"><![CDATA[
The epiphany that sparked the creation of Final Form back in
[2017](https://erikras.com/blog/final-form-announcement), was that form state
has _nothing_ to do with frontend frameworks. It was always my intention that
Final Form should outlive React, the seemingly untoppleable ubiquitous
framework. Sure enough, it easiliy survived the transition to hooks, and people
built wrappers for Vue and Svelte, just as I'd hoped.

A few years ago, I got excited about a potential React killer in
[SolidJS](https://www.solidjs.com/) and wrote
[a proof of concept](https://erikras.com/blog/solid-final-form-poc) showing how
easily Final Form could be plugged into Solid. Indeed, it was delightfully easy.

I have not really been that excited about a "new framework" until this year when
[Dominic Gannaway](https://x.com/trueadm), who has recently become a colleague
of mine at Attio, released a _very_ pre-alpha proof of concept frontend
framework, called [Ripple](https://www.ripplejs.com/), where he has taken all
the best parts of React, Vue, Solid, and Svelte, and put them together into a
framework that should not feel _too_ foreign to the developers of those
frameworks. It's pretty clear that future frontend frameworks will all have some
sort of compile step; the days of "It's _just_ JavaScript!" are long gone; so
why not lean into the compiler for maximum developer experience?

> It's like Esperanto for frontend.

When playing with Ripple, every time I went to the docs with an "I wonder if it
supports...?" question, the answer was not just "Yes", but "Yes, and that API is
surprisingly nice!"

The API, however, is still in constant flux. The code I wrote a week ago working
on this no longer works, and likely what I'm showing today will not work next
week. **_THIS IS NOT PRODUCTION READY!_** The metaphor that comes to mind is a
polar bear walking on ice that has not yet solidified.

<img
  src="/images/polar-bear-on-ice.jpg"
  alt="Polar bear walking on ice"
  width={1024}
  height={1024}
/>

That said, let's look at what I've built!

## Proof of Concept

The primary component is one called `RippleFinalForm`, which creates a Final
Form instance, saves it to the context, and then renders a `<form>` component
with its `children` inside it. Then the inputs and error components are
connected to the Final Form instance via the `ref` syntax.

```typescript
export component App() {
  <RippleFinalForm {onSubmit} {validate}>
    <label>
      <input name="firstName" placeholder="First Name" {ref field} />
      <span {ref error("firstName")} />
    </label>
    <label>
      <input name="lastName" placeholder="Last Name" {ref field} />
      <span {ref error("lastName")} />
    </label>
    <label>
      <select name="favoriteColor" {ref field}>
        <option value="">{"Choose a color"}</option>
        <option value="#FF0000">{"Red"}</option>
        <option value="#00FF00">{"Green"}</option>
        <option value="#0000FF">{"Blue"}</option>
      </select>
      <span {ref error("favoriteColor")} />
    </label>
    <button type="submit">{"Submit"}</button>
    <Values />
  </RippleFinalForm>
}
```

Notice that a "component" is not a "function", and it doesn't "return" anything,
it just declares JSX. Also spot the `ref`s that allow attaching logic directly
to the DOM nodes.

I was able to implement a proof of concept that connects Ripple inputs to Final
Form in
[86 lines of code](https://stackblitz.com/edit/github-wqe28xcx?file=src%2FApp.ripple)
(Stackblitz).

### Cool, now what?

My instincts tell me that Ripple might actually be capable of toppling the React
Empire, but it's going to take a lot of work to get there.

**DO** check out [the docs](https://github.com/trueadm/ripple) and learn how
Ripple does things.

**DO** play around with it and submit issues and pull requests.

**DO NOT** (yet, in October 2025) use this anywhere near production.

Remember, form state – and much of frontend state – has nothing to do with UI
frameworks. Choose a headless solution and it just might outlast your UI
framework.

Thank you for reading.
]]></content>
        <author>
            <name>Erik Rasmussen</name>
            <email>rasmussenerik@gmail.com</email>
            <uri>https://erikras.com</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Solid Final Form – Proof of Concept]]></title>
        <id>https://erikras.com/blog/solid-final-form-poc</id>
        <link href="https://erikras.com/blog/solid-final-form-poc"/>
        <updated>2022-11-26T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Since its inception, Final Form was designed to outlive existing frameworks.]]></summary>
        <content type="html"><![CDATA[
[Five years ago](/blog/final-form-announcement), I challenged myself to create a
form state management library that was framework independent. In 2017, the idea
that maybe React wasn't the best place to manage application state was not
nearly as common as it has become. Formik had just been released and took all of
the form library NPM downloads for the following few years. Formik went _all-in_
on React for state management. The current King of Forms in React is
[React Hook Form](https://react-hook-form.com), again, betting all the marbles
on React's state management prowess.

From the very beginning of Final Form, it was designed to outlive existing
popular frontend frameworks. Form state is two things:

1. more complicated than you think
2. not inherently related to any frontend framework

When our great grandkids are filling out their applications for Space Academy,
they are going to be entering data, be it with a keyboard or a neural implant,
into a form field that is going to have some validation rules on it, and the UX
is gonna _SUCK_ because the engineers at Space Academy didn't spend enough
effort making the form entry experience good.

Just because the form is rendered in the then-popular `Neural4D-VR.js` UI
framework, the flow of the form data will be just like it is now.

> Form State is framework independent.

## Solid JS

I've been hearing rumors of this new React competitor getting more and more
popular. I'd glanced at it a few times, but at a recent conference, I got to
meet the creator, and generally sense the wind of interest headed in its
direction.

The "no virtual DOM" and "reactivity" claims are incredibly intriguing.

[insert React is not
[reactive](https://en.wikipedia.org/wiki/Reactive_programming) joke here]

What really put me over the edge to attempt what I'm about to present to you is
realizing that Solid lets you tack on "directives" to your JSX, which are little
functions that get to manipulate the DOM directly, a benefit of not having a
virtual DOM. This could make a very interesting way to connect inputs to a form
management library.

Especially if there were a form state management library that were framework
agnostic... 🤔

## Proof of Concept

What I'm about to show you is an example of what a Solid Final Form library
_might_ look like. It calls `useForm()` to create a form instance and expose its
API. Potentially all form state could be exposed via Solid's
[signals](https://www.solidjs.com/docs/latest/api#createsignal). From the docs:

> Signals are the most basic reactive primitive. They track a single value
> (which can be any JavaScript object) that changes over time.

In this proof of concept, I have only exposed `values` and `isSubmitting`, but
Final Form has a
[veritable plethora](https://final-form.org/docs/final-form/types/FormState) of
other form state it manages.

`useForm()` also gives us two
[directives](https://www.solidjs.com/docs/latest/api#use___), `field` and
`error`. To connect an input (i.e. attach blur, change, and focus listeners) to
Final Form, we:

1. Throw a `use:field` directly on the `<input/>`
2. There is no step 2, you're done already!
3. How fun are `<li>` gags?

The `field` directive reads the `name` prop on the `input` and knows what field
it is.

To show errors, we don't have a `name` prop, so we have to tell the `error`
directive with:

```jsx
<span use:error="firstName" />
```

Here's the full form component:

```tsx
export const App: Component = () => {
  // Create Form
  const { error, field, handleSubmit, isSubmitting, values } = useForm(
    onSubmit,
    initialValues,
    validate
  );
  // Calculated Signal
  const formattedValues = () => JSON.stringify(values(), undefined, 2);
  return (
    <form onSubmit={handleSubmit}>
      <h1>Solid Final Form 🏁</h1>
      <label>
        <input name="firstName" placeholder="First Name" use:field />
        <span use:error="firstName" />
      </label>
      <label>
        <input name="lastName" placeholder="Last Name" use:field />
        <span use:error="lastName" />
      </label>
      <label>
        <select name="favoriteColor" use:field>
          <option value="">Choose a color</option>
          <option value="#FF0000">Red</option>
          <option value="#00FF00">Green</option>
          <option value="#0000FF">Blue</option>
        </select>
        <span use:error="favoriteColor" />
      </label>
      <button type="submit" disabled={isSubmitting()}>
        Submit
      </button>
      <pre>{formattedValues()}</pre>
    </form>
  );
};
```

In this
[100-line proof of concept](https://codesandbox.io/s/agitated-maxwell-7jpnxw?file=/src/useForm.tsx),
what are the features we're getting?

- Realtime client side validation (as you fix errors, they go away)
- Tracking if a field is "touched" (focused and then blurred) to only show the
  error if a user has visited the field _or attempted to submit the form_
- Blocking submission when there are validation errors
- A signal to tell us if the form is being submitted
- None of that "Is the whole form rerendering? Waaahhh!! 😭🍼" nonsense that
  React gives us
- Render props? What are those??
- A robust battle-tested framework (300k weekly downloads) for managing our form
  state

## Sandbox

<iframe
  src="https://codesandbox.io/embed/solid-final-form-7jpnxw?fontsize=14&hidenavigation=1&theme=dark"
  style={{
    width: "100%",
    height: 500,
    border: 0,
    borderRadius: 4,
    overflow: "hidden",
  }}
  title="Solid Final Form"
  allow="accelerometer; ambient-light-sensor; camera;
encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb;
vr; xr-spatial-tracking"
  sandbox="allow-forms allow-modals allow-popups
allow-presentation allow-same-origin allow-scripts"
></iframe>

## Future

At the time of writing, this is just a fun Saturday morning fun project. I don't
really feel like I have the bandwidth right now to pursue maintaining another
open source library, but I'd be happy to work closely with anyone who would like
to take this and run with it. You can find me
[on Twitter](https://twitter.com/intent/follow?screen_name=erikras).
]]></content>
        <author>
            <name>Erik Rasmussen</name>
            <email>rasmussenerik@gmail.com</email>
            <uri>https://erikras.com</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Solid for React Developers]]></title>
        <id>https://erikras.com/blog/solid-for-react-developers</id>
        <link href="https://erikras.com/blog/solid-for-react-developers"/>
        <updated>2023-06-05T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[A shallow dive into understand what makes Solid fundamentally different from React]]></summary>
        <content type="html"><![CDATA[
I'd never been to Florida. Not counting one time when I flew into Fort
Lauderdale to hop on a cruise ship. So when my friends at
[Infobip Shift](https://shift.infobip.com/), my Croatian peeps who have invited
me twice already to their lovely homeland, announced that they were introducing
a [conference in Miami](https://shift.infobip.com/us/), I jumped at their
generous offer.
[Everything I know about Miami, I learned from Burn Notice and Dexter](https://twitter.com/erikras/status/1635620326330576901),
so I assumed there would be plenty of bikinis, cocktails, explosions, and
pristine vigilante murder. TL;DR: let's just say they named their NBA team
correctly. 🔥

<p>
<img
  src="/images/infobip-shift-miami-stadium.jpg"
  alt="The Kaseya Center"
  width={4032}
  height={3024}
/>

<caption>
  The speakers' hotel was right across the street from where the Miami Heat
  play. On the second day of the conference, they hosted, and were beaten
  by, the Boston Celtics.
</caption>
</p>

The day before the conference, I spent the entire day with a soon-to-be good
friend, [Alexandra Spalato](https://alexandraspalato.com), a French woman who
lives on the Spanish island of Mallorca. Both being expats in Spain, we had a
ton to talk about. She had the brilliant idea of visiting Little Havana, a
gorgeous little _barrio_ of Miami full of Cuban culture. We then felt obligated
to check out the famed Miami Beach, but both living near good beaches, we were
very underwhelmed. The pelicans were cool, though.

<p>
<img
  src="/images/infobip-shift-miami-alexandra.jpg"
  alt="Me and Alexandra at lunch"
  width={4032}
  height={3024}
/>

<caption>
  Lunch with Alexandra.
</caption>
</p>

<p>
<img
  src="/images/infobip-shift-miami-cuban-fajitas.jpg"
  alt="Cuban Fajitas"
  width={3024}
  height={2859}
/>

<caption>
  Cuban fajitas for lunch. Just like Mexican fajitas, but instead of
  tortillas, it's crispy banana cakes.
</caption>
</p>

## Infobip Shift Miami – 2023

<p>
<img
  src="/images/infobip-shift-miami-venue.jpg"
  alt="The venue"
  width={8316}
  height={3870}
/>

<caption>
  Look at this disgusting venue! 🌴🌴🌴🌴🌴
</caption>
</p>

The conference itself was great! Much like how it's nice to transition back and
forth between sandals and sweaters as the seasons change, it's refreshing to
alternate between single-track and multi-track conferences. So it was nice to
know that there was only ever one talk going on at this one. I tried to attend
most of them, both for knowledge, but also for emotional support for my fellow
speakers. It's so nice to have someone in the front row nodding when you're
behind the podium. There were three talks that really stood out to me.

The first was from [John Romero](https://twitter.com/romero), the creator of
[DOOM](<https://en.wikipedia.org/wiki/Doom_(franchise)>), on the timeline of how
they created a genre-defining masterpiece in a fury of development effort that
can only be mustered in one's twenties. I especially appreciated how, despite
being in Texas at the time, they modeled all the weapons after toys they found
in Toys Я Us. My favorite part of seeing him was getting to talk to him
afterwards, and telling him how, as a school project in high school, a friend
and I modeled our school in a DOOM level editor, somehow got teachers into the
racquetball court (white background) to pose in all 32 (64?) poses to generate
sprites for them, and then we ended up with a DOOM level that allowed the player
to walk around the school shotgunning teachers in the face. This was before
Columbine. Everyone took it in good fun. Nowadays, you'd be #canceled for life
for such abhorrent ideas.

<p>
<img
  src="/images/infobip-shift-miami-john-romero.jpg"
  alt="Me and John Romero"
  width={4032}
  height={3024}
/>

<caption>
  The creator of DOOM with the harbinger of it.
</caption>
</p>

The second was from [Joyce Lin](https://twitter.com/PetuniaGray). I met her two
years earlier at my first conference in Croatia, and we clicked immediately. Our
senses of humor align disturbingly well.
[Her talk](https://www.youtube.com/watch?v=8aHzQEgM1gg) was about the history of
APIs, and, having been a web developer for 20+ years, I knew every single part
of it. She did a gimmick where she gave the audience quizzes and awarded Postman
swag as prizes. About halfway through, she had to tell me to STFU because I knew
all the answers.

<p>
<img
  src="/images/infobip-shift-miami-joyce-lin.jpg"
  alt="Me and Joyce Lin"
  width={4032}
  height={3024}
/>

<caption>
  Joyce Lin and I, just getting started with the tequila.
</caption>
</p>

The one that probably most blew me away was from a guy I've known on Twitter for
years, named [Jhey Tompkins](https://twitter.com/jh3yy). Turns out he really
does wear that hat all the time! Gotta respect his personal branding.
[His talk](https://www.youtube.com/watch?v=88D6jLHFw3Q) included some crazy
ambitious things, like interacting with his slides in realtime via voice and
hand gestures with his phone. He concluded by showing a QR code where we, the
audience, could all connect and pop up emojis and comments over his slides.
Credit to the audience for being completely tasteful when we had full control to
be jerks.

## My Talk

My talk was one I've been thinking about giving for 6 months, ever since seeing
a talk about Solid JS and meeting its creator, Ryan Carniato, at
[[a disaster of a conference](https://blog.tomayac.com/2022/11/23/modern-frontends-live/)]
in London last November. I find Solid extremly interesting. Hence my blog posts
about
[Solid Final Form – Proof of Concept](https://erikras.com/blog/solid-final-form-poc)
and [XState in Solid](https://erikras.com/blog/xstate-in-solid). As a React
developer by profession, I figured I had a unique perspective to explain Solid
to other React devs.

<video>vxTbDKaSnkk</video>

The day after the conference, as a result of a "what to do in Miami" search, I
took a boat tour of celebrity houses. I saw the mansions of Antonio Banderas,
Jackie Chan (which is available on Airbnb for $7k/night for 10 people), Floyd
Mayweather, Celia Cruz, Ricky Martin, DJ Kali, Puff Daddy, JLo, Will Smith,
Rihanna, David Beckham, Gloria and Emilio Estefan, and Fisher Island, with no
bridges, where property is owned by Tom Cruise, Julia Roberts, and Oprah.

<p>
<img
  src="/images/infobip-shift-miami-skyline.jpg"
  alt="Miami skyline selfie"
  width={3924}
  height={2943}
/>

<caption>
  Miami skyline selfie.
</caption>
</p>

<p>
<img
  src="/images/infobip-shift-miami-rainy-plane.jpg"
  alt="Rainy sunset plane"
  width={3336}
  height={3024}
/>

<caption>
  There was a fantastic Florida lightning storm in the distance as I waited
  to board my plane back to the Olde Worlde.
</caption>
</p>

Thank you so much to the Infobip Shift team for knocking another conference out
of the park. It was an amazing experience. 💙
]]></content>
        <author>
            <name>Erik Rasmussen</name>
            <email>rasmussenerik@gmail.com</email>
            <uri>https://erikras.com</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[iOS Detective: The Case of the Stolen iPhone]]></title>
        <id>https://erikras.com/blog/stolen-iphone</id>
        <link href="https://erikras.com/blog/stolen-iphone"/>
        <updated>2018-09-17T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[What follows is a true story that happened to me last night.]]></summary>
        <content type="html"><![CDATA[
Due to exhaustion and the offer of a ride, I arrived to the Alicante airport
four hours before my flight. I mostly spent it sitting around with my eyes half
closed, half listening to podcasts. When it was getting close to boarding time,
I checked the monitor and it said that my flight was delayed. This was not good
news for me because I had a very tight 50 minute layover in Madrid to catch my
next flight. The monitor said it would be 15 minutes until the next information
about my flight, so I sat back down, read some tweets, and, for some reason, put
my phone between suitcase and my backpack on top of the suitcase. A few minutes
later, got up, leaving my bags within sight to glance at the monitor again. My
flight was boarding!

I went and grabbed my suitcase and backpack and rolled it towards the gate.
After about 15 meters, the podcast audio stopped in my AirPods. Ironically, I
was listening to a podcast about the new models of iPhone that were released
last week, none of which I’d rather have than the $1,500 in my bank account. I
reached down to my pocket to grab my phone to start the podcast again, only to
find my pocket empty! Then I remembered that I had put my phone between the
bags, and I looked, and it was not there. I walked back to where I had been
sitting, checking the floor along the entire 15 meters, but the phone could not
be seen! I retraced these 15 meters about five times before I was convinced that
it was not on the floor.

I looked at my Apple Watch, and it was showing the “not connected to phone”
icon, so the Watch could not see the phone either. I cursed under my breath. My
iPhone had fallen from its precarious place between my bags, and someone had
taken it.

> My flight was boarding.

I walked towards my gate and saw a line of about 25 people scanning their
boarding passes. I looked down at my watch to see the time to get an idea of if
I would make my next flight, and…

_My watch was connected to my phone!!_

One of the most useful features on the Apple Watch is the ability to make the
iPhone it’s paired with emit a loud beeping sound, even if it is in silent mode.
I’ve lost count of the number of times it has helped me locate my phone between
the couch cushions. I quickly swiped up and hit the “Ping” button. Ears wide
open, amidst the airport bustle…

I heard nothing. _Shit!_

Then it occurred to me that my iPad might have a better Bluetooth range than the
Watch, so I pulled out my iPad (wifi only, not cellular), which I had recently
had attached to the phone’s Personal HotSpot. After a moment of searching, push
notifications started rolling in. It was connected!!

I opened up the _Find iPhone_ app, which was ironically using the wifi hotspot
of the iPhone I was searching for to hit iCloud to ask the iPhone for it’s GPS
location to pass back to me through iCloud and then through the phone’s wifi to
me. There it was, on the map, very close to me. Before searching, I tapped the
“Lost Mode” button, and it asked me to enter a phone number to display on the
device. I struggled to remember my home landline phone and typed it in.

> The search was on!

To try to orient myself on the map, I walked 10 meters in one direction, but
both the little dots were bouncing around in unpredictable ways. I turned 90º
and walked another 10 meters. The dots seemed to be closer!

I kept walking, frantically searching the crowd of anonymous faces. In the movie
version of this story, the camera would be whirling around the protagonist as he
searched the crowd for a person he couldn’t identify. You know the trope.

As I was walking in the direction that made the dots closer, an airline employee
stopped me. “Sir, you can’t go past this security checkpoint.” I explained that
I had lost my phone, but that I knew it was around here somewhere. She asked if
I’d like to report it to the police, and I expressed some doubt that I had time
for that, since I was about to board a flight, but it turned out the police had
a little kiosk very close by.

I explained the situation to the cops, and they said, have you tried to make it
ping? I said yes, but that I hadn’t heard it. They said, “Do it again.” I did,
this time via the _Find Phone_ app, which I think emits a louder ping.

> WE HEARD IT!!

The three police officers jumped out of their little kiosk and said, “Again!” I
tapped it again. It was coming from the other side of the security barrier! The
cops raced through a door through the barrier not open to the public, and I
followed.

> PING!

We turned to see a man at the front of a long queue about to board a flight,
holding my phone. “Is that it?”, asked the cop. “Yes!”, I said, as I swiped it
from the man’s hand. “I called the number it said to call!”, shrugged the
British man, in English. (I did later confirm that I had a missed call on my
landline phone from my phone at that time)

I thanked the police profusely and ran off towards my gate. I grabbed my bags
and fumbled for my boarding pass and walked down the corridor towards my plane.
_So relieved!_

But of course the corridor was full of passengers waiting to get on the bus to
take us to the plane. I started up my podcast again and breathed a long sigh.

And
[tweeted about the incident](https://twitter.com/erikras/status/1041391205542711296),
of course. 🙄

<s>1041391205542711296</s>

A few minutes later, the airline employee that had just scanned my boarding pass
came down the corridor and said the police wanted to talk to me. My face must
have expressed my concern of not making the flight, and she told me that I
needn’t worry, that the bus wasn’t leaving until she told the driver it was
okay. The cop wanted my name, address, and info, so I gave in my ID card to copy
it from. He then requested the IMEI number from the phone. Weird, but I guess he
had to write it up for his report. I didn’t quite understand if they had
detained the other guy or not, but I’m sure they pulled him out of the line to
get his details as well.

I guess the guy at least made an effort to call the owner of the phone, but he
did not do the responsible thing of turning it in to the police, like I have
done on several occasions, much to my own personal inconvenience, with phones
and wallets. I’m sure he was hoping to walk/fly away with it.

---

After some serious sprinting through the Madrid airport, my iPhone, Apple Watch,
iPad, MacBook Pro, and I _barely_ caught our next flight and made it home
safely.

---

Thank you, Apple, for adding features like these to your operating systems.

And _muchas gracias_ to the Alicante Airport police for your quick action. 🙏
]]></content>
        <author>
            <name>Erik Rasmussen</name>
            <email>rasmussenerik@gmail.com</email>
            <uri>https://erikras.com</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Building Complex UI with State Machines]]></title>
        <id>https://erikras.com/blog/ui-with-state-machines</id>
        <link href="https://erikras.com/blog/ui-with-state-machines"/>
        <updated>2024-12-16T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[City JS Athens, React Alicante, What The Stack, and React Africa – 2024]]></summary>
        <content type="html"><![CDATA[
When deciding what to talk about in 2024, I wanted to showcase how powerful
state machines and XState can be by building something complicated, but not
_too_ complicated. That was the balance I was trying to strike. The year before,
I had built a little autocomplete dropdown component for my
[Model Based E2E Testing with State Machines](https://erikras.com/blog/model-based-e2e-testing)
talk, which desipite not being the state machine used in the talk, _was_
implemented with a relatively simple state machine. It seemed like the perfect
amount of complexity and asynchronous loading state. I turned it into a nice 30
minute talk, that I was lucky enough to be able to give at
[CityJS Athens](https://greece.cityjsconf.org/),
[What The Stack](https://wts.sh/), in Skopye, North Macedonia,
[React Alicante](https://reactalicante.es/), and
[React Africa](https://react-africa.com/), in Casablanca, Morocco.

Below I will share some photos of my vists to those places.

## Talk

<video>6yM839PycHU</video>

The code can be found
[on Github](https://github.com/erikras/search-state-machine).

<p>
<img
  src="/images/ui-with-state-machine-chart.png"
  alt="The state chart"
  width={3118}
  height={1580}
/>

<caption>
  The state chart.
</caption>
</p>

## CityJS – Athens, Greece

<p>
<img
  src="/images/2024-athens01.jpg"
  alt="The Acropolis at night"
  width={4032}
  height={3024}
/>

<caption>
  The Acropolis at night.
</caption>
</p>

<p>
<img
  src="/images/2024-athens02.jpg"
  alt="The Temple of Poseidon with Kadi"
  width={4032}
  height={3024}
/>

<caption>
  The Temple of Poseidon with <a href="https://bsky.app/profile/kadi.bsky.social">Kadi</a>.
</caption>
</p>

<p>
<img
  src="/images/2024-athens03.jpg"
  alt="All the speakers at the Temple of Poseidon"
  width={2048}
  height={1536}
/>

<caption>
  All the speakers at the Temple of Poseidon
</caption>
</p>

<p>
<img
  src="/images/2024-athens04.jpg"
  alt="The Parthenon"
  width={2048}
  height={1536}
/>

<caption>
  The <a href="https://en.wikipedia.org/wiki/Parthenon">Parthenon</a>
</caption>
</p>

## React Alicante – Alicante, Spain

<p>
<img
  src="/images/2024-alicante01.jpg"
  alt="Speakers Selfie"
  width={3895}
  height={2720}
/>

<caption>
  Speakers Selfie
</caption>
</p>

<p>
<img
  src="/images/2024-alicante02.jpg"
  alt="Plisplay with Stately"
  width={4032}
  height={3024}
/>

<caption>
  <em>Plisplay</em> with <a href="https://stately.ai/">Stately</a>
</caption>
</p>

<p>
<img
  src="/images/2024-alicante03.jpg"
  alt="Speakers' Night Out"
  width={3528}
  height={1608}
/>

<caption>
  Speakers' Night Out
</caption>
</p>

## What The Stack – Skopje, North Macedonia

<p>
<img
  src="/images/2024-skopje01.jpg"
  alt="Alexander the Great"
  width={4032}
  height={3024}
/>

<caption>
  Statue of Alexander the Great, the most famous Macedonian.
</caption>
</p>

<p>
<img
  src="/images/2024-skopje02.jpg"
  alt="The organizers, Blagica and Darko"
  width={4032}
  height={3024}
/>

<caption>
  The organizers, Blagica and <a href="https://bsky.app/profile/darko.io">Darko</a>
</caption>
</p>

## React Africa – Casablanca, Morocco

<p>
<img
  src="/images/2024-casablanca01.jpg"
  alt="Hassan II Mosque"
  width={4032}
  height={3024}
/>

<caption>
  <a href="https://en.wikipedia.org/wiki/Hassan_II_Mosque">Hassan II Mosque</a>, the must-see building in Casablanca
</caption>
</p>

<p>
<img
  src="/images/2024-casablanca02.jpg"
  alt="Interior of the Hassan II Mosque"
  width={4032}
  height={3024}
/>

<caption>
  Interior of the Hassan II Mosque. As a non-muslim, I was not allowed inside.
</caption>
</p>

<p>
<img
  src="/images/2024-casablanca03.jpg"
  alt="Colorful grains at the market"
  width={3024}
  height={4032}
/>

<caption>
  Colorful grains at the market
</caption>
</p>

<p>
<img
  src="/images/2024-casablanca04.jpg"
  alt="Speakers on a bus"
  width={4032}
  height={3024}
/>

<caption>
  We took a speaker trip to Rabat.
</caption>
</p>

I am so grateful to the organizers that invited me to their conferences,
[Aris](https://x.com/arismarko), [Darko](https://bsky.app/profile/darko.io),
[Victoria](https://bsky.app/profile/vicqr9.bsky.social), and
[Aymen](https://bsky.app/profile/aymenbenamor.bsky.social); I love you all. 🫶

What a great year! I can't wait to do it all again next year. I've already got
my talk planned. Stay tuned...
]]></content>
        <author>
            <name>Erik Rasmussen</name>
            <email>rasmussenerik@gmail.com</email>
            <uri>https://erikras.com</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[JavaScript Unsubscribe Sugar Proposal]]></title>
        <id>https://erikras.com/blog/unsubscribe-sugar-proposal</id>
        <link href="https://erikras.com/blog/unsubscribe-sugar-proposal"/>
        <updated>2022-09-14T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[I think it's fair to say that JavaScript is an "event-driven" language.]]></summary>
        <content type="html"><![CDATA[
I think it's pretty fair to say that

> JavaScript is an "event-driven" language.

As a JavaScript developer, I spend all day "subscribing" and "unsubscribing" to
events (["Follow"](https://erikras.com/blog/follow-observables) never caught
on), from the user/DOM, a database subscription, or literally any other actor in
the system. It is so easy, for even a veteran, senior, 10x developer to forget
to call `unsubscribe()`. It's _such_ an easy footgun. And so I ask myself,

> How could we make it impossible to leak memory by forgetting to unsubscribe?

There are three primary ways that I see subscriptions happening in JavaScript.

## addEventHandler / removeEventHandler

When you're down and dirty mucking about in the DOM, these are your tools. And
it's _so_ awkward because of the instance-equality (`===`) check on
`removeEventHandler`.

```ts
div.addEventHandler("click", () => {
  alert("Nice click!");
});
// don't forget to unsubscribe!
div.removeEventHandler("click", () => {
  alert("Nice click!");
});
```

❌ WRONG! Those are different functions.

```ts
const onClick = () => {
  alert("Nice click!");
};
div.addEventHandler("click", onClick);
// don't forget to unsubscribe!
div.removeEventHandler("click", onClick);
```

✅ There ya go! You had to define the function before subscribing and
unsubscribing!

## on / off

In my experience, this feels more node-y to me, but I think the real culprit is
Our Holy Savior jQuery, that really popularized this syntax. It's less explicit
than `addEventListener` / `removeEventListener`, but `on` makes sense, and
"Gosh, what's the opposite of 'on'??"

```ts
const onClick = () => {
  alert("Nice click!");
};
div.on("click", onClick);
// don't forget to unsubscribe!
div.off("click", onClick);
```

It suffers from the same exact problem as `addEventListener` /
`removeEventListener`, namely that you've got to maintain the same instance...
and also remember to actually `off` each of your `on`s.

## subscribe() returns unsubscribe()

This is the most elegant of the solutions, because it's so beautifully
_functional_, and you don't have to worry about instance-equality – the closures
do that for you. As a library developer, it's really comfortable to use. I first
noticed this with RxJS, and adopted it when I wrote
[Final Form](https://erikras.com/blog/final-form-announcement). Imagine my
delight when upgrading React Final Form to use React Hooks, and the API for
`useEffect()` was exactly the same as my existing API!

However, the problem of _remembering to unsubscribe_ is still ever-present!

(These examples are going to be React-y, but the issue remains throughout the JS
ecosystem)

```ts
useEffect(() => {
  return subscribeToPosts((posts) => setPosts(posts));
}, []);
```

It's not immediately obvious from this code that there's a function being
returned there. And it's even more non-obvious if you eliminate the braces:

```ts
useEffect(() => subscribeToPosts((posts) => setPosts(posts)), []);
```

But what if you need multiple subscriptions in the same effect?

```ts
useEffect(() => {
  const unsubscribes = [];
  unsubscribes.push(subscribeToPosts((posts) => setPosts(posts)));
  unsubscribes.push(subscribeToUser((user) => setUser(user)));
  // ...
  return () => unsubscribes.forEach((unsubscribe) => unsubscribe());
}, []);
```

I really wish there was a way for me to mark a function as:

> I'm gonna subscribe to some stuff in here. Please return a function that
> unsubscribes!

# Proposal

Remember what a pain working with `Promise`s was before `async`/`await`? _So_
many `.then()`s!!!

It occurred to me that it might be cool if we had something similar for the
ubiquitous subscribe/unsubscribe pattern. I'm going to share the first thing
that occurred to me, but I'd love for us, as a community, to iterate on it.

Best idea wins!

## Sugar

`sub` – marks a function as "imma subscribe to some stuff", sort of like
`async`. In TypeScript land, it forces that the function return a `() => void`

`unsub` – marks a line that returns an unsubscribe function

## Examples

```ts
useEffect(sub () => {
  unsub subscribeToPosts((posts) => setPosts(posts))
  unsub subscribeToUser((user) => setUser(user))
  // Look, Ma! I'm returning nothing!
}, [])
```

The syntax sugar will take care of establishing the array of `unsubscribes` and
calling them all when the function completes, ideally inside of a `finally`
block.

I'd like it to special case the common handler systems of before.

```ts
useEffect(sub () => {
  document.addEventListener('keypress', () => { alert('Nice key!') })
  someThing.on('download', () => { alert('One file, coming right up!') })
})
```

There must be a way for those of you, that understand AST better than I, to
transpile this into some JS that manages the requisite `removeEventHandler` and
`off`.

# Conclusion

Of course, in the same way that your `async` won't do you much good if you
forget the `await` keyword, you still have to remember the `unsub` keyword, you
can still shoot yourself in the foot, but I think a syntax like this might
remove a _lot_ of boilerplate from typical JS code, and fix a lot of
yet-undetected bugs in our existing codebases.
]]></content>
        <author>
            <name>Erik Rasmussen</name>
            <email>rasmussenerik@gmail.com</email>
            <uri>https://erikras.com</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[What I learned at Distru]]></title>
        <id>https://erikras.com/blog/what-i-learned-at-distru</id>
        <link href="https://erikras.com/blog/what-i-learned-at-distru"/>
        <updated>2021-08-20T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[As I leave this amazing company, I want to share a few of the things I learned.]]></summary>
        <content type="html"><![CDATA[
Today is my final day working for [Distru](https://www.distru.com), helping to
build a SaaS product to manage inventory, traceability, and distribution for the
budding cannabis industry. Aside from being an
[ERP](https://en.wikipedia.org/wiki/Enterprise_resource_planning), the primary
pain point that Distru alleviates is keeping every transaction and transfer
synced up with the government database in this extremely regulated industry. As
you might imagine, the APIs for communicating with the government are...
...challenging.

The whole company is full of amazing people with expertise ranging from sales to
design to engineering to cannabis itself. The engineering team I have been a
part of is fantastic. Everyone is so generous and humble and hard working. I'm
going to miss them greatly.

At this turning point in my career, I thought I'd make a note of some of the
things I've learned, mostly for myself, but for you all, too.

## Standups

Before I worked at Distru, I was part of a 2-developer "team" for 15 years. I'd
heard words like Agile and Scrum, but I'd never really lived any of it. I know
this is common in my industry, but it was new to me. Normally, a
[standup](https://en.wikipedia.org/wiki/Stand-up_meeting) – like all compound
words, it will lose the hyphen eventually; embrace it – is a quick meeting,
typically every morning, where everyone at the meeting quickly says what they
did yesterday, and what they will be working, and, most importantly, if anything
is impeding their progress. "No blockers" is a common "over and out" way to
announce that you're done talking. Because many of the team is in California,
and I'm in Spain, my standup would be at the end of my workday, where I would
report what I had accomplished. I found knowing that I would have to report
progress every day very motivating.

### Standups at Distru

I assume that typically, a standup is run by the team leader, and it is they
that call on everyone in the meeting to speak. Distru doesn't do this. At the
end of every standup, we open up [Wheel of Names](https://wheelofnames.com), and
randomly select a team member to lead the following standup. If you're
smart/lazy, you learn to call on people in the order the Zoom has placed them on
your screen. Each time, the current leader's name is from the wheel, until
everyone has done it, and then they start over will all the names on the wheel.

When it's your turn to lead the standup, you are expected to entertain everyone
at the beginning of standup with an "interesting fact" that you learned
recently. This is no trouble for me, since I regularly listen to podcasts in the
"interesting facts" genre, such as
[No Such Thing as a Fish](https://www.nosuchthingasafish.com) and
[The Omnibus Project](https://www.omnibusproject.com). What's great about this
practice is that it lets you get to know your team mates...by the facts they
choose. One engineer, who literally hunts alligators in the Louisiana bayou, can
rival Bubba in enumerating all the ways to cook a shrimp. Another, from Brazil,
would regail us with the political shenanigans of his elected officials.
Another, based in Barcelona, taught us all about the importance of
[public human defecation](https://en.wikipedia.org/wiki/Caganer) in the Catalan
Christmas tradition. Another would get so into her facts, that she prepared
slides, and would sometimes have to be told that her fact was going on too long,
and we really should start standup. It was great fun! I highly recommend
adopting this quirk to your standups.

The fact I shared on my last day is that
[badminton shuttlecocks can only be made with feathers from the **_left_** wings of geese](https://www.inverse.com/article/19768-2016-rio-olympics-goose-geese-left-wing-badminton-shuttlecock).

## Retros

A
[Retrospective](https://en.wikipedia.org/wiki/Retrospective#Software_development)
is a meeting held at regular intervals – I think Distru does it on the last
Friday of the month – in which participants are encouraged to thoughtfully
reflect upon the previous month. I'd never done this before, so I can only speak
of how they do.

### Retros at Distru

Distru uses a SaaS product called [Retrium](https://www.retrium.com) to manage
the Retro meetings. All the team members log into Retrium, and then a Retro is
initiated. Everyone is encouraged to create little cards with thoughts organized
into three categories:

- What went well
- What went poorly
- Concerns about the future

The cards are clearly skeuomorphic representations of Post-It™ notes. Part of me
suspects that 3M, the manufacturer of Post-It™ notes, is behind the Agile
movement. You can just see them brainstorming: "How can we sell more of these
sticky pieces of colored paper?" I digress...

While people are adding cards, you can only see the contents of your own cards.
Everyone else's cards are blurred out, but you can see them typing. I once tried
to inspect the DOM to see if I could see what the others were typing, but
Retrium is just putting Lorem Ipsum nonsense to encourage you to contribute,
because your colleauges are. It works.

After a set amount of time, usually 5 minutes, everyone puts down their virtual
pencils, and the contents are revealed. Everyone can participate in the next
step, which is grouping cards about the same topic. For example, there might be
three different people complaining about how slow Continuous Integration has
been running lately, or happy about a new hire, or pleased that the value of
TypeScript finally clicked with them.

Then we read over all the "What went well" cards, and anyone can comment. No one
is ever afraid to step forward and say, "I wrote that one", and explain why.

Next is the anonymous voting phase. Retrium lets gives us all some number of
votes (depending on how many people are in the meeting). You can think of it
like placing poker chips next to the card that you most want to discuss, but you
can't see other people's chips. If something is really important, you can put
all your chips on that one card. This goes on until everyone has spent all their
votes.

Retrium then sorts the cards, and we discuss the ones with the most votes. This
is really great at capturing mutual sentiment. We then have a really
enlightening egoless discussion about how we can solve whatever problem or
concern has been voted on. Using this technique, we have implemented policies
such as having a special Slack channel where we can request PR reviews, or
discuss urgent production issues.

There is also some concept of writing down action items at the end, and
reviewing them at the beginning of every Retro, but we haven't followed that
policy very well, and I think that's fine.

## One-on-Ones (1:1)

Yet another practice that, from my internet reading, seems to be generally
understood as Good Management Practice™, but that was new to me. Every month (or
sometimes every week), there would be a meeting on my calendar to have a
30-minute call with my boss.

### One-on-Ones at Distru

My boss, Johnny, made it very clear from the very beginning that he expected us
to have extreme candor with each other. Distru offered to buy
[The 15 Commitments of Conscious Leadership: A New Paradigm for Sustainable Success](https://amzn.to/2W3yVmF)
for everyone, but I bought it on my own. It's a very interesting book.

Johnny would enter these meetings with a shared Google Doc with headers like
Touching Base, Talking Points, and Takeaways. He'd ask questions like, "What
could you do to be more productive?", or "What could _I_ do to help make you
more productive?", or "What are your goals six months from now; what do you hope
to have accomplished?" Very thought provoking questions that encourage
introspection. Never once was there any emotion or resistence when we told each
other how we could improve. It was just a good, healthy, egoless, selfless
discussion.

It made me feel listened to and respected, and it gave me the confidence to
suggest things at any time.

## #muchlove

Showing gratitude is a great way to feel good and make others feel good.
Sometimes, at work, a colleague will really help you out of a bind, or will
really stand up to a challenge. Distru has a Slack channel called **#muchlove**,
in which posts always start with the name of the channel and tags someone. e.g.

- \#muchlove to @susan for jumping in on an important sales call when I had
  another call scheduled and closing the deal!
- \#muchlove to @adam for helping me figure out a complex database query
  yesterday. Couldn't have done without you, mate!
- \#muchlove to @amy for these gorgeous designs for the new \_\_\_\_ interface.
  I can't wait to implement them!

You get the drift. It seems so silly, but when you get one about you, it feels
SO GOOD!! Every company should impement a system of public displays of
gratitude.

## Welcome Letters

Whenever Distru sends an official offer letter of employment to a candidate,
they post the candidate's email address in Slack and encourage employees to send
the candidate emails congratulating them on getting an offer. This is _so_
clever! It makes you feel so warmly welcomed even before you've joined. It's
such a great psychological hack to encourage the candidate to accept. Genius!

## Feature Channels

For every new feature added to Distru, a Slack channel is created called
something like #feature-label-printing. All discussion about how that project is
going is contained in that channel. People that care can follow, and people that
don't needn't be bothered by it. When the feature is released the channel is
renamed, e.g. to #feedback-label-printing. As an engineer building the feature,
I was encouraged to post daily updates about how I was progressing. I'm sure
lots of companies do this, but I found it very useful.

## Education Sessions

Every so often – it feels like it might've been fortnightly – there would be a
meeting for the whole company where someone would give a presentation about the
cannabis industry. Before joining Distru, I had no idea about
[terpenes](https://hightimes.com/guides/what-are-terpenes/),
[trichomes](https://wayofleaf.com/education/what-are-trichomes-cannabis-terminology-101),
[resin](https://wayofleaf.com/cannabis/101/cannabis-resin-uses),
[how to trim cannabis flower](https://www.greenbudguru.com/how-to-trim-weed),
and how cannabis plants have clear genders, and if you want to maximize your THC
production, you want only female plants, and keep them away from any male plant,
[so they get really horny and produce more THC](https://www.greenbudguru.com/how-to-spot-female-cannabis).
Who knew? I sure didn't! It was fascinating to learn. Did it help me draw
rounded rectangles in the application? Not really, but it gave a sense of
purpose and wider mission to what I was building.

## Knowledge Transfer

The brainspawn of one of our Retros, we began having Knowledge Transfer sessions
within the engineering department, where every other week one or two of us would
prepare a 30-60 minute presentation about one aspect of the product we were
working on. These were recorded Zoom calls that could later be watched if you
could not attend. I gave one on forms 🙄 and another on how I was using XState
to build several of my projects. Most of them were backend stuff that I didn't
really need knowledge of, but it was fun to hear my fellow engineers speak
passionately about what they had been working on.

## Engineering Demo Days

Whenever a new feature was released, we would have an hour-long meeting for the
engineer(s) that built the feature to demonstrate how it worked to the rest of
the company, specifically the sales and customer support departments. These were
also recorded Zoom calls for posterity.

## PR QA Videos

Another idea that came out of a Retro was to require all PRs to have an
accompanying [Loom](https://www.loom.com/) video where the engineer demonstrates
that the bug fix really fixes the bug, or that the feature really works. For
bugs, I would often switch back to the `main` branch, demonstrate the bug, pause
the recording, switch to my branch, and demonstrate the fix. This serves two
purposes:

1. It provides much needed context to the code reviewer to be able to visualize
   the user flow being altered.
2. It forces the engineer to actually test their code.

That last one sounds silly, right? I only ever record my Loom video when I'm
_certain_ my code works. Even so, about **70%** of the Loom videos I made for my
PRs found a problem, and I had to stop recording, fix it, and start again. These
little mind hacks can be so useful! You can think of it like a
[Rubber Duck](https://en.wikipedia.org/wiki/Rubber_duck_debugging) Demo. We all
know that, if you want to find a bug, give a demo.

Without this practice, I would've shipped _many_ more bugs into production.

## Clean Commit History

I honestly think I had never used `git rebase` before starting at Distru, where
they have a policy of having a "clean commit history". That refers to not having
commit messages like "Tweaks" and "Let's see if CI passes now". At Distru a PR's
commits should "tell a story", e.g. First I created this component, then I used
it over here, then I connected it to the backend. My boss at Distru, Johnny,
wrote
[a fantastic Medium post](https://johnnyisji.medium.com/engineering-culture-keeping-a-clean-commit-history-453f950c1f2d)
about how to achieve this with `git rebase -i`.

This can be a lot of work, and I wouldn't necessarily recommend it for startups
where you can still feed all the engineers with
[two pizzas](https://buffer.com/resources/small-teams-why-startups-often-win-against-google-and-facebook-the-science-behind-why-smaller-teams-get-more-done/),
but for larger organizations, being able to understand commits can become
invaluable.

## Gaming Sessions

The office manager would often organize 30 minute optional "games" meetings at
least once a month using online gaming platforms like
[Jackbox](https://www.jackboxgames.com/games/). Everyone that was participating
would hop on a Zoom call, and then we'd all go to some unique URL, often on our
phones, and it would be trivia or games where we had to ask questions about a
scenario and determine which of us was the spy. That sort of party game. The
most memorable one was an escape room. We are all remote on Zoom, how can we do
an escape room? Google "virtual escape rooms". There was some service that
provided a guy with a GoPro on his head that would do whatever we told him, e.g.
"Turn that vase so it's facing the other vase", and we would decipher clues to
help him escape. It was way more fun than it sounds!

Through these "team building" exercises, I really did feel close to my team, and
put personalities to the faces from other departments that I only saw during...

## All Hands Meetings

This is a term I've heard in the industry, but my first experience was with
Distru. These were monthly hour-long meetings where the heads of each department
would present how their department was progressing, if goals were being met,
etc. The most interesting part of these meetings for me was the total
transparency of the company's financials. Things like:

- We currently have $X in the bank.
- Last month we experienced $X in churn (lost customers), which is N% more than
  last month. This was due to factors A, B, and C.
- At our current rate of burn, we have X months of runway.

Those kinds of candid discussions made me feel like the captain of the ship knew
what he was doing, and I could feel safe toiling away below deck.

All Hands meetings were often ended with...

## Meditation Sessions

Mental health and productivity is so important at Distru that they hired a
professional meditation coach to guide the entire company in 15-30 minute
meditation sessions. We'd all hop on a Zoom call, turn off our cameras, and
she'd walk us through breathing and concentrating. Can you imagine your employer
telling the entire company to "Stop what you're doing!" and meditate for 20
minutes, 2-3 times a week?

I ended up not attending very many, because, while they were in the morning
between coffees for the Californians, it was 6pm for me, and more than once I
dozed off. But I've got to applaud the progressive thinking behind the practice.

## Two Sweet Anecdotes

The funniest thing that happened while I was at Distru was during an engineering
team meeting, where one person was discussing a problem they were having with
cookies. Half the team thought he was talking about HTTP browser cookies, and
the other half thought he was talking about an edibles product in the database.
🤣

I had been working at Distru for about six months when my birthday came around.
There's a birthday Slackbot that reminds everyone. Whatever, that's a low bar.
But on the afternoon of my birthday, my doorbell rings, and there's a delivery
for me. The head of HR at Distru is a lovely woman from Barcelona who lives in
the US. She had used my address to find a local bakery – in my little Spanish
fishing village, mind you, where I guarantee you the baker doesn't know the
English word for "cake" – and had them bake and deliver me a birthday cake!
That's how Distru cares for their own.

<img
  src="/images/distru-birthday-cake.jpg"
  alt="Distru Birthday Cake"
  width={4032}
  height={3024}
/>

## Conclusion

No one at Distru asked me to write this. In fact, I'm about to ask them for
permission to post it. If you're reading this, they approved. Distru is an
amazing place to work, and I wanted to write down all these brilliant ideas to
suggest them to every subsequent employer I ever have.

If Distru sounds like the kind of place you'd like to work, I happen to know
that they could really use a frontend engineer right now.
[Apply here](https://www.distru.com/careers).

<img
  src="/images/distru-team.jpg"
  alt="Distru Team"
  width={1493}
  height={854}
/>

<caption>I'm gonna miss these beautiful faces. 💚 #muchlove</caption>
]]></content>
        <author>
            <name>Erik Rasmussen</name>
            <email>rasmussenerik@gmail.com</email>
            <uri>https://erikras.com</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[XState in Solid]]></title>
        <id>https://erikras.com/blog/xstate-in-solid</id>
        <link href="https://erikras.com/blog/xstate-in-solid"/>
        <updated>2022-12-08T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[How to use the best state machine library in Solid]]></summary>
        <content type="html"><![CDATA[
The more I play with [Solid](https://www.solidjs.com/), the more I find its
abstraction incredibly fascinating. I think it might be as revolutionary as
React's "just render everything every time and diff the DOMs" approach was back
in 2013. In my own words as a total n00b:

> When you write your JSX with conditionals and props that are
> [signals](https://www.solidjs.com/docs/latest/api#createsignal), Solid is able
> to notice exactly which pieces of your UI depend on which signal value, and
> can rerender _only that dependent UI_ when the signal value changes.

When I wrote my [Proof of Concept](/blog/solid-final-form-poc) using an external
state management system with Solid, I realized that I had to communicate via
signals, i.e. I had to keep the state I wanted to use in the UI in a signal.
This is _vaguely_ analogous to how the way to force a React component to
rerender is to change some part of its state with `setState()`.

> One important difference in Solid, when coming from React, is realizing that
> your "render function" (the component body itself) is **ONLY CALLED ONCE** on
> first render. All UI updates are dependent on signal changes _from inside the
> JSX_.

So let's try and plug my favorite framework agnostic state management library
into Solid!

## XState

The way you use XState is that you create a machine definition of your finite
state machine, and then you "boot up an instance" of that machine using
`interpret()`, which gives you a "service". Once you `.start()` your service,
you can `.send()` events to it, and generally observe it via listeners or
grabbing a snapshot of the state of the service at any given time.

### `select`

The primary way that you can subscribe to one particular slice of the machine's
"context" (where it stores data) is via a "selector", very much
[like Redux](https://redux.js.org/usage/deriving-data-selectors).

### `matches`

The way you determine what state the machine is in is with a `.matches()`
function. From
[the XState docs](https://xstate.js.org/docs/guides/states.html#state-matches-parentstatevalue):

> The `state.matches(parentStateValue)` method determines whether the current
> state.value is a subset of the given `parentStateValue`. The method determines
> if the parent state value “matches” the state value. For example, assuming the
> current `state.value` is `{ red: 'stop' }`:
>
> ```ts
> state.matches("red"); // => true
> state.matches("red.stop"); // => true
> state.matches({ red: "stop" }); // => true
> state.matches("green"); // => false
> ```

### `can`

A predicate that tells you if you "can" send a given event – i.e. that the
machine is currently listening for that event.

Quoth
[the docs](https://xstate.js.org/docs/guides/states.html#state-methods-and-properties):

> The `state.can(event)` method determines whether an `event` will cause a state
> change if sent to the interpreted machine. The method will return `true` if
> the state will change due to the event being sent; otherwise the method will
> return `false`.

This is especially useful for visually disabling buttons that don't do anything
right now.

### `send`

A state machine isn't much good if you can't send events to it, so we're going
to need to expose the `.send()` function as well.

## `useMachine()`

I've written a ~~hook~~ function called `useMachine()` that takes a state
machine definition and then exposes the above API to be consumed in Solid.
`can`, and `matches` use signals internally, and `select` returns the actual
signal to the data selected. Remember that we must use signals if we want Solid
to rerender parts of our UI.

Oh, and it's all strongly typed AF.

This is what it looks like to consume:

```ts
import { Component, Match, Switch } from "solid-js";
import { useMachine } from "./useMachine";
import { trafficMachine } from "./trafficMachine";
import "./App.css";

export const App: Component = () => {
  const { can, matches, select, send } = useMachine(trafficMachine);
  const cycles = select((s) => s.context.cycles); // defines a signal on first render
  return (
    <div class="app">
      <div>
        <div>Cycles: {cycles()}</div>
        <button onClick={() => send("Reset")}>Reset</button>
      </div>
      <div>
        <Switch>
          <Match when={matches("Stopped")}>
            <div>Stopped</div>
            <button disabled={!can("Start")} onClick={() => send("Start")}>
              Start
            </button>
          </Match>
          <Match when={matches("Started")}>
            <div>Started</div>
            <button disabled={!can("Stop")} onClick={() => send("Stop")}>
              Stop
            </button>
          </Match>
        </Switch>
      </div>
      <div>
        <div class="traffic-light">
          <div class={`red ${matches("Started.Red") ? "on" : "off"}`} />
          <div class={`yellow ${matches("Started.Yellow") ? "on" : "off"}`} />
          <div class={`green ${matches("Started.Green") ? "on" : "off"}`} />
        </div>
      </div>
    </div>
  );
};
```

## State Chart

Just a humble traffic light that counts how many times it's gone through `Red`.

<img
  src="/images/xstate-in-solid-state-chart.png"
  alt="Traffic Light State Chart"
  width={2320}
  height={1592}
/>

## Demo

Click "Open Sandbox" to dig into the actual `useMachine()` code.

<iframe
  src="https://codesandbox.io/embed/xstate-solid-ztsy8t?fontsize=14&hidenavigation=1&theme=dark"
  style={{
    width: "100%",
    height: 500,
    border: 0,
    borderRadius: 4,
    overflow: "hidden",
  }}
  title="XState Solid"
  allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
  sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>

If you inspect the DOM, you'll see that _only the nodes that need to change are
changing_.

<img
  src="/images/xstate-in-solid-dom.gif"
  alt="Traffic Light DOM GIF"
  width={642}
  height={228}
/>

## Conclusion

This was mostly a learning exercise for me. I'd be happy to contribute this code
to initiate an official `@xstate/solid` library. The Stately people, that
maintain XState, know how to contact me.

Thanks for reading, and I hope you learned something.
]]></content>
        <author>
            <name>Erik Rasmussen</name>
            <email>rasmussenerik@gmail.com</email>
            <uri>https://erikras.com</uri>
        </author>
    </entry>
</feed>