Redux Principles — Approaching a Problem

Tomas Barry
Butternut Engineering
7 min readMay 5, 2020

At Butternut, we are big fans of Redux. We have been using it in our product-focused teams for a number of years. There are many different approaches that can be taken to dealing with a problem in Redux. However, some of these approaches are better than others. Some approaches make it easier to iterate on the Redux application down the line.

In this article, I will outline an approach that we take to features that we want to implement in our Redux applications. I hope that by the end of this article you either realise that you take a similar approach or you have a new approach to try for yourself.

Photo by Artem Sapegin on Unsplash

Setting The Stage

We’re going to start with a very simple component connected to a very simple Redux store. Let’s say that we have a component called Greeting that displays a greeting to a logged-in user:

import * as React from 'react'
import { connect } from 'react-redux'
const mapStateToProps = (state) => {
const { user } = state
const { fullName } = user
return {
fullName
}
}
const Greeting = ({ fullName }) => (
<p>
{ `Hello, ${fullName}!` }
</p>
)
export default connect(
mapStateToProps,
null,
)(Greeting)

You can infer from just this component that our store at the very least has a user object that has a key called fullName . For the purpose of this article, you can assume that this is what our Redux store looks like:

{
user: {
fullName: 'Joe Bloggs'
}
}

Now that the stage is set, we can look into how we would go about approaching an iteration on our project.

The Feature Request

One day, your product manager comes to you and says “Quick, we need to change the greeting in our app! After doing research we have found out that our customers respond more positively if we only use their first name in the greeting instead of their full name. Can you make that change?”.

Now, I’m first going to explain the wrong way to go about implementing this feature and then I will explain the right way (at least, the right way according to how we approach these sort of problems). Don’t get too hung up on the specifics of the example. You should be on the lookout for the principles that are applied.

The Wrong Approach

First things first, let’s pull up our Greeting component:

import * as React from 'react'
import { connect } from 'react-redux'
const mapStateToProps = (state) => {
const { user } = state
const { fullName } = user
return {
fullName
}
}
const Greeting = ({ fullName }) => (
<p>
{ `Hello, ${fullName}!` }
</p>
)
export default connect(
mapStateToProps,
null,
)(Greeting)

Ok. Well, we have the full name. If we split the fullName into an array where the delimiter to .split is a space then we know that the first element of that array is the first name. Something like this should do the job:

import * as React from 'react'
import { connect } from 'react-redux'
const mapStateToProps = (state) => {
const { user } = state
const { fullName } = user
return {
fullName
}
}
const Greeting = ({ fullName }) => (
<p>
{ `Hello, ${fullName.split(' ')[0]}!` }
</p>
)
export default connect(
mapStateToProps,
null,
)(Greeting)

For good measure, so as to not have too much logic in our Greeting component, let’s also abstract the .split out of the component. After all, we know that Greeting should only take the first name:

import * as React from 'react'
import { connect } from 'react-redux'
const mapStateToProps = (state) => {
const { user } = state
const { fullName } = user

return {
firstName: fullName.split(' ')[0]
}
}
const Greeting = ({ firstName }) => (
<p>
{ `Hello, ${firstName}!` }
</p>
)
export default connect(
mapStateToProps,
null,
)(Greeting)

Test it out, confirm that it works, and ship it. Job done. Nice and quick.

The Right Approach

Ok, let’s have a peek at our component and get a feel for where things sit right now:

import * as React from 'react'
import { connect } from 'react-redux'
const mapStateToProps = (state) => {
const { user } = state
const { fullName } = user
return {
fullName
}
}
const Greeting = ({ fullName }) => (
<p>
{ `Hello, ${fullName}!` }
</p>
)
export default connect(
mapStateToProps,
null,
)(Greeting)

Right, so we just pluck the fullName from the store and present it. Let’s have a proper look at the store and see what data structure we’re dealing with:

{
user: {
fullName: 'Joe Bloggs'
}
}

Hmm, it’d be really handy if we had a more flexible store. We would be golden if we had something like:

{
user: {
firstName: 'Joe',
lastName: 'Bloggs'
}
}

If that was the case, then our Greeting component could just be written as:

import * as React from 'react'
import { connect } from 'react-redux'
const mapStateToProps = (state) => {
const { user } = state
const { firstName } = user
return {
firstName
}
}
const Greeting = ({ firstName }) => (
<p>
{ `Hello, ${firstName}!` }
</p>
)
export default connect(
mapStateToProps,
null,
)(Greeting)

Let’s look at where our data is coming from and see if we can decouple our app from having to figure out what the first name of a string is and make it so that our store ends up with a firstName and a lastName on the user.

What’s The Difference?

I’ll admit, the example laid out here is very contrived. I have also glossed over a bit of complexity around what constitutes as a first name as different cultures have different standards when it comes to naming. Furthermore, some people have multiple first names and I have made the basic assumption that in a string separated by spaces it is the first word. Even still, there are key differences to each approach.

In The Wrong Approach, the engineer made all the assumptions that I have just outlined. They have assumed that a first name is the first word in a string separated by spaces. Additionally, they have baked in logic to their connected component that is defined in isolation from the rest of the app. The Greeting component does not have logic, but they have just hid the logic away in the mapStateToProps method.

The worst part of The Wrong Approach is that the engineer did not look at the store. At the very least, the engineer should have checked the store to ensure that firstName wasn’t a property on the user. But what is more worrying is that the engineer considered the problem in isolation from the rest of the app. If another component needs to use the first name of the user then the engineer tasked with tackling that other component will have to spend the same amount of effort plucking the first name from the store. The engineer didn’t consider the app as a whole. The engineer did not think about how they could evolve the store to match the new requirements of the app thus making the store better able to handle future changes.

The approach shown in The Right Approach is to first get your bearings by identifying the components that you will be acting upon but not getting too hung up about how they are currently implemented. You’re changing them after all. You should then immediately go to the store and get very familiar with how it is structured. Sometimes you might strike gold and find out that the data you seek is right there. If it is not, think about how the store would have to change to better suit your needs and then change the store.

Every feature request demands a change to your app. This should demand a change to your store. Your store is your app. If you don’t have a good store, you don’t have a good app. A well-built store makes it significantly easier to have a well-built app.

With your data defined in a central location you can have a single reference point for all data in your app, you can take logic away from your components and away from the mapStateToProps method. Most importantly, you now have access to firstName across your app. Now, you may ask “what about other components that used fullName, they have now lost that data”. Well, you can replace that with a selector. Selectors will keep business logic away from your components and close to your store.

What Am I Trying To Say?

Principles are important. Having a set of principles that you can follow when tackling your engineering problems will make you write better code. The principle that I am trying to get across here is this:

When you approach a problem in Redux, look at the store. Ask yourself does the store satisfy what I am trying to do?. If the answer is anything other than an obvious yes then ask yourself “what would the perfect store look like for my problem?”. Making my point even more concise — Evolve your store to match the changing demands on your app, not the other way around.

I hope that this article has made sense. Tackling a problem by looking at the data structure that you are dealing with is a far more enjoyable way of dealing with problems. Approaching problems with a store first mindset means that you can write better code.

By The Way

At Butternut, our product-focused engineering teams use Redux as well as a whole host of other interesting tools. If you’re interested in joining a company that is all about dogs, then have a look at our job postings here. If you would like to hear more about the life of an engineer at Butternut then you can read about James’ first year here or you can reach out to me on LinkedIn.

--

--