VueConf US 2025 — Epicmax is speaking!Register now →
Epicmax
June 12, 2024· by Epicmax Team

Fixing Vue SFC templates for React developers

Coming to Vue from React? Learn how to reuse template blocks in Vue SFCs with VueUse's createReusableTemplate — the closest thing to JSX render functions.

Vue.jsReactVueUse
Fixing Vue SFC templates for React developers

Moving from React to Vue.js can be tricky. This article shares tips to help you get comfortable with Vue, especially with reusing templates. Learn how to make your Vue.js development smoother with VueUse.

The pain with Vue templates

One of the most common bummers with templates comes from the inability to define a template part and reuse it across the template — for example, when some block needs to appear in different places in the markup depending on a condition.

It doesn't always make sense to extract a part into a separate component of its own. Even with a separate component it's quite common that they need to be passed numerous props, and simply duplicating prop declarations multiple times is too much repetition that grows your template and leaves you open to bugs: when a new prop needs to be added to all of the occurrences it's easy to overlook one (and then debug it for half an hour 🥲).

Let's consider a case where you need to render some template block in the header or footer depending on whether the view is compact. This is not a problem in React, where you can easily do something like this:

// Component.jsx

const block = (
  <div
    class="block"
    onClick={handleClick}
  >
    Block that should be reused
  </div>
);

return (
  <div class="container">
    <div class="header">
      Header content
      {!isCompact && block}
    </div>
    <div class="footer">
      Footer content
      {isCompact && block}
    </div>
  </div>
);

With Vue, by default you have no other option but to duplicate the block:

<template>
  <div class="container">
    <div class="header">
      Header content
      <div v-if="!isCompact" class="block" @click="handleClick">
        Block that should be reused
      </div>
    </div>
    <div class="footer">
      Footer content
      <div v-if="isCompact" class="block" @click="handleClick">
        Block that should be reused
      </div>
    </div>
  </div>
</template>

What is VueUse

VueUse is a utility library aimed at solving common use-cases, packed with useful composables and other helpers — similar to react-use in the React ecosystem.

Reusing parts of the template

VueUse provides a createReusableTemplate function that can help to DRY your templates. As it's a separate package and not part of the Vue framework API, it needs to be installed:

npm i @vueuse/core

Check out the installation guide for instructions for your package manager of choice or meta-framework (if you're using Nuxt).

To reuse markup you need to first call createReusableTemplate() in the <script> section:

<script setup lang="ts">
import { createReusableTemplate } from '@vueuse/core'
const [DefineTemplate, ReuseTemplate] = createReusableTemplate()
</script>

It returns two component constructors: DefineTemplate to wrap the template part you want to reuse, and ReuseTemplate to render the part in place. You can then use them in <template> like so:

<template>
  <DefineTemplate>
    <div class="block" @click="handleClick">
      Block that should be reused
    </div>
  </DefineTemplate>
  <div class="container">
    <div class="header">
      Header content
      <ReuseTemplate v-if="!isCompact" />
    </div>
    <div class="footer">
      Footer content
      <ReuseTemplate v-if="isCompact" />
    </div>
  </div>
</template>

<DefineTemplate> must be used before <ReuseTemplate>.

You can't, however, use the return of one createReusableTemplate() call to reuse different blocks simultaneously:

<template>
  <DefineTemplate>
    <div>Reused block A</div>
  </DefineTemplate>
  <ReuseTemplate />
  <!-- registers another template instead -->
  <DefineTemplate>
    <div>Reused block B</div>
  </DefineTemplate>
  <!-- will now render new template -->
  <ReuseTemplate />
</template>

<!-- Output: -->
<!-- Reused block A -->
<!-- Reused block B -->

So instead you would need to call createReusableTemplate() for each individual reused piece you want to use simultaneously in the markup:

<script setup>
import { createReusableTemplate } from '@vueuse/core'

const [DefineBlockA, ReuseBlockA] = createReusableTemplate()
const [DefineBlockB, ReuseBlockB] = createReusableTemplate()
</script>

<template>
  <DefineBlockA>
    <div>Reused block A</div>
  </DefineBlockA>
  <DefineBlockB>
    <div>Reused block B</div>
  </DefineBlockB>
  <!-- the two can exist simultaneously -->
  <ReuseBlockA />
  <ReuseBlockB />
</template>

Analogue to React render functions

Having the ability to reuse the same template part is good, but with JSX in React one can also define a render function to dynamically pass different props to the reused piece:

const renderBlock = (className) => (
  <div
    class={`block ${className}`}
    onClick={handleClick}>
    Block that should be reused and customized.
  </div>
);

return (
  <div class="container">
    <div class="header">
      Header content
      {!isCompact && renderBlock("full")}
    </div>
    <div class="footer">
      Footer content
      {isCompact && renderBlock("compact")}
    </div>
  </div>
);

Vue provides scoped slots as the general alternative to React JSX render functions, and createReusableTemplate supports this feature to pass data to the reused template. You can use the same v-slot you would normally use to pass data from a child component back to the parent:

<script setup lang="ts">
import { createReusableTemplate } from '@vueuse/core'

const [DefineTemplate, ReuseTemplate] = createReusableTemplate<{ className: string }>()
</script>

createReusableTemplate accepts a generic type argument for typing the data passed to the template, similar to specifying a render function signature. You can then bind props to ReuseTemplate and access them in the reusable part inside DefineTemplate via v-slot:

<template>
  <DefineTemplate v-slot="{ className }">
    <div :class="['block', className]" @click="handleClick">
      Block that should be reused and customized.
    </div>
  </DefineTemplate>
  <div class="container">
    <div class="header">
      Header content
      <ReuseTemplate v-if="!isCompact" className="full" />
    </div>
    <div class="footer">
      Footer content
      <ReuseTemplate v-if="isCompact" className="compact" />
    </div>
  </div>
</template>

Render functions in JSX can also accept JSX as arguments to be rendered somewhere inside. No worries — createReusableTemplate has you covered here as well, as ReuseTemplate can be passed slots that can be accessed via $slots on v-slot and rendered inside DefineTemplate with dynamic components syntax:

<template>
  <DefineTemplate v-slot="{ className, $slots }">
    <div :class="['block', className]" @click="handleClick">
      Block that should be reused with custom content.
      Custom content:
      <!-- To render the slot -->
      <component :is="$slots.default" />
    </div>
  </DefineTemplate>
  <div class="container">
    <div class="header">
      Header content
      <ReuseTemplate v-if="!isCompact" className="full">
        <div>Full content</div>
      </ReuseTemplate>
    </div>
    <div class="footer">
      Footer content
      <ReuseTemplate v-if="isCompact" className="compact">
        <div>Compact content</div>
      </ReuseTemplate>
    </div>
  </div>
</template>

That's it!

If you're a React dev struggling to embrace Vue because you feel down having to write templates, consider giving it another shot with this addon. Alternatively, if this still isn't enough to make templates plausible for you, you can learn how to use JSX with Vue to make your experience with Vue more close to home.

Vue.js is our passion. Open Source is our culture 😍