# Lightweight Components with Vue 3 and JSX

Vue - JavaScript - February 23rd, 2020
Lightweight Components with Vue 3 and JSX

I recently started looking into Facebook's React and I really liked how lightweight JSX feels in comparison to Vue's single file components.

Creating and using a component in Vue sometimes feels like a chore, especially for very small components. You need to create a new file, write a template, register some data, add some methods and then register your component either locally or globally. That is a lot of work if you have many small components in your application.

# How does JSX help?

There is a lot of benefits in using JSX in combination with Vue but also some drawbacks. Of course JSX is more abstract than Vue templates and more difficult to learn in my opinion. There is a lot of magic going on in Vue templates that removes a lot of work that we would need to do manually in JSX. The main drawback for me is that this is not the standard way of writing Vue components and convincing your team to try this approach might be an uphill battle.

There was a lot of controversy in the community when the Composition API was announced. People were worried that in might split the community in two camps. One for the new Composition API and one for the old Object API.

Now imagine if we need to split these camps in half one more time when it comes to writing components with compiled templates or using JSX.

But enough of drawbacks, let's look into the advantages of using JSX!

# Multiple Components per File

Like mentioned above, when using single file components, you can only define a single component per file. With this approach, you can create as many components as you like, which keeps your code organized and your file tree clean.

So something like this is possible:

// Component.jsx
import { defineComponent } from "@vue/composition-api"

const Child = defineComponent({
  render() {
    return (
      <div>Child</div>
    )
  }
})

export default defineComponent({
  render() {
    return (
      <div>
        Parent
        <Child />
      </div>
    )
  }
})

Info

At the time of writing, Vue 3 has not released yet. So we use the @vue/composition-api package instead.

We use defineComponent to create a new Vue component. Alternatively you may also use Vue.extend which basically does the same thing.

The great thing is, by using JSX we don't need to register our Child Component in inside our Parent. We can simply omit the components attribute. In a normal component we would need to specify the following:

<script>
export default {
    // No need for this!
    components: {
        Child
    },

    // ...
}
</script>

This might not be such a big deal, but it's nice to be able to remove a little bit of extra work and complexity which is a reason single file components sometimes feel a little heavy.

# Adding Data and Interactivity

The example above doesn't do much but render some HTML. Let's change that and write a new Counter component, like you have seen a million times before.

// Counter.jsx
import { defineComponent } from "@vue/composition-api"

export default defineComponent({
  data() {
    return {
      count: 0
    }
  },

  methods: {
    increment() {
      this.count++
    }
  },

  render() {
    return (
      <div>
        <button onClick={this.increment}>Increment</button>
        <span>Count: {this.count}</span>
      </div>
    )
  }
})

I don't think I need to explain what this component does. It is very simple with not a lot going on, but still we need to define data and methods as attributes, which seems very verbose.

# setup to the Rescue!

Using setup we have a single function which is responsible for registering data, computed properties, as well as methods. It keeps our small component nice and simple!

Let's take a look at the following:

import { defineComponent, ref } from "@vue/composition-api"

export default defineComponent({
  setup() {
    const count = ref(0)
    const increment = () => count.value++

    return {
      count, increment
    }
  },

  render() {
    return (
      <div>
        <button onClick={this.increment}>Increment</button>
        <span>Count: {this.count}</span>
      </div>
    );
  }
})

Now in our setup method we declare the count data as well as the increment method, which we reference in our render function. This is very nice and organized, but we can do even better!

Refs

Please note that ref returns a so called value wrapper. In normal Vue templates and in render functions, we can simply reference it by the variable name. In this case count or this.count. But our increment method must explicitly increment the value of the wrapper, so we call count.value.

# Hooks and Reusability

Hooks are a relatively new feature in React which allows more reusability between components. With the new Composition API in Vue, we can write our own hooks too! This is great if we want to reuse the same logic in multiple components.

Let's write a hook for our count logic.

// hooks/useCounter.js
import { ref } from "@vue/composition-api"

export default function useCounter() {
  const count = ref(0)
  const increment = () => count.value++

  return {
    count, increment
  }
}

And now we use this hook in our setup method.

setup() {
  return {
    ...useCounter()
  }
}

We could also pass an argument to our hook, for example for the initial value of count. We would need to change our function as follows:

// hooks/useCounter.js

export default function useCounter(initialCount) {
  const count = ref(initialCount)

  // ...
}

And now in our setup:

setup() {
  return {
    ...useCounter(5)
  }
}

Now if we have another component which needs to keep track of a count and needs to be able to increment said count, we can simply call this hook again and even pass a different value for our initial count.

// AnotherCounter.jsx

export default defineComponent({
  setup() {
    return {
      ...useCounter(10)
    }
  },

  render() {
    return (
      <div>
        <button onClick={this.increment}>Another Count increment Button</button>
        <div>Another Count: {this.count}</div>
      </div>
    )
  }
})

This keeps a different count state, so when we click on another increment button, it doesn't increment the count of our other components.

With hooks we can easily compose more complex components.

setup() {
  return {
    ...useCounter(),
    ...useAnotherHook()    
  }
}

If you need some inspiration what you can do with hooks, check out VueUse who created a lot of great hooks for all kinds of use cases.

# In conclusion

Of course all of this is just an opinion. Personally, I enjoy writing JSX since there isn't such a great abstraction from JavaScript like in Vue templates. I cannot wait for Vue 3 and using hooks in my projects. Of course I will still be writing single file components, but it is great to have JSX in my toolbelt.

Lightweight in this article doesn't refer to raw performance, but rather to the developer's perception of the component's code. JSX is more straightforward in some way and using setup we omit some complexity from our component and make it less verbose.

# Isn't this just React with extra steps?

"This is stupid. If I am using JSX and hooks I can also just use React!"

That's what you are probably thinking right now. But hear me out: I am not saying that you should write all of our components like this. I think Vue's single file components are its greatest feature and for complex and logic-heavy components we should definitely still use that approach.

But I think writing components in the way that is described in this article is very useful for the simple stuff. Render functions with JSX makes setting up these small, little components feel less like a chore.