Skip to content

Instantly share code, notes, and snippets.

@aduth
Created April 30, 2018 15:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aduth/bc66310cd9390a88c54a6ae40974094e to your computer and use it in GitHub Desktop.
Save aduth/bc66310cd9390a88c54a6ae40974094e to your computer and use it in GitHub Desktop.
/**
* Higher-order component used to inject state-derived props using registered
* selectors.
*
* @param {Function} mapStateToProps 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.
*/
export const withSelect = ( mapStateToProps ) => createHigherOrderComponent( ( WrappedComponent ) => {
function getNextMergeProps( nextProps, prevState ) {
const nextMergeProps = mapStateToProps( select, nextProps );
return isShallowEqual( nextMergeProps, prevState.mergeProps ) ?
prevState.mergeProps :
nextMergeProps;
}
return class ComponentWithSelect extends Component {
constructor( props ) {
super( props );
this.runSelection = this.runSelection.bind( this );
this.unsubscribe = subscribe( this.runSelection );
this.state = {};
}
static getDerivedStateFromProps( nextProps, prevState ) {
if ( isShallowEqual( nextProps, prevState.props ) ) {
return null;
}
return {
props: nextProps,
mergeProps: getNextMergeProps( nextProps, prevState ),
};
}
shouldComponentUpdate( nextProps, nextState ) {
// Component should update if one of either:
// - Props reference changes incurred in getDerivedStateFromProps
// after checking shallow equality difference.
// - Merge props are not shallow equal, either via runSelection
// (state change) or getDerivedStateFromProps (props change).
return (
nextState.props !== this.state.props ||
nextState.mergeProps !== this.state.mergeProps
);
}
componentWillUnmount() {
this.unsubscribe();
// While above unsubscribe avoids future listener calls, callbacks
// are snapshotted before being invoked, so if unmounting occurs
// during a previous callback, we need to explicitly track and
// avoid the `runSelection` that is scheduled to occur.
this.isUnmounting = true;
}
runSelection() {
if ( this.isUnmounting ) {
return;
}
this.setState( ( prevState, props ) => ( {
mergeProps: getNextMergeProps( props, prevState ),
} ) );
}
render() {
return <WrappedComponent { ...this.props } { ...this.state.mergeProps } />;
}
};
}, 'withSelect' );
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment