Leveraging Render Props in Vue

Dillon Chanis
18 min readApr 5, 2018

--

Code sharing between your Vue components is an important concept to consider when designing and developing your components. Render props, a technique being largely adopted by the React community, helps to solve this issue. So what are render props?

“The term “render prop” refers to a simple technique for sharing code between React components using a prop whose value is a function.”
React Documentation

So essentially, a component that is utilizing a render prop takes a function which will return an element (this could also be another Vue component) and uses that function to render the view rather than defining your own template.

What does that actually look like? Let’s start small and work our way up.

We’ll create a component, Greeter, which will take in a function as a prop to execute. It will expose a method, greet, which will just pop open an alert window.

Let’s break this down really quick. First we are defining the props we will be receiving. We can see we are expecting a prop named render of type Function and by default it is just a function that returns nothing. Next, we define our greet method which takes no parameters and alerts a simple message. Lastly, we define a render method to tell the component how to actually render. Let’s look into this in more detail.

Vue Render Methods

If you already know about a Vue component’s render method go ahead and skip this section.

Typically in Vue components you will see the markup for them defined within <template> tags in the case of Single File components. Alternatively, you may see the template property on the Vue instance itself or the el property of where to mount that component. But there is another option to define your component’s templates and that is the render method.

This render method allows you to tap into the raw JavaScript that powers template rendering behind the scenes. This render method receives a function as a parameter called createElement. If you do come across these in the real world you will probably see this function aliased to just h. And if you are asking why h like I did, here is what Evan You had to say:

“It comes from the term “hyperscript”, which is commonly used in many virtual-dom implementations. “Hyperscript” itself stands for “script that generates HTML structures” because HTML is the acronym for “hyper-text markup language”.”

This createElement function, which I’ll now be just calling h as well, takes in three parameters. The first is the element to render. This could be ’div’, ‘ul’, ‘p’, or event a component like MyComponent. The second is an object which would be equivalent to the attributes you would pass on that element within a template. Finally, the third parameter is a string or an array of VNodes to render within that element. An entire blog post can be dedicated to the render function alone so refer to the Vue guides for more information.

As a quick example if we wanted to create a div that had a class name container with the text Hello World in it, it would look something like this:

Which would result in the markup:

<div class=”container”>
Hello World
</div>

Writing Your Render Prop

We have our Greeter component setup, let’s actually use this thing. We’ll define our application root.

const app = new Vue({
el: ‘#app’
})

And on this we will declare a render method which will pass a render prop to our Greeter component.

This is kinda low level stuff…at least compared to declaring regular HTML within template tags or even writing some JSX. So let’s go through it line by line.

We declare our render method and get our h (createElement) function as a parameter. First we create a <div> with no attributes but it holds our Greeter component. Our Greeter component does take an attribute though! This is the component that will be receiving the “render prop”.

On our object we’ll define a key props which you may have guessed are the props we are passing to that component. So we define our render function here. Remember above in our Greeter component’s render we did this:

render () {
return this.$props.render({
greet: this.greet
})
}

Within our render we defined an object with a key of greet. We are destructuring it in our parameters here. This render function, which remember is a prop of our Greeter component, returns a <button> element. And we can see this button has a click attribute which when clicked will execute the greet function we destructured above. And this greet function actually refers to the greet method we defined on our Greeter component.

Completed Example:

https://codepen.io/dchanis/pen/JLBpvP

Spicing Things Up

Let’s boost the complexity up a bit more just to get used to the idea of render functions and passing things into render props. We’ll keep our Greeter component but we’ll introduce a new component People which will just render a list of people from an array. We’ll also add some new functionality, our alert will alert the person’s name we select and it will provide some contextual background-color so we know who was selected last.

First we’ll define the necessary state and methods to our Greeter component.

Our state, selected, just defaults to the first element of an array. We then added a select method which takes an array index and just updates our component’s state to that index. And lastly, we expose these new properties within our render.

Next, we will begin to stub out our root component. Let’s create some local state on our data called people which will initialize to an array of three names. And we will start building out our render method, starting off the way we did in our previous example. This time within our render prop we will extract the new data and methods exposed in our Greeter component. And for now, the render prop will just return null. Let’s get that People component started!

Our People component will be what our Greeter component will be passing its methods and data to. And just to reiterate, above we said this component will take in an array of names (just strings) and print them out within a <ul>.

Again, this looks a little obtuse. Especially if you are not used to render methods but I really want to expose how your usual ways of defining templates are being interpreted behind the scenes.

You’ll notice some things different about this component. For one, we defined this component as functional which just means it is stateless. Secondly, you might notice that it is accepting a prop of people which is an array but we are not defining any props on our component. In Vue 2.3.0+ we can omit this on functional components. Finally, you may notice a second parameter we did not talk about before being passed to our h function.

Within functional components we do not have access to this. So we get a context parameter and one of the things this context object contains is our props. You can read more about functional components and the context object on the Vue guide.

Our component is then creating a <ul> tag with no attributes. But as child nodes we our iterating over our people array passed in as a prop using Array.prototype.map method. And within our map we are just returning <li> elements. These <li> elements have an on click action, which on click takes a function off our props called select and passes in the current index of the array. It also takes another function called greet, which we saw in the previous example, and passes in the current person’s name. Furthermore, we see another class attribute which checks a selected property on our props and see if it matches the current array index. If it does, we throw a selected class onto that <li> element.

.selected {
background-color: pink;
}

Finally, as the text node for the <li> element we are just passing in the person’s name. If that’s still a little confusing, here is what it would look like within a template tag:

With that created, let us revisit our root instance component and finish our render prop.

Just to really drive it home, this is all our render prop is:

Let’s revisit what we said a “render prop” was in the beginning. It is a function which will return an element (this could also be another Vue component) and uses that function to render the view rather than defining your own template.

We define our prop render, which is a function. It returns our People component. Our Greeter component never actually defines any HTML to render, all it does is expose state and methods which is used by our People component.

Completed Example:

The Big Picture

We just went through two pretty basic examples and you may still be wondering…what’s the point? The whole appeal of utilizing this pattern is for code reuse.

Vue utilizes a component architecture for developing applications. We define components, how they look, how they behave, and such and we reuse those components throughout our app. But how would you go about sharing similar behaviors between components? Let’s look at some alternative patterns.

Alternative Patterns

Exploring Mixins

Mixins can work in some cases and are perfectly viable solution to the issue of code reuse. But they do come with some issues you should be aware of.

Naming Collisions
Since Mixins are available on this context of a Vue component we may find a property defined on our Mixins clash with something that is defined on our component. This may not be an issue at first you could just rename it. However, it does become an issue if multiple components depend on a Mixin and renaming a property on a Mixin could cause those components to break…which brings us to

They can introduce “hidden” dependencies
A component’s method, lifecycle hook, or any other property might rely on a function defined within a mixin. A mixin’s function might depend even on another mixin! Removing, renaming, or adding state, methods, and other properties might break mixins.

Hidden Complexity
As Mixins grow they introduce more dependencies your component relies, increase the chance for naming collisions, which makes for a harder to reason about codebase.

No Functional Components
If you wanted to use a method on a mixins for example, your component must have access to this. Since functional components do not have any access to this context we can not use functional components with mixins.

Higher Order Components (HOCs)

The React community’s answer to Mixins was HOCs. HOCs at their core are just components that return components. Due to the functional nature of React, composing components through HOCs seemed natural. I never really saw this pattern take off within the Vue community. There was an interesting back and forth between LinusBorg (Vue Core member) and others in this issue.

HOCs were pretty pervasive though in React…that is of course until render props. See this tweet from Michael Jackson:

The Real World

I know what you are probably thinking, ”I’m not touching those render function with that ‘h’ nonsense.” Or possibly thinking of the spawn of Cthulhu that one person at work decides to birth into existence. Trust me, I know. I can already feel half of you shudder at the idea of using JSX in Vue…or in general. It’s okay. You’re in luck. There is a more ‘Vue-like’ way of pulling this pattern off. And it lies within Scoped Slots.

Scoped Slots

If you are already familiar with scoped slots go ahead and skip this section. Otherwise stick around.

Scoped slots were added in 2.1.0 and are a powerful feature. They allow you to expose data from a child component to a parent component. So in your child component you can define your <slot> tags where you want your slotted content to go. But now, we can use the v-bind directive to attach any data from within the child component on the <slot> tag that we want to expose up to the parent component.

We can imagine a UserList component (the parent) which renders individual user information.

This might not work in all scenarios, what if we wanted the user’s email too? And we wanted that to be in anchor tag with a mailto href. Do we create an entirely new component, which would basically do the same thing? There has to be a better way. And there is a better way utilizing scoped slots.

We use the v-bind shorthand syntax here on our <slot> and we are binding a user property to the current user within our v-for iteration.

Now we can go to where the component is being used and do this.

We define a local variable called props and you can name this anything you want its just a variable. This variable, props, is an object containing whatever data you bound to your <slot> tag in the component file. And now within those <template> tags you can freely use whatever data you passed up within your parent component.

Another cool thing about this, we can destructure this object.

Render Props via Scoped Slots

In most cases, we probably want to avoid using the raw render functions presented earlier. There is a lot going on, it is hard to keep track of all the different elements and their attributes. For me, a huge incentive to learn Vue was looking at Single File components and I’m sure that was the case for a lot of you too.

And to be honest, scoped slots are the Vue way of doing render props. Don’t believe me? Evan You even said it in Adam Wathan’s podcast, Full Stack Radio. And I would go ahead when you’re done here to give that a listen!

The awesome thing about render props and scoped slots are they separate behavior from presentation. I have told others that we can expose an interface (not in the OOP sense, think more like an API) that other components can leverage.

Out in Production

At my current job there are certain sections of the application that require us to expose a lot of data to the user. Previously, we just used data tables and called it a day. However, we were tasked with presenting some of these views in a more user-friendly way. And in some cases, the data isn’t tabular and might be more appropriate in a list format. However, we were running into patterns where basic CRUD functionality and other actions such as filtering through the data were common among all of them.

So let’s walk through a similar scenario. We will:

  • Create a simple data-table component
  • Create a list component
  • Extract similar behavior through scoped slot

Data Table

We will start off with our data table component.

Requirements

  • Display table of data from an array of objects
  • Client-side filtering
  • Delete row from table

Let’s keep things basic and make some assumptions about our data.

- We will receive a prop, records, which is an array of objects containing our tabular data
- We will receive a prop, columns, which is an array of objects defining our <th>
- We will receive a prop, filter, which is a string containing a filter query — what keywords to search the table by

So in main parent component we would probably fetch some data through an API or continue passing down a prop to this component. For brevity sake, we’ll just hard code it in our component’s state.

Next, we will stub out our AppTable component.

This is pretty basic. So far we are just defining our prop definitions. The table is just basic v-for directives making sure to set a key on every loop. But notice we are not iterating over a filteredRows value even though it is not defined anywhere on our component’s JavaScript. Let’s add that now.

We are defining a computed property on our component named filteredRows which has a dependency on our records prop and our filter prop. Vue will automatically detect if either of these changes and reevaluate the computed property for us, otherwise it keeps the data cached.

The filtering itself is happening within our .filter callback. We are receiving each element within the array here, which we are aliasing to row. Since each row is an object we are creating an array of those object keys with Object.keys and using Array.prototype.some on that array. The array method some iterates over an array just like map, reduce, and filter does. The callback function tests whether the current element results in a truthy or falsey value based on the expression.

We then test whether the String representation of our data matches the filter query. If it does, we return that object which eventually gets passed into our new array created by .filter. If it doesn’t match, it gets filtered out.

Back in our parent component let’s register our AppTable component.

<script>
import { AppTable } from ‘./components’
export default {
name: “App”,
components: {
AppTable
},
// ...
</script>

Next, we will define our App’s template to render our table and a simple <input> which will v-model the filter we defined on our component’s local state.

So now that we have the table displaying let’s tackle removing a row. We’ll revisit our <thead> within our component and add another <th> for our “remove” action.

Now let’s add a small button to delete the row.

<td>
<button class=”delete” @click=”removeRow(row)”>&times;</button>
</td>

Finally, add our method to go with our new click handler.

Here we are taking our records prop and filtering out any object that has the same ID as the row we passed in from our click handler and sending that as a payload on a ‘remove’ event.

Back on our parent component we can listen for our new event like so:

And now we should see the rows being deleted.

List Component

Requirements

  • Display list of data from an array of objects
  • Client-side filtering

Immediately, you’ll probably notice the requirements are pretty much the exact same. For now we will not be adding or removing items from the list but later we can see how we can bake in that functionality if we wanted to using scoped slots.

This isn’t anything groundbreaking and I’m sure you’ve done something similar a million times before. We are just iterating over an array. You’ll notice however, that we are using the exact same computed property that we used for our AppTable component.

Completed Example:

Another thing to note here is unless we update the template and styles we are strictly locked to a basic <ul>. What if we wanted the same functionality but within a inline list? Or what if we had a list group of contacts that were to be displayed within a custom <card> component?

We could just create new components and repeat the same logic over and over again. Instead let’s extract this logic out to a new component.

Refactor using Scoped Slots

Taking our <app-list> component let’s refactor it using scoped slots. And this is going to be really simple. We will start by just iterating over our records prop.

And we don’t even have to touch our component’s JavaScript. So we are iterating over our filteredList computed property which is just our filterable records prop. We then take each item within the records array and pass that up to the parent component by binding it to the value of item.

In our parent component we can do something like this:

Play around with it yourself!

Completed Example:

Why is this cool?

For one, our List component became really flexible. We can render it however we want to. The view that is presented to the user is completely up to us, not the component.

I’m sure you have come across a component library or single component that was a pain to style how you want it. Perhaps it wasn’t accessible to screen readers or the markup used to render wasn’t semantic at all. Sometimes it is just easier to be able to finely tune the component to fit your needs. And that is exactly what we can accomplish with scoped slots.

Let’s continue.

Leveraging the Power of Scoped Slots and Render Props

The central idea behind a “render prop component” is to utilize scoped slots. And that’s it, we won’t be returning any markup at all. Instead, we create a component which will handle all the state and actions for a set of components which share common functionality. So we have our filterable table and list components above. Let’s add another feature to our List component, the ability delete an item.

First, let’s identify all the common features between these two components.

  • Iterate over an array of objects
  • Filterable data
  • Delete Item

Okay, let’s stub out our CollectionManager component.

We see that render method again on our Vue component. And don’t worry, we won’t be messing with any createElement functions here. The basic idea here is we have a component that will take in the default scoped slot, basically a non-named slot, and invoke it. Currently, ours does nothing other than render the given slot. So how can we add features to this?

Remember that this is still just a Vue component. We still have access to everything a Vue component has access to like methods, computed properties, lifecycle hooks, etc. So let’s define some props.

You should recognize these because we used both of these props in our <app-table> and <app-list> components. But this still doesn’t really do anything…

Let’s think back to why you would want to use this pattern. For code reuse between components. And now remember that computed property that literally used the exact same code between components? Let’s put that here instead.

Now to expose this computed property back up the parent we would place it on the <slot> tag like so:

<slot :items=”items”></slot>

The idea is the same within this render method. Instead we pass an object to the function which will hold our scoped data.

render () {
return this.$scopedSlots.default({
items: this.items
})
}

Our computed property items are now available on our slot-scope. Let’s see how we actually use this component now. We’ll import it to our App component, the same place where we are rendering our old Table and List components. Let’s tackle refactoring our <app-table>.

Let’s update our template to use our <collection-manager>.

We take our <collection-manager> component and completely wrap our <app-table>. Remember that within our <collection-manager> we are just returning the default scoped slot within our render. Next we define our slot-scope and destructure the items computed property off it. Now within our slot-scope we have access to it. Instead of just passing records directly we first pass it through as a prop on our <collection-manager> because that component uses it to create the computed property, items. This is the same items that is currently available as our slot-scope so then we can pass that to our <app-table> component.

Furthermore, we can also pass our filter prop through the <collection-manager> since it is needed to correctly filter through the records prop, which eventually is passed to our <app-table> as items.

This allows us to refactor our <app-table> even further. Let’s revisit the component.

Since the <collection-manager> is handling the filtering for us we can go ahead and remove the filter prop declaration along with the entire computed section. Remember that items is now the same as our previous filteredRows computed and items is being passed in as our records prop now.

That allowed us to remove a lot of code from our component. Let’s see if we can move the item removal here as well.

We will define a method removeItem which takes an item object and removes it from our array of records. For it to work, we just need to create it and expose it on our slot-scope object.

And now let’s use this within our <app-table>.

We’ll update our prop definitions to accept our remove function from our <collection-manager> component.

remove: {
type: Function
}

We can also remove our entire methods section since we will just be utilizing the remove prop.

Lastly, we’ll update our <template> to use the remove function as the click handler for the delete button.

<button class=”delete” @click=”remove(row)”>&times;</button>

Now in our App component let’s pass in our remove function. And remember to listen for the remove event to update our records.

One final thing to note, looking at our <app-table> component we no longer have anything that is stateful. So if we wanted to we could define it as a functional component.

We can now move onto our <app-list> component. We can wrap our <collection-manager> around it as well and expose those slot-scoped properties to it. And we really don’t have to do much to make this work for us based off our previous refactor of this component. We will actually be getting rid of a large chunk of it.

We can safely remove the computed property and the filter prop from the component definition. Next we’ll just have to update our <template> to use just records instead of the previous computed property.

Now in our App component let’s wrap it within our <collection-manager>.

We updated our records prop to take in the items exposed by our <collection-manager> Next, we added a button to remove the item from the list. Notice how these are the exact same things we just passed to our <app-table> component.

Completed Example:

Closing Thoughts

I hope you can notice the power behind this pattern. It allows us to do some really cool things and keep our code DRY between components. I really enjoy how this pattern allows us to create more functional components than compared to another pattern like Mixins.

Is there a time to avoid this pattern?

I would say it depends. If you find yourself repeating code within your components then this may be something to reach for. Otherwise, don’t bother with the added complexity. Keep your components as simple and small as possible.

Render Prop Code Smells

If you do decide to use a render prop it’s sometimes easy to get out of control with it. If you find yourself basically interfacing all of Lodash’s methods through a render prop or exposing a list 100 lines long in your render method you should probably really stop and consider if you should break it out into a different component.

Additional Materials

--

--