Lately, I’ve been shocked by how many problems Redux’s selectors can solve. That’s why I’ve become a big fan and I’m sad to see that pattern treated as an advanced one. They are easy to understand and, at the same time, enable low coupling / high cohesive code at the Redux’s state level.

I got to know selectors a couple of months ago. In fact, I started working on a crazy search page for a project: besides a normal text query, users can select a number of filters. Thing is, filters come in all sorts and shapes: some are a set of checkboxes, some are nested trees of checkboxes, some trees can be filtered with a text box. Not only that, they produce really complex queries with combinations of AND and OR. Not only that, they interact between each other. And the list of “not only that”s goes much longer.

At the beginning I felt overwhelmed by all of that complexity. Eventually, I worked my way towards the light step by step. In this journey, selectors have saved my ass and become my best friend.

What follows is a story from the project on how I got to know selectors and why they are so awesome.

Keeping state flat

At the beginning I had a store shape similar to this

store = {

filters: {

locations: []

...

}

}

where locations could be

locations = [{

id: 1

value: 'Europe'

checked: false

children: [{

id: 2

value: 'Central Europe'

checked: false

children: [{

id: 3

value: 'Italy'

checked: false

}],

}]}, ...]

Does it look bad enough? Well, imagine the pain of doing an immutable update to check the node with id 3 . In plain JavaScript that would look like this

newLocations = [

...locations.slice(1)

{

...locations[0]

children: [

...locations[0].children.slice(1)

{

...locations[0].children[0]

children: [

...locations[0].children[0].children.slice(1)

{

...locations[0].children[0].children[0]

checked: true

}

]

}

]

}

]

Of course, I could use some library like dot-prop but that would be like sweeping the code smell under the rug.

A better representation could be

locationsById = {

1: {

id: 1

value: 'Europe'

checked: true

children: [2, 3]

}

2: {

id: 2

value: 'Central Europe'

checked: true

children: [3]

},

3: {

id: 3

value: 'Italy'

checked: true

}, ...}

Or the one I ended up with

locationsByPath = {

'1': {

id: 1

value: 'Europe'

checked: true

}

'1/2': {

id: 2

value: 'Central Europe'

checked: true

}

'1/2/3': {

id: 3

value: 'Italy'

checked: true

}, ...}

with the trees’ hierarchies encoded in the paths (eg 1/2 is child of 1 and parent of 1/2/3 ).

With locationsByPath the update looks much nicer

newLocationsByPath = {

...locations

'1/2/3': {

...locationsByPath['1/2/3']

checked: false

}

}

Separating concerns

While working on the feature I’ve noticed something: locationsByPath knows too much. As a matter of fact, it has both domain knowledge (ie id , value and hierarchy) and UI data (ie checked ).

That’s wrong because most of the times domain and UI data have different needs and lifecycles. For example, in my case, once the filters are loaded in the store they are never updated. On the contrary, the checked property is toggled every time the checkbox’s state changes. Therefore, I decided to separate the two:

locationsByPath = {

1: {

id: 1

value: 'Europe'

}

'1/2': {

id: 2

value: 'Central Europe'

}

'1/2/3': {

id: 3

value: 'Italy'

}, ...} checkedLocationsPaths: ['1', '1/2', '1/2/3']

First of all this makes checking a node incredibly easy

checkedLocationsPaths.includes('1/2/3') ?

checkedLocationsPaths :

checkedLocationsPaths.concat('1/2/3')

Also, checkedLocationsPaths and locationsByPath are independent now. That means, they can follow different lifecycles. For example, checkedLocationsPaths can be present in the store before loading locationsByPath .

Unfortunately, refactoring to a flat state made my life easier with reducers but harder with components. In fact, the initial nested shape is much easier for components to deal with:

const Component = ({ value, checked, children }) =>

<div>

<input type='checkbox' value={value} checked={checked} /> {children.map(({ value, checked, children }) =>

<Component

value={value}

checked={checked}

children={children}/>

}

</div>

Well, I could add some logic to the component to handle the flat state. But is it the right place for that? I don’t think so. In fact, doing so would mean coupling the component with the state’s shape. Also, I believe in really dumb™ components.

Selector to the rescue

Reducers are in charge of writing the state to a specific shape. Therefore, it’s a good idea to have something as close as possible to them to handle the reading part. That’s where selectors come into play.

If you don’t want to take my word, listen to Dan Abramov’s.

As a matter of fact, having the initialState , write side and read side in the same place is right on spot:

// reducer.js initialState = {

locationsByPath: {}

checkedLocationsPaths: []

...

} // write side

const filters = (state = initialState, action) => {

if (action.type === 'LOAD_LOCATIONS') {

return { ...state, locations: action.locations };

} else if (action.type === 'CHECK_LOCATION') {

const checkedLocationsPaths =

state.checkedLocationsPaths.includes('1/2/3') ?

state.checkedLocationsPaths :

state.checkedLocationsPaths.concat('1/2/3')

return { ...state, checkedLocationsPaths }

}

...

} // read side // nests locations

const nest = locationsByPath => ... // adds `checked: true` where appropriate

const withChecks = (locationsByPath, checks) => ... const getNestedLocationsWithChecks = state => {

const {

locationsByPath,

checkedLocationsPaths,

} = state; return nest(

withChecks(locationsByPath, checkedLocationsPaths)

);

}

Let’s see how some the selector works with an example

locationsByPath = {

1: {

id: 1,

value: 'Europe',

},

'1/2': {

id: 2,

value: 'Central Europe'

},

'1/2/3': {

id: 3,

value: 'Italy',

}, ...} checkedLocationsPaths: ['1', '1/2', '1/2/3'] state = {

locationsByPath,

checkedLocationsPaths

} nestedLocationsWithChecks(state)

=> [{

id: 1,

value: 'Europe',

checked: true,

children: [{

id: 2,

value: 'Central Europe'

checked: true,

children: [{

id: 3,

value: 'Italy',

checked: true,

}]

}]},

...]

with nestedLocationsWithChecks(state) ready to be passed to the Component from the previous section.

In other words, on one side the application works with flat state. Which is more convenient for data management. On the other side, the application works with nested data. Which is more convenient for the UI.

More importantly, they can evolve independently since selectors work as an anti-corruption layer and prevent leaking structural coupling.

Bubbling up a selector

In the previous section I’ve worked top to bottom by refactoring the state first and worked my way down to the component.

Let’s see how the inverse looks like by refactoring some code I’ve written before getting to know selectors.

There’s a page in the application where some articles are shown grouped by their category (ie articles belong to one category only). All the categories must be rendered always besides the last one which should be hidden if empty.

categoriesFrom performs the group by and translates the title for the current locale. Component renders each category separately.

const categoriesFrom = resources => {

const byCategory = [

{ id: 1, name: I18n.t('cat1'), resources: resources.cat1 },

{ id: 2, name: I18n.t('cat2'), resources: resources.cat2 },

{ id: 3, name: I18n.t('cat3'), resources: resources.cat3 },

{ id: 4, name: I18n.t('cat4'), resources: resources.cat4 },

]; if (resources.cat5.length !== 0) {

const name = I18n.t('cat5')

return byCategory

.concat({ id: 5, name, resources: resources.cat5 })

} else {

return byCategory

}

} const Component = ({ resources }) =>

categoriesFor(resources).map(category =>

<Category key={category.id} category={category} />)

)

Component is too smart and is coupled to the state shape (eg resources.cat1 ).

Let’s bubble that logic up to a selector:

// reducer.js // selector

const getResources = state => state.resources; // selector

const getResourcesGroupedByCategory = state => {

const resources = getResources(state); const byCategory = [

{ id: 1, name: I18n.t('cat1'), resources: resources.cat1 },

{ id: 2, name: I18n.t('cat2'), resources: resources.cat2 },

{ id: 3, name: I18n.t('cat3'), resources: resources.cat3 },

{ id: 4, name: I18n.t('cat4'), resources: resources.cat4 },

]; if (resources.cat5.length !== 0) {

const name = I18n.t('cat5')

return byCategory

.concat({ id: 5, name, resources: resources.cat5 })

} else {

return byCategory

}

} // Component.jsx const Component = ({ resources }) =>

resources.map(category =>

<Category key={category.id} category={category} />)

) connect({ resources: getResources(state) })(Component)

Ever heard about dumb components? With selectors you get dumber components™: they just destructure props and render them without any additional logic.

Outro

In general, the shape of the state is something only reducers should know about. The moment it leaks out of the store, code becomes structurally coupled.

Since reducers decide what’s the shape because they write it. It’s just common sense to make the “read” happen close to them with selectors.