DEV Community

Cover image for Building Recursive components in Vue
Jakub Andrzejewski
Jakub Andrzejewski

Posted on

Building Recursive components in Vue

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: () => ({})
  }
})
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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))
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

Finally, the component can be used like following:

<template>
  <NodeTree v-if="data" :content="data" :summary="data.summary" />
</template>
Enter fullscreen mode Exit fullscreen mode

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:

Vue School Link

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)