-
-
Notifications
You must be signed in to change notification settings - Fork 3.4k
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
useTrackedState from reactive-react-redux #1503
Closed
Closed
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
919c94d
convert useTrackedState from reactive-react-redux
dai-shi 17c266e
createTrackedStateHook for custom context
dai-shi 390921b
add spec for useTrackedState
dai-shi 6377cd6
add docs
dai-shi 63b52cc
Update docs/api/proxy-based-tracking.md
dai-shi 9fcdbd3
apply suggestions in docs
dai-shi 2ecb0b2
move useTrackedState docs into hooks.md
dai-shi c3d5bbc
useDebugValue for tracked info, always unwrap proxy, update 3rd caveat
dai-shi c01ddc0
minor fix in useAffectedDebugValue
dai-shi 7d20db4
Merge branch 'master' into use-tracked-state
dai-shi File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
/* eslint-env es6 */ | ||
|
||
import { | ||
useReducer, | ||
useRef, | ||
useMemo, | ||
useContext, | ||
useEffect, | ||
useDebugValue | ||
} from 'react' | ||
import { useReduxContext as useDefaultReduxContext } from './useReduxContext' | ||
import Subscription from '../utils/Subscription' | ||
import { useIsomorphicLayoutEffect } from '../utils/useIsomorphicLayoutEffect' | ||
import { ReactReduxContext } from '../components/Context' | ||
import { createDeepProxy, isDeepChanged } from '../utils/deepProxy' | ||
|
||
// convert "affected" (WeakMap) to serializable value (array of array of string) | ||
const affectedToPathList = (state, affected) => { | ||
const list = [] | ||
const walk = (obj, path) => { | ||
const used = affected.get(obj) | ||
if (used) { | ||
used.forEach(key => { | ||
walk(obj[key], path ? [...path, key] : [key]) | ||
}) | ||
} else if (path) { | ||
list.push(path) | ||
} | ||
} | ||
walk(state) | ||
return list | ||
} | ||
|
||
const useAffectedDebugValue = (state, affected) => { | ||
const pathList = useRef(null) | ||
useEffect(() => { | ||
pathList.current = affectedToPathList(state, affected) | ||
}) | ||
useDebugValue(pathList.current) | ||
} | ||
|
||
function useTrackedStateWithStoreAndSubscription(store, contextSub) { | ||
const [, forceRender] = useReducer(s => s + 1, 0) | ||
|
||
const subscription = useMemo(() => new Subscription(store, contextSub), [ | ||
store, | ||
contextSub | ||
]) | ||
|
||
const state = store.getState() | ||
const affected = new WeakMap() | ||
const latestTracked = useRef(null) | ||
useIsomorphicLayoutEffect(() => { | ||
latestTracked.current = { | ||
state, | ||
affected, | ||
cache: new WeakMap() | ||
} | ||
}) | ||
useIsomorphicLayoutEffect(() => { | ||
function checkForUpdates() { | ||
const nextState = store.getState() | ||
if ( | ||
latestTracked.current.state !== nextState && | ||
isDeepChanged( | ||
latestTracked.current.state, | ||
nextState, | ||
latestTracked.current.affected, | ||
latestTracked.current.cache | ||
) | ||
) { | ||
forceRender() | ||
} | ||
} | ||
|
||
subscription.onStateChange = checkForUpdates | ||
subscription.trySubscribe() | ||
|
||
checkForUpdates() | ||
|
||
return () => subscription.tryUnsubscribe() | ||
}, [store, subscription]) | ||
|
||
if (process.env.NODE_ENV !== 'production') { | ||
useAffectedDebugValue(state, affected) | ||
} | ||
|
||
const proxyCache = useRef(new WeakMap()) // per-hook proxyCache | ||
return createDeepProxy(state, affected, proxyCache.current) | ||
} | ||
|
||
/** | ||
* Hook factory, which creates a `useTrackedState` hook bound to a given context. | ||
* | ||
* @param {React.Context} [context=ReactReduxContext] Context passed to your `<Provider>`. | ||
* @returns {Function} A `useTrackedState` hook bound to the specified context. | ||
*/ | ||
export function createTrackedStateHook(context = ReactReduxContext) { | ||
const useReduxContext = | ||
context === ReactReduxContext | ||
? useDefaultReduxContext | ||
: () => useContext(context) | ||
return function useTrackedState() { | ||
const { store, subscription: contextSub } = useReduxContext() | ||
|
||
return useTrackedStateWithStoreAndSubscription(store, contextSub) | ||
} | ||
} | ||
|
||
/** | ||
* A hook to return the redux store's state. | ||
* | ||
* This hook tracks the state usage and only triggers | ||
* re-rerenders if the used part of the state is changed. | ||
* | ||
* @returns {any} the whole state | ||
* | ||
* @example | ||
* | ||
* import React from 'react' | ||
* import { useTrackedState } from 'react-redux' | ||
* | ||
* export const CounterComponent = () => { | ||
* const state = useTrackedState() | ||
* return <div>{state.counter}</div> | ||
* } | ||
*/ | ||
export const useTrackedState = /*#__PURE__*/ createTrackedStateHook() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While it's not an immediate concern, I'm noticing that we now have 3 different parts of our codebase where we're subscribing to the store in a hook. That may be worth looking at to see if there's some way to abstract it.