Magical React - Recurser Component

Part of the Magical React Components series

This is a component idea I had that I thought was pretty clever. It isn't useful in all cases, but it is something that you can use when you have nested structures with a dynamic depth. This would be helpful if you happen to be rendering a file system.

The component is simple:

export const Recurser = ({ render, getItems, id }) => {
  const Component = render // for react capital convention
  const { items } = getItems(id)
  return <Component id={id}>
    {items.map(itemId => <Recurser render={render} getItems={getItems} id={itemId}/>)}   
  </Component>
}

Let's break this thing down.

The Recurser component takes render which is a react component it renders as. This can be anything and is super useful in that it is flexible. Usually, in the render prop I don't immediately render children. I wait for a click or something to expand and show the next set of children. Otherwise, you will immediately render the entire tree. If that is a large tree structure with a lot of nodes, and the getItems call includes a network call, you are going to be making a lot of calls and rendering a lot of nodes. So keep that in mind.

Next the getItems function. The getItems should be a function that the component can call to get the next set of items. If you have a tree-like structure that only expands if a node isn't a leaf node and you want to improve the snappiness of your UI, you may choose to have this function actually load all grandchildren into your store, and then just return the children in the function. This means when a person expands a branch, grandchildren are already loaded, so you can know whether or not children are expandable or not.

Lastly, the id. The id gets passed into the render component, so that component can connect to a store and get its own data. This means that the render prop should be able to display itself only given an id.

This component goes super well together with a surrounding React context that acts as a store for all the nodes in the recursive tree, probably stored by its id, so that render prop components can connect and get all the data it needs by its id (which again is the only prop it has to know how to render). I tend to store objects of the same type in a large object, with the keys as ids, so that I can pass around a string that a component can use to look up any data it needs in a store in constant time.

So something like

<ContextProvider>
  <Recurser>
    <RenderProp /> // consume the context in the render prop
  </Recurser>
</ContextProvider>

You could write a component that has this type of recursive behavior without creating it generically, and it would probably be simpler to understand. But we have done a bit of work here to create a component and a method for all recursive use cases. You just swap out the render prop, tell the recursive component how to get the next set of children if there is any, and pass a component an id so it can get data and display properly.

Pretty slick if you ask me.

Here is a simple codesandbox that demonstrates a simple example of how it might work. Notice that there is some work to be done on whether or not a node should be even show to be able to be expanded.

As homework try forking the sandbox and adding that functionality. Also consider using swapi.dev to build a tree of related characters (it might be infinite, but fun!) to get an idea of how to use this with asynchronous calls.

I'm here to answer questions or if you need any help!

Photo by Almos Bechtold on Unsplash

Comments (1)

jackyjoy's photo

Nice efforts, Thanks for sharing a Effective Post.