DEV Community

Cover image for Recursive rendering in React
Mohammad Hossain
Mohammad Hossain

Posted on

Recursive rendering in React

In one of my very first tech interviews earlier this year, I was given an interesting problem and I terribly messed it up. Meaning I couldn’t solve it. And the solution to the problem was recursion but in react. So I decided to write a blog about it.

What is Recursion?

In computer science, recursion is a method of solving a computational problem where the solution depends on solutions to more minor instances of the same problem. Recursion solves such recursive problems by using functions that call themselves from within their own code.

For example, a factorial(5) is 5*4*3*2*1. If we want to solve it recursively, we can say a factorial(5) is 5 times the factorial(4): 5*4!

And factorial(4) is 4*3! until we reach the number 1.

5!
    5 * 4!
        5 * 4 * 3!
            5 * 4 * 3 * 2!
                5 * 4 * 3 * 2 * 1
Enter fullscreen mode Exit fullscreen mode

We eventually reach the same solution which is 5 * 4 * 3 * 2 * 1. If we have to write a function for factorial we can write the following:

function factorial(n){
   return n * factorial(n  1);
}
Enter fullscreen mode Exit fullscreen mode

If we invoke the factorial() function and pass an integer n, it will keep calling the factorial() * n, and in each invocation, n will be n-1.

Image description

However, we have a serious problem. We did not tell our function when to stop and therefore our factorial() function will end up in an infinite loop and we will never reach the solution. Telling the function when to terminate is called a base case. Base case is always required when defining a recursive function. The base case in this case would be n = 1. Since factorials of numbers that are less than 1 don't make any sense, we stop at the number 1 and return the factorial of 1 (which is 1).

function factorial(n){
    if(n === 1)
        return 1;
    else
        return n * factorial(n  1);
}
Enter fullscreen mode Exit fullscreen mode

Now if we pass 5 as n: factorial(5)

Image description

How do we handle recursion in React?

What would a recursive call look like in react? Let’s say you are making an API call and the response data has children that is also the same key and value format.

{
  text: "some text",
  data: {
      text: "some text",
      data: {
        text: "some text",
        data: {
          text: "No more data here"
        }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

As you can see the object data is continuously stacked on top of another data object. We can have indefinite amount of stacked data. Using recursion we will render the data.

Say that our component is called <RecursivelyRenderComponent> which takes the data object as a prop and will render the text inside the data object.

function RecursivelyRenderComponent({ data }) {
  return (
    <div>
      <h1>{data.text}</h1>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

This will return the first instance of the text. But what about the nested ones?

We need to recursively return the same component within itself for the amount of time we encounter data.

Our recursion will look like:

  • Base case: If data does’t exist we stop recursion
  • Recursion rule: If data exist render <RecursivelyRenderComponent> again and pass data as a prop.

Our component will look like this:

function RecursivelyRenderComponent({ data }) {
  if (data.data)
    return (
      <div>
        <h1>{data.text}</h1>
        <RecursivelyRenderComponent
          data={data.data}
        ></RecursivelyRenderComponent> {/*calling the same component recursivly */}
      </div>
    );
  else return <h1>{data.text}</h1>;
}
Enter fullscreen mode Exit fullscreen mode

We can simplify it more using ternary:

function RecursivelyRenderComponent({ data }) {
  return (
    <div>
      <h1>{data.text}</h1>
      {data.data ? (
        <RecursivelyRenderComponent
          data={data.data}
        ></RecursivelyRenderComponent> {/*calling the same component recursivly */}
      ) : null}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

And our DOM will look like this:

Image description

Let’s analyze a real-life scenario

Image description

This is a thread from a random Reddit post. As seen in the picture a Reddit post can have multiple replies, and the replies can also have multiple replies. The replies to a post can be heavily nested. In this example, each new thread has a space and a line indicating the parent reply. Similar to our previous example of <RecursivelyRenderComponent/>, this type of data could be handled using recursion.

How do we render reddit style replies?

Sample response data:

const data = [
  {
    post: {
      id: 1,
      author: "goodKitty",
      title: "A day in a life of a kitten!",
      text: "Claws in your leg the best thing in the universe is a cardboard box lie on your belly and purr when you are asleep so chase ball of string yet i like big cats and i can not lie wake up human for food at 4am. Ask for petting stand with legs in litter box, but poop outside yet climb leg, yet favor packaging over toy. Stare at wall turn and meow stare at wall some more meow again continue staring the dog smells bad miaow then turn around and show you my bum and cry louder at reflection yet missing until dinner time lasers are tiny mice.",
      reply: [
        {
          author: "KittyLord",
          text: "Love it!",
          reply: [
            {
              author: "Hungrycat22",
              text: "Meow To!",
              reply: [
                {
                  author: "catnipDealer",
                  text: "purrrrrrrr!!",
                  reply: [
                    {
                      author: "KittyLord",
                      text: "Purrfect!",
                    },
                  ],
                },
              ],
            },
            {
              author: "Hungrycat22",
              text: "Mice!!!",
            },
          ],
        },

        {
          author: "CatM0m",
          text: "I have a cat!",
          reply: [
            {
              author: "dirtyKitten",
              text: "I have 2 cats",
              reply: [
                {
                  author: "CatM0m",
                  text: "Hahahaha",
                },
              ],
            },
          ],
        },
      ],
    },
  },
]
Enter fullscreen mode Exit fullscreen mode

This is similar data to a Reddit post. We have posts, which contains replies, and the replies contain more replies. The replies should be rendered under the post and should keep nesting under other replies until there are no replies in the data.

function Reply({ reply }) {
  return (
    <div>
        {reply.author} replied
      </p>
        {reply.text}
      </p>
      <div>
         {reply.reply
           ? reply.reply.map((reply) => <Reply reply={reply}></Reply>)
           : null}
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Our <Reply/> component takes a prop reply, it renders the author and text of the reply and check if the data has any reply inside. If there is a reply field, we map through it (since reply returns an array) and re-render the <Reply/> for each element of the array and pass the element as a prop: reply.reply.map((reply) => <Reply reply={reply}></Reply>).

If there is no reply we stop the recursion and that’s our base case.

Adding some forum style spacing to it.

function Reply({ reply }) {
  return (
    <div>
      <p
        style={{
          marginLeft: "10px",
          textDecoration: "underline",
          color: "grey",
          fontStyle: "italic",
        }}
      > 
        {reply.author} replied
      </p>
      <p
        style={{
          marginLeft: "10px",
                  backgroundColor: "beige",
          padding: '20px'
        }}
      >
        {reply.text}
      </p>
      <div className="forum">
        <div className="break"></div>
        <div>
          {reply.reply
            ? reply.reply.map((reply) => <Reply reply={reply}></Reply>)
            : null}
        </div>
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Css:

.forum{
  display: flex;
  flex-direction: row;
}

.break{  /* this is the line */           
  margin-left: 40px;
  background-color: cadetblue;
  width: 2px;
}
Enter fullscreen mode Exit fullscreen mode

Let’s also render the post data in our <App/> component and render the replies.

function App() {
  return (
    <div className="App">
      <h1>{data[0].post.title}</h1>
      <p>By: {data[0].post.author}</p>
      <p>{data[0].post.text}</p>

      {data[0].post.reply
        ? data[0].post.reply.map((reply) => <Reply reply={reply}></Reply>)
        : null} {/* we are mapping through the direct replies to the post */}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

And finally our DOM will look like:

Image description

It’s as simple as that. This will work for indefinite amount of nested data without a problem.

Top comments (1)

Collapse
 
omar3o3 profile image
omar3o3

Great read, love how you tied in real world examples to make the content easier to visualize in its potential uses.