-
Notifications
You must be signed in to change notification settings - Fork 16
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
make work as general react-redux memoizer #3
Comments
for example, this one is not working: const mapState = ({ category, videosByCategory, videosHash }, { dispatch }) => {
const slugs = videosByCategory[category] || []
const videos = slugs.map(slug => videosHash[slug])
return { videos, dispatch }
} |
I'm thinking we just need to do this: const state = selector(storeState, props)
return isShallowEqual(state, prevState) ? null : state Perhaps that's the perfect combination with your library. I just wasnt sure if |
Yeah, so first of all isShallowEqual will solve The correct way to use memoizeState for "redux" is memoize(mapStateToProps, {strictArguments: true}); It will reduce passed arguments to the real function arity. PS: But this is not related to your problem. So - I've got the task to solve, give me some time to play with it. |
cool. ...got it working well enough with the additional shallow equal check: https://codesandbox.io/s/64mzx7vjmz?module=%2Fsrc%2Fconnect%2Findex.js with this simple/sample API, we always pass both state + props, so the arity is always 2. I tried it both with and without Im gonna delete my last comment on the original PR about this not working. I just misunderstood that the returned object shouldn't be compared, but rather one level deep. That said, it almost feels like it should return the exact same object reference as last time if all the key/vals are equal. That saves having to do an additional shallow equality check. I think you might already have this info before |
You know, you are actually was and is right about it. I am not sure what shallow equal did help. |
try logging static getDerivedStateFromProps(nextProps, prevState) {
const { storeState, ...props } = nextProps
const state = selector(storeState, props)
if (state.videos) { // consistently capture the results to same selector that has `state.videos`
window.state = state
window.prev = prev
}
return shallowEqual(state, prevState) ? null : state // `null` indicates no state changes occurred
} and then played with the ps: i think the mapState, mapDispatch, mapMergeProps is friggin stupid. You don't really need to ever |
So currently the goal is to understand what that is missing. const mapStateToProps = (state, props) => {
onAction: () => dispatch(state.something, props.something)
}; It did not access I am ok with simplifying things, but we have to found a way to handle simplified thing. const mapStateToProps = (state, props) => {
// pass state and props to function
onAction: (state, props) => dispatch(state.something, props.something)
}; Nope, you could not prevent a user from accessing variables from a scope, which does not work. Imho, splitting the function as "the old" redux do - a way to easy writing, easy testing, and easy using. But, as long you have dispatch as a prop, who will stop you from going in a "new" way? Another way, is just to call the stored function with fake dispatch on selector creation. Or on first component mount. This will detect major keys memoize should react for, but will also require some way to inject "additional" keys to track. And may have side effects. So many ways to go, and all of them may require something "external" to memoize-state approach. |
the fake dispatch is a great idea! The side effects would have to simply not be permitted. What do you mean by "way to inject 'additional' keys to track"? ...as for multiple components, we could do this: static getDerivedStateFromProps(nextProps, prev) {
const { dispatch, storeState, ...props } = nextProps
const selector = prev.selector || memoize(sel)
const state = selector(storeState, props)
if (!prev.selector) state.selector = selector
return state === prev ? null : state
} that way its memoized per instance. The codesandbox has been updated, and this works. I also added a comment about handling action creators: https://codesandbox.io/s/64mzx7vjmz?module=%2Fsrc%2Fconnect%2Findex.js ...im starting to think you're right, memoizing action creators is a bad idea. I know I have apps that do dom manipulation side effects in action creators, etc. But maybe not, if it was clearly documented that its not allowed here. For ACs with side FX, you can simply pass the entire AC to your component, rather than pre-fill arguments in our selector. |
const selector = memoize((state,props) => () => props.dispatch(state.videos.length ? 1 : 2);
selector.setAdditionalTrackingOn(['.videos.length'],['dispatch']); |
but we don't need "key tracking" if we inject a fake ...i guess we do if we wanted to allow arbitrary/any function, not just ACs. i think we can check that use case off. we explicitly disallow it. |
"the latest value provider" could solve any function case, but I'll better skip requirements and possibility of so black magic. |
So
I've played with your example, and it seems to be ok. It also removes areStatesEqual before the selector(as long memoize state is doing the same), and might could remove shallowEqual after(as long selector is a "whole" memoized), stripping away a lot of comparisons. The only thing is missing - some trash dispatchers(a clock?) to stress test the approach. |
...yea, we can optimize it a lot still. and bring back to the arguments detection of the selector like in the original react-redux. I think the next steps are:
Feel free to fork it and do anything you think would be good. It's basically based on how much time we have to incrementally improve it. ps. your option is called |
static getDerivedStateFromProps(nextProps, prevState) {
const { storeState, ...props } = nextProps
const selector = prevState.selector || memoize(sel)
const result = selector(storeState, props)
return shallowEqual(result, prevState.result) ? null : { result, selector }
} Now you dont mix result with system information (ie "selector"), and maintain the "same" object across the changes.
const mapAllToProps = (state, {dispatch, ...props}) => ({
name: state.name,
action: () => dispatch({type:superActionAndAnaliticsEvent, payload:{state, props})
}); ^^ that is legal, but will update on anything update. |
|
|
|
What we could do with data adapters? // allocate "persistent" object
const tracker = state.tracker = state.tracker || {};
// store in that object "last" state
tracker.state = state;
adapt = state => Proxy(state, {
get(target, props) {
// redirect "all" reads from to the "last" version
return tracker.state[prop];
}
});
const result = selector(adapt(state)) But it will not work, as long breaks immutability of the state, and break all comparisons between the old state and the new, cos old state IS lifted to the new. adapt = state => Proxy(state, {
get(target, prop) {
if(global_noLifting) return state[prop]
return tracker.state[prop];
}
});
global_noLifting=true;
const result = selector(adapt(state))
global_noLifting=false; ^ that already might work out of the box, only for top level keys, fast enought and even stable. |
Then that task is done.
|
code has been updated, and it works! https://codesandbox.io/s/64mzx7vjmz?module=%2Fsrc%2Fconnect%2Findex.js |
I found an important selector use-case that does not work, but really should: const mapState = ({ page, direction, ...state }) => ({
page,
direction,
isLoading: isLoading(state)
})
If we refactor it to the following it works: const mapState = state => ({
page: state.page,
direction: state.direction,
isLoading: isLoading(state)
}) So obviously it loses its reference capabilities when you destructure a portion of state. Is there any way around this? |
@faceyspacey - lets continue with spread in separate issue - #5 |
Hey Anton, so basically for one we need it to work in the demo:
https://codesandbox.io/s/64mzx7vjmz?module=%2Fsrc%2Fconnect%2Findex.js
You mentioned that function references was the primary issue. So we need that.
Here's a primary test that must work:
As far as this:
Perhaps, we don't in fact need that. So let's just forget that for now.
Feel free to fork the demo, upgrade the deps, and paste a link to the working demo on the react-redux PR. I think it would be quite impressive to see all this come together in that demo. So for now, let's just think about what's actually being done in the demo, and then later I'm sure other cases will be brought to us.
The text was updated successfully, but these errors were encountered: