How to write reusable sane API-based Preact, React or Vue.js components using the Render Props pattern
- Published at
- Updated at
- Reading time
- 9min
I play around with JavaScript SDKs a lot. And most of my projects make use of the Contentful SDK and the Contentful content infrastructure to pull in content from editors and non-technical people into my applications.
It doesn't matter if I'm dealing with edited content or GitHub statistics, almost every dataset is available to me via API endpoints. Those datasets then go into my React, Preact or Vue.js applications. Up until today I hadn't figured out the best way to work with API data in a component-driven world. But you know what — now I know.
Whether you use the widely supported fetch
method or an SDK, using API data across tons of components can be tricky. This is because you have to figure out in which component you fetch the data, how you handle state and how to propagate data across components.
Consider the following Preact snippet:
// Preact | app.js
import { Component, render } from "preact";
import { Item } from "./item";
import { createClient } from 'contentful'
// create Contentful SDK with needed credentials
const client = createClient({
space: '...',
accessToken: '...'
})
export default class App extends Component {
componentDidMount() {
client.getEntries({ content_type: 'tilPost', limit: 5, order: '-fields.date' })
.then(({ items }) => this.setState({
learnings: items
}))
.catch(error => this.setState({
error
}));
}
render(props, { learnings = [], posts = [] }) {
return (
<div>
<h1>Preact with SDK usage example</h1>
<h2>My Recent Learnings</h2>
{ learnings.map(item => <Item item={item} />) }
</div>
);
}
}
if (typeof window !== "undefined") {
render(<App />, document.getElementById("root"));
}
In this code, the App
component fetches the data in the lifecycle method componentDidMount
. It then sets the response data to the given state of the component which will in turn be used in its render
method.
But what happens if I have to make two calls to fetch data?
// Preact | app.js
export default class App extends Component {
componentDidMount() {
client.getEntries({ content_type: 'tilPost', limit: 5, order: '-fields.date' })
.then(({ items }) => this.setState({
learnings: items
}))
.catch(error => this.setState({
error
}));
client.getEntries({ content_type: '2wKn6yEnZewu2SCCkus4as', limit: 5, order: '-fields.date' })
.then(({ items }) => this.setState({
posts: items
}))
.catch(error => this.setState({
error
}));
}
render() { /* ... */ }
}
You can play with this example on CodeSandbox if you like.
Now I have to make two calls using the getEntries
Contentful SDK client – both included in componentDidMount
. But to me this code feels a bit messy. And it will get worse the more calls you have to make.
In the past, this situation would force me to restructure some code and abstract the API calls away — and maybe use a state management library like Redux or Vuex to keep the components clean.
The downside of abstracting things into models or utils is that it increases the component complexity. As a result, it might not be obvious what's going on for a developer joining the project. I have to jump between files to understand functionality, and components have to include an increasing number of features.
On the other hand, using state management with something like Redux comes at a cost. So using state management should be very well considered since it might add unwanted complexity.
I've always dreamt of a magical component that abstracts everything away and provides me with data in a particular "scope." In Vue.js when you loop over items, there are objects magically available inside of directives:
<!-- template element of Vue.js typical single file components -->
<template>
<ul>
<li v-for="item in items" :key="item.id">
{{ item.title }}
</li>
</ul>
</template>
As seen above, every item
is available inside of the looping li
element. Can this approach be used for handling API calls?
<!-- template element of Vue.js typical single file components -->
<template>
<HttpCall :query="{ q : 'foo' } as data">
{{ data.title }}
</HttpCall>
</template>
I planned to dig into the Vue.js core to figure out if that's possible, but then...
I was listening to an episode of Fullstack Radio with Kent C. Dodds as the guest. The title was "Building Reusable React Components with Render Props." Interesting - I'm listening!
This immediately caught my attention. Kent talked about the downshift react component, which is a reusable autocompletion component. It can be used to build, well... custom autocompletion components.
The catch is that it provides functionality like keypress handling and item selection — and as a user I only have to deal with providing the interface. I only have to define the markup structure and "make it pretty" so to say.
The way it works is that it provides its functionality by making heavy use of the so-called "render props" pattern. Kent explained how it works - it matched my expectations of a beautiful reusable component structure that makes it possible to share functionality without increased complexity.
The idea of the "render props"
The "render props" pattern works like so:
I use a provided component and pass in a render
function via props. This render
function will then be called inside of the render
method of the provided component. Possible logic and API calls can be done in the "wrapper component", and data can be passed to the function I handed in.
Just because downshift is written in React, let's have a look at how the "render props" pattern could work for making API calls in React.
Using a prop named render
First I have to write the "render props" component to fetch data from a Contentful API endpoint.
// React | contentful.js
import React from 'react'
import PropTypes from 'prop-types'
import { createClient } from 'contentful'
const client = createClient({
space: '...',
accessToken: '...'
})
export default class Contentful extends React.Component {
// make the query for the SDK
// and the render function required
static propTypes = {
query: PropTypes.object.isRequired,
render: PropTypes.func.isRequired
}
// set default state for the data to be fetched
// and possible errors
constructor(...args) {
super(...args)
this.state = {
error: null,
items: [],
query: this.props.query
}
}
componentDidMount() {
// make the API call
client.getEntries(this.state.query)
.then(({ items }) => this.setState({
items
}))
.catch(error => this.setState({
error
}))
}
render() {
// return and render the function
// that was passed in via `render` prop
return this.props.render({
items: this.state.items,
error: this.state.error
})
}
}
The snippet above looks like a lot of code for just making an API call — but now I have "component superpowers". So how can I clean up this API call?
// React | app.js
const App = () => (
<div>
<Contentful query={{ content_type: 'tilPost', limit: 5, order: '-fields.date' }} render={({ items }) => (
<ul>
{ items.map(item => <li>{item.fields.title}</li>) }
</ul>
)} />
</div>
)
You can play with this example on CodeSandbox.
It might seem funny to pass an anonymous function as a prop, but when you look at it, this looks very close to what I had imagined making API calls – a component wrapper that hides the call and lets me define the visual presentation.
The anonymous function is executed with an object including items
that are part of the API response. Pretty sweet!
Using the children
prop
I understand that this pattern might make some people uncomfortable, because writing JSX inside of a prop might seem weird. Thankfully there is an even more beautiful way to do it. The React docs describe "render props" as:
It’s important to remember that just because the pattern is called “render props” you don’t have to use a prop named render to use this pattern. In fact, any prop that is a function that a component uses to know what to render is technically a “render prop.”
It turns out that when you place a function inside of a component, this function is also available as props
. Remember the following lines?
// React | contentful.js
export default class Contentful extends React.Component {
/* ... */
render() {
// return and render the function
// that was passed in via `render` prop
return this.props.render({
items: this.state.items,
error: this.state.error
})
}
}
I can modify it to make use of the children
prop.
// React | app.js
export default class Contentful extends React.Component {
/* ... */
render() {
// return and render the function
// that was passed in via `children` prop
return this.props.children({
items: this.state.items,
error: this.state.error
})
}
}
And now it becomes even more magical! 🎉
// React | app.js
const App = () => (
<div>
<Contentful query={{ content_type: 'tilPost', limit: 5, order: '-fields.date' }}>
{({ items }) => (
<ul>
{ items.map(item => <li>{item.fields.title}</li>) }
</ul>
)}
</Contentful>
</div>
)
You can play with this example on CodeSandbox if you like.
If I put one function inside(!) of the component it will be available via this
of the wrapper component.
Quick side note: If you place several functions inside of a component children
will become an Array.
The code above now looks like 95% of what I dreamed of! (That's something I can live with.)
I started this article with talking about Preact – is this pattern also usable in other frameworks than React?
Yes it is! When using this pattern in Preact, there is only one tiny difference. Preact does not provide the convenience functionality of children
being a function when there is only one child available. That means that props
is always an Array. But hey... this is a no-brainer.
// Preact | contentful.js
export default class Contentful extends Component {
/* ... */
render(props, state) {
return props.children[0](state);
}
};
You can play with this example on CodeSandbox if you like.
The rest stays the same. Pretty neat!
By now I have covered React and Preact. So what about my darling Vue.js? Vue.js is a little bit special. You can use JSX in Vue.js, but yeah... everybody I know writes single file components and mixing template
elements with JSX render functions doesn't feel right to me. Darren Jennings clearly describes how you can make it work with mixing these two ways of writing Vue.js components in this article.
Scoped slots in Vue.js
If you write a lot of Vue.js code, you might wonder if you could transfer the idea of handing in a template that gets data passed to it into a component using slots. And you're right! It turns out since Vue.js v2.1 there is the possibility to use scoped slots that make it possible to pass data into the content you want to put into slots.
This principle is hard to explain without code, so let's have a look.
<!-- Contentful.vue -->
<template>
<div>
<!-- define a named slot `render` and pass items into it -->
<slot name="render" :items="items"></slot>
</div>
</template>
<script>
import { createClient } from 'contentful'
const client = createClient({
space: '...',
accessToken: '...'
})
export default {
props: {
// make the `query` object required
// no query no call ;)
query: {
type: Object,
required: true
}
},
data () {
// set default data
return {
items: [],
error: null
}
},
beforeMount () {
// make the API call using the passed in query
// and set it to the object
// -> it will be passed to the `render` slot
client.getEntries(this.query)
.then(({ items }) => {
this.items = items;
})
.catch(error => this.error = error)
}
}
</script>
This Contentful component defines a named slot called render
, and then passes the given data into it – items
in this case. The other functionality is more or less the same as in the Preact and React examples. The component includes validations for props and makes the actual API call.
The exciting part is the following:
<!-- App.vue -->
<template>
<div>
<Contentful :query="{ content_type: 'tilPost', limit: 5, order: '-fields.date' }">
<!-- set this part of the template to go into the named slot `render` -->
<!-- make the scoped data available via `slot-scope` -->
<ul slot="render" slot-scope="{ items }">
<li v-for="item in items" :key="item.sys.id">
{{ item.fields.title }}
</li>
</ul>
</Contentful>
</div>
</template>
You can play with this example on CodeSandbox if you like.
I can now define the query on the Contentful
component and use the named slots to pass my template into the component. To retrieve the API data I can use the slot-scope
attribute (supported since Vue.js v2.1) and make items
available inside of my passed in template.
This way I don't have to deal with the JavaScript SDK client at all! Because of its verbosity, it doesn't look as lovely as the "render props" pattern in React/Preact, but the outcome is the same – a reusable API component. 🎉
After building single page apps for more than five years now, I have to say that these patterns convinced me. It finally feels easy to make calls in simple apps following a component mindset. Hide the functionality in a component and allow me to make it pretty! I like that.
And please don't get me wrong, proper abstractions are needed when you have significant complexity, and Redux and co are great tools to deal with large apps full of functionality. But when it's just me fetching some data in a simple app this is the way to go for me now.
Additional resources
If you're not convinced yet that "render props" are the way to go react-router
maintainer Michael Jackson gave a fantastic talk on this topic comparing mixins, higher order components and the "render props" pattern. Make sure to check this one out!
Join 5.5k readers and learn something new every week with Web Weekly.