Skip to content

Instantly share code, notes, and snippets.

@cmacrander
Created December 23, 2018 03:29
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 cmacrander/ba5d6bd0981e94af60b239f9ed10f6d8 to your computer and use it in GitHub Desktop.
Save cmacrander/ba5d6bd0981e94af60b239f9ed10f6d8 to your computer and use it in GitHub Desktop.
`withData` React HOC for loading and flux-like data flow
import React from 'react';
import omit from 'lodash/omit';
const LoadingDefault = () => <div>Loading&hellip;</div>;
/**
* Higher-order component factory, acting as a layer between App and the scenes
* that want to load data. Pass in a scene component and a collection of
* functions that "select" i.e. pick out certain parts of the "appState" i.e.
* App's `this.state` to use as props. The scene will then only re-render when
* those relevant parts of the state change.
* @see https://reactjs.org/docs/react-api.html#reactpurecomponent
* @param {PureComponent} WrappedComponent concerned with rendering & loading
* @param {Object} select map of prop names to selector functions, each of
* whick pick out some value from the app-wide state
* @return {PureComponent} designed to receive props `appState` and
* `routerProps`.
*/
function withData(WrappedComponent, select) {
return class extends React.PureComponent {
constructor(props) {
super(props);
// Maintain unchanging references to these styles, rather than putting
// object literals in render(), which would trigger unnecessary
// re-renders.
this.showStyle = { display: 'inherit' };
this.hideStyle = { display: 'none' };
}
select(props) {
// Separate state from props so the selectors can handle them.
const statelessProps = { ...omit(this.props, ['appState']) };
const selectedData = {};
Object.entries(select).forEach(([name, selector]) => {
// This is why selectors always have the args `state` and `props`.
selectedData[name] = selector(this.props.appState, statelessProps);
});
return selectedData; // to be sent as props to WrappedComponent
}
render() {
// Take out props intended only for the HOC.
const localProps = { ...omit(this.props, ['appState', 'routeProps']) };
const selectedProps = this.select(this.props);
if (selectedProps.loading === undefined) {
// Wrapped component didn't specify a loading selector.
return <WrappedComponent {...localProps} {...selectedProps} />;
}
// Wrapped component DID specify a loading selector, so take care of
// masking the UI.
// Note that we always must render the wrapped component, even if we're
// masking it, because it is responsible for initiating any potential
// load action.
return (
<div>
{selectedProps.loading && <LoadingDefault />}
<WrappedComponent
{...localProps}
{...selectedProps}
style={selectedProps.loading ? this.hideStyle : this.showStyle}
/>
</div>
);
}
};
}
export default withData;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment