Reselect: Using getters

Created on 8 Oct 2017  路  6Comments  路  Source: reduxjs/reselect

Hi. Have you considered using getters to decrease the boilerplate of selectors?
I was thinking something like following:

//index.js
import {initSelectors} from 'reselect;
import selectorTemplates from './selectorTemplates.js'
initSelectors(selectorTemplates);
...
//selectors.js 

export default = {
  todos(state, props)
     return state.todos;
  },  
  users(state, props){
     return state.users;    
  }
}
// initSelectors would overrides the implementations of selectorTemplet functions
...

//selectors.js
import {createSelector} from 'reselect'

export const todos = createSelectors(
  ({todos}) => () => { // <-- register get. If memoized return prev value else invoke callback
    return Object.values(todosArray);
  }
);

Do you see some technical reasons that would prevent implementing this?

Most helpful comment

I'm very unsure what exactly you're proposing here. Could you explain what you're suggesting in more detail?

All 6 comments

I'm very unsure what exactly you're proposing here. Could you explain what you're suggesting in more detail?

I have to get back to this when I've thought about it a bit longer...
I'll take a fork you this repo and try to implement something.
I would be nice if it was possible to get rid of the first parameters (array of functions) of createSelector.

That doesn't seem particularly useful, since many different selector functions in an application would need a different set of inputs. For example:

const selectA = state => state.a;
const selectB  = state => state.b;

const selectC = createSelector(
    selectA,
    a => a.c
);

const selectD = createSelector(
    selectB,
    b => b.d
);

Those two selectors don't have their "input selectors" in common.

If you really want to do something like that, it might just be better to create a factory function:

function createPreparedSelector = (...inputSelectors) => {
    return (outputSelector) => createSelector(inputSelectors, outputSelector);
}

const createSelectorWithAAndB = createPreparedSelector(selectA, selectB);

const selectCD = createSelectorWithAAndB(
    (a, b) => {c : a.c, d : b.d}
);

This is a sketch came up with:

const { keys, values, assign, defineProperty } = Object
const select = { functions: {}, proxy: {} }
let accessed = []

export function initSelectorFunctions(obj) {
  for(const name in obj) {
    select.functions[name] = obj[name] 
    defineProperty(select.proxy, name, {  // create a way to detect which objects values a terse selector uses
      get() {
        accessed.push(name)
        return {}
      }
    })
  }
}

export function createSelectorShorthand(func) {
  func(select.proxy) // select.proxy pushes the accessed function names to 'accessed' array
  const selectors = accessed.reduce((acc, name) => {
    acc[name] = select.functions[name]
    return acc
  }, {})
  accessed = []
  return createSelector(values(selectors), function (...results) {
    // assume results are in the same order as previously accessed values
    const obj = keys(selectors).reduce((acc, name, i) => assign(acc, { [name]: results[i] }), {})
    return func(obj)()
  })
}

Instead of:

const getTodo = (state, props) => state.todos[props.todoId]
const getUser = (state, props) => state.users[state.todos[props.todoId].userId]

export const formattedTodo = createSelector(
  [getTodo, getUser],
  (todo, user) => {
    const {id, task, done} = todo;
    const {id: userId, firstname, lastname} = user;
    return {id, task, done, name: firstname + ' ' + lastname}
  }
)  

could be done like:

// is run one on index.js
initSelectorFunctions({
   todo(state, props) {
      return state.todos[props.todoId]
   },
   todosUser(state, props) {
      return state.users[state.todos[props.todoId].userId]
   }
})
...

// selectors.js
export const formattedTodo = createSelectorShorthand(
    ({ todo, todosUser }) => () => {
      const { id, task, done } = todo
      const { id: userId, firstname, lastname } = todosUser
      return { id, task, done, userId, name: firstname + ' ' + lastname }
    }
)
//There is a problem with createSelectorShorthand.
//If selector is defined like so:
export const someSelector = createSelectorShorthand(
    ({parent: { child: { subChild } } }) => { ... }
    /*this would throw an error on first line of createSelectorShorthand function, 
    that is because parent is just an empty object when first line of createSelectorShorthand get's 
     executed*/
})

using createSelectorShorthand could also be implemented like so:

export const formattedTodo = createSelectorShorthand(
    ({ todo, todosUser }) => 
         ({id, task, done}, {id: userId, firstname, lastname}) =>
              ({ id, task, done, userId, name: firstname + ' ' + lastname })
)

Even though I probably would not use it like to because it would be harder to debug...

Closing due to inactivity

Was this page helpful?
0 / 5 - 0 ratings