Arena channel pages are now powered by a (much improved) implementation of block rendering that I initially experimented with in arena-next. More info in the PR.

The biggest benefit right now is that scroll position in a channel grid is maintained when navigating back/forward in your browser. This feature is a bigger deal than it initially seems, I recommend trying it out yourself. Maintaining context of where your were in a channel really helps reduce the mental overhead of deep Arena exploration sessions.

Another benefit, for the future:

This will allow us to build out completely new UIs for channels, now that the data business logic is completely abstracted from UI

Block titles aren't live yet, but here's a pretty cool view into the changes I've made to get them working: https://github.com/tvler/arena-next/compare/5f4d4af04c3663e5baefc62f5e8a92448715bfda..d6e3bc7bc272c60d8388c2749e05dcb90a8aea01

Basically, every block variant is now responsible for emitting its content, href and title values. These values can change at any time and can handle a block's loading state just by emitting nothing for all these values.

These values are rendered through a renderProps pattern, where the parent Block passes down a function to the variant expecting it to be called with the variant's values. Through some typescript magic, I'm able to enforce that the variants actually call & return this renderProp instead of just returning normal JSX. This is done by casting the renderProps return value as a slight extension of a ReactElement, and enforcing that a variant component returns this value instead of normally-inferred JSX.

export interface BlockVariantComponentChildrenReturn
  extends React.ReactElement {
  __isBlockVariantComponentChildrenReturn: true;
}

Channel details are now live! Sample: arena-next.vercel.app/channel/a-drop-of-love-in-the-cloud

Coming up: Grid improvements (block titles & mobile optimizations)

3

Blocks

React API

A block is a component that can render anything that is connectable, as well as users and groups. Blocks are usually found alongside other blocks in a grid-like layout, and further backing this, don't contain any intrinsic dimensions besides being a flex element.

Every variant of a block can be expressed as an enum like this:

enum BlockVariant {
  user,
  channel,
  group,
  text,
  image,
  link,
  embed,
  attachment,
}

An empty block can be created by rendering a Block with no props:

<Block />

An empty block is used as a loading state, as well as a data-error state.

To render a block with actual content, you pass in 2 props:

type BlockProps = {
  id: number,
  variant: BlockVariant,
}

These two props are all that's required to render every type of block in Are.na. You don't need to pass in a photo URL for image blocks or a filename for attachments. The entry-point for each block is exactly the same for every variant and has been reduced to the smallest representation possible. This keeps the actual React application tree extremely small and readable, and also has performance benefits since the id and variant props are both primitive values that keep reference equality between render cycles.

Since blocks are rendered at such a high rate, performance optimization like maintaining a small memoizable API are extremely important. Other optimizations have been considered as well, the 2nd biggest one probably being that blocks are not allowed to make network requests. The networking responsibility is delegated up to the containing grid element. This leads to the question: How does a block's actual content get injected into the component if it's not passed down by props or loaded from a network?

Querying block contents

arena-next was built in a way that a cache of graphql queries made by Apollo is stored in a user's browser, which persists between page transitions. As a user makes more and more graphql queries, Apollo builds up this cache in such a way that a second graph is created in the browser, which is shaped like a subset of the graph that exists on the server.

A block is intrinsically expected to be rendered along-side other blocks in a grid-like container. Since that grid component is in charge of rendering many blocks, it leads to the idea that it can also be in charge of making paginated network requests to the server based on which blocks are being rendered at that time.

Inside of a block, we still use the exact same GraphQL query API but specify that we can only make queries to the cache. The block's query shares the same exact data fragment as the grid's pagination query, so we can be certain that the block's requested data will be correctly loaded. Apollo ensures that query-data maintains reference equality and immutability, which means that as the graph continues the grow and be modified, a block will only re-render if its associated data changes.

To me this feels like a very graph-first way of building an application, where the design of the graph is at the forefront of the frontend code instead of being obfuscated by a middle layer of props or global state.

Two more ways to explore arena are now live, both relating to channels. A good starting point to test it out is the checking out the channel "cool channel names/channels I like" and following through to all the connections.

  1. The channel page header now has the channel owner in its taxonomy, with the ability to navigate to the owner's page (clicking only works for users now, not groups).

  2. You can now click on a channel block to be taken to its channel page.

It's hard for me to describe the underlying data-structure of arena, but something so clear is that it has an insanely wide breadth, and the ease of exploring the breadth of it is what makes it so powerful. I'm slowly starting to feel like this project is hitting this same feeling of discovery and that's super exciting & motivating for me.