And how the modern Web platform’s component model enabled a new kind of editor for WordPress.
This piece is a repost from an old blog I no longer keep up.
The WordPress community is going through a bit of a renaissance at the moment. The Gutenberg project started with the goal of building a new, visual, user-centric block-based editor. Now it encompasses a broader effort to integrate the past decade’s explosion of Web technologies with WordPress for the goal of setting a new standard for the experience that folks looking to create their place on the Web expect.
Naturally, a big part of this effort involves “React” and the paradigms it brings to Web development. I am a big fan of Dan Abramov’s writing on it and was inspired by “React as a UI Runtime” to write something similar in the WordPress editor context and note how many parallels can be drawn between its framework and React.
React and the block editor’s data structure both implement a top-down data/rendering flow. This flow makes changes very easy to reason about but is generally less performant than two-way reactive binding systems. A data change at a high-level node can fire off a chain of updates that snowballs to hundreds of leaf nodes.
Memoization solves the most straightforward cases, but things get more challenging when you need all components to update, and some can and should update before others. In those cases, you need a way for different sections to update concurrently in a non-blocking fashion.
React provides this through its new concurrent mode and a set of APIs that let you declare that some parts of the UI or updates can be interrupted so that other, more pressing updates can execute. Similarly, the block editor has an async mode wrapper for components that, when toggled on, will queue updates until the main thread is idle. It wraps all blocks, toggled on for all blocks that are not part of the current selection because selected blocks usually change with user input, and we want that to reflect in the UI immediately.
Block Serialization and Hydration
Both React and the block editor can generate semantic markup for static display. Suppose we draw a parallel between how React can be used to server render components and how the block editor saves and retrieves blocks as serialized block content. In that case, we can also draw a line between how they hydrate things to continue execution client-side.
React traverses the document and attaches event listeners to the already existent markup, warning about mismatches between what rendered and what the client would have rendered. Likewise, the block editor traverses the serialized block content, parsing it into the various object representations that the engine uses, and warns any mismatches between what saved and what the editor would now save.
React lets you perform 2-pass rendering by triggering a state change upon mounting in the component that needs it when you intend the server to render something different. The block editor has a dynamic block abstraction, which allows you to extract the dynamic parts into a server-side render function used in front end rendering.
When clients run a more recent version of a component or block than the one which saved the content that they are loading, React doesn’t do much besides allowing you to suppress the warnings. The block editor has a block deprecation feature for declaring how to upgrade serialized block content to more recent versions of the block they represent.
When used with Redux, React manages state changes similarly to how the block editor handles attribute changes. Both provide functions for changing values and then rerender everything affected by the transition, from root to leaf.
The React context API allows you to broadcast some values and changes to everything within a context provider boundary. It’s an elegant pattern when the same values need to be accessed by multiple components in a tree at different nesting levels. It also makes it trivial to layer different contexts on top of each other, creating a hierarchical contextual data tree parallel to the component tree.
In the block editor, it powers many features like async mode and inner blocks. An interesting implementation of it is the entity provider. It’s interesting because if you picture the blocks that make up a site, there is a clear hierarchy in the data shown. The compositional nature of nesting blocks allows for patterns like a post block, which sets a post context, and a nested post title block that reads from that context. The post block could then have another post block within it, which references another post, and all of this could be inside a reusable block or template part block. This layered approach to data feels very natural when visually building websites.
These are probably some of the most intuitive abstractions in the block editor. Like React components have children components (and fragments and primitives), the block editor’s blocks have inner blocks. Block implementations choose where to place the slot for them, like React components do in theirs. Having this in place is crucial for building anything besides a trivial scroll-down post or page.
In React, you can create a component that applies a set of predefined props to another component and then reuse it in multiple places.
In the block editor, you can create reusable blocks with a predefined set of attributes.
Both abstractions provide reuse and a single place to make changes.
React has a portals API for rendering children into nodes outside the hierarchy of the containing component.
The block editor leverages this pattern to provide a predefined set of “slots” in the interface in which blocks can render arbitrary extensions by using their corresponding “fills.”
There are still many areas where the block editor could learn and adopt things from React. Especially around batching and concurrent mode optimizations.
For example, React is currently iterating on a progressive/selective hydration process to make trees interactive for users before the entire document is hydrated. It is also useful for massive block editor documents where it doesn’t make sense for the user to have to wait for the parsing of all blocks when most won’t even be in the window.
The entity provider APIs could also learn a thing or two from React Suspense to more painlessly set up hierarchical loading sequences.
All in all, both React and the block editor continue to evolve rapidly. I am excited to see what new ideas and patterns they discover.