Using the State Reducer Pattern for an Add To Cart Button

React has some pretty awesome patterns that allow for all sorts of composition and re-usability. State Reducer is one such pattern. This pattern allows us greater control of our state changes and the luxury of keeping our dumb components, dumb. By using per-component, custom reducers we can gate our setState calls and thus prevent unnecessary renders. This pattern isn’t to be confused with Redux’s reducers. Although, functionally, their APIs are similar this is a React-only pattern and is named with the more general CS “reducer” concept in mind. With that noted, let’s check it out!

This article assumes you’re familiar with React’s Render Props pattern. If you’re not quite cozy with it yet, I highly suggest this article by Dan Benson.

Let’s say we have a standard e-commerce site where we have a button that allows visitors to add items to their cart. This button is everywhere. It has various UI implementations, but generally it behaves identically with the exception of a few use cases here and there. The visitor sees something they want, they click it, and it adds that item to their shopping cart. Simple enough, right? Well, what if (for whatever reason) we wanted to limit the amount of a specific item they could place in their cart?

Let’s pretend we have a limited supply of the item or -deviously- want to keep the supply low in an attempt to increase the demand (because… C.R.E.A.M.). Let’s also assume we’re lazy and we don’t want to keep repeating button logic in multiple components. Assuming we’re using Redux we could simply throw that stipulation into our reducer: we could check the number and type of the items in our store and prevent state changes there. But what if we’re not using Redux? Further, what if we are but we want to keep this on the React side of things to keep our state management less complicated?

The State Reducer pattern allows us to cleanly manage our state’s updates based on some criteria of our (or an implementors) choosing. If Render Props allow a user to control how a child’s UI is rendered, State Reducers allow a user to control how a child’s state is managed.

If Render Props allow a user to control how a child’s UI is rendered, State Reducers allow a user to control how a child’s state is managed.

Using the Render Props pattern, we’ll create a wrapper for our buttons and pass the result of that logic to the stateless UI (dumb) components. We’ll keep this example as simple as possible to keep the point in focus.

class ButtonWrapper extends React.Component {

state = { added: 0 } controlledSetState (stateOrFunc) {

this.setState(state => { /*

* In this example we’re only invoking

* the `controlledState` function in

* one place, so we can be sure `stateOrFunc`

* is a function. Nonetheless, we’ll check

* here. In some use cases it might be a

* normal piece of the state.

*/

const changedState = typeof stateOrFunc === ‘function’

? stateOrFunc(state)

: stateOrFunc /*

* `stateReducer` is guaranteed to return an

* empty object in this example,

* but it could be possible in other use cases

* that the return value is null.

* This will make sure we at least have a {}

* to return appropriately below.

*/

const reducedState = this.props.stateReducer(state, changedState) || {} /*

* We’ll return the `reducedState` if something

* interesting happens, i.e. we stay below our

* limit of 8. In other instances ( >= 8) we’ll

* return to prevent excessive re-renders.

*/

return Object.keys(reducedState).length > 0

? reducedState

: null

})

} addToCart = () => {

// Just being explicit for readability

const fn = ({ added }) => ({ added: added + 1 })

this.controlledSetState(fn)

} getWrapperState () {

return {

addToCart: this.addToCart,

added: this.state.added,

}

} render () {

return this.props.children(this.getWrapperState())

}

}

I think this example speaks for itself, but basically the only time we setState is if there was a result from the stateReducer (i.e. a non-empty object of state changes were returned). Otherwise we return null from setState which does nothing to the overall application (no renders). But how might this actually be used? Let’s do that too.

class CartButton extends React.Component { /*

* This is the only “intelligent” thing our dumb component

* does: define its reducer. This is then passed on to

* the ButtonWrapper as a prop.

*/

addToCartStateReducer = (nextState, updatedState) => {

if (nextState.added >= 8) {

return { }

}

return nextState

} render () {

return (

<ButtonWrapper

stateReducer={this.addToCartStateReducer}

>

{buttonLogic => (

<div>

<button onClick={buttonLogic.addToCart}>Add Item</button>

</div>

)}

</ButtonLogic>

)

}

}



Our CartButton defines its own reducer. In this implementation we’ve chosen to limit the number of items a person can add to their cart to eight. Aside from the addToCartStateReducer method, this is a very simple component. It’s main job is to display a button and our ButtonWrapper is responsible for everything else. Pretty clean, right?

We’ve simplified this example from other examples you may have seen around in videos and in other blogs. For starters, notice that our inheriting html button is as simple as possible. It may be the case that your inheriting component needs to maintain some of its own state. This scenario gets a little more confusing so it has been left out. The important point to grasp is that the “parent” of the button is the one that’s controlling the state changes, but it’s the child who’s telling the parent how it should be managed.

[…] the “parent” of the button is the one that’s controlling the state changes, but it’s the child who’s telling the parent how it should be managed.

If that isn’t Inception-y enough for you… apologies.

Using the State Reducer Pattern: Conclusions

The State Reducer pattern clearly has some utility, but it does break encapsulation. We’re checking ButtonWrapper 's state in CartButton . That probably makes some people recoil in disgust, I’m sure. Despite this it does allow for cleaner and more reusable state management while maintaining the ability for separation of concerns.

It could be justifiably argued that this pattern is barely usable. I mean, we have Redux, right? Why not just use that. Well, you might not need redux, but you still might need some of those functionalities. If that’s your reality and you’d prefer to confine your state management to your components/containers this pattern really starts to shine.

Thanks for reading!

P.S. Dollar Shave Club is hiring! Come join the team, make some technological waves and shave like the wind!

This article was originally published on my personal blog mttyng.com. I rant about a lot of nerd-stuff there, check it out!