Few days ago, I wanted to build a Vue component that would be able to display a nested node tree where we don't know the exact nesting number - so it would need to be able to work recursively. I was surprised about the fact that there are not too many resources in the web about it so I decided to write an article about it.
The main idea is to have one component that will accept data property (with unknown data structure for now) and two other properties for summary
an open
; first used for defining the name used in the details
tag and the displayed text, and the second used to see if the node tree is open or not.
I hope you will like it :)
Enjoy!
🟢 Building Recursive components in Vue
In this example, I am using VueUse onClickOutside
utility function to handle outside click action (I just love this utility!).
First, let's define the props that this component will accept:
const props = defineProps({
summary: {
type: String,
default: 'disclosure'
},
open: {
type: Boolean,
default: false
},
content: {
type: Object,
default: () => ({})
}
})
Next, let's define the reactive ref and computed variables:
const computedOpenState = ref(props.open)
const details = ref(null)
const hasNestedChildren = computed(() => props.content.children?.length)
And finally, last in the script tag, let's add function to handle both toggle details and outside click:
const toggleDetails = (event: ToggleEvent) => {
computedOpenState.value = event.newState === 'open'
}
onClickOutside(details, () => (computedOpenState.value = false))
Now, let's add a bit of template to our component. First if the nested content does not have more nested children, we just want to display a paragraph with the name of the summary:
<p v-if="!hasNestedChildren">
{{ summary }}
</p>
In other case, we want to utilise the details
HTML tag:
<p v-if="!hasNestedChildren">
{{ summary }}
</p>
<details
v-else
:open="computedOpenState"
:name="summary"
ref="details"
@toggle="toggleDetails"
>
...
</details>
Inside the details
tag, we want to add following code to handle both displaying the summary and nested node tree:
<summary>
{{ summary }}
</summary>
<template v-if="hasNestedChildren">
<NodeTree
v-for="(child, key) in content.children"
:key
:summary="child.summary"
:content="child"
@click="toggleChildren"
/>
</template>
Below, you can see full code of this component:
<script setup lang="ts">
import { ref, computed } from 'vue'
import { onClickOutside } from '@vueuse/core'
const props = defineProps({
summary: {
type: String,
default: 'disclosure'
},
open: {
type: Boolean,
default: false
},
content: {
type: Object,
default: () => ({})
}
})
const computedOpenState = ref(props.open)
const details = ref(null)
const hasNestedChildren = computed(() => props.content.children?.length)
const toggleDetails = (event: ToggleEvent) => {
computedOpenState.value = event.newState === 'open'
}
onClickOutside(details, () => (computedOpenState.value = false))
</script>
<template>
<p v-if="!hasNestedChildren">
{{ summary }}
</p>
<details
v-else
:open="computedOpenState"
:name="summary"
ref="details"
@toggle="toggleDetails"
>
<summary>
{{ summary }}
</summary>
<template v-if="hasNestedChildren">
<NodeTree
v-for="(child, key) in content.children"
:key
:summary="child.summary"
:content="child"
@click="toggleChildren"
/>
</template>
</details>
</template>
Finally, the component can be used like following:
<template>
<NodeTree v-if="data" :content="data" :summary="data.summary" />
</template>
And that's it! You have fully working recursive node tree component :)
📖 Learn more
If you would like to learn more about Vue, Nuxt, JavaScript or other useful technologies, checkout VueSchool by clicking this link or by clicking the image below:
It covers most important concepts while building modern Vue or Nuxt applications that can help you in your daily work or side projects 😉
✅ Summary
Well done! You have just learned how to build a recursive Vue component.
Take care and see you next time!
And happy coding as always 🖥️
Top comments (0)