Improvements to store-react useSelected #43
Merged
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Liveness
In this reworked implementation, the selected value is always 'derived' from props inline in the render. This means the render has the very latest value of
selected
.Previously the updating of selected was handled via a
setState
that was wired up in auseEffect
hook which put state propagation outside the render.In the worst circumstances, (for example if an earlier-subscribed watcher had already triggered a synchronous render before later watchers executed), a later watcher's state changes would require a further render, and the state wouldn't be updated until the later render.
The same effect occurred when the selector was defined as a callback dependent on some component props - rather than immediately affecting state, the change of selector would simply invalidate the
useEffect
meaning the eventual state change would only propagate after the render.With this new implementation, the render itself recomputes the selected. If the render has already run, it will have already derived selected state, and synchronized it to the React tree. When notified, since it shares a memoized version of the selector the watcher detects there is no more work to do, and doesn't trigger a render. This means state changes propagate quicker and more predictably, and fewer renders are needed for these edge cases.
'Caching' computed values
useSelected
didn't previously wrap your selector function in a memoizing wrapper (guaranteeing the same return given the same state).Memoization is a subtle art for less confident React developers. Since memoization of a single arg function is very lightweight and easy to implement, and is a requirement for the change to state propagation above, this is now added by default.
Now if your selector and the state are identical since the last execution, then
useSelected
will return the previous value.Crucially this eliminates the case where derived computed values give a new unique value on every render, even when the underlying state hadn't changed.
For example, if you wrote a hook like this, it would force any component that consumed the summary value to re-render, even when nothing had changed, because it constructs a new object every time.
With the new implementation,
summarySelector
is only re-executed if it or the state has actually changed. Until then, the firstsummary
object is used.