Master Vue's render function by creating a custom grid component
Tags: vue, javascript
Published: 2019-07-24 17:44:59

For those of you writing Frontend applications with ReactJs, you are probably familiar with `render` functions to control what is rendered once the component is ready.

In Vue you usually go for its [template solution](https://vuejs.org/v2/guide/syntax.html) to control what is being rendered. It is really powerful and offers lots of [custom directives](https://vuejs.org/v2/guide/syntax.html), which are prefixed by a `v-`. You can for example conditionally render a DOM node depending on your current state.

While this already offers a great amount of flexibility, you may sometimes reach for [Vue's render function](https://vuejs.org/v2/guide/render-function.html) to fully control the html being rendered. Imagine having to apply custom class attributes to children being passed to your custom component.

This is exactly what we will be going for in this tutorial.

I will be utilizing [Tailwind CSS](https://tailwindcss.com/docs/what-is-tailwind/), as it offers everything we will need for a basic flexbox grid. I found the class names it is using very intuitive and its utility-first approach goes very well with Vue components.


## The render function

Using render gives you control over a function that is called `createElement`, or `h` for short.

**Hint**: In this tutorial I will be naming the `createElement` function `h`.

A very simple example of a render function can look like the following:

{% gist https://gist.github.com/chrisg86/e9d63a766007128cdbba83378c9bb2bf %}

The example above will render an h1 element with the content of "This is a heading". Pretty straightforward.


## createElement syntax

So let's have a look of how createElement works and what can be supplied.

{% gist https://gist.github.com/chrisg86/d86818186f35115d7b843283741a8859 %}

As you can see, the function accepts 3 arguments:

- The HTML tag name that should be rendered
- The data object, which defines how to configure this element. You can find more information about it [here](https://vuejs.org/v2/guide/render-function.html#The-Data-Object-In-Depth).
- The children

With this information, let's create a basic grid component.

{% gist https://gist.github.com/chrisg86/ad8f86215575141d98ccff2c91d2efa5 %}

This component doesn't do much yet. It simply renders a div element with a class of "flex" and no children.

I would like the grid to render anything what I pass in. Vue uses the concept of slots for this. To read the default prop in Vue, you can use `this.$slots.default`

{% gist https://gist.github.com/chrisg86/8e5d764dc6e3853d7f0a73d1cd847b71 %}

Great, this will render the children the pass into the grid component.

So how do we make the grid more flexible? We would like to the tell the grid of many columns it consists of. This way we know which class names to apply. So it sounds like we need a prop in our grid component:

{% gist https://gist.github.com/chrisg86/cd2ebe2e96638628afd59104bdf4d860 %}

I am using prop validation here, to make sure we are not using any arbitrary class name that the default tailwind configuration doesn't support. By default it uses w-1/2 through w-1/6.


## Apply the class names to children

As we don't want to modify the children directly, we will add an additional wrapper around each child and apply the tailwind class names to them. This way we don't have to mess with the child elements directly, which might potentially mess them up:

{% gist https://gist.github.com/chrisg86/c36dc8dd608ea926dee9c4641e61711a %}

This reads as follows:

- Create a `div` element with class "flex"
- Map over the default slot and for each child create a new `div` with a class depending on the `cols` prop and put the actual child inside of that.


## Example usage

So how do you actually use this component in your application?

{% gist https://gist.github.com/chrisg86/d378c289b95b68e901948da9023520c6 %}

To make clear what is rendered, this is the final HTML:

{% gist https://gist.github.com/chrisg86/22b9e2fcda7f5b7ee59d9249fb7fe9a4 %}

Please note that passing in `:cols="3"`, but only 2 children will mess up the layout. You could easily account for that by checking before you render. Depending on your requirements, you could not render nothing or just spit out a console.error/warning.


## Troubleshooting

I noticed `this.$slot.default` consisting of 3 children instead of the expected 2. This was because there has been a space character somewhere between the components. To account for this I added an additional filter before iterating the children:

{% gist https://gist.github.com/chrisg86/2cc53c6528221b95546ead94ce65188d %}