Simplify your React components with Apollo and Recompose
Sashko Stubailo
One of the main goals of Apollo Client and the Apollo project in general is to fit seamlessly into the modern React developer’s toolbox. That’s why we’ve invested in simple higher-order components, server-side rendering, and React Native.
Increasingly, React developers are working more and more with small, focused components, and the Recompose library by Andrew Clark takes that to the next level. When Andrew presented it at React Europe last year, he referred to it as a toolbox like Underscore or Lodash, but for React components. Here are some things you can do with Recompose:
- Optimize your React components by making them pure-rendered
- Set default props
- Add limited state variables
And you can do all of this while using the React functional component syntax, which makes your code much more straightforward and reduces your chances of accidentally introducing some state or complexity into your UI rendering.
The React higher order component you use from react-apollo
fits this mold. It just does one thing well: It enables you to co-locate your GraphQL query with your React component and get that data in your props
, while handling all of the details like fetching and caching the data. It doesn’t do anything else, like manage variable state. That’s where React component composition and Recompose come in.
Without further ado, here is our first end-to-end example of how to use Recompose together with React Apollo to have a nice, concise UI with GraphQL!
Manage your variables using ‘withState’
The best feature of functional components is that they are stateless. This means for a certain set of props you will always get the same output, making the component predictable and easy to test. However, sometimes you still want some temporary state, and it’s best to put that outside of the component that handles rendering to keep things clean.
Let’s look how we can use the withState
container from Recompose to build a React component that has both a search box and a GraphQL query that uses that search term:
import React from 'react';
import { withState, pure, compose } from 'recompose';
import gql from 'graphql-tag';
import { graphql } from 'react-apollo';
import { Link } from 'react-router';
// The data prop, which is provided by the wrapper below, contains
// a `loading` key while the query is in flight, and the bookSearch
// results when they are ready
const BookSearchResultsPure = ({ data: { loading, bookSearch } }) => {
if (loading) {
return <div>Loading</div>;
} else {
return (
<ul>
{bookSearch.map(book =>
<li key={book.id}>
<Link to={`/details/${book.id}`}>
{book.title} by {book.author.name}
</Link>
</li>
)}
</ul>
);
}
};
// The `graphql` wrapper executes a GraphQL query and makes the results
// available on the `data` prop of the wrapped component.
//
// Note that if you type a search field and then hit backspace, the
// Apollo cache kicks in and no actual data loading is done.
const data = graphql(gql`
query BookSearchQuery($keyword: String!) {
bookSearch(keyword: $keyword) {
id
image
title
author {
id
name
}
}
}
`, {
// The variable $keyword for the query is computed from the
// React props passed to this container.
options: (props) => ({
variables: { keyword: props.keyword },
}),
});
// Attach the data HoC to the pure component
const BookSearchResults = compose(
data,
pure,
)(BookSearchResultsPure);
// Use recompose to keep the state of the input so that we
// can use functional component syntax
const keyword = withState('keyword', 'setKeyword', '');
const BookSearchPure = ({ keyword, setKeyword }) => (
<div>
<input
type="text"
value={ keyword }
onChange={(e) => setKeyword(e.target.value)}
/>
<BookSearchResults keyword={keyword} />
</div>
);
// Attach the state to the pure component
const BookSearch = compose(
keyword,
pure,
)(BookSearchPure);
export default BookSearch;
One nice side effect is that since we are keeping all of the state and data loading outside of the component, we can drop in the pure
mix-in everywhere to make sure our components don’t rerender when they don’t need to.
In the above code snippet, all of our concerns are very nicely separated through the React component paradigm. We have two components for rendering HTML, one for keeping track of the search box state, and one for data loading. We can test and inspect each one separately.
Displaying loading state with ‘branch’
One thing that’s not so nice about the BookSearchResultsPure
component above is that it has an if statement with multiple returns. This means you have to interpret that logic in your mind to see what the result might look like. We could also use an HoC to flatten this out:
import React from 'react';
import { pure, branch, renderComponent, compose } from 'recompose';
import gql from 'graphql-tag';
import { graphql } from 'react-apollo';
import { Link } from 'react-router';
// Define a very basic loading state component - you could make this
// a nice animation or something
const Loading = () => (
<div>Loading</div>
);
// Define an HoC that displays the Loading component instead of the
// wrapped component when props.data.loading is true
const displayLoadingState = branch(
(props) => props.data.loading,
renderComponent(Loading),
);
// The pure component that renders UI
const BookSearchResultsPure = ({ data: { bookSearch } }) => (
<ul>
{bookSearch.map(book =>
<li key={book.id}>
<Link to={`/details/${book.id}`}>
{book.title} by {book.author.name}
</Link>
</li>
)}
</ul>
);
// The GraphQL query wrapper, provides a data prop with a loading
// field on it
const data = graphql(gql`
query BookSearchQuery($keyword: String!) {
...
}
`, {
options: ({ keyword }) => ({ variables: { keyword } }),
});
// Put everything together!
const BookSearchResults = compose(
data,
displayLoadingState,
pure,
)(BookSearchResultsPure);
Now, we can very easily share a common loading
component between all of our UI that uses data from React-Apollo, and the logic is contained in a separate chunk that isn’t obscuring the structure of our pure component!
Take-away
The react-apollo
API was designed to be the best way to pull GraphQL data into the props of a React component. Combined with a library like Recompose, it gives you an incredibly powerful and flexible way to declaratively connect server-side data to your react components while keeping them pure.
Abhi Aiyer has already written a great follow-up about how to use Recompose to work nicely with mutations:How I write Mutations in Apollo w/ RecomposeSince Sashko Stubailo launched his post about Apollo + Recompose, which is a great blog post by the way, I wanted to show everyone how I…medium.com
Thanks Abhi! And if anyone else has great ideas about how to use Apollo and Recompose together, please let me know in the responses!
Want to work full-time on GraphQL technology, collaborate with the open source community, and write articles like these? We’re actively hiring for a variety of positions! Check them out.