How to reuse Redux components?

Mattia Manzati
6 min readOct 5, 2016

--

HIGH ALERT! The code provided in this article is highly experimental and could be soon provided as a reusable library.

The demo above uses the same redux-connected <Counter /> component 4 times

DEMO: http://www.webpackbin.com/4JwSTRaT-

While Redux reducers can be easily composed, the same sentence could not be used when talking about Redux components. That could be a problem when creating reusable Redux-connected UI components.

As Dan Abramov encourages, you don’t actually need to Redux all the things, you could reuse your reducer with the component state and setState. But do we really want to lose all that time travelling and app state serialization bounty?

Vanilla Redux

Let’s take an example

This example could be pretty easy and trivial, but does it really works well when used multiple times in our Redux application?

The connect method provided by Redux resolves the current component state from the top level state; that could be convenient when building page containers, but also it could be a mess when working with reusable Redux-connected components such as Autocomplete fields.

Reusing the same component does not work properly in vanilla Redux

This behaviour is expected, but what can we do to solve it?

The Elm architecture let the parent component hold the state for the child one and pass it down through some kind of “prop”, and that could be a great solution, but is it really performant when using immutable objects?

If a child component, like the counter, will change it’s state, also the parent one will have a different object as the “state” prop.

That’s expected because when working with the immutable objects changing a property will result in dropping the object and return a new one for that property; so when working with a big state shaped as a tree that will result in changing recursively that property parent objects.

Changing recursively that will indeed result into an entire app container update, since the change will bubble up to the root object, unless we setup some shouldComponentUpdate tricks that can became hard and difficult to debug.

Embrace local selectors

What if, when using a component, instead of passing it’s state as a property, we pass down some kind of knolege of where the component state is located in the entire app state?

Selectors can easily and with readable code represent a state slice, so why don’t use them?

That selector can either be a constant function, or a memoized one based on some of the parent component props. (Think about a collection of counters, where the single counter state is holded in the appropriate parent array index).

This way, instead of passing down an object that keeps changing over time, we have a more “constant” way of map to the local state slice.

Moreover, if the component are nested multiple times, you’ll need to get the parent selector manually and compose it with the new one.

To solve that issue we could take advance of the React context feature and store the parent selector, and when a selector is passed to a container component, it will be stored in the context and passed down to child components, as well as prop to the given component.

Pass down the local state selector and compose it

Connecting components to local state

We now need an advanced version of React-Redux’s “connect()” that’s able to understand that before calling connect, it should resolve the local component state using the given selector.

That could be easily achieved by creating an HoC that will call the given mapStateToProps with the resolved local state, and optionally pass the entire state as third parameter to the mapStateToProps function.

Obviously, this “connect” will act as drop in replacement for the react-redux one.

Provide a drop-in replacement for react-redux connect.

The dispatch problem

As far, we have seen how easily map each component local state to a slice of the entire app state. Have we done? No!

Using the regular combineReducers would lead to listen to any action. This way ALL COUNTERS will be incremented when an INCREMENT action will be dispatched.

Elm provides a function called “forwardTo”, which simply wraps an action into an address. Inside the reducer, you’ll check if the given action has that address and if so, pass the action to the child reducer.

Wrapping and unwrapping the action

Implementing forwardTo is just a matter of wrapping an object. On the other hand, unwrapping needs to recursively call unwrap to remove the wrapping “FORWARD” actions.

How to check if the given action should be passed down to a child reducer? This is pretty easy, if the action has the forward type, you’ll just check that the target name is the same of the forward action and pass down the wrapped action, if not undefined will be returned and no action will be passed down.

Notice that any non-FORWARD action will also be passed down. As a PubSub system, any action broadcasted without a precise target will be broadcasted to the child elements.

This allows the parent component (or any component using the globalDispatch given as third parameter to the mapDispatchToProps parameter of connect) to pass action to the children components. This way, actions like ROUTE_CHANGE, or API_CONNECTION_REFUSED will bubble down to any subcomponent, and any interested subreducer can easily catch that action as you would do in vanilla Redux.

Redirecting the action in the parent reducer

We could now change our CounterPair reducer to correctly handle and forward actions to the correct child component reducer.

Notice that updateProperty does not simply create a new object with the updated property, but also avoid creating a new object if the target property has the same value you are going to set. Forgetting to do this will result into returning accidentaly a new object each time the reducer is called.

This principle is already implemented in any immutable library. So using immutable-js should reduce your boilerplate ;)

Passing the dispatch to connect

We now need to change the default mergeProps of connect() to prioritize the component props over the store dispatch. Then, as done in mapStateToProps, the local dispatch will be given as first arg, and the global one as third arguments.

Dispatch will be now a required argument when using Redux containers component. If you don’t feel right about it, you can default to the store dispatch instead.

And here we are! It works as expected!

The demo above consists in 2 CounterPair components, which are all based on the Counter component.

Notice that parent components can listen to events of their childrens; you can for example listen to any counter increment. In this example, the app reducer will listen to any increment click of any of its Counter childrens.

Next investigations: Redux Saga and Redux-Observable

Redux used in large scale apps always come along with some side effect managing library. In the past I used redux-saga and redux-observable.

Wrapping and forwarding side effects seems difficult but in reality can be considered kind of easy. Implementing a “forwardSagaTo(saga, name, localSelector)” can be done by altering the object yielded by the saga generator and wrapping any selector or action contained in it. This way, the counter pair saga can fork and cancel the children saga at any time.

In redux-observable we can easily wrap and forward epics by simply mapping the action stream to an “.map(action => actionFor(BOTTOM, action))” stream and then merge that new stream with the children epic.

I am investigating about this part, and code examples should appear here in the near future! ;)

Demo

Thanks for reading!

As said before, this article is in highly experimental stage, so any problem or feedback is highly appreciated! :D

--

--