Most modern social networks include a feature where users can reply to a comment by commenting on that specific comment. If we visualize that, the data for our comments would look like the structure below:
- Comment A - comment a1 - comment a12 - comment a2 - Comment B - Comment C
Comment A
has sub-comments comment a1
and comment a2
. In turn, comment a1
has sub-comment comment a12
, which could also have its own sub-comments.
With this structure, we could have a comment with an infinite number of layers of sub-comments. You’re probably already familiar with this method of structuring data, which is known as a tree. For example, think of the directories on your PC where a folder could have sub-folders and so on.
In this article, we’ll explore how we can use recursive components in Vue to manage tree-like structured data. It’s hard to talk about Vue recursive components without talking about recursion, so let’s review the basics first before exploring what a recursive component is and learning how to create one in Vue.
In its simplest form, recursion is the term we use to refer to a function that calls itself. For example, consider the function below:
function sum_numbers(arr, n) { return sum_numbers(arr, n - 1) + arr[n - 1]; }
Although it is faulty, the function above could be considered recursive because it references itself within its body. However, this definition is not all-encompassing. Recursion is an approach to problem-solving. It is based on the premise that given a problem, we could find its solution if we know the solution to its sub-problem.
For example, the sum_numbers
function above finds the sum of all the numbers in a given array, arr = [1, 2, 3, 4, 5]
. In the sum problem, if we know the sum of all the numbers before five, then we can reduce our problem to the sum of numbers in arr
equals the sum of all the numbers before the last element and the last element.
The expression return sum_numbers(arr, n - 1) + arr[n - 1];
in the sum_numbers
function we defined above does exactly what we just described .
To picture how our function will execute from beginning to the end given the input, [1, 2, 3, 4]
, consider the code below:
**sum_numbers([1, 2, 3, 4], 4) | calls |** **sum_numbers([1, 2, 3], 3) + 4 | calls | sum_numbers([1, 2], 2) + 3 | calls | sum_numbers([1], 1) + 2 | calls | sum_numbers([], 0) + 1 --** WE HAVE A PROBLEM HERE
You may notice that we still have a problem; our recursive function tries to add an empty list to a number. In fact, the bigger problem is that our recursive function will keep calling itself infinitely.
To ensure that our recursive function does not execute to infinity, we need a base case. You can think of the base case as the point at which we want our function to stop calling itself. In our case, the sum_numbers
function should stop calling itself if it has just one number in it. If the array has just one number left, then there’s nothing to multiply that number with, in which case we just return the number.
With that knowledge, we can now modify our function to have a base case as shown below:
// where n is the length of arr function sum_numbers(arr, n) { if(n <= 1){ //Base Case return arr[0]; }else{ return sum_numbers(arr, n - 1) + arr[n - 1]; } }
That’s fundamentally what recursion is about, but what’s the connection to Vue recursive components?
Remember, components in Vue are reusable Vue instances. Most of the time, when we create a component in Vue, it’s just so we can reuse it in other places. For example, think of an ecommerce website where you could have products displayed on multiple pages. You could have just one Product Component
that you could render on different pages as opposed to repeating the code for the Product Component
on every page it’s needed.
A Vue component is considered recursive if it references itself within its own template. Recall that we mentioned that components are designed to be reused in other components. Recursive components differ from regular components in this regard. In addition to being reused in other places, recursive components also reference themselves within their templates.
Why would a component reference itself? When you render a component in some other component, the guest component is the child, and the component within which it is rendered is the parent.
In our earlier Product Component
example, that component could have ProductReview
as its child component. In this case, it makes sense that we have two different components for the entities those components represent because Product
and Reviews
are different in every respect.
But, if we take the case of a Comment
and Sub-comment
, then the story changes. Both components represent the same thing. A sub-comment is also a comment. Therefore, it doesn’t make sense that we have two different components for Comment
and Sub-comment
because they are structurally the same. We could just have one Comment
component that references itself. Still too abstract? See the snippet below:
<template> <li class="comment"> <span>{{ comment.comment }}</span> <comment v-for="reply in comment.replies" :comment="reply"></comment> </li> </template> <script> export default { name: "comment", props: { comment: Object } }; </script>
Although it is buggy, the component above could be considered recursive because it references itself. Like recursive functions, a recursive component too must have a base case, which is missing in the code above. One other important thing to note here is that for a component to be able to reference itself, the name
option must be defined. In our case, ours is name: "comment"
.
Now that we understand what recursive components are in Vue, let’s see how we could use them to build a nested comments interface.
First, we’ll initialize a new Vue project. Launch your system’s terminal window and navigate to any directory you want to create your Vue project in. Run the command vue create nested-comments
in your terminal. nested-comments
is our project’s name.
You’ll then be prompted to add configuration. Go ahead and select the defaults. Once you’re done, a project with the structure shown in the image below will be generated for you:
Run the vue serve
command in your project’s root directory to fire up Vue’s development server and test things out.
To render our nested comments to the DOM, first, delete all the files in src/views
and src/components
. Then, create src/components/Comment.vue
. The Comment.vue
component would house our recursive component code.
Copy and paste the code below in your Comment.vue
component:
<script> export default { name: "recursive-comment", props: { comment: { type: String, required: true, }, replies: { type: Array, default: () => [], }, }, }; </script>
In the code snippet above, we’ve named our component recursive-component
. Remember, in Vue, a recursive component must have a name
declared. Furthermore, our component would expect the props comment
and replies
to be passed to it anywhere it’s referenced.
Now that we have the script
section of our component set up, copy and paste the code snippet below in your component directly above the script section:
<template> <li> <span class="comment">{{ comment }}</span> <ul class="replies" v-if="replies.length"> <div v-for="(item, index) in replies" :key="index"> <recursive-comment v-bind="{ comment: item.comment, replies: item.replies, }" /> </div> </ul> </li> </template>
The code snippet above sets up the template
section of our component. The recursive-comment
component references itself within its own template. Fundamentally, this makes our Comment component
recursive. v-if="replies.length"
is our base case. Once that condition evaluates to false
, the recursive call would terminate.
Next, we just need to render our component in App.vue
. To do so, override the content of src/App.vue
in your project with the code snippet below:
<template> <ul v-for="(item, index) in comments" :key="index" class="comments"> <Comment v-bind="{ comment: item.comment, replies: item.replies, }" /> </ul> </template> <script> import Comment from "@/components/Comment"; export default { data: () => ({ comments: [ { comment: "First comment", replies: [ { comment: "sub-comment 1 for comment 1", replies: [ { comment: "sub-sub-comment 1", replies: [ { comment: "sub-sub-sub-comment 1", }, { comment: "sub-sub-sub-comment 2" }, ], }, { comment: "sub-sub-comment 2" }, ], }, { comment: "sub-comment 2 for comment 1" }, ], }, { comment: "Second comment", replies: [ { comment: "sub-comment 1 for comment 2", replies: [ { comment: "sub-sub-comment 1" }, { comment: "sub-sub-comment 2" }, ], }, { comment: "sub-comment 2 for comment 2" }, ], }, ], }), components: { Comment, }, }; </script> <style> .comments ul { padding-left: 16px; margin: 6px 0; } </style>
In the code snippet above, we’ve initialized our list of comments with some dummy data, but ideally, you’d be pulling this data from your database. We then rendered our Comment.vue
in the App.vue
template, passing each component to it as prop
.
Save your changes and run your server with vue serve
. Finally, point your browser to http://localhost:8080
. You should see the interface below:
Although our example isn’t what a typical comment section would look like, our goal was to explore how we could leverage the power of recursive components in Vue to render nested data, like comments, to the DOM.
We saw how we could do that by creating a component that references itself within its own template. This recursive approach is especially useful when rendering data entities that may seem different but are structurally the same. For example, take the case of our comments and replies.
At first glance, it would appear that we’d need two components, one for comments and the other for replies. But, with the recursive approach, we were able to render both with one component. Most importantly, our component would render all comments and replies until it hits the base case. I hope you enjoyed this article, and be sure to leave a comment if you have any questions.
Debugging Vue.js applications can be difficult, especially when there are dozens, if not hundreds of mutations during a user session. If you’re interested in monitoring and tracking Vue mutations for all of your users in production, try LogRocket.
LogRocket is like a DVR for web and mobile apps, recording literally everything that happens in your Vue apps, including network requests, JavaScript errors, performance problems, and much more. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred.
The LogRocket Vuex plugin logs Vuex mutations to the LogRocket console, giving you context around what led to an error and what state the application was in when an issue occurred.
Modernize how you debug your Vue apps — start monitoring for free.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowBuild scalable admin dashboards with Filament and Laravel using Form Builder, Notifications, and Actions for clean, interactive panels.
Break down the parts of a URL and explore APIs for working with them in JavaScript, parsing them, building query strings, checking their validity, etc.
In this guide, explore lazy loading and error loading as two techniques for fetching data in React apps.
Deno is a popular JavaScript runtime, and it recently launched version 2.0 with several new features, bug fixes, and improvements […]
One Reply to "Vue recursive components: Rendering nested comments"
@Nyior Clement, great article: Vue recursive components: Rendering nested comments
I have a question in regard of that. In my case, I am creating a component to render categories and subcategories. The recursive component fits very well, so, in our case, we can have multiple levels but do not want to render them in a tree-like structure. The goal is, that when the user clicks the top-level category, second-level category children will be open, and when the user clicks one of the second-level category items (row), the third row with a third-level category children will show up, and so on. Do you have a better approach to solving this challenge using native VueJs? maybe using render function inside the component? I am using vuejs 2.7 and found portal-vue to render the items in a different DOM outside the component, but it isn’t good enough. Maybe teleport for vuejs3 could work.
Thanks in advance,
Joel