investtools / extensible-duck

Modular and Extensible Redux Reducer Bundles (ducks-modular-redux)

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Selectors bug or my misunderstanding

akelmanson opened this issue · comments

@aga5tya,

Am I doing something wrong?

    it('lets the creators access the selectors', () => {
      const duck = new Duck({
        selectors: {
          sum: numbers => numbers.reduce((sum, n) => sum + n, 0)
        },
        creators: ({ selectors }) => ({
          calculate: () => dispatch => {
            dispatch({ type: 'CALCULATE', payload: selectors.sum([ 1, 2, 3 ]) })
          }
        })
      })
      const dispatch = sinon.spy()
      duck.creators.calculate()(dispatch)
      expect(dispatch).to.have.been.calledWith({ type: 'CALCULATE', payload: 6 })
    })

Result:

 1) Duck constructor lets the creators access the selectors:
     TypeError: numbers.reduce is not a function
      at Object.sum (test/unit/extensible-duck.js:73:35)
      at src/extensible-duck.js:98:22
      at Array.forEach (native)
      at deriveSelectors (src/extensible-duck.js:96:26)
      at new Duck (src/extensible-duck.js:132:21)
      at Context.<anonymous> (test/unit/extensible-duck.js:71:20)

https://github.com/investtools/extensible-duck/blob/master/src/extensible-duck.js#L98

@akelmanson,, selectors needs initial or some state(defaulted to empty object) for them to be composed,, also selectors should be handled in a way that either it gives undefined or the intended state slice.
https://github.com/investtools/extensible-duck/blob/master/src/extensible-duck.js#L98

If some other reducer agnostic state or some deep state is involved(Since usually its redux store state), i put a defensive check in my selectors
state => state.numbers && state.numbers.map(/logic/)

const duck = new Duck({
       initialState: [], // or [1,2,3]
       selectors: {
          sum: numbers => numbers.reduce((sum, n) => sum + n, 0)
        },
        creators: ({ selectors }) => ({
          calculate: () => dispatch => {
            dispatch({ type: 'CALCULATE', payload: selectors.sum([ 1, 2, 3 ]) })
          }
        })
      })

OK, got it!

Anyway, what do you think of this change?

function deriveSelectors(selectors, Duck) {
  const composedSelectors = {}
  Object.keys(selectors).forEach(key => {
    const originalSelector = selectors[key]
    composedSelectors[key] = (...args) => {
      const result = originalSelector(...args)
      return isFunction(result) ? originalSelector(selectors)(...args) : result
    }
  })
  return composedSelectors
}

@akelmanson: This looks neat, but one question,

return isFunction(result) ? originalSelector(selectors)(...args) : result

Here the shouldn't we pass the composedSelectors Object, since selectors still has raw functions that need previous selectors ? Haven't tested it tho, been busy with some travel. Will check once I get free,, meanwhile let me know your thoughts.

I've refactored the selectors implementation in order to avoid the defensive check obligation.

Please check out https://github.com/investtools/extensible-duck#defining-the-selectors