npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@comfy/redux-selectors

v0.4.0

Published

Redux selectors

Downloads

484

Readme

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:

  • https://heygrady.github.io/redux-selectors/
  • https://github.com/heygrady/redux-selectors/tree/master/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'