React Rendering as OCaml Modes
I watched this great
talk on adding low level
memory management into OCaml with modes, and it got me thinking. Modes
have an interesting behavior. They’re essentially a different
annotation on values that’s completely orthogonal to types. They can
be used to annotate whether a type is stack allocated (local
), or
heap allocated (global
). They’re deep, i.e. a type with a global
mode can only contain other types with a global
mode. This is
important for avoiding use-after-free bugs, since a local
mode value
is stack allocated, and therefore can deallocated once the function
returns. However, there’s a natural subtyping relation in that a value
with a global
mode can be used as a local
value (because the
lifetime of a global
value is always longer than that of a local
one). If this doesn’t make sense, I’d read the blog
post for a
better explanation.
Anyways, what else is orthogonal to types, deep, and has a natural subtyping relation? React components! React components can be rendered in two places: client and server. A client component can only render client components (deepness). However, you can treat a client component as a server component (subtyping). In other words, you can have a server component render a client component, but not vice versa.

A server component can render a server component

A server component can render a client component

But a client component cannot render a server component
The use client
directive is really an indicator of a mode. It’s
telling the bundler that this code is in the client mode and therefore
certain features are enabled like hooks. It’s also indicating to the
bundler that it should error if any server components get imported
into this client code.
That’s cool. But what’s the point? First, there’s more than just
server and client modes in the React ecosystem. There’s also build
time. It’s quite common to have pages that are statically generated,
whether that’s using an API queried at build time, or incrementally
re-rendering
them
on the server every few seconds. Perhaps you could have a build
mode
that refers to components that are rendered at build time. Therefore
the subtyping relation would then be: build < server < client
.
You could also think about caching using modes. If a client component
gets some props from a server component, those props are static since
the server component can’t re-render. In other words, props carry
modes and they indicate the props’ mutability. If a component relies
only on server
mode props, then we can cache more aggressively.
This can also help with linting, since you can track the values and
their modes and make sure you’re not trying to use the wrong
mode. Yes, there are linters that already check for use client
violations, but they’re not as granular.
I’m curious to see if there are other ideas for modes in different contexts. Or maybe more modes for rendering? Maybe you could make a custom mode for an API and invalidate components depending on whether they have this mode? Originally I was trying to model this using Rust lifetimes, but I find OCaml modes are easier to reason about.