DEV Community

Cover image for Recursive Lists in React
Blake Wight
Blake Wight

Posted on

Recursive Lists in React

Recently I have been working a lot with recursive trees and displaying them in React. While working with them I came up with a pattern that I like, and that I want to share with those that run into this pattern. They can overcome this hurdle in a way that can be reused quite easily.

To start, you want to create a context that has all of the functionality of each node in your recursive tree might need. Lets for the sake of this article assume we are creating a nested keyword structure that we can use to tag stuff (pictures, tweets, posts, etc…) with. Nested meaning that keywords that have children that are selected, make themselves selected (dog -> retriever, selecting retriever, also tags it with dog). Great. The context now has functions for selecting, and deselecting keywords.

Now, you create a keyword component that consumes the context and displays something, maybe what I would call a row. The only requirement I would make here is that you should have the component accept a callback that returns a function to retrieve data (in this case, the next page of data). You pass this component as a render method to the recurser.

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

In general, your structure would look something like this:

<ContextProvider>
  <Recurser>
    <ContextConsumer&Render />
  </Recurser>
</ContextProvider>

This assumes some things.

  1. You have to pass a function (or a react hook) to the Recurser component so that it can get children given the parent’s id. A function can work, but a dynamic hook can let you connect to redux, or consume a react context.

  2. The context provider holds the state for knowing what an item is tagged with, and provides functions for (in the case of keywords) changing that state.

  3. The context consumer and render component connects to the context provider and probably another context or redux state. This would allow the render component to get (again for keywords) the keyword’s name given the id. In the case of more complex objects, you could retrieve all properties of that object given an id.

This allows you to have different recursive lists, but keep the way they are nested and loaded. This could be good for a component in a keyword setup page, to set up possible keyword structures, and a second keyword component for tagging items with keywords. The lists can have separate functionality and layout but keep the recursion.

It seems pretty trivial and seems small enough it’s almost not worth implementing, but it has saved us a lot of time as you can change it to be more complex which for us means wrapping the children in an infinite scroll component. Wrapping children in an infinite scroll allows us to lazy load children, but requires more to be returned from getItems (for us that is stuff like {loading: bool, done: bool, loadMore: func, trigger: string}).

If this was confusing, please ask questions! This is my first post like this.

Check out a codepen example here: https://codesandbox.io/embed/fervent-beaver-9btj1

Note: The codepen is a pretty big example, but I tried to replicate the fact that in redux you get data with selectors, and you don’t get the promise (or observable, or callback or whatever) in the component itself. It uses a custom hook in the index file that has comment explanations.

Top comments (0)