Tutorial

Using JSX Templates with Vue

Draft updated on Invalid Date
Default avatar

By Samuel Oloruntoba

Using JSX Templates with Vue

This tutorial is out of date and no longer maintained.

Introduction

Vue.js has an easy API and several options for defining HTML templates in our components.

We can use the <template> tag option, define a template property on our root component instance, or use Single-File components.

The options above are awesome and work perfectly, but, there comes a time in the lifecycle of your application where they either feel clunky, over-engineered or very inflexible.

So, why would we want to JSX instead of any of the other template definitions?

  • JSX is easy to read. <div>...</div> is subjectively better than this.$createElement('div', {}, [...])
  • Seriously, it’s just JavaScript.
  • Vue has support for JSX.
  • JSX makes custom Vue components easier to import and manage.

A quick intro

Let me give you an example of why JSX is good.

We want to build a <TextField/> component that can either be a normal single-line text input or a multiline input (textarea). Our template declaration might look like this.

<div>
  <textarea v-if="multiline" v-model="content" :name="name" :placeholder="placeholder" :aria-invalid="false">
  <input v-else v-model="content" :name="name" :placeholder="placeholder" :aria-invalid="false">
</div>

As you can see from the snippet above, we’ll quickly run into a few problems like duplicate code and many more. Imagine having to support a variety of properties on the input. This little snippet above will grow and be a nightmare to maintain.

To fix this, we need to go low-level with Vue. We need to get closer to Vue’s internal API to fix this mess.

The render() method

Note: I’m not saying that there’s not a simple way or ways to handle the above problem without JSX, all I’m saying is that moving this logic to the render() method with JSX in tow can make for a more intuitive component. Keep reading to find out why?

Every component we create in Vue has a render method. This is where Vue chooses to render the component. Even if we don’t define this method, Vue will do it for us.

This means that when we define HTML templates in Vue — Vue’s template compiler compiles it to a createElement function that takes a couple of parameters and returns the result from the render function.

To fix the code in the previous section, we remove the template property or the template tag and define a render() method on the component. If the render method is defined on a component, Vue will ignore the template definition.

...
export default {
  name: 'TextField',
  render (createElement) {
    const tag = this.multiline ? 'textarea' : 'input'

    return createElement(tag, {
      class: {
        'text-input': true,
        'is-disabled': false
      },
      attrs: {
        name: this.name,
        placeholder: this.placeholder,
        'aria-invalid': false
      }
    })
  }
}
...

The above code does a few things:

  1. The render method takes a createElement helper from Vue.
  2. We programmatically define our tag.
  3. Then we create the tag and pass its attributes, classes, etc. as an object. There are quite a few options we can pass to createElement.
  4. We return the newly created element for rendering.

Note: Every template we define for a Vue component will be converted into a render method that returns a createElement function. It’s because of this reason the render method will take precedence over a template definition.

Take this example:

<div>
  <p>Only you can stop forest fires</p>
</div>

The template compiler will convert the HTML above into:

...
render (createElement) {
  return createElement(
    'div',
    {},
    createElement(
      'p',
      {},
      'Only you can stop forest fires'
    )
  )
}
...

Okay! now you might ask this question, “Isn’t this bad for readability?” The answer is yes. Once you define a component with many levels of elements nesting or has several sibling elements — we run into a new problem. We just sacrificed readability. Like they say, “we’ve moved from the frying pan to fire.”

Cue JSX. This is where we’ll have JSX bring back the readability we lost.

What is JSX

If you already know about JSX, feel free to skip to the next section where I’ll show you how to use JSX in Vue.

JSX is a term coined by Facebook’s engineering team.

JSX is an XML-like syntax extension to JavaScript without any defined semantics.

JSX is NOT intended to be implemented by engines or browsers. Instead, we’ll use transpilers like Babel to convert JSX to regular JavaScript.

// this line below is an example of JSX
const heading = <h1>Welcome to Scotch</h1>;

Basically, JSX lets us use an HTML-like syntax in JavaScript.

Unfortunately, this article assumes you already know JSX, so teaching JSX is beyond the scope of this article. I’ll still point you in the right direction. JSX is very easy to grok and can be done in a couple of minutes.

