Emulate render props in Vuejs

Titouan Créac'h
2 min readDec 18, 2017

--

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:

<Async 
promise="promise"
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.

<template>
<div>
<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>
</template>

We create a template and we expose 3 named slots:

  • onFulFilled used when the promise is resolved
  • onRejected used when the promise is rejected
  • onPending used when the promise is pending

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

<script>
export default {
props: ['promise'],

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

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>
<template slot="onRejected" slot-scope="props">
<div> Promise is rejected with error: {props.error} </div>
</template>
<template slot="onPending">
<div> Pending... </div>
</template>
</Async>

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

--

--