We use cookies to enhance your experience on the site
CodeWorlds

Renderless Components and Functional Patterns

In the NOVA LAB Command Center, not all interface modules need a visual representation. Some are responsible solely for logic — these are renderless components. Let's explore advanced Vue component patterns.

What Is a Renderless Component?

A renderless component is a component that does not render its own HTML — instead, it exposes logic through a slot. The parent component defines all the visual appearance.

1<script setup>
2import { ref, onMounted, onUnmounted } from 'vue'
3
4// Renderless component — clock logic
5const now = ref(new Date())
6let timer = null
7
8onMounted(() => {
9  timer = setInterval(() => {
10    now.value = new Date()
11  }, 1000)
12})
13
14onUnmounted(() => clearInterval(timer))
15
16defineExpose({ now })
17</script>
18
19<template>
20  <slot :now="now" :formatted="now.toLocaleTimeString('en-US')" />
21</template>

Usage:

1<template>
2  <Clock v-slot="{ formatted }">
3    <div class="station-clock">{{ formatted }}</div>
4  </Clock>
5</template>

The

Clock
component has no HTML of its own — it passes data through a scoped slot, and the parent decides on the appearance.

Renderless Pattern with Toggle Logic

A popular pattern — a component managing toggle state:

1<!-- Toggle.vue -->
2<script setup>
3import { ref } from 'vue'
4
5const isOn = ref(false)
6
7function toggle() {
8  isOn.value = !isOn.value
9}
10
11function setOn() { isOn.value = true }
12function setOff() { isOn.value = false }
13</script>
14
15<template>
16  <slot :isOn="isOn" :toggle="toggle" :setOn="setOn" :setOff="setOff" />
17</template>

Usage in different contexts:

1<template>
2  <!-- As a switch -->
3  <Toggle v-slot="{ isOn, toggle }">
4    <button @click="toggle">
5      System: {{ isOn ? 'ACTIVE' : 'OFFLINE' }}
6    </button>
7  </Toggle>
8
9  <!-- As an expandable panel -->
10  <Toggle v-slot="{ isOn, toggle }">
11    <div>
12      <h3 @click="toggle">Diagnostics ▼</h3>
13      <div v-if="isOn">System details...</div>
14    </div>
15  </Toggle>
16</template>

Functional Components

In Vue 3, every component with

<script setup>
is already optimized. But you can also create simple functional components as plain render functions:

1import { h } from 'vue'
2
3// Functional component — no state, only props
4function StationBadge(props) {
5  return h('span', {
6    class: 'badge',
7    style: {
8      color: props.color || '#00ff88',
9      border: '1px solid ' + (props.color || '#00ff88'),
10      padding: '4px 12px',
11      borderRadius: '12px',
12      fontSize: '12px'
13    }
14  }, props.label)
15}
16
17StationBadge.props = ['label', 'color']

Usage in a template:

1<template>
2  <StationBadge label="ONLINE" color="#2ecc71" />
3  <StationBadge label="ALERT" color="#e74c3c" />
4</template>

Render Functions with h()

The

h()
function (hyperscript) creates a VNode — a virtual DOM element:

1import { h } from 'vue'
2
3// h(tag, props, children)
4h('div', { class: 'panel' }, [
5  h('h2', null, 'Title'),
6  h('p', null, 'Panel content')
7])

A component with a render function instead of a template:

1import { h, ref, defineComponent } from 'vue'
2
3const Counter = defineComponent({
4  setup() {
5    const count = ref(0)
6
7    return () => h('div', { class: 'counter' }, [
8      h('p', null, 'Counter: ' + count.value),
9      h('button', {
10        onClick: () => count.value++
11      }, '+1')
12    ])
13  }
14})

Higher-Order Components (HOC)

An HOC is a function that takes a component and returns a new component with added functionality:

1import { h, ref, onMounted } from 'vue'
2
3function withLoading(WrappedComponent) {
4  return defineComponent({
5    setup(props, { attrs }) {
6      const loading = ref(true)
7
8      onMounted(() => {
9        setTimeout(() => {
10          loading.value = false
11        }, 1000)
12      })
13
14      return () => {
15        if (loading.value) {
16          return h('div', { class: 'loading' }, 'Loading module...')
17        }
18        return h(WrappedComponent, { ...attrs })
19      }
20    }
21  })
22}

Usage:

1const MissionPanelWithLoading = withLoading(MissionPanel)

When to Use These Patterns?

| Pattern | When to Use | |---------|-------------| | Renderless component | Reusable logic with flexible UI | | Functional component | Simple, stateless presentational elements | | Render function (h()) | Dynamic DOM structure generation | | HOC | Adding behavior to existing components |

Renderless components and scoped slots are Vue's idiomatic way of separating logic from presentation — especially useful in systems as complex as the NOVA LAB Command Center.

Go to CodeWorlds