Recursive rendering in React
Mohammad Hossain
Posted on November 18, 2022
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
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);
}
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.
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);
}
Now if we pass 5 as n: factorial(5)
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"
}
}
}
}
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>
);
}
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 passdata
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>;
}
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>
);
}
And our DOM will look like this:
Let’s analyze a real-life scenario
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",
},
],
},
],
},
],
},
},
]
This is similar data to a Reddit post. We have post
s, 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>
);
}
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>
);
}
Css:
.forum{
display: flex;
flex-direction: row;
}
.break{ /* this is the line */
margin-left: 40px;
background-color: cadetblue;
width: 2px;
}
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>
);
}
And finally our DOM will look like:
It’s as simple as that. This will work for indefinite amount of nested data without a problem.
Posted on November 18, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.