Use these links to learn The basics of JSX, Learn JSX in-depth, finally, if you really want to know about the specification that is JSX, visit its official website.

Configure Vue to use JSX

If you use Vue-cli greater or equal to version 3.0 you are in luck as JSX is supported.

If you are using an older version of Vue-CLI that doesn’t support JSX, you can add it by installing babel-preset-vue-app and add it to your .babelrc file.

To install using npm:

  1. npm install --save-dev babel-preset-vue-app

Using yarn:

  1. yarn add --dev babel-preset-vue-app

In your .babelrc file, all you have to do is:

{
    "presets": ["vue-app"]
}

There, we can now use JSX in our component’s render function.

Vue’s JSX syntax gotchas

There are few gotchas to using JSX in Vue.

First, you can no longer use the : and @ shortcuts for binding and listening to events. They are invalid JSX syntax and your code won’t compile.

To listen for events in JSX, we need the “on” prefix. For example, use onClick for click events.

render (createElement) {
    return (
        <button onClick={this.handleClick}></button>
    )
}

To modify events, use:

render (createElement) {
    return (
       <button onClick:prevent={this.handleClick}></button>
    )
}

To bind a variable, instead of : use:

render (createElement) {
    return (
        <button content={this.generatedText}></button>
    )
}

To set HTML string as the content of an element, instead of v-html use:

render (createElement) {
    return (
        <button domPropsInnerHTML={htmlContent}></button>
    )
}

We can also spread a large object.

render (createElement) {
    return (
        <button {...this.largeProps}></button>
    )
}

Using JSX in render

Going back to our initial “TextField” component. Now that we have JSX enabled in our Vue app, we can now do this.

render (createElement) {
    const inputAttributes = {
        class: 'input-field has-outline', // class definition
        onClick: this.handleClick // event handler
        backdrop: false // custom prop
    }
    const inputMarkup = this.multiline
        ? <textarea {...inputAttributes}></textarea>
        : <input {...inputAttributes}/>

    return inputMarkup
}

Importing Vue JSX Components

Another benefit to using JSX in Vue is that we no longer have to register every component we need. We just import and use.

import {Button} from '../components'

export default {
    render (createElement) {
        return <Button primary={true}>Edit</Button>
    }
}

How to make JSX work with TypeScript

TypeScript is used as a mechanism that adds type-checking to JavaScript.

To add JSX support to TypeScript all we need to do is modify our tsconfig.json.

To enable JSX in TypeScript, first save the file as a .tsx file and modify your tsconfig.json to include:

{
  "compilerOptions": {
    ...
    "jsx": "preserve",
  }
}

Setting the jsx option to “preserve” means that TypeScript should not process the JSX. Doing this lets Babel take control of everything JSX and TypeScript stick to types as it does not yet support Vue JSX. You can learn more.

Then create a jsx.d.ts file in your project and add the TypeScript JSX declarations for Vue.

import Vue, {VNode} from 'vue'

declare global {
  namespace JSX {
    interface Element extends VNode {}
    interface ElementClass extends Vue {}
    interface ElementAttributesProperty {
      $props: {}
    }
    interface IntrinsicElements {
      [elemName: string]: any
    }
  }
}

Make sure that TypeScript can load the declaration file. Or, you can add autoloading for it in tsconfig.json via:

{
  "compilerOptions": {
    ...
    "typesRoot": ["./node_modules/@types", "./types"]
  }
}

Conclusion

That’s it for today. Enjoy having some or all of your Vue.js templates in JSX.

And, please no complaints about JSX breaking SOC (separation of concerns), I can’t take another one of those arguments. If you prefer using the createElement function with objects by all means enjoy!!

Let me know your thoughts and suggestions in the comments.

Cheers!

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about us


About the authors
Default avatar
Samuel Oloruntoba

author

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
Leave a comment


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Get our biweekly newsletter

Sign up for Infrastructure as a Newsletter.

Hollie's Hub for Good

Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.

Become a contributor

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

Welcome to the developer cloud

DigitalOcean makes it simple to launch in the cloud and scale up as you grow — whether you're running one virtual machine or ten thousand.

Learn more
DigitalOcean Cloud Control Panel