-
-
Save epiqueras/7eae39ba6b903286cf17a4907902a630 to your computer and use it in GitHub Desktop.
`@wordpress/data` Hooks Prototype
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
/** | |
* WordPress dependencies | |
*/ | |
import { useContext } from '@wordpress/element'; | |
/** | |
* Internal dependencies | |
*/ | |
import { Context } from '../components/async-mode-provider'; | |
export default function useAsyncMode() { | |
return useContext( Context ); | |
} |
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
/** | |
* Internal dependencies | |
*/ | |
import useRegistry from './use-registry'; | |
export default function useDispatch() { | |
return useRegistry().dispatch; | |
} |
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
/** | |
* WordPress dependencies | |
*/ | |
import { useContext } from '@wordpress/element'; | |
/** | |
* Internal dependencies | |
*/ | |
import { Context } from '../components/registry-provider'; | |
export default function useRegistry() { | |
return useContext( Context ); | |
} |
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
/** | |
* WordPress dependencies | |
*/ | |
import { createQueue } from '@wordpress/priority-queue'; | |
import { useLayoutEffect, useEffect, useCallback, useMemo, useReducer, useRef } from '@wordpress/element'; | |
import { isShallowEqualObjects } from '@wordpress/is-shallow-equal'; | |
/** | |
* Internal dependencies | |
*/ | |
import useRegistry from './use-registry'; | |
import useAsyncMode from './use-async-mode'; | |
const useIsomorphicLayoutEffect = | |
typeof window !== 'undefined' ? useLayoutEffect : useEffect; | |
const renderQueue = createQueue(); | |
export default function useSelect( _mapSelect, deps ) { | |
const mapSelect = useCallback( _mapSelect, deps ); | |
const registry = useRegistry(); | |
const isAsync = useAsyncMode(); | |
const queueContext = useMemo( () => ( { queue: true } ), [ registry ] ); | |
const [ , forceRender ] = useReducer( ( s ) => s + 1, 0 ); | |
const latestMapSelect = useRef(); | |
const latestIsAsync = useRef( isAsync ); | |
const latestMapOutput = useRef(); | |
const latestMapOutputError = useRef(); | |
const isMounted = useRef(); | |
let mapOutput; | |
try { | |
if ( latestMapSelect.current !== mapSelect || latestMapOutputError.current ) { | |
mapOutput = mapSelect( registry.select, registry ); | |
} else { | |
mapOutput = latestMapOutput.current; | |
} | |
} catch ( err ) { | |
let errorMessage = `An error occured while running \`mapSelect\`: ${ | |
err.message | |
}.`; | |
if ( latestMapOutputError.current ) { | |
errorMessage += `\nThe error may be correlated with this previous error:\n${ | |
latestMapOutputError.current.stack | |
}\n\nOriginal stack trace:`; | |
} | |
throw new Error( errorMessage ); | |
} | |
useIsomorphicLayoutEffect( () => { | |
latestMapSelect.current = mapSelect; | |
if ( latestIsAsync.current !== isAsync ) { | |
latestIsAsync.current = isAsync; | |
renderQueue.flush( queueContext ); | |
} | |
latestMapOutput.current = mapOutput; | |
latestMapOutputError.current = undefined; | |
isMounted.current = true; | |
} ); | |
useIsomorphicLayoutEffect( () => { | |
const onStoreChange = () => { | |
if ( isMounted.current ) { | |
try { | |
const newMapOutput = latestMapSelect.current( registry.select, registry ); | |
if ( isShallowEqualObjects( latestMapOutput.current, newMapOutput ) ) { | |
return; | |
} | |
latestMapOutput.current = newMapOutput; | |
} catch ( err ) { | |
latestMapOutputError.current = err; | |
} | |
forceRender( {} ); | |
} | |
}; | |
const unsubscribe = registry.subscribe( () => { | |
if ( latestIsAsync.current ) { | |
renderQueue.add( queueContext, onStoreChange ); | |
} else { | |
onStoreChange(); | |
} | |
} ); | |
return () => { | |
isMounted.current = false; | |
unsubscribe(); | |
renderQueue.flush( queueContext ); | |
}; | |
}, [ registry ] ); | |
return mapOutput; | |
} |
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
/** | |
* WordPress dependencies | |
*/ | |
import { createHigherOrderComponent } from '@wordpress/compose'; | |
import { memo } from '@wordpress/element'; | |
/** | |
* Internal dependencies | |
*/ | |
import useSelect from '../use-select'; | |
const withSelect = ( mapSelectToProps ) => createHigherOrderComponent( | |
( WrappedComponent ) => memo( ( ownProps ) => { | |
const mapSelect = ( select, registry ) => mapSelectToProps( | |
select, | |
ownProps, | |
registry | |
); | |
const mergeProps = useSelect( mapSelect ); | |
return <WrappedComponent { ...ownProps } { ...mergeProps } />; | |
} ), | |
'withSelect' | |
); | |
export default withSelect; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
useEffect
is non-blocking.useLayoutEffect
runs synchronously, before the browser has a chance to paint or anything.If a store update happens before
useEffect
is called for the latest render, it can call the callback with stale values.Does that make more sense?