Emulate render props in Vuejs

Render props explained

React has a nice pattern to reuse code, it’s called render-props. This pattern allow the developer to elegantly replace the popular High Order Component. We can imagine a component that take a promise as a props and that render a component when the promise is pending, another component when the promise is fulfilled and another when the promise is rejected.

Here is an example with pseudo code and High Order Component:

const AsyncHOC = HOCAsyncConstructor(promise)(onPending, onFulFilled, onRejected); 

Now we have a component AsyncHOC that will render onPendingcomponent when the promise is pending, onFullFilled when the promise is resolved and onReject when the promise is rejected.

The problem pointed out by the react community is that the HOC component is hidden. Child component and parent component aren’t aware of the existence of the HOC component. A props collision can happen. if the parent component decide to call the child component with the prop and the HOC also decide to call the component with the same prop, then, the prop given by the parent is lose.

What we learnt from react

React come with a pattern named: Render props.

With this pattern, we don’t create a hidden component which operate as a proxy. We just tell our component how to render its children:

onPending={() => (<div> Pending... </div)}
onFulFilled={(value) => (<div> {value} </div>)}
onRejected={(error) => (<div> {error} </div>)} />

As we can see, all the behavior is explicit. We know what we render on the different state of the promise.

Use this pattern with scoped slots in vue

Since this pattern is easy to produce with react (jsx). It not goes well with vue templates. But we can use scoped slot to emulate the behavior very easily.

First, create our async component.

<slot name="onFulFilled" :value="value" v-if="value !== null" />
<slot name="onRejected" :error="error" v-else-if="error !== null" />
<slot name="onPending" v-else /> </div>

We create a template and we expose 3 named slots:

  • onFulFilled used when the promise is resolved

onFulFilled is displayed when value is not null. Otherwise, if error is not null, then, display the slot onRejected, otherwise, render the onPending slot.

The script tags

export default {
props: ['promise'],

mounted() {
.then(value => this.value = value)
.catch(error => this.error = error);
}, data: () => ({
value: null,
error: null

Now we can use our component as follow:

<Async :promise=mypromise>  <template slot="onFulFilled" slot-scope="props">
<div> Promise is resolved with value: {props.value} </div>
<template slot="onRejected" slot-scope="props">
<div> Promise is rejected with error: {props.error} </div>
<template slot="onPending">
<div> Pending... </div>

Now, we can reuse our Async component everywhere without hiding what it does.

Written by

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store