2018-11-29 15:57:49 +00:00
|
|
|
/**
|
|
|
|
* NOTE: This is temporary code. It exists only until a version of `@wordpress/data`
|
|
|
|
* is released which supports this functionality.
|
|
|
|
*
|
2019-02-06 06:41:53 +00:00
|
|
|
* @todo Remove this and use `@wordpress/data` `withSelect` instead after
|
2018-11-29 15:57:49 +00:00
|
|
|
* this PR is merged: https://github.com/WordPress/gutenberg/pull/11460
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* External dependencies
|
|
|
|
*/
|
|
|
|
import { isFunction } from 'lodash';
|
|
|
|
import { Component } from '@wordpress/element';
|
|
|
|
import isShallowEqual from '@wordpress/is-shallow-equal';
|
|
|
|
import { createHigherOrderComponent } from '@wordpress/compose';
|
|
|
|
import { RegistryConsumer } from '@wordpress/data';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Higher-order component used to inject state-derived props using registered
|
|
|
|
* selectors.
|
|
|
|
*
|
|
|
|
* @param {Function} mapSelectToProps Function called on every state change,
|
|
|
|
* expected to return object of props to
|
|
|
|
* merge with the component's own props.
|
|
|
|
*
|
|
|
|
* @return {Component} Enhanced component with merged state data props.
|
|
|
|
*/
|
2020-02-14 02:23:21 +00:00
|
|
|
const withSelect = ( mapSelectToProps ) =>
|
|
|
|
createHigherOrderComponent( ( WrappedComponent ) => {
|
2018-11-29 15:57:49 +00:00
|
|
|
/**
|
|
|
|
* Default merge props. A constant value is used as the fallback since it
|
|
|
|
* can be more efficiently shallow compared in case component is repeatedly
|
|
|
|
* rendered without its own merge props.
|
|
|
|
*
|
|
|
|
* @type {Object}
|
|
|
|
*/
|
|
|
|
const DEFAULT_MERGE_PROPS = {};
|
|
|
|
|
|
|
|
class ComponentWithSelect extends Component {
|
|
|
|
constructor( props ) {
|
|
|
|
super( props );
|
|
|
|
|
|
|
|
this.onStoreChange = this.onStoreChange.bind( this );
|
|
|
|
this.subscribe( props.registry );
|
|
|
|
|
2018-12-13 19:24:54 +00:00
|
|
|
this.onUnmounts = {};
|
2018-11-29 15:57:49 +00:00
|
|
|
this.mergeProps = this.getNextMergeProps( props );
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Given a props object, returns the next merge props by mapSelectToProps.
|
|
|
|
*
|
|
|
|
* @param {Object} props Props to pass as argument to mapSelectToProps.
|
|
|
|
*
|
|
|
|
* @return {Object} Props to merge into rendered wrapped element.
|
|
|
|
*/
|
|
|
|
getNextMergeProps( props ) {
|
2018-12-07 23:04:52 +00:00
|
|
|
const storeSelectors = {};
|
|
|
|
const onCompletes = [];
|
|
|
|
const componentContext = { component: this };
|
|
|
|
|
|
|
|
const getStoreFromRegistry = ( key, registry, context ) => {
|
|
|
|
// This is our first time selecting from this store.
|
|
|
|
// Do some lazy-loading of handling at this time.
|
|
|
|
const selectorsForKey = registry.select( key );
|
|
|
|
|
|
|
|
if ( isFunction( selectorsForKey ) ) {
|
|
|
|
// This store has special handling for its selectors.
|
|
|
|
// We give it a context, and we check for a "resolve"
|
2020-02-14 02:23:21 +00:00
|
|
|
const {
|
|
|
|
selectors,
|
|
|
|
onComplete,
|
|
|
|
onUnmount,
|
|
|
|
} = selectorsForKey( context );
|
|
|
|
if ( onComplete ) {
|
|
|
|
onCompletes.push( onComplete );
|
|
|
|
}
|
|
|
|
if ( onUnmount ) {
|
|
|
|
this.onUnmounts[ key ] = onUnmount
|
|
|
|
}
|
2018-12-07 23:04:52 +00:00
|
|
|
storeSelectors[ key ] = selectors;
|
|
|
|
} else {
|
|
|
|
storeSelectors[ key ] = selectorsForKey;
|
2018-11-29 15:57:49 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-02-14 02:23:21 +00:00
|
|
|
const select = ( key ) => {
|
2018-12-07 23:04:52 +00:00
|
|
|
if ( ! storeSelectors[ key ] ) {
|
2020-02-14 02:23:21 +00:00
|
|
|
getStoreFromRegistry(
|
|
|
|
key,
|
|
|
|
props.registry,
|
|
|
|
componentContext
|
|
|
|
);
|
2018-12-07 23:04:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return storeSelectors[ key ];
|
|
|
|
};
|
|
|
|
|
2020-02-14 02:23:21 +00:00
|
|
|
const selectedProps =
|
|
|
|
mapSelectToProps( select, props.ownProps ) ||
|
|
|
|
DEFAULT_MERGE_PROPS;
|
2018-12-07 23:04:52 +00:00
|
|
|
|
|
|
|
// Complete the select for those stores which support it.
|
2020-02-14 02:23:21 +00:00
|
|
|
onCompletes.forEach( ( onComplete ) => onComplete() );
|
2018-12-07 23:04:52 +00:00
|
|
|
return selectedProps;
|
2018-11-29 15:57:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
componentDidMount() {
|
|
|
|
this.canRunSelection = true;
|
|
|
|
|
|
|
|
// A state change may have occurred between the constructor and
|
|
|
|
// mount of the component (e.g. during the wrapped component's own
|
|
|
|
// constructor), in which case selection should be rerun.
|
|
|
|
if ( this.hasQueuedSelection ) {
|
|
|
|
this.hasQueuedSelection = false;
|
|
|
|
this.onStoreChange();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
componentWillUnmount() {
|
|
|
|
this.canRunSelection = false;
|
|
|
|
this.unsubscribe();
|
2020-02-14 02:23:21 +00:00
|
|
|
Object.keys( this.onUnmounts ).forEach( ( key ) =>
|
|
|
|
this.onUnmounts[ key ]()
|
|
|
|
);
|
2018-11-29 15:57:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
shouldComponentUpdate( nextProps, nextState ) {
|
|
|
|
// Cycle subscription if registry changes.
|
2020-02-14 02:23:21 +00:00
|
|
|
const hasRegistryChanged =
|
|
|
|
nextProps.registry !== this.props.registry;
|
2018-11-29 15:57:49 +00:00
|
|
|
if ( hasRegistryChanged ) {
|
|
|
|
this.unsubscribe();
|
|
|
|
this.subscribe( nextProps.registry );
|
|
|
|
}
|
|
|
|
|
|
|
|
// Treat a registry change as equivalent to `ownProps`, to reflect
|
|
|
|
// `mergeProps` to rendered component if and only if updated.
|
|
|
|
const hasPropsChanged =
|
2020-02-14 02:23:21 +00:00
|
|
|
hasRegistryChanged ||
|
|
|
|
! isShallowEqual( this.props.ownProps, nextProps.ownProps );
|
2018-11-29 15:57:49 +00:00
|
|
|
|
|
|
|
// Only render if props have changed or merge props have been updated
|
|
|
|
// from the store subscriber.
|
|
|
|
if ( this.state === nextState && ! hasPropsChanged ) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( hasPropsChanged ) {
|
|
|
|
const nextMergeProps = this.getNextMergeProps( nextProps );
|
|
|
|
if ( ! isShallowEqual( this.mergeProps, nextMergeProps ) ) {
|
|
|
|
// If merge props change as a result of the incoming props,
|
|
|
|
// they should be reflected as such in the upcoming render.
|
|
|
|
// While side effects are discouraged in lifecycle methods,
|
|
|
|
// this component is used heavily, and prior efforts to use
|
|
|
|
// `getDerivedStateFromProps` had demonstrated miserable
|
|
|
|
// performance.
|
|
|
|
this.mergeProps = nextMergeProps;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Regardless whether merge props are changing, fall through to
|
|
|
|
// incur the render since the component will need to receive
|
|
|
|
// the changed `ownProps`.
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
onStoreChange() {
|
|
|
|
if ( ! this.canRunSelection ) {
|
|
|
|
this.hasQueuedSelection = true;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const nextMergeProps = this.getNextMergeProps( this.props );
|
|
|
|
if ( isShallowEqual( this.mergeProps, nextMergeProps ) ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.mergeProps = nextMergeProps;
|
|
|
|
|
|
|
|
// Schedule an update. Merge props are not assigned to state since
|
|
|
|
// derivation of merge props from incoming props occurs within
|
|
|
|
// shouldComponentUpdate, where setState is not allowed. setState
|
|
|
|
// is used here instead of forceUpdate because forceUpdate bypasses
|
|
|
|
// shouldComponentUpdate altogether, which isn't desireable if both
|
|
|
|
// state and props change within the same render. Unfortunately,
|
|
|
|
// this requires that next merge props are generated twice.
|
|
|
|
this.setState( {} );
|
|
|
|
}
|
|
|
|
|
|
|
|
subscribe( registry ) {
|
|
|
|
this.unsubscribe = registry.subscribe( this.onStoreChange );
|
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
2020-02-14 02:23:21 +00:00
|
|
|
return (
|
|
|
|
<WrappedComponent
|
|
|
|
{ ...this.props.ownProps }
|
|
|
|
{ ...this.mergeProps }
|
|
|
|
/>
|
|
|
|
);
|
2018-11-29 15:57:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-14 02:23:21 +00:00
|
|
|
return ( ownProps ) => (
|
2018-11-29 15:57:49 +00:00
|
|
|
<RegistryConsumer>
|
2020-02-14 02:23:21 +00:00
|
|
|
{ ( registry ) => (
|
|
|
|
<ComponentWithSelect
|
|
|
|
ownProps={ ownProps }
|
|
|
|
registry={ registry }
|
|
|
|
/>
|
|
|
|
) }
|
2018-11-29 15:57:49 +00:00
|
|
|
</RegistryConsumer>
|
|
|
|
);
|
|
|
|
}, 'withSelect' );
|
|
|
|
|
|
|
|
export default withSelect;
|