Recompose patterns

Recompose is a toolbelt for working with React components in a reusable, functional way. The workflow is similar to libraries like Underscore or Lodash, which can help you avoid re-implementing common patterns and keep your code DRY. Check out the Recompose API for all the details.

Loading status

A common use-case when using the graphql HOC is to display a "loading" screen while your data is being fetched. We often end up with something like this:

Recompose has a utility function branch() which lets us compose different HOCs based on the results of a test function. We can combine it with another Recompose method, renderComponent(). So we can say "If we are loading, render LoadingPlaceholder instead of our default component", like so:

Error handling

Similar to the loading state above, we might want to display a different component in the case of an error, or let the user refetch(). We will use withProps() to include the refetch method directly in the props. This way our universal error handler can always expect it to be there and is more decoupled.

Now we can count on results being available for our default component and don't have to manually check for loading state or errors inside the render function.

Query lifecycle

There are some use-cases when we need to execute code after a query finishes fetching. From the example above, we would render our default component only when there is no error and loading is finished.

But it is just a stateless component; it has no lifecycle hooks. If we need extra lifecycle functionality, Recompose's lifecycle() comes to the rescue:

The above works well if we just want something to happen at component mount time.

Let's define another more advanced use-case, for example, using react-select to let a user pick an option from the results of a query. I want to always display the react-select, which has its own loading state indicator. Then, I want to automatically select the predefined option after the query finishes fetching.

There is one caveat: we need to be aware that the query can skip the loading state when data is already in the cache. That would mean we need to handle networkStatus === 7 on mount.

We will also use recompose's withState() to keep value for our option picker. For this example we will assume the default data prop name is unchanged.

Controlling pollInterval

We’re not usually running any migrations, so a nice, slow polling interval like 30 seconds seemed reasonable. But in the rare case where a migration is running, I wanted to be able to see much faster updates on its progress.

The key to this is knowing that the options parameter to react-apollo’s main graphql function can itself be a function that depends on its incoming React props. (The options parameter describes the options for the query itself, as opposed to React-specific details like what property name to use for data.) We can then use recompose's withState() to set the poll interval from a prop passed in to the graphql component, and use the componentWillReceiveProps React lifecycle event (added via the recompose lifecycle helper) to look at the fetched GraphQL data and adjust if necessary.

Note that we check the current value of pollInterval before changing it because, by default in React, nested components will get re-rendered any time we change state, even if you change it to the same value. You can deal with this using shouldComponentUpdate or React.PureComponent, but in this case it’s straightforward just to only set the state when it’s actually changing.

Other use-cases

Recompose is a powerful tool and can be applied to all sorts of other cases. Here are a few final examples.

Normally, if you wanted to add side effects to the mutate function, you would manage them in the graphql HOC's props option by doing something like { mutate: () => mutate().then(sideEffectHandler) }. But that's not very reusable. Using recompose's withHandlers() you can compose the same prop manipulation in any number of components. You can see a more detailed example here.

Mutations can also be tracked using recompose's withState, since it has no effect on your query's loading state. For example, you could use it to disable buttons while submitting form data.