Comfy Redux Selectors

A suite of composable functions that make it very easy to work with selectors. This library is intended to be used with redux but it works for any situation where you need to read values from structured objects.

Redux-selectors allows you to:

  • Easily create selectors from a path string
  • Easily memoize dependent selectors
  • Easily create configurable selectors
  • Plus: utility functions like combineSelectors and composeSelectors make it easy to stitch selectors together

Note: If reselect is working for you, keep using it. If you find yourself commonly bumping in "missing features" in reselect, keep reading.

Installation

yarn add @comfy/redux-selectors redux

Note: composeSelectors depends on compose from redux

Getting started

The main point of this library is to create selectors that will read from state. Here you can see an example of creating a "path selector" that will return the value of state.fruit.apples.

import { createSelector } from '@comfy/redux-selectors'

const selectApples = createSelector('fruit.apples')

// ---

const state = {
  fruit: { apples: 1, oranges: 2 }
}

selectApples(state) // => 1

API

Docs:

Here are the key functions:

Examples

For each of the examples below, we'll be using the following structure for state and ownProps.

Why state and ownProps? Typically selectors are used to select values from state within a mapStateToProps function. React-redux provides a connect function which supplies mapStateToProps with the current redux state and the wrapper component's ownProps.

const state = {
  department: {
    produce: {
      fruit: {
        apples: [
          { id: 1, size: 'big' }
        ],
        oranges: [
          { id: 2, size: 'medium' }
        ]
      },
      veggies: {
        potatoes: [
          { id: 3, size: 'small' }
        ]
      }
    }
  },
  filter: { size: 'big' }
}
const ownProps = { id: 1, type: 'apples' }

Path selectors: createSelector(path)

You can create a selector from a path. Under the hood it uses get(state, path) (with the same API as lodash get) to read the value. The get function allows path to be either a string or an array. You can read more about path selectors in the docs.

Path selectors are not memoized.

import { createSelector } from '@comfy/redux-selectors'

const selectFirstApple = createSelector('department.produce.fruit.apples[0]')
const selectFirstPotato = createSelector(['department', 'produce', 'veggies', 'potatoes', 0])

selectFirstApple(state) // => { id: 1, size: 'big' }
selectFirstPotato(state) // => { id: 3, size: 'small' }

You can also create a path selector by hand, using get(state, path).

import get from 'lodash.get'

const selectFirstOrange = state => get(state, 'department.produce.fruit.oranges[0]')

selectFirstOrange(state) // => { id: 2, size: 'medium' }

Dependent selectors: createSelector(...selectors, resultsFunc)

You can also combine many dependent selectors using a "results function". This is very similar to how reselect works. You can supply several selectors and the results will be fed into a final results function. You can see below that the resultsFunc receives an argument for each selector. You can read more about dependent selectors in the docs.

Dependent selectors are memoized.

import { createSelector } from '@comfy/redux-selectors'

const selectAppleCount = createSelector('department.produce.fruit.apples.length')

const selectTotal = createSelector(
  selectAppleCount, // selector
  'department.produce.fruit.oranges.length', // path selector
  state => state.department.produce.veggies.potatoes.length,
  (apples, oranges, potatoes) => apples + oranges + potatoes // resultsFunc
)

selectTotal(state) // => 3

Configurable selectors: withOptions(creator)

Sometimes you need to configure your selectors to make them more reusable. A configurable selector is a curried function that accepts configuration on the first call and state on the second. You can read more about configurable selectors in the docs.

Configurable selectors are memoized.

import { composeSelectors, createSelector, withOptions } from '@comfy/redux-selectors'

const selectApples = createSelector('department.produce.fruit.apples')
const selectAppleById = withOptions(id => composeSelectors(
  selectApples,
  apples => apples.find(apple => apple.id === id)
))

selectAppleById(id)(state) // => { id: 1, size: 'big' }

Configurable selectors: withProps(creator)

It is recommendable to use ownProps to provide configuration for your selectors. Using withProps will create a selector that accepts both state ane ownProps. Under the hood, withProps is a thin wrapper around withOptions. There is an important memoization edge case that withProps will optimize for you. You can read more about configurable selectors in the docs.

Configurable selectors are memoized.

import { composeSelectors, createSelector, withProps } from '@comfy/redux-selectors'

const selectFruit = createSelector('department.produce.fruit')
const selectFruitById = withProps(props => composeSelectors(
  selectFruit,
  props.type,
  items => items.find(item => item.id === props.id)
))

selectFruitById(state, ownProps) // => { id: 1, size: 'big' }

Configurable selector: using state

const selectSizeFilter = createSelector('filter.size')
const selectApples = createSelector('department.produce.fruit.apples')
const selectApplesBySize = withOptions(size => composeSelectors(
  selectApples,
  apples => apples.filter(apple => apple.size === size)
))
const selectApplesBySizeFilter = state => composeSelectors(
  selectSizeFilter,
  selectApplesBySize,
  selectFilteredApples => selectFilteredApples(state)
)(state)

selectApplesBySizeFilter(state) // => [{ id: 1, size: 'big' }]

Using mapStateToProps and withProps(creator)

In cases where you are combining your selectors using mapStateToProps, you can use withProps to efficiently pass configuration from ownProps to selectors created with withOptions.

You can read more about using mapStateToProps in the docs.

import { composeSelectors, createSelector, withOptions, withProps } from '@comfy/redux-selectors'

const selectApples = createSelector('department.produce.fruit.apples')
const selectAppleById = withOptions(id => composeSelectors(
  selectApples,
  apples => apples.find(apple => apple.id === id)
))

const mapStateToProps = withProps(props => combineSelectors({
  apple: selectAppleById(props.id)
}))

mapStateToProps(state, ownProps) // => { apple: { id: 1, size: 'big' } }

Using mapStateToProps and withState(selector)

Sometimes you need to ensure that your selector function accepts only a single argument. If you are using react-redux's connect function, you will see a small performance boost by specifying mapStateToProps functions that only accept state. This tells connect to recompute your selectors only when state changes, ignoring props altogether.

You can read more about using mapStateToProps in the docs.

import { withState } from '@comfy/redux-selectors'

const selectApples = createSelector('department.produce.fruit.apples')
const selectAppleById = withOptions(id => composeSelectors(
  selectApples,
  apples => apples.find(apple => apple.id === id)
))

const mapStateToProps = withState(combineSelectors({
  apple: selectAppleById(1)
}))

mapStateToProps(state, ownProps) // => { apple: { id: 1, size: 'big' } }

Combining selectors: combineSelectors(selectorMap)

You can read more about combining selectors for usage with mapStateToProps in the docs.

import { combineSelectors, composeSelectors, createSelector, withProps } from '@comfy/redux-selectors'

const selectFruit = createSelector('department.produce.fruit')
const selectFruitById = withProps(props => composeSelectors(
  selectFruit,
  props.type,
  items => items.find(items => items.id === props.id)
))
const selectFruitSize = composeSelectors(
  selectFruitById,
  'size'
)

const mapStateToProps = combineSelectors({
  size: selectFruitSize
})

mapStateToProps(state, ownProps) // => { size: 'big' }

Composing selectors: composeSelectors(...selectors)

You can read more about composing selectors in the docs.

import { composeSelectors, createSelector, withOptions, withProps, withState } from '@comfy/redux-selectors'

const selectProduce = createSelector('department.produce')
const selectFruit = composeSelectors(selectProduce, 'fruit')
const selectApples = composeSelectors(selectFruit, 'apples')
const selectAppleById = withOptions(id => composeSelectors(
  selectApples,
  apples => apples.find(apple => apple.id === id)
))
const selectFruitSize = withProps(props => composeSelectors(
  withState(selectAppleById(props.id)),
  'size'
))

selectFruitSize(state, ownProps) // => 'big'

results matching ""

    powered by

    No results matching ""