- Notifications
You must be signed in to change notification settings - Fork666
Reselect v5 Roadmap Discussion: Goals and API Design#491
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
Update: Reselect 4.1 Now Available!Reselect 4.1 is now available, and addresses most of the concerns listed here. It adds a configurable cache size to https://github.com/reduxjs/reselect/releases/tag/v4.1.0 Original TextReselect 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 PlanningIn 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 ProblemsCache Size and Selector Instance ReuseThese 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 constselectItemsByCategory=createSelector(state=>state.items,(state,category)=>category,(items,category)=>items.filter(item.category===category))selectItemsByCategory(state,"a");// first call, not memoizedselectItemsByCategory(state,"a");// same inputs, memoizedselectItemsByCategory(state,"b");// different inputs, not memoizedselectItemsByCategory(state,"a");// different inputs from last time, not memoized 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 constmakeMapState=(state)=>{constselectItemsForThisComponent=makeUniqueSelectorInstance();returnfunctionrealMapState(state,ownProps){constitems=selectItemsByCategory(state,ownProps.category);return{items}}}; With function components, this is a bit less obnoxious syntax-wise, but still annoying to have to do: functionCategoryItems({category}){constselectItemsForThisComponent=useMemo(makeUniqueSelectorInstance);constitems=useSelector(state=>selectItemsForThisComponent(state,category));} 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 Optimizing Comparison BehaviorReselect 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: constselectTodoDescriptions=createSelector(selectTodos,todos=>todos.map(todo=>todo.text)) This recalculates the result any time the Related to this, it's also possible to write poorly-optimized selectors that have too broad an input (such as using Finally, Reselect doesn't do anything to help with the output itself taking a long time to calculate (Issue ref:#380 ). Debugging Selector RecalculationsReselect 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 (seethe selectors file from the WebAmp project as an example). Other Issues
Existing Ecosystem Solutions and AddonsOpen Reselect PRsThere's a bunch of open PRs that are trying to add various small changes in functionality and behavior. Some relevant ones:
Ecosystem: CachingThere are a bunch of different packages that either wrap Reselect directly, or implement similar behavior separately. The biggest one ishttps://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 ashttps://github.com/josepot/redux-views . There's alsohttps://github.com/ralusek/reselectie , which is an alternative lib with a similar API. Ecosystem: ComparisonsThe best option I found for dealing with cases that return arrays and such ishttps://github.com/heyimalex/reselect-map , which has specialized wrappers like Ecosystem: DebuggingThe biggest piece here ishttps://github.com/skortchmark9/reselect-tools , which adds a wrapper around While searching NPM for Reselect-related packages, I also ran across:
Alternative Selector LibrariesThere's also other selector-style libraries with varying approaches and APIs: The one I find most intriguing ishttps://github.com/dai-shi/proxy-memoize.@dai-shi has been doing amazing work writing micro-libs that use Proxies. I think that @theKashey previously wrotehttps://github.com/theKashey/kashe , which uses WeakMaps to do the caching behavior. https://github.com/taskworld/rereselect andhttps://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 SummariesSince 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:
ConclusionsReselect is Widely UsedFor reference, Github shows 1.4M+ repos depending on Redux, and 400K+ depending on Reselect. So, any changes we make shouldtry to keep the API similar to minimize breakage. Biggest Issue: Caching and Output ComparisonsThis 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 ExistI really like how Rewrite Reselect in TypeScriptWe 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 TweaksThere'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 ThoughtsSo, 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 themand the rest of the Redux community! I'd specifically recommend reading throughthe "Reselect v5.0" proposal by @josepots andthe |
BetaWas this translation helpful?Give feedback.
All reactions
👍 26❤️ 10🚀 2👀 4
Replies: 15 comments 32 replies
-
Thanks for putting this together@markerikson! Really hope this gains some traction. |
BetaWas this translation helpful?Give feedback.
All reactions
👍 2
-
So should we focus on Redux + Reselect, or only on Reselect? Some API decisions can be different, as Redux can hide some extra operations behind its own facade, including:
For the cases without Redux Reselect can look towards atomic state managers like:jotai orreatom, as they all are about Tecnhically speaking - two cases can be combined into one, and reselect can feed redux "model", not actually doing anything extra, letting redux manage relationship between used atoms as long as it "can" do it. |
BetaWas this translation helpful?Give feedback.
All reactions
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
Just Reselect. Granted, we can assume that the vast majority of Reselect usage is with Redux state (and likely React-Redux as well), but I'm not planning to add any special APIs or behavior to React-Redux or Redux Toolkit that would be specific to Reselect. So, the goal for this discussion is: given Reselect v4 as a starting point, and this list of pain points and other ecosystem libs, what improvements to Reselect's API and implementation should we be aiming for? |
BetaWas this translation helpful?Give feedback.
All reactions
👍 2
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
It's tricky to contribute to this because you've so comprehensively covered all of the issues at play here, and so it feels like there's little to actually say other than adding weight to the specific problems I face with Reselect. It may be that one of reasons you've perhaps not gotten the engagement you wanted on this is because you've already laid out a lot of the thoughts people would want to contribute, and extremely comprehensively. All of that being said, let me try and add some weight. (Please don't expect any of this to be unique or anything different to what you've said before) I agree that one of the major problems with Reselect is the intersection between selectors that depend on non-state inputs, and a cache size being fixed at 1 – especially in connection to React where you may be frequently running the selector with different parameters (both in terms of store state and arguments). I understand that there are arguments that if you didn't expose the create selector factory, there are more options for fixing the above problem. In my experience, using the create selector factory has always felt like a hack to work around a different problem. And if you solve this problem, you may be able to reduce use of the create selector factory to an even lower level than currently. Wherever I have used the create selector factory, it has been to work around issues where an input selector has a non stable output (i.e, running it twice with the same input gives you referentially different outputs, but this input selector has its own inputs that are wider than strictly necessary) and I don't want this selector to run on every state change, because its directly depended on by In my usage of Reselect, component renders have always been a more expensive process than selector computation – I've rarely particularly cared about how frequently selectors run. I do care that by the time they're used in To be clear, my main motivation for using reselect is reducing React re-renders where my selectors are doing cheap derivation that results in referentially unequal results call-to-call, not to avoid running expensive state computations. I don't know if that puts me in a different place than most users of re-select. Consider the following structure – a list of files that each have contents in them, and then a selector that returns a list of file names. constselectFiles=state=>state.files;constselectFileNames=createSelector([selectFiles],files=>files.map(file=>file.name)); If I use I.e, something like this constcreateCompareEqualForKeySelector=(keySelector:(_:unknown,__:unknown,key:any)=>boolean|undefined)=>(currentEntities:any,previousEntities:any)=>{if(currentEntities.length!==previousEntities.length){returnfalse;}for(letidx=0;idx<currentEntities.length;idx++){constcurrentEntity=currentEntities[idx];constpreviousEntity=previousEntities[idx];if(!isEqualWith(currentEntity,previousEntity,keySelector)){returnfalse;}}returntrue;};constcreateFieldSelector=<T,FextendskeyofT>(selector:(state:any)=>T[],field:F)=>createSelectorCreator(defaultMemoize,createCompareEqualForKeySelector((curr,prev,key)=>!key||field===key ?undefined :true)constselectFiles=state=>state.files;constselectFileNames=createFieldSelector(selectFiles,"name"); I think if you make it easier for selectors to return a previous output if they previous output and the new output are equal by some user-defined predicate, then you can reduce the need for In terms of approach, I think you may be biting off more than you can chew with trying to solve (or at least consider/address) all of the topics in this discussion at once. I think you should try and address as many things as you can in the above that can be done without a breaking change, and then you'll be able to identify what really are the priorities for the next breaking change. Basically I'm saying there's a fair amount that can be done to improve reselect (not that it isn't already brilliant) without a breaking change – you're not there yet, and I think waiting for a breaking change might be holding some real QoL changes back. Another thing to mention is that I think the lack of a sanctioned "Reselect dev tools" makes it really hard to know if you're using reselect well or not. I'd love something that showed me my reselect "tree", and also track how frequently memoization is a cost vs a benefit to performance (i.e, how frequently did the memoization check save you from running this selector). This could help identify where reselect is costing you more than its benefitting you. I'd also contend that the main benefit of Reselect isnot memoization (I really don't find myself doing expensive state derivation in selectors very often, its the composition API – especially in TypeScript). I'd love a way of using the composition API whilst opting out of memoization – and maybe then the dev tools can use some heuristic for suggestion when a selector should be memoized. I think the fact that we have entirely different syntax for memoized/non memoized selectors results in an overuse of reselect, and if it was more akin to flipping a flag or changing the name of the create function used, we'd get smarter about when to use/not use memoization. You see this in the React world where the team are frequently arguing for not using memoization as a default – this is a hard argument to make in the world of Reselect when you lose the composition API if you want to turn of memoization. Finally, I definitely think a breaking change should remove variadic arguments. I know it's easier to type now, but I still find them harder to read, and it also prevents you easily adding a "configuration" object to the Just some musings on my thoughts – I don't know how valuable they are. Sorry it's taken so long to get these out! |
BetaWas this translation helpful?Give feedback.
All reactions
👍 4
-
Great thoughts from all of you! :) I think at this point we've described the problem space pretty well. So, what I'm really interested in is some concrete suggestions for changes to the API and feature set, including thoughts on what could go into a 4.x release and what should go into a new 5.0 major. Per the "Reselect dev tools" bit, thereis actually such a thing:https://github.com/skortchmark9/reselect-tools , which I linked above in the "Debugging" section. I haven't actually used it myself yet, but it seems pretty neat, and perhaps it would be worth trying to coordinate with the author to get that added in to the core somehow. |
BetaWas this translation helpful?Give feedback.
All reactions
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
reselect-tools looks initially useful but being I tend to normalize and use re-reselect for entity data I would be hard-pressed to use something that doesn't cover all the selectors I am using. reselect-tools does have anopen request to add re-reselect support. Before the integration of dev tools into the core, I would want to understand the performance impact of the said tools. An easy way to turn it on and off would be nice since I don't want it always affecting perceived performance as I build. Re-reselect in particular solved the biggest gripe I had with reselect's API which was the factory functions, especially the one required for the component I think one drawback of the current API is that we don't take advantage of our knowledge of the inputs. We currently don't distinguish them when creating the selector which leads to more boilerplate after the fact such as with re-reselect's resolver function. For myself, there are 3 types of selectors, inputs, that cover a majority of my use cases and could possibly automate much of the cashing I require without the need for a cache resolver function. Wild idea but what if we used arrays to identify these types? For example: constselectFiles=state=>state.files// shouldn't affect cache size, let's call it a "standard input"constselectCurrentUserId=createSelector(selectCurrentUser,// in state, not from propuser=>user.id)// here we want cache based on the result, let's call it a "cache result input"constselectPropsFileId=(state,props)=>props.fileId// prop selector, another example of "cache result input"constselectFileId=(state,fileId)=>fileId// straight up input but not from prop, let's call it a "cache input"// although personally I would make this like the prop selector for composition, let's call it a "cache input"// what if an API could allow us to identify these types and cache appropriately?constselectFileAuth=createSelector(// use array args to identify caching strategy?[selectFiles],// array of standard inputs (selectors)[selectCurrentUserId],// array of cache result inputs (selectors)[selectFileId],// array of cache inputs (selectors)// order in the result function remains unchanged(files,fileId,currentUserId)=>files[fileId][currentUserId])// generates and uses`${fileId}-${currentUserId}` as cache key,// multiple input/result cache selectors are simply concat with dashes?// Error when not string/number signaling you need to use cache resolver function instead?// same example but with the prop selector for fileIdconstselectFileAuth2=createSelector([selectFiles],[selectPropsFileId,selectCurrentUserId],(files,fileId,currentUserId)=>files[filed][currentUserId])// generates and uses`${fileId}-${currentUserId}` as cache key// with prop selectorsconstselectFileAuth3=createSelector([selectFiles][selectPropsFileId,selectPropsUserId],(files,fileId,userId)=>files[fileId][userId])// generates and uses`${fileId}-${userId}` as cache key// or when you want to break it up because more than one selector can take advantage of the file entity caching such as being fed into different selectors for different viewsconstselectFile=createSelector([selectFiles],[selectPropsFileId],(files,fileId)=>files[fileId])// fileId as cache keyconstselectFileAuth=createSelector([selectFile],[selectPropsUserId],(file,userId)=>file[userId])// userId as cache key This would remove a lot of the repeated work I do with re-reselect's resolver function API. Granted we would still need access to create a cache resolver function if the caching couldn't be determined by a number/string input/result of a selector. |
BetaWas this translation helpful?Give feedback.
All reactions
-
I would strongly encourage not to follow re-reselect API because it creates memory leaks as there is no "clean" condition. In other words, re-select functionalityhas to be implemented on a |
BetaWas this translation helpful?Give feedback.
All reactions
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
+1 on the devtools point. I would really like to see Reselect be integrated with Redux DevTools and have selectors shown alongside state. Not being able to see my derived state through debug tools is a big pain point for me when using Redux compared to Overmind and MST. There is an issue here about this with many upvotes but discussion died out in 2018.
The author left a comment in that issue above saying that they would love to work on integrating into redux devtools and that was their plan all along. Not sure if they still have the bandwidth or interest but maybe you can reply and revive the discussion.
I'm curious then what is your debug process when you see unexpected behavior related to app state and want to confirm that your derived state looks as expected? Seeing selector values feels like an important debug step to me Also probably worth considering dev tool support for |
BetaWas this translation helpful?Give feedback.
All reactions
-
@adamerose : I usually just look at the output value in a debugger or console log. I think this would be a useful idea to explore, although the first question would be how to expose debugging info from the selectors. This would likely require looking athttps://github.com/skortchmark9/reselect-tools and figuring out how to port that functionality into Reselect itself. I've got enough stuff going on I don't think I could tackle that personally, but if you or someone else would like to look into it that would be great! |
BetaWas this translation helpful?Give feedback.
All reactions
-
Hi there!@markerikson makes huge research, as always, I found out something new from this post, thanks 👍 But I want to point one more problem about the reselect and a selector. It encourages an inconsistent state if an error was thrown from it. Checkthis example. One of the solutions is allowed to call a selector inside a reducer. Here some related project -topologically-combine-reducers,reatom. Also, I thinklimitations about standard Map/Set in |
BetaWas this translation helpful?Give feedback.
All reactions
-
Hmm, I'm not sure if my descriptions give you the right understanding. Or, maybe I'm confused. Didn't even know reatom is proxy based. or, not...? |
BetaWas this translation helpful?Give feedback.
All reactions
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
Here are some thoughts I started on a while ago but have updated since seeing some of the other replies. There have been some fantastic discussion points so far. Mark'stweet lit a fire under my butt to finish off this post. Update on type changesI'm currently (very slowly) working on finishing off the non-breaking changes for v4 (see#486). I was porting over the changes to v5 simultaneously, but I now think we should hold on to those until this discussion is brought closer to resolution. Rewiting in TypeScriptI'm, personally, all for this idea. I think it's a reasonable assumption that TypeScript will pretty much take over as the lingua franca for web development. However, this suggestion, in and of itself, isn't that "important". Well, at least there isn't much to discuss about it. It's a fairly binary opinion; you either agree or disagree. I think a simple poll with favorable results towards moving to TS is enough to justify moving forward.
Yeah, this is definitely true. I think rewriting the whole thing in TS will help simplify this by quite a bit. You also get the added benefit of the implementations exercising the type definitions automatically. What should be brought into v5? (sans types)@markerikson listed these issues out in exquisite detail, so I won't repeat them here. I'll sprinkle in my opinions here and there. Cache Size and Selector Instance ReuseThis is a fascinating problem. There are many good suggestions to solve this already. I personally think
It might be worth discussing either incorporating or absorbing this functionality into Optimizing Comparison BehaviorIthink this could be solved with a solution inspired by Some default "result memoizers" could be provided out of the box. Something like exportconstshallowArrayMemoizer=<T>(prev:T[],next:T[]):T[]=>{constsameLength=prev.length===next.lengthconstsameValues=sameLength&&prev.every((val,idx)=>val===next[idx]);returnsameValues ?prev :next;} This would even allow for composing "cached selectors" with "cached result selectors". If this approach is to be entertained, it might be worth considering renaming "cached selectors" to "cached input selectors" to differentiate them from the "cached result selectors". I dunno, just spitballing here. There might be massive holes with this approach that I don't see. Debugging Selector RecalculationsThis is an awesome problem, but I haven't thought about it enough to even comment, honestly. I'll think about it and come back later if I have anything I think might help the conversation. TLDR
|
BetaWas this translation helpful?Give feedback.
All reactions
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
Great feedback! Yes, I'm fully intending that v5 should be in TS at this point. No reason not to. I'd also be inclined to pull the trigger on dropping variadic arguments and requiring the array format for input selectors. I think that might make it easier to add some kind of an options object as the last argument. In fact, if would be nice if the options object could be used for things like supplying memoization parameters, and we could avoid having a dozen variations of constselectSomething=createSelector([inputSelector1,inputSelector2],outputSelector,{cacheSize:10,cacheKey:(arg1,arg2)=>arg1.id,outputComparison:shallowArrayMemoizer}) FWIW, I see that@josepot has already created a codemod that would convert uses of https://github.com/josepot/redux-views/blob/master/transforms/to-array-dependencies.js |
BetaWas this translation helpful?Give feedback.
All reactions
👍 5
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
With "updated" typescript definitions, variadic support isn't really problematic. I do think a decision should just be made and go in one direction over the other. Supporting both isn't really necessary. Array support is probably the cleaner approach. If I had to pick one, that would be my vote. I really like the suggestion you provided. The options object as a third argument would be cleaner than curried functions and would support aton of features simultaneously. |
BetaWas this translation helpful?Give feedback.
All reactions
-
I've read this conversation once again, and look like there are 3 different use cases we are trying to solve:
And then let's look at this separation from WeakMap based cache control (like kashe), where "single cache entry" is replaced by WeakMap
Let's try to solve a few examples: constselectTodoDescriptions=createSelector(selectTodos,todos=>todos.map(todo=>todo.text))
constselectItemsByCategory=createSelector(state=>state.items,(state,category)=>category,(items,category)=>items.filter(item.category===category)) Is a more complicated case, as constselectItemsByCategory=createSelector(state=>state.items,(state,category)=>getPicker(/*from*/items,/*what*/category),(items,category)=>items.filter(item.category===category.value))// wheregetPicker=(source:any,value:any)=>{cosntmap=getOrCreateWeakMapEntry(source);// pseudo codereturnmap.get(value)||map.create(value,{ value});} Downside - on items update all "pickers" will be reset as well as data stored in the combine function. But probably this is what is needed. In other words - even if the result seems a little less readable - what if we can resolve a few technical problems by introducing extra functionality and enforcing it via TypeScript types (all selectors used in selectors has to return object) or some babel magic. 👉 The ability to memoize is bound to the ability to forget, and Rust got it right –https://doc.rust-lang.org/1.9.0/book/lifetimes.html |
BetaWas this translation helpful?Give feedback.
All reactions
👍 1
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
Hi,@markerikson ! https://github.com/vlanemcev/reselect-debugger-flipper Here`s the PR to add this debugger into Reselect README: Thanks! |
BetaWas this translation helpful?Give feedback.
All reactions
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
Reselect 4.1 is now available, and addresses most of the concerns listed here. It adds a configurable cache size to |
BetaWas this translation helpful?Give feedback.
All reactions
-
The below challenge is a result of using redux-toolkit, which leverages reselect. Since Reselect doesn't offer its own react hooks, hooks like useSelector are implemented one level higher. There's a large jump in complexity from RTK's I'd like to see reselect own the I'm going to make three assumptions here
That said, the following code is for thecombiners pattern, called Like Hook Exampleimport{useState}from"react";import{useDerivedSelector}from"redux-toolkit";// from "reselect/react"constMyComponent=()=>{const[items,setItems]=useState();constderivedValue=useDerivedSelector(selectFoo,(foo)=>{// combine foo and items here},[items]);} ConsequencesHooks Across Redux Ecosystem Right now, the majority of hooks are in redux-toolkit. I don't think this changes, it just makes some hooks available in the lower level library closer to their relevant concerns. It does change exports from the module(s) though, and individuals using both Reselect and RTK might be confused as to which import to use. Encourages Pure Selectors This pattern makes it more obvious that Encourages Selectors Aside Slices This pattern also encourages the pure selectors to live next to slices created by RTK. In theory, a set of Pure selector functions can also be auto-generated from the Sample Hook// hypothetical hook code, written quickly to show the idea in practiceconstuseDerivedSelector=(...args)=>{constdeps=Array.isArray(args[args.length-1]) ?args.pop() :[];constcombiner=useRef(args.pop());constresult=useMemo(()=>{combiner.current(...args);},[...args, ...deps])} In practice, I wouldn't use the splatted args, and I'd add types for the hook. There is no need for an equality function in this example, as we're relying on React's reconciler to determine if the combiner must be reran based on the selector results. Finally, none of this code prevents us from using more complex |
BetaWas this translation helpful?Give feedback.
All reactions
-
Hey, thanks for the comment. I think there's a few misunderstandings here - let me see if I can clarify things. First, it's important to understand that React-Redux, Reselect, and Redux Toolkit are three entirely different libraries with different responsibilities:
Because of that, it doesn't make sense conceptually to talk about "Reselect owning Also, the phrase "the majority of hooks are in Redux Toolkit" isn't correct - RTK doesn't haveany hooks except for the RTKQ query hooks. Wehave vaguely tossed around the idea of adding additional React-specific functionality to RTK at some point, and now that we have the RTKQ query hooks as an additional entry point, we at least have a conceptual pattern that would let us implement that while keeping the bulk of the package UI-agnostic for use outside of React. As for the |
BetaWas this translation helpful?Give feedback.
All reactions
👍 1
-
Yep. I think my mental model of "what goes where & who's responsible for that API" was totally sideways. :) I'll trace back through my notes relearning RTK and see where I made the jump that the hooks came from RTK and not the react-redux layer. If I do find something off in docs, I'll go open a PR over there. The motivation behind wanting something like constselectItems=(s)=>s.items;constMyComponent=()=>{const[filter,setFilter]=useState();constitems=useSelector(selectItems);constfilteredItems="?";} The docs I referenced when encountering this problem (and my thoughts as I looked at how to solve this):
The thing is, I'm not entirely sure that this is on reselect as a library. What reselect is doing makes perfect sense, but our component has changed in a way that became difficult to follow: constselectItems=(s)=>s.items;constmakeSelectFilteredItems=()=>createSelector(selectItems,// same as our useSelector from before(_,filter)=>filter,// https://github.com/reduxjs/reselect#api a "props" argument, I think?(items,filter)=>// complex filtering of items goes here// but now it's also decoupled from the component)constMyComponent=()=>{const[filter,setFilter]=useState();constselectFilteredItems=useMemo(makeSelectFilteredItems,[]);constfilteredItems=useSelector((state)=>selectFilteredItems(state,filter));} We memoize the factory method, then use our constselectItems=(s)=>s.items;constMyComponent=()=>{const[filter,setFilter]=useState();constitems=useSelector(selectItems);constfilteredItems=useMemo(()=>{// complex filtering of items goes here},[filter,items]);} And again, coming back to the earlier point, I'm not actually sure this is a reselect API issue. It's just that |
BetaWas this translation helpful?Give feedback.
All reactions
-
Yeah, there's some scattered references to selectors throughout the React-Redux and RTK docs. That said, I just recently wrote a newDeriving Data with Selectors page in the Redux docs that's intended to consolidate and explain a lot of the usage patterns - I'd suggest taking the time to read through that. I definitely agree that the The other issue you're seeing is that Ultimately, yes, there is a lot of overlap between trying to memoize filtered results via a |
BetaWas this translation helpful?Give feedback.
All reactions
-
Resurrecting this discussion thread: I filed this two years ago. That led to me doing a bunch of work to put out Reselect 4.1 in late 2021, including applying a new set of TS types that used newer TS inference abilities to extract types from the input selectors and determine the final selector arg types, and new options for We're starting work on Redux Toolkit 2.0, and that will likely mean bumping major versions for Given that, it makes sense to review Reselect and consider what further improvements we could make in a v5, including completely breaking API changes. So, similar to the original questions:
|
BetaWas this translation helpful?Give feedback.
All reactions
👍 1❤️ 4
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
Not knowing when it works, when it don't, and how to fix it
The current API is quite declarative and simple - you are just connecting the dots and that is all you should be thinking about.
There are options:
The first option is our immutable past, the second is our future already possible in other (👋@dai-shi ) solutions. |
BetaWas this translation helpful?Give feedback.
All reactions
👀 1
-
Hey! First of all, thank you for all your work on Redux, it really is a great library! I have been thinking a lot about how to design selectors in a scalable way, and I am currently using my own custom selector library as I found Reselect (and any other selector library out there) rather inadequate for my needs, so I will describe here the latest iteration of the API I am using to show some of the issues I faced, in the hope that it can inspire a future version of Reselect. TL;DRSorry for the very long post, here is a short list of the key takeaways, described in more details below.
ContextI have been working for the last two years on an internal tool at my current company. It's a graphical editor for creating complex animations (think something like Figma but for animations, with a UI heavily inspired by After Effects), and it's entirely implemented as a React + Redux web app. I have a very complex state structure, as in a given project you have many animations, each animation has many layers, each layer has many keyframes, and so on, and there is a lot of different places the data needs to be queried by components. The state is partially normalized, in the sense that I use the The main issue I faced was how to design parameterized selectors (i.e. selector with arguments) in a way that makes them easy to define, easy to use, and performant. The main function in my library is called Simple selectorsA (simple)selector is any pure function that can be used as an argument to the In addition to being pure, it is very important for selectors not to return new references needlessly, like In my library, I also added a debugging feature that calls all selectors twice (in development mode) and logs an error message if the two results are not referentially equal, because that means something is wrong with the selector. I found this invaluable in order to find selectors that may be improperly memoized. Simple selectors are fine for simple global state, but in my case the vast majority of selectors need to select data corresponding to a particular animation, or to a particular layer in a particular animation, and to handle that we need parameterized selectors. Parameterized selectorsAparameterized selector is a currified function that takes some arguments and returns a simple selector, for instance: selectAnimationById:({animationId:string})=>(state:State)=>Animation Note that this differs from Reselect's notion of parameterized selectors, which is a function with several arguments in addition to the state: selectAnimationById2:(state:State,animationId:string)=>Animation I must say I really don't understand the point of such selectors with several arguments, because they cannot easily be used in constanimation=useSelector(selectAnimationById({animationId}));constanimation=useSelector(state=>selectAnimationById2(state,animationId)); I always want to use the first version, so that's what I tailored my library towards. I don't support selectors with several arguments, and I don't even call them selectors. Combining functionsThe "output selector" of Reselect is what I call acombining function. I don't think the word "selector" should be used here at all because it is a completely different kind of function:
So I think it makes sense to clearly distinguish between (parameterized) selectors and combining functions because they correspond to different concepts. Another related point is that I don’t really see the point of Reselect's CachingIf the combining function is computationally expensive/returns new references, then caching can/must be used. Like in Reselect, caching means that we first run all input selectors, and if it turns out that we get the exact same values than last time, then we do not rerun the combining function, but instead simply return whatever it returned last time. A very important thing to look out for (which is what re-reselect was designed to fix), is that calling the same selector but with different arguments should not invalidate the cache, otherwise it completely destroys all memoization as soon as you have two different uses of the same selector at the same time: // No memoization at all with Reselect's default behavior!conststuff=useSelector(selectComputationallyExpensiveStuff(animationId));constotherStuff=useSelector(selectComputationallyExpensiveStuff(otherAnimationId)); Figuring out how to properly cache thing is pretty tricky, though. The simplest solution is to have a infinite cache, but it can of course create memory leaks. But a fixed cache size (as in Reselect's
Ideally I think that there should be an unlimited cache but which gets cleaned up after each render, so that we only cache instances of selectors that are currently in use. I think this is where 99.9% of the benefits of caching come from: to optimize successive renders and making sure that when we change one part of the app, the rest of the app that is untouched doesn’t rerender/runs computationally expensive functions. So every time an action is dispatched and Redux has run all the selectors again, it should remove from the cache all cached selectors that did not rerun in this cycle. I haven’t implemented that in my library yet, but I think that would be the best solution. In addition, re-reselect allows you to provide a custom function to compute a cache key based on the arguments. In my case, I feel like Globalizing selectors and reducing boilerplateOne of the main problem I wanted to solve was actually reducing boilerplate when globalizing selectors. For instance a keyframe has a timestamp, so there should be a constselectKeyframeTimestamp=(props:{animationId:string,layerId:string,keyframeId:string})=>(state:State)=>state.animations.byId[props.animationId].layers.byId[props.layerId].keyframes.byId[props.keyframeId].timestamp but this is clearly not sustainable, especially when you need many such selectors. Instead I can now write it as follows, where // selectKeyframeByIds has type// ({animationId: string, layerId: string, keyframeId: string}) => (state: State) => KeyframeconstselectKeyframeTimestamp=combineSelectors([selectKeyframeByIds],keyframe=>keyframe.timestamp);// with type// ({animationId: string, layerId: string, keyframeId: string}) => (state: State) => number// Usage in a componentconsttimestamp=useSelector(selectKeyframeTimestamp({animationId, layerId, keyframeId})); which is a lot better. The thing to note in particular is that I am not using The APIFinally, here is how my API looks like on a general example. Given several parameterized selectors: selector1:(props:Arguments1)=>(state:State)=>Result1selector2:(props:Arguments2)=>(state:State)=>Result2selector3:(props:Arguments3)=>(state:State)=>Result3 and a combining function combiner:(value1:Result1,value2:Result2,value3:Result3)=>Result it produces a new parameterized selector combineSelectors([selector1,selector2,selector3],combiner):(props:Arguments1&Arguments2&Arguments3)=>(state:State)=>Result which calls For instance let's say I want the timestamp of a keyframe but expressed in frames (which requires knowing the fps of the animation), I can write it like this: // Assume we have// selectAnimationById: ({animationId: string}) => (state: State) => Animation// selectKeyframeByIds: ({animationId: string, layerId: string, keyframeId: string}) => (state: State) => KeyframeconstselectAnimationFps=combineSelectors([selectAnimationById],(animation)=>animation.fps,);// of type ({animationId: string}) => (state: State) => numberconstselectKeyframeTimestampInFrames=combineSelectors([selectKeyframeByIds,selectAnimationFps],(keyframe,fps)=>keyframe.timestamp*fps,);// of type ({animationId: string, layerId: string, keyframeId: string}) => (state: State) => number and then I can simply use it as follows consttimestampInFrames=useSelector(selectKeyframeTimestampInFrames({animationId, layerId, keyframeId})); It will call both The other API I am using is the following function, which allows you to add arguments to a selector: constselectArgument=<Kextendsstring>(key: K) =><T>(props: Record<K,T>) =>(_:State)=>props[key]; It is a function returning a parameterized selector that ignores the state and simply returns one of the parameters. It can be used to add additional arguments to a selector, for instance if you want the fps to be provided as an argument to the selector rather than taking it from the animation: constselectKeyframeTimestampInFrames=combineSelectors([selectKeyframeByIds,selectArgument("customFps")<number>],(keyframe,fps)=>keyframe.timestamp*fps,);// of type ({animationId: string, layerId: string, keyframeId: string, customFps: number}) => (state: State) => number Additional thoughtsI tried to use Another thing I implemented in my library is a debugging tool that counts how often selectors are called and how much time they take to run, to find computationally expensive selectors. I have found that this gives great insights and allows one to know whether some selectors need to be optimized. |
BetaWas this translation helpful?Give feedback.
All reactions
❤️ 1
-
@guillaumebrunerie thank youvery much for taking the time to write up this extensive comment! I genuinely appreciate it. Unfortunately my own brain is kinda fried and overloaded atm (a combination of not quite enough sleep the last couple days, and juggling too many other tasks at work and on the Redux side atm). I've skimmed your comment, and there's clearly a lot of great info here. Iwill come back to this later and give it the deeper read and better response that it deserves, but it will probably be some time before I'm ready to turn my attention back to working on Reselect. My couple immediate thoughts I can toss out are:
|
BetaWas this translation helpful?Give feedback.
All reactions
-
Brilliant idea - automatic garbage collection for the cache. It is worth further investigation.
It's been |
BetaWas this translation helpful?Give feedback.
All reactions
-
@markerikson I have now made a codesandbox showing my implementation, seehere. I've plugged it into some default Redux template to check that it works, but this is just an example application, way simpler than my real project.
When it comes to anonymous functions like
|
BetaWas this translation helpful?Give feedback.
All reactions
-
Hey Mark, Thanks for looking into this. As I mentionedon twitter, it would be nice to have a version of (copied fromrtk docs) interfaceMyData{// ...}interfaceMyKnownError{errorMessage:string// ...}interfaceUserAttributes{id:stringfirst_name:stringlast_name:stringemail:string}constupdateUser=createAsyncThunk<// Return type of the payload creatorMyData,// First argument to the payload creatorUserAttributes,// Types for ThunkAPI{extra:{jwt:string}rejectValue:MyKnownError}>('users/update',async(user,thunkApi)=>{const{ id, ...userData}=userconstresponse=awaitfetch(`https://reqres.in/api/users/${id}`,{method:'PUT',headers:{Authorization:`Bearer${thunkApi.extra.jwt}`,},body:JSON.stringify(userData),})if(response.status===400){// Return the known error for future handlingreturnthunkApi.rejectWithValue((awaitresponse.json())asMyKnownError)}return(awaitresponse.json())asMyData}); I would love to have something similar with constselectUserById=createSelectorWithParameters<// type of root stateRootState,// Type of the first argument to the selector creator functionnumber>(// select slice(state)=>state.main,// select users(main)=>main.users,// select specified user(users,id)=>users.filter(val=>val===id)); which would work something like this: // App.jsxfunctionMyComponent(){constuser=useSelector(selectUserById(3));// ...} |
BetaWas this translation helpful?Give feedback.
All reactions
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
There is another way to solve this which maintains the memoization capabilities, |
BetaWas this translation helpful?Give feedback.
All reactions
-
@triptu Do you mean that you have a custom import{useSelectorasuseSelectorOriginal}from"react-redux";exportconstuseSelector=(selector,props)=>{returnuseSelectorOriginal(state=>selector(state,props));}; I like this syntax, and I've been thinking about introducing it to my own projects as well, although it's kinda weird to use the same name ( |
BetaWas this translation helpful?Give feedback.
All reactions
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
@guillaumebrunerie yes very similar, typed like this with typescript to ensure the IDE autocompletion works fine. exporttypeStateSelector<T,R,Argsextendsany[]>=(state:T, ...args:Args)=>R;exportconstuseStore=<R,Argsextendsany[]>(selector:StateSelector<T,R,Args>, ...args:Args):R=>{returnuseSelector((state)=>selector(state, ...args));};// usageconstrooms=useStore(selectRooms);constroom=useStore(selectRoomById,roomId);constuserConfig=useStore(selectRoomUserConfig,roomId,userId);
I use I have a lot of selectors which take arguments and really like having them defined at one common place. This especially becomes helpful when the selectors are doing more than just selecting one field. The DX is good. |
BetaWas this translation helpful?Give feedback.
All reactions
👍 1
-
@triptu Actually, I just realized that separating the selector function from the arguments makes it very easy to implement a custom hook for the"selector factory" pattern, which ... should solve essentially all cache problems? exportconstuseParameterizedSelector=(selectorFactory, ...args)=>{constselector=useMemo(selectorFactory,[]);returnuseSelector(state=>selector(state, ...args));}; With this, each use of constselectorFactory=()=>createSelector(...); instead of constselector=createSelector(...); Have you tried something like that? |
BetaWas this translation helpful?Give feedback.
All reactions
-
Cool idea. The main issue I see is that there will be duplicate memoization in contrast to using maxSize with the defaultMemoize function. Every place which needs say |
BetaWas this translation helpful?Give feedback.
All reactions
-
While we're at it... I always felt that slices were an almost-there solution. Reducers are aware only of the portion of state that is the current slice (rather than the root of the store, which is defined higher up than the slice module), but selectors have to know where in the store the current slice lives. I get why, but it always felt wrong. I would love it if createSelector (when called from within a userSlice) was NOT required to have Rather than this: constselectUserAddress=createSelector((state:RootState)=>state.user,(user:UserState)=>user.address); We would have this: constselectUserAddress=createSelector((user:UserState)=>user.address); |
BetaWas this translation helpful?Give feedback.
All reactions
-
We do have some plans in RTK 2.0 to try adding a There's a WIP PR here - take a look and give feedback? |
BetaWas this translation helpful?Give feedback.
All reactions
-
Posting about performance problems in Reselect in response tothis. I first want to say that problems like this are very rare. Reselect is plenty fast for most situations. You have to have some crazy amounts of heavy data to hit the hard limit. Here's a little insight into how we did: The StageThe company I work for develops a bond trading platform. We deal with updates over websockets to the tune of thousands per second. This requires advanced async flow management using RxJS to throttle and buffer updates before they hit the state management layer. In React, it also requires precise control over rerenders. In Redux, that means Reselect naturally came into play a lot. We aggregate as much data as possible on the server before streaming to the UI, but some things aren't practical to handle that way. For these, we had a big graph of Reselect selectors pulling data from various data streams and aggregating them on the fly. The ProblemWe can't buffer updates for too long (about 2-3 seconds was the hard limit for most data streams):
But if we flush updates too often, too many calculations have to run too often:
Long story short, Reselect was the bottleneck - not Reselect's own code, but the code we had to put in selectors and the frequency with which we needed Reselect to run it. We came to call it a thread hogger. 90% of our time spent improving performance was spent working around Reselect. We ultimately moved away from Redux 2 years ago. Reselect is the main reason why. The InterludeBefore I get to the fix, I want to say a few things: First, I personally am a big fan of Redux. I've used it a lot, love the concepts and theory, and generally enjoy working with it. Second, just to reiterate, this is an extreme situation. If I was maintaining Reselect, I probably wouldn't change a thing after reading this comment. The root of these problems is more fundamental to the state model - beyond Reselect's control. Take this more as an FYI if you're curious to know when and how the model breaks down and what we did to fix it. Third, I am the bad guy in this story: The FixIn 2020, I proposed a new model for state management patterned after Recoil and Jotai, but much more performant, flexible, and easy to migrate to incrementally. I don't want to hijack this thread with it. If you're curious, it was actually publicly released earlier this week and you can check it outhere. After 3 months of development, we started plugging it into our apps. It fixed many problems, but here are the pieces relevant to what I've mentioned: Modular StateWith atoms, state is naturally broken up, meaning we can flush more stuff with less overhead. While each individual atom has more overhead than a state slice in Redux, atoms can scale almost indefinitely since each one is completely isolated/modular/autonomous. // hits thousands of reducers/useSelector subscriptionsreduxStore.dispatch(action)// hits only this atom and its dependents in the atom graph (inc. components)atom.dispatch(action) This is not as big a deal as it sounds - despite Redux's global approach, it's still very performant. But this did help a little and, more importantly for us, it will continue to scale beyond what we'll ever need. Selector Buffering/ThrottlingAtoms and selectors use the same graph. This means that at any point in a selector tree, you can bump down to an atom to grab complete control over memoization and async flow. We use this power to selectively buffer or throttle updates via RxJS at certain points in the selector treeonly where they're needed. This means we can flush updates often, keeping most of the UI snappy while only deferring updates in specific places where needed. It also removed backpressure problems and lots of ugly code managing it. I'll use code from our library to demonstrate. You should be able to see how this equates to a similar Redux/Reselect setup, but let me know if anything is unclear. // In practice, each entity would get its own atom. And yes, you'd normalize the// data structure. But for demo purposes:constentitiesAtom=atom('entities',()=>({fruits:{apples:['pink lady apple','granny smith apple'],lemons:['lisbon lemon','eureka lemon'],limes:['key lime','kaffir lime'],oranges:['navel orange','mandarin orange'],},}))constgetFruits=({ get})=>get(entitiesAtom).fruitsconstgetApples=({ select})=>select(getFruits).applesconstgetLemons=({ select})=>select(getFruits).lemonsconstgetLimes=({ select})=>select(getFruits).limesconstgetOranges=({ select})=>select(getFruits).orangesconstgetCitrusFruits=({ select})=>[ ...select(getLemons), ...select(getLimes), ...select(getOranges),]constgetSortedCitrus=({ select})=>select(getCitrusFruits).sort() I'll stop there for the sake of brevity, but imagine this selector graph growing to 10x this size, full of many expensive operations - filters, maps, sorts, object spreads, Immutable With atoms, any selector can be turned into an atom fairly easily. // turn the above `getCitrusFruits` selector into an atom (ion is just an atom// specially designed for selection operations):constcitrusFruitsAtom=ion('citrusFruits',({ select})=>{conststore=injectStore()constselectors=[getLemons,getLimes,getOranges]// subscribe to updates in upstream selectors, but discard results hereselectors.forEach(selector=>select(selector))// a custom injector that hides implementation details. See below if curiousinjectThrottle(()=>{store.setState(selectors.flatMap(selector=>select(selector)))})returnstore}) (ignore me, I'm just some implementation details for the above example)Here's an example of how constinjectThrottle=updateState=>{// set initial state on first runif(!injectWhy().length)updateState()// injectors are like hooks for atoms. This works just like `useMemo` ...constsubject=injectMemo(()=>newSubject(),[])subject.next('update happened!')// ... and this works just like `useEffect`:injectEffect(()=>{// use RxJS to throttle updates, only updating every 2 secondsconstsubscription=subject.pipe(throttleTime(2000)).subscribe(updateState)return()=>subscription.unsubscribe()},[])} Full codesandbox demonstrating thishere. Apologies for making this section so long, but it was the feature that benefitted us the most. This capability fixed everything but our most difficult table. For that we needed a serious escape hatch: Action StreamsThis is a "don't push the big red button" type of escape hatch, but we needed it in our most extreme table. I won't go into too much detail here, but basically our stores can be consumed both as streams of state and streams of actions. When hooking into an action stream to perform DOM updates, you skip React and the reactive paradigm completely, giving you a big performance boost at the cost of very brittle code (we love reactivity for a reason!). This is obviously a big antipattern and I don't want to encourage it, so I'll stop there. The FinaleSo what can Reselect get out of this? Well, as I said, probably not much without some major overhauls that are beyond the scope of Reselect itself. But at the very least, I hope you learned something about managing state derivations in the extremes. Happy to hear if anyone did manage to glean some insights from this that could apply to Reselect. Let me know if anything needs clarifying. If anything does come out of this, I'm willing to contribute to discussions and possibly even help with implementing changes or at least writing documentation or articles to help educate on this matter. Cheers 🥂 1 I believe Reselect's |
BetaWas this translation helpful?Give feedback.
All reactions
-
Thank you for the writeup! I have to head out in a minute, so I'll try to come back later and give it some more thought. My first reaction is that both React and Redux work best in the "80%" use case, where there's decent amounts of data being updated reasonably frequently, but when you start hitting these "real-time/thousands of updates per second" use cases that's not really what they're meant to handle. That's also true for Reselect. In general, I'd love to come up withsome kind of mechanisms that help better optimize Redux updates and Reselect selectors, but I'm not sure thereare alternate approaches without fundamentally changing both libraries. |
BetaWas this translation helpful?Give feedback.
All reactions
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
Yep, so similar conclusion. The 3 fixes I gave represent 3 different categories of problems:
As far as whether any of these are actionable for Reselect, I'm in the same boat: I don't think so. At least not for the specific problems we ran into. The current paradigm is good enough for most things (way more than 80% 😄) and any changes would require massive overhauls. Not worth it. What I was more intending to communicate is that React itselfwas fast enough for us in every case but one. However, React's overheadcombined with Reselect's overhead was too much for us in lots of other cases too. Reducing our data layer overhead enabled React for us in all those other cases. React's flavor of reactivity has amazing Dev X and I'm glad we didn't shun it just because our app type technically lies outside React's intended use case. IMO it's way better to use React for everything possible and bump down to imperative, brittle models only exactly where you need them than to use a view layer with worse Dev X for everything. React gives all the escape hatches necessary for this. Anyway, I hope this was helpful or at least alittle interesting. |
BetaWas this translation helpful?Give feedback.
All reactions
👍 1
-
I wonder if you took a look at SolidJS? It's an alternative to React with pretty similar syntax but a quite different model of reactivity, and it's been praised for being a lot more performant. The reactivity model is based onsignals (single reactive values whose life cycle is not tied to components, similar to your atoms I think) andeffects (side effects that rerun whenever the signals it depends on change). You can create graphs of signals that depend on other signals (using I'd suggest watching the talk "The world beyond components" by Ryan Carniato, it's more about how SolidJS is a replacement for React rather than Redux and Reselect, but it's really an eye opening talk. |
BetaWas this translation helpful?Give feedback.
All reactions
-
A little offtopic, butwhat problem we solve? It's some sort of mnemonic to clarify the problem. And we try to solve many different problems:
One of the biggest problems of reselect is cache size and it does not have a direct solution except WeakMaps, which are nowhere to store due to the ever-changing immutable nature of redux. Unless we can store cache in redux itself. This is less about what wecould do, more about the idea of looking for a problem not for
|
BetaWas this translation helpful?Give feedback.
All reactions
-
Note that I did add user-configurable cache sizes to I'm also playing around with a couple new memoizers over in#605 - an "autotrack" memoizer based on ideas from Ember's Glimmer engine, and a "weakmap" memoizer based on React's internal |
BetaWas this translation helpful?Give feedback.
All reactions
-
I am keeping an eye on your activity, and it looks very promising 👍. |
BetaWas this translation helpful?Give feedback.
All reactions
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
General update for folks: Perreduxjs/redux-toolkit#958 (comment) , my current goal is to get RTK 2.0 out the door just as soon as possible. RTK 2.0 will include Redux core 5.0 with updated TS types, and also include Reselect 5.0. (React-Redux 9.0 will be going out simultaneously.) Those package updates areprimarily focused onmodernizing our published ESM/CJS compatibility, but have some tweaks around removing deprecated APIs, improving TS types, etc. For Reselect 5.0 specifically: I'm stillnot making any massive changes to
Right now all I want is to get the RTK 2.0 release wavedone so that folks can use the updates we've got, without major breaking changes. We've already deferred any RTK Query feature changes toafter RTK 2.0, including being willing to publish RTKQ-specific breaking changes in a follow-on RTK 3.0. Similarly, I'm still open to future Reselect API design changes in a future Reselect 6.0, I just don't have time to meaningfully consider anything right now and don't want that sort of design work to block getting RTK 2.0 out the door. So. I know there's a lot of discussion in this thread. We've addressed the initial "hard to customize" concerns, and some of the cache size concerns. I'm still up for future improvements down the road! |
BetaWas this translation helpful?Give feedback.
All reactions
❤️ 4
This discussion was converted from issue #490 on February 16, 2021 16:47.