This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
Reselect v5 Roadmap Discussion: Goals and API Design #490
Labels
You can continue the conversation there. Go to discussion →
Reselect has been loosely maintained over the last couple years. There's been a lot of PRs filed that have been sitting around, including some that require a new major version. The goal of this discussion is to:
I'd like to thank @ellbee, who's been the primary maintainer. Real life has taken up his time lately, so he's given myself and @timdorr publish rights on NPM and a green light to work on PRs.
I already have more than enough on my todo list with the rest of the Redux family of libraries, so I need to limit my involvement in maintaining Reselect. However, I'm happy to help shepherd the conversation here, define some vision, and provide guidance.
I'd love to see some folks in the community volunteer to help do whatever work's needed here, and even come on board as an active maintainer for Reselect.
Prior Reselect v5 Planning
In a discussion with @ellbee earlier today, he said:
So, as a starting point, it seems reasonable to assume that we'd rewrite Reselect's source to TypeScript, update the types to work better with variadic args, and try to address known pain points and additional use cases.
Current Reselect Pain Points and Problems
Cache Size and Selector Instance Reuse
These two problems go hand-in-hand. Reselect only has a cache size of 1 by default. This is fine when a selector is only being given
state
as its only argument. However, it's very common to want to reuse a selector instance in a way that requires passing in varying arguments, such as a "filter items by category" selector:In cases like this, multiple components all call the same selector with different arguments one after the other. So, it will never memoize correctly.
The current workaround here, when used with React-Redux, is to create unique selector instances per component instance. With
connect
, this required a complex "factory function" syntax formapState
:With function components, this is a bit less obnoxious syntax-wise, but still annoying to have to do:
Clearly this is a major use case that is difficult to work with right now.
It's possible to customize Reselect's caching behavior by calling
createSelector(customMemoizer)
, but that's an extra level of complexity as well.Optimizing Comparison Behavior
Reselect works by:
However, the use of shallow equality / reference checks here can lead to calculating a new output result in cases where it wasn't truly necessary. Take this example:
This recalculates the result any time the
todos
array changes. However, if wedispatch(toggleTodo(3))
, we create a new todo object andtodos
array. That causes this selector to recalculate, but none of the todo descriptions changed. So, we end up with a newdescriptions
array reference even though the contents are shallow equal. Ideally, we'd be able to figure out that nothing really changed, and return the old reference. Or, even better, not even run the final calculation, because it might be relatively expensive. (Issue ref: #451)Related to this, it's also possible to write poorly-optimized selectors that have too broad an input (such as using
state => state
as an input selector) and thus recalculate too often, or may just not be well memoized.Finally, Reselect doesn't do anything to help with the output itself taking a long time to calculate (Issue ref: #380 ).
Debugging Selector Recalculations
Reselect was made to work with selectors acting as inputs to other selectors. This works well, but when multiple selectors are layered on top of each other, it can be hard to figure out what caused a selector to actually recalculate (see the selectors file from the WebAmp project as an example).
Other Issues
createSelector(input1, input2, output)
) was bad for TS usage previously. This might not be an issue now with TS 4.x.undefined
selector (which can happen due to circular imports)Existing Ecosystem Solutions and Addons
Open Reselect PRs
There's a bunch of open PRs that are trying to add various small changes in functionality and behavior. Some relevant ones:
defaultMemoize
memoizedResultFunc
so you can clear itdefaultMemoize
resultCheckMemoize
#456: adds aresultCheckMemoize
memoizer that can be used instead ofdefaultMemoize
Ecosystem: Caching
There are a bunch of different packages that either wrap Reselect directly, or implement similar behavior separately.
The biggest one is https://github.com/toomuchdesign/re-reselect , which specifically creates a customized memoization function that supports multiple cached keys so that one selector instance can be reused in multiple places.
Meawhile, @josepot came up with an approach for keyed selectors, submitted it as #401 , and also published it as https://github.com/josepot/redux-views .
There's also https://github.com/ralusek/reselectie , which is an alternative lib with a similar API.
Ecosystem: Comparisons
The best option I found for dealing with cases that return arrays and such is https://github.com/heyimalex/reselect-map , which has specialized wrappers like
createArraySelector
that deal with one item at a time.Ecosystem: Debugging
The biggest piece here is https://github.com/skortchmark9/reselect-tools , which adds a wrapper around
createSelector
that tracks a dependency graph between created selectors. It also has a really neat browser DevTools extension that visualizes that dependency graph.While searching NPM for Reselect-related packages, I also ran across:
createSelector
to keep some stats on call durationsAlternative Selector Libraries
There's also other selector-style libraries with varying approaches and APIs:
The one I find most intriguing is https://github.com/dai-shi/proxy-memoize. @dai-shi has been doing amazing work writing micro-libs that use Proxies. I think that
proxy-memoize
actually does solve some of Reselect's pain points, and I want to start officially recommending it as another viable option. I suggest reading reduxjs/react-redux#1653 , which has discussion between myself, @dai-shi, and @theKashey regarding howproxy-memoize
works and whether it's sufficiently ready.@theKashey previously wrote https://github.com/theKashey/kashe , which uses WeakMaps to do the caching behavior.
https://github.com/taskworld/rereselect and https://github.com/jvitela/recompute both use their own internal forms of observables to track dependencies and updates.
https://github.com/pzuraq/tracked-redux uses the Ember "Glimmer" engine's auto-tracking functionality to provide a tracked wrapper around the Redux state.
Ecosystem: Library Summaries
Since I was researching this, I threw together a table with some of the more interesting selector-related libs I found. Some are wrappers around Reselect, some are similar to Reselect API-wise, and some are just completely different approaches to sorta-similar problems:
createSelector
to track debugging statsConclusions
Reselect is Widely Used
For reference, Github shows 1.4M+ repos depending on Redux, and 400K+ depending on Reselect. So, any changes we make should try to keep the API similar to minimize breakage.
Biggest Issue: Caching and Output Comparisons
This seems like the main problem people are concerned about and is the biggest annoyance working with Reselect right now.
Reselect Should Be Updated Even If Other Options Exist
I really like how
proxy-memoize
looks and I think it's worth us promoting it officially. That shouldn't stop us from improving Reselect while we're at it.Rewrite Reselect in TypeScript
We might as well unify the code and the types so they don't get out of sync, and start building Reselect against multiple versions of TypeScript.
Coordinate on API Tweaks
There's a bunch of overlapping PRs with small tweaks, and we should try to figure out a coordinated and coherent approach to updating things vs just randomly merging a few of them.
Final Thoughts
So, here's the questions I'd like feedback on:
I'd like to tag in a bunch of folks who have either contributed to Reselect or are likely to have relevant opinions here:
@ellbee, @timdorr, @josepot, @OliverJAsh, @dai-shi, @theKashey, @faassen, @Andarist, @eXamadeus
I'd like to get feedback from them and the rest of the Redux community!
I'd specifically recommend reading through the "Reselect v5.0" proposal by @josepots and the
proxy-memoize
discussion over in the React-Redux issues as background for this.The text was updated successfully, but these errors were encountered: