DEV Community

Cover image for πŸ§‘β€πŸ³ Cooking with Vue 3: Nested (Recursive) component rendering
Dmitrii Zakharov
Dmitrii Zakharov

Posted on • Edited on

πŸ§‘β€πŸ³ Cooking with Vue 3: Nested (Recursive) component rendering

In one of my earlier posts, I briefly mentioned the concept of nested component rendering in Vue. Despite this technique's usefulness, there aren't many clear examples online showing how and when to use it effectively.

So let's fix that.


The problem

Let's say we're building a dynamic table. The table has rows and columns, and each cell needs to render different content intelligently depending on its data type.

For example:

  • If the value is a date, it should format it.
  • If it's a number and marked as a "price", it should be formatted as currency.
  • If it's a boolean, it should display "yes" or "no".
  • If it's a nested array of cells, it should recursively render a set of cells inside itself.

We want a single, reusable TableCell component that can handle all these cases on its own.


The goal

Our goal is to create a flexible <TableCell /> component that receives a type and a value as props and renders the content accordinglyβ€Šβ€”β€Ševen recursively, if needed.


The implementation

We'll use Vue 3's <script setup> syntax and TypeScript for better type safety.

Here's a simplified version of what our component might look like:

<template>
  <div
    :class="`_${data.type}`"
    class="tableCell"
  >
    ...

    <template v-if="data.type === TypesEnum.Boolean">
      {{ data.value ? 'yes' : 'no' }}
    </template>

    <template v-else-if="data.type === TypesEnum.Primitive">
      {{ data.value ?? '-' }}
    </template>

    <template v-else-if="data.type === TypesEnum.Array">
      <!-- Recursive call -->
      <TableCell
        v-for="(item, key) in data.value"
        :key="key"
        :data="item"
      />
    </template>
  </div>
</template>

<script lang="ts" setup>
import { type Component } from 'vue';

/** Important to ensure the component knows where to get component from. */
defineOptions({
  name: 'TableCell',
});

enum TypesEnum {
  Array = 'array',
  Boolean = 'boolean',
  Component = 'component',
  Date = 'date',
  Icon = 'icon',
  Price = 'price',
  Primitive = 'primitive',
}

type PrimitiveValue =
  | { type: TypesEnum.Boolean; value?: boolean }
  | { type: TypesEnum.Component; value: Component }
  | { type: TypesEnum.Date; value: Date }
  | { type: TypesEnum.Icon; value: Component }
  | { type: TypesEnum.Price; value: number }
  | { type: TypesEnum.Primitive; value?: boolean | number | string };

type ComplexValue = {
  type: TypesEnum.Array;
  value: PrimitiveValue[];
};

defineProps<{
  data: ComplexValue | PrimitiveValue;
}>();
</script>
Enter fullscreen mode Exit fullscreen mode

Comments

Parts responsible for formatting were intentionally left out of the example to keep it simple. However, some basic ones were included to give a more complete picture.
It’s also important to ensure the component knows where to get its data from.

When to use it

Recursive components are ideal when:

  • You're dealing with tree-like or nested data.
  • You want to minimize repeated boilerplate logic for rendering similar structures.
  • You need consistent behavior and formatting rules for the same data type, even when nested.

Just be mindful of recursion limits, performance, and base case fallbacks.

🧠 Final thought

I hope you found this useful and maybe even a bit fun to explore.
If it sparked your interestβ€Šβ€”β€Šstay tuned and consider subscribing to my blog!

See you in the commentsβ€Šβ€”β€Šand thanks for reading! πŸš€

Top comments (0)