-
Notifications
You must be signed in to change notification settings - Fork 46
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Key selectors composition #73
Key selectors composition #73
Conversation
Hi @sgrishchenko, I see the point of supporting selectors' composability. I'd like to consider the possibility of providing this feature as an external utility function. Something to directly provide as |
If I try provide external utility function, I receive something like this: const keySelector = combineKeySelectors(
dependency1,
dependency2,
(state, props) => props.someProp
)
const cachedSelector = createCachedSelector(
dependency1,
dependency2,
(state, props) => props.someProp,
(first, second, someProp) => ({
first,
second,
someProp
})
)(keySelector); As you can see, I need again copy-paste dependencies and again so easy when adding a dependency, forget to update key selector. What if I create separate function |
# Conflicts: # src/__tests__/index.spec.js
It seems that the arguments of the hypothetical Not that I like this syntax, but as a proof of concept, might it be expressed like this? const inputSelectors = [
dependency1,
dependency2,
(state, props) => props.someProp,
];
const cachedSelector = createCachedSelector(
inputSelectors,
(first, second, someProp) => ({
first,
second,
someProp
})
)(combineKeySelectors(...inputSelectors)); |
At first, I can't pass all dependencies in
For this reason, I cannot distinguish state selectors from property selectors and I need do something like this: const keySelectors = [
dependency1,
dependency2,
(state, props) => props.someProp,
];
const cachedSelector = createCachedSelector(
[
state => state.foo,
state => state.bar,
...keySelectors,
],
(foo, bar, first, second, someProp) => ({
foo,
bar,
first,
second,
someProp
})
)(combineKeySelectors(...keySelectors)); Secondly, imagine that you future user of re-reselect library. You have two helpers: Additionally, I understand your concern about granularity of this approach. I suggest to expose two new helper:
What do you think about it? |
I've personally never dealt with such a scenario, therefore take what I say with a grain of salt: what if It might get cumbersome (wrappers wrapping wrappers... mhh..), but I'd definitely go for the helper approach. I'd also suggest to find a way of introducing these non-core feature gradually: maybe a separate Same goes for source code and tests: I'd try to keep core logic and the rest well separated. Rollup will think about shipping all together. If there are still blurry areas about what these utility are supposed to do, we might consider writing down the documentation first. Thanks for caring @sgrishchenko! |
I just saw https://github.com/sgrishchenko/reselect-utils! Amazing job, there. I'd keep this issue open as a remainder to update the docs to extensively mention |
I'm investigating the feasibility of such an API for the use case @sgrishchenko mentioned: import createCachedSelector, {combineKeySelectors} from 're-reselect';
const cachedSelector = createCachedSelector(
dependency1,
dependency2,
(state, props) => props.someProp
(first, second, someProp) => ({
first,
second,
someProp
})
)(combineKeySelectors(options)); If we somehow extended the data provided to the |
Closing in favour of #78. Thanks @sgrishchenko for the inspiring contribution! |
Hi @dkaledin @veksa @sv1809 @eaglus, With #78 I also tried to provide a So I'm inviting you to provide some real-world examples and possible API's to reason about the possibility of shipping such a Thanks to you all. Cheers! |
@toomuchdesign @sgrishchenko Interesting following the back-and-forth here. I actually stumbled upon this thread through having misinterpreted the library similarly to the users in this other thread, and coming to realize that cache key selection didn't follow the mechanism I originally believed. I think some of the frustrations expressed here would be mitigated through a design or option which just provided dependency function results to the cache key selector automatically. This would eliminate redundancies between the 'actual' selector and the cache key selector. Although, maybe this is precisely what @sgrishchenko was alluding to with his It is unfortunate that reselect does not expose its internal mechanism for building dependency results as an abstraction, meaning this library would have to replicate that behavior. However, allowing a key selector option to facilitate a predefined, "default" function createDependencyResultsKeySelector(keySelector, dependencies) {
return function() {
const dependencyResults = dependencies.map(dependency => dependency.apply(null, arguments));
return keySelector.apply(null, dependencyResults);
};
}; would allow usage like this: const selectABC = createCachedSelector(
selectA,
selectB,
selectC,
(a, b, c) => compute(a, b, c)
)(
(a, b, c) => `${a}:${b}:${c}`,
{ useDependencyResults: true }
);
...
// Boom
selectABC(state); There would of course be (potentially) redundant work between I do want to emphasize the utility of allowing this library to use dependency results, rather than just initial selector arguments, to derive cache keys. It can be unwieldy to have to independently determine and pass in those values in cases where the selectors aren't normally written to take them, just to ensure that I can take advantage of const mapStateToProps = createStructuredSelector({
stuff: selectStuff,
otherStuff: selectOtherStuff
}); ...unless I want to write my key selectors to re-select (hah!) the "dependency results" manually, from I can't see getting around a certain amount of awkwardness either way. For example, this is a little awkward: /**
* Using the 'extra argument' form: selectPersonByName(state, 'John')
*/
const selectPersonByName = createCachedSelector(
selectPeople,
// This 'dependency' just exists to forward the
// selector's second argument to the result function
(state, name) => name,
(people, name) => people[name]
)(
(_, name) => name
); and this is a little awkward: /**
* Using the 'single state argument' form: selectCurrentStudentTestAverage(state)
*/
const selectCurrentStudentTestAverage = createCachedSelector(
selectCurrentStudent,
// This 'dependency' just exists to forward something
// to the key selector (also, let's pretend currentStudent
// does not already have the studentId property)
selectCurrentStudentId,
currentStudent => {
const { testResults } = currentStudent;
return testResults.reduce((acc, result) => acc + result, 0) / testResults.length;
}
)(
(_, studentId) => studentId,
{ useDependencyResults: true }
); Anyway, that's my 2 cents! |
Hi @mbellman, I 100% agree that the confusion about what argument are actually received by the As you mentioned, there are a few reasons why
I will open an issue to reason about the opportunity of adding the possibility of providing I'm a bit reluctant to proceed lighthearted for 2 reasons:
But I'm totally curious to better understand whether such a feature could provide real value. |
What kind of change does this PR introduce? (Bug fix, feature, docs update, ...)
feature
What is the current behaviour? (You can also link to an open issue here)
Now if you try use cached selectors in dependecies, you need copy-paste all logic from their key selectors.
What is the new behaviour?
In this PR I propose solution for automatic key selectors composition of selector dependecies (will be composed only key selectors which returns strings).
Does this PR introduce a breaking change? (What changes might users need to make in their application due to this PR?)
This PR does not introduce a breaking change but changes some API:
() => ''
):
like in here)true
)Other information:
When I use cached selector as dependency in simple selector, cached selector invalidates simple selector cache. To fix this I need to make simple selector cached (it is OK) and copy-paste key selector from dependecy (it is not OK). For example:
All this leads to a terrible DX:
If compose key selectors automatically you can rewrite previous example like this:
As result key selector for cachedSelector will be
If you introduce additional props, you can write something like this:
And result key selector again will be composed to this:
Please check if the PR fulfills these requirements: