Reselect: Document better how to access props inside createSelector?

Created on 20 Jul 2017  路  4Comments  路  Source: reduxjs/reselect

Hi,

I have tried to create a selector which needs to access some props. I've found a solution but I think the current documentation is a bit confusing.

image

The last part says:

So now getVisibleTodos has access to props, and everything seems to be working fine.

Yet, the getVisibleTodos method does not access any prop reference directly, which is the thing I want.


I've still made it work by using a selector that selects the prop:

export const shootingById = createSelector(
  [shootings,(state,shootingId) => shootingId],
  (shootings, shootingId) => head(shootings.filter(s => s._id === shootingId))
);

@connect((state,props) => ({
  shooting: shootingById(state,props.shootingId),
}))

So, first, I'd like to know if there's a more idiomatic way to do that? I would like to use a more idiomatic syntax like:

export const shootingById = createSelector(
  [shootings],
  (shootings,props) => head(shootings.filter(s => s._id === shootingId))
);

@connect((state,props) => ({
  shooting: shootingById(state,props),
}))

Would such a thing be possible? Would it be less peformant due to not picking only the prop I need with a selector?

If the code can't be improved, would you accept a PR to make the doc clearer on this usecase?

Most helpful comment

The way createSelector works is by calling _all_ "input selectors" with the same arguments. It's effectively:

function createSelector(inputSelectors, outputSelector) {
    return function selector(...selectorArgs) {
        const inputResults = inputSelectors.map( inputSelector => inputSelector(...selectorArgs) );

        return outputSelector(...inputResults);    
    }
}

So, if you call selector(state, ownProps), every input selector will be called with inputSelector(state, ownProps). As a result, all input selectors for a given created selector should be "signature-compatible". If inputSelector1 is expecting (state, props), and inputSelector2 is expecting state, someNumber), then one of those two is probably going to break when it tries to read its second argument.

Your working example looks fine to me - providing an input selector that basically grabs the second argument and forwards it through:

export const shootingById = createSelector(
  [shootings,(state,shootingId) => shootingId],
  (shootings, shootingId) => head(shootings.filter(s => s._id === shootingId))
);

If you've got suggestions for improving the docs, yeah, let us know.

All 4 comments

The way createSelector works is by calling _all_ "input selectors" with the same arguments. It's effectively:

function createSelector(inputSelectors, outputSelector) {
    return function selector(...selectorArgs) {
        const inputResults = inputSelectors.map( inputSelector => inputSelector(...selectorArgs) );

        return outputSelector(...inputResults);    
    }
}

So, if you call selector(state, ownProps), every input selector will be called with inputSelector(state, ownProps). As a result, all input selectors for a given created selector should be "signature-compatible". If inputSelector1 is expecting (state, props), and inputSelector2 is expecting state, someNumber), then one of those two is probably going to break when it tries to read its second argument.

Your working example looks fine to me - providing an input selector that basically grabs the second argument and forwards it through:

export const shootingById = createSelector(
  [shootings,(state,shootingId) => shootingId],
  (shootings, shootingId) => head(shootings.filter(s => s._id === shootingId))
);

If you've got suggestions for improving the docs, yeah, let us know.

ok thanks I'll try to find a better way to explain that in the doc because it wasn't clear to me

Actually reading the doc let me think the 2nd arg should necessarily be props while it seems we can pass as many args as we want to the selectors right?

Exactly. The simplest/suggested approach is to pass the ownProps arg from mapState as the second arg to a selector, but you could easily do something like:

const {field1, field2} = ownProps;
const selectedValue = someSelector(state, field1, field2, someOtherValueAsWell);

Ok I got the @slorber version going and it's ok but I was thinking wouldn't it be better to use it as input selector with props? Of course this way will not work once you want to chain more selectors as you can do in createSelectors.

way of @slorber

const dataLinkByIdSelector = createSelector(
  dataLinkMapSelector,
  (_, dataLinkId) => dataLinkId,
  (dataLinkMap, dataLinkId) => getOr(null, [dataLinkId], dataLinkMap)
);

My thinking

const dataLinkByIdSelector = (state, props) => getOr(null, [props], dataLinkMapSelector(state));
Was this page helpful?
0 / 5 - 0 ratings