Easy data management from a REST API using redux-recompose v2

Manuel V Battan
Wolox
Published in
6 min readNov 29, 2018

--

Previously on redux-recompose…

In the previous post of this library, we’ve introduced concepts like targeted actions, effects, completers, and even injections. We’ve also introduced some creators that lend us a hand while creating entities like actions or reducers.

We’ve also found that in reducers that handles data fetching, using completers may be an approach to reduce a bit the boilerplate (and pain) per se introduced by Redux.

When you take a look at a project that uses Redux.

As a result, we reduced this reducer…:

…to something like this one:

And this actions files…:

…to this one:

Introducing fetchMiddleware

One day, at an internal training, while presenting this tool to my coworkers, one of them said:

‘Hey yo, createThunkAction is a function that has too many params. I started to learn this yesterday but I cannot remember well the order of these params’.

And that was true. Generalizing into that function costs many params. That was unacceptable.

NOOO 😱

‘Maybe we can name those params so we can remember them more easily’

And instantly I imagined something like this:

That’s annoying to write!

NO! ☠️

Notice that, if we could remove createThunkAction call, this is pretty much as classic, plain actions.

At that time, we came with an idea. Maybe we can add logic so that plain object can be recognized as a thunk action.

💡

That’s how the fetchMiddleware born. Now, redux-recompose accepts these actions as async actions by including this middleware (along with redux-thunk) to your middlewares:

import { fetchMiddleware } from ‘redux-recompose’;...
applyMiddleware(thunk, fetchMiddleware);

Put a service field within them and voilà! Your actions that fetch data to a REST API look like your common plain actions! No more createThunkAction or functions that take many params 🎉.

However, there is a problem with this approach.

There is still code that is being repeated. Every time we need to fetch data, we need to write our reducer, our actions (completed or not), and perhaps, this pattern will be present in two or more reducers that manage data fetching. This SUCCESS-FAILURE pattern is being repeated among reducers.

Extracting your cross-reducer logic using an invisible reducer

What?

First of all, what is an invisible reducer? 🤔

Imagine that we are using combineReducers utility from redux. This helps us to ‘merge’ two or more reducers into a root one and to accomplish that, redux slices for us the state, giving each reducer a slice of this global state.

combineReducers result

This way, every reducer of ours will only modify its granted slice. But, what if we need to extract logic that is being repeated in every reducer we write? Isn’t repeated logic annoying? 🍊

We’d like to have a more general, global reducer capable of modifying the state granted to other reducers:

A global reducer that modifies the state granted with combineReducers

That’s the idea of invisible reducer. But what makes it invisible? Is it a ghost? Kind of.

👻

The idea is to configure this reducer only once in your app, so this logic keeps isolated and reusable by our other reducers. Also, it keeps hidden from other reducers, so 👻.

To use it, we’ll need to override combineReducers behavior.
That’s how we do it:

And that’s all, invisible in action.

By default, redux-recompose ships with a default invisible reducer that is the following:

Here is the source of this one.

We can tell to our invisible reducer to handle actions that are being repeated in our reducers. We will refer to those actions as external actions since these are no longer handled by our reducers.

How do they work? These use createExternalActions, which is a utility to distinguish local actions from external ones.

Just a working idea.

What if we need to customize that ghost? createExternalActions take another param that corresponds to the allowed action names (like createTypes). Also, wrapCombineReducers takes an extra param that allows specifying which will be our invisible reducer and how this will behave.

Then, if we need to extract logic among reducers, we simply provide a global reducer and the action names that this reducer may handle.

An application: dispatchable services

At Wolox, we usually group all API calls related to a specific business concept or by the screen into files. We call these files services and look like this:

What if we add an invisible reducer just to manage this SUCCESS-FAILURE in ‘background’, so we just focus on write ideas instead of boilerplate?

This is possible via wrapService utility, that does this for us. Wrap down your API calls:

And then, use them like actions!

Using this requires to configure an invisible reducer, it works well with the default provided by redux-recompose.

So, this approach led us to reduce the list introduced in this post to nearly this one:

  1. Create the component to show a list of users (no problems here).
  2. Create the fetch call to the API.
  3. Extract the data from the Redux state, by using connect() .
  4. Add a defaultProp or declare the initial state of the slice being created with wrapService.
  5. Declare the extracted data field in our component’s prop-types.
  6. Call the service in the componentDidMount() function.
  7. Finally, render the data in the DOM.

There is an example we provided in this library to use it. You can clone this example and run it if you like 😊.

wrapService is a utility that uses the fetchMiddleware, so we can customize our API calls with injections. From the example app:

And that is! We’ve fetched data from our REST API and stored it globally without writing a reducer or an actions file! 🎉

Classic thunk actions, fetchMiddleware actions, external actions, and dispatchable services can live together in the same project without conflicts 😊

I’d like to thanks to all the team that makes this library grow and be maintained, and to you for reading this proposal!

More detailed documentation is available at https://github.com/Wolox/redux-recompose

Also, an npm package is available:

npm install --save redux-recompose

If you have any suggestions, any ideas you want to talk about or find a bug, please post an issue or create a PR on GitHub and I’ll gladly reply to it. Let us know what you think! I’ll be very thankful if you drop a comment below or clap this story! 🤓

We also are looking for people who like open source and likes to contribute to this library!

--

--