Using JSX with Vue
With Vue templates being the recommended way to build components by the framework standard, Vue also supports writing component markup using JSX or TSX with Typescript.
Despite common conception, JSX is not exclusive to React, although it was initially developed for and popularized by React as it’s way for writing markup within JavaScript. And even though Vue has it’s own alternative template syntax, official docs acknowledge it’s limitations and the benefits that come from having the full programmatic power of JavaScript at your disposal when generation the markup and recognize the need for it for solving complex scenarios.
Let’s have a look on what are the options with using it in Vue.

How JSX is possible in Vue
Vue templates are syntactic sugar for you to right a more readable and DX-friendly way to create virtual DOM nodes (vnodes how Vue calls them) in an HTML-like markup.

You can check in the compiled templates that what it translates into under the hood are render functions with calls to function that creates vnodes that is h() function. You can read more on relationship between templates and render function in Vue docs.
const vnode = (
  h(
    'div',
    {
      id: 'foo',
      class: 'bar',
      // here may go other props, possibly complex ones
    },
    [
      /* children */
    ]
  )
);

return (
  <div class="container">
    <div class="header">
      Header content
      {!isCompact && vnode}
    </div>
    <div class="footer">
      Footer content
      {isCompact && vnode}
    </div>
  </div>
);
Vue also provides APIs that allow us to skip the template compilation step and directly author render functions via exporting h() function that you can manually call. This is described in docs on Render Functions & JSX.
JSX here comes into play here as an alternative syntactic sugar to define render functions, but contrary to Vue templates that are static JSX is written as part of your code directly so can be manipulated with it.

Setup
create-vue and Vue CLI both have options for scaffolding projects with pre-configured JSX support. In order to manually enable an existing Vite-based Vue project with JSX you should perform the following configuration. (If you have different environment, see Vue docs on JSX/TSX for configuring options)
Install Vite plugin with command like:
npm install @vitejs/plugin-vue-jsx -D
Next, add plugin to the vite.config.ts:
// vite.config.js

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'

export default defineConfig({
  plugins: [
    vue(),
    vueJsx(),
  ],
  // other configurations
  ...
});
If you are using Typescript, make changes to tsconfig.json :
// tsconfig.json

{
  "compilerOptions": {
    "jsx": "preserve",
    "jsxImportSource": "vue",
    // other compiler options
    ...
  }
}
Vue docs are not so exhaustive in the section covering the topic and just tell that in Composition API a render function can be returned from setup() hook to be treated as component’s render output. Then they only showcase usage with h() function for explicitly creating vnodes:
import { ref, h } from 'vue'

export default {
  props: {
    // define props here
    ...
  },
  setup(props) {
    const count = ref(1)

    // return the render function
    return () => h('div', props.msg + count.value)
  }
}
The docs then imply that the render function can be written with JSX instead but don’t provide a complete example on this so let’s explore the options more closely.
The equivalent of this with JSX would be:
// Component.jsx

import { ref } from "vue"

export default {
  setup(props) {
    const count = ref(1)

    // render function with JSX
    return () => (
      <div>
        {props.msg + count.value}
      </div>
    )
  }
}
The file extension should be .jsx here to enable it.
Or the same in Typescript in .tsx and wrapped with defineComponent:
// Component.tsx

import { defineComponent, provide, inject, Ref, ref } from "vue"

export default defineComponent({
  setup(props) {
    const count = ref(1)

    // render function with JSX
    return () => (
      <div>
        {props.msg + count.value}
      </div>
    )
  }
})
The former is what most articles show as examples and this may suggest that in order to use JSX in Vue one should ditch SFC and instead declare Options API style component mixing it up with setup() hook if you want to stick to the Composition API. But this is not the case.
You can use to JSX or TSX inside Vue SFC with specifying lang="jsx" or lang="tsx" on <script> tag:
// Component.vue

<script lang="tsx">
import { defineComponent } from 'vue'

export default defineComponent({
  setup(props) {
    return () => (
      <div>
        Hello, World
      </div>
    )
  }
})
</script>
You can even use JSX in <script setup> with lang="tsx" or lang="tsx". In this scenario you can declare **Functional Components** in <script setup> and use them in <template> to render part of the markup**.**

Functional components are a lightweight form of component that must be pure functions and can’t have state or side-effect (pretty much like Function components in React before React hooks).

You can declare a single **Functional Components** to produce your entire SFC markup like we did in the previous examples with component definition, you just need to call it not as a function but instantiate as a component:
// Component.vue

<script lang="tsx" setup>
const RenderComponent = () => (
  <div>
    Hello, World
  </div>
);
</script>

<template>
  <RenderComponent />
</template>
But perhaps the most potent is that you can still use <template> for most of your template like you normally would in Vue and occasionally embed a **Functional Component** to produce some parts of the markup that can’t be easily expressed with the template syntax.

One of the notable limitations is inability to define a template part and reuse it across template, for example when some block needs to appear in different places in the markup depending on some condition (although there is now an alternative pure-template solution for this problem that is explored in another article).
This part can be extracted into a **Functional Component** and reused across <template> :
// Component.vue

<script lang="tsx" setup>
import { ref } from 'vue';

const ReusedPart = () => (
  <div>
    Hello, World
  </div>
);

const someCondition = ref(true);
</script>

<template>
  <div>
    <ReusedPart v-if="someCondition" />
  </div>
  <div>
    <ReusedPart v-if="!someCondition" />
  </div>
</template>
Functional Component can be passed props, just like you would pass them to normal components:
// Component.vue

<script lang="tsx" setup>
interface Props {
  label: string;
}

const SubComponentA = (props: Props) => (
  <div>
    <label>{props.label}</label>
    <input type="number" />
  </div>
);

const SubComponentB = (props: Props) => (
  <div>
    <label>{props.label}</label>
    <textarea />
  </div>
);
</script>

<template>
  <SubComponentA label="Label 1" />
  <SubComponentB label="Label 2" />
  <SubComponentA label="Label 3" />
  <SubComponentB label="Label 4" />
</template>
Hope that this was a usefull tour on how to incorporate JSX in your Vue app to help you write your templates more easily.

Autor: Alexandra Petrova, Lead Frontend developer
Be the first to know our news!
Once a month, get the latest updates on our new features, open-source projects, and key releases. No spam—just valuable insights for Vue.js developers.