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 rendering a server component

A server component can render a server component

A server component rendering a client component

A server component can render a client component

A server component rendering 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.