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.

···