Skip to content

Instantly share code, notes, and snippets.

@morten-olsen
Last active March 15, 2018 22:46
Show Gist options
  • Select an option

  • Save morten-olsen/e59492abcdb441eeec7136abf05944ad to your computer and use it in GitHub Desktop.

Select an option

Save morten-olsen/e59492abcdb441eeec7136abf05944ad to your computer and use it in GitHub Desktop.
import React, { Component, Fragment } from 'react';
import { compose } from 'redux';
import log from 'services/log';
/**
* @typedef {Object} Options
* @property {function(error: Error, message: string, retry: function)} handleError
* - The function for handling exceptions
* @property {string} defaultError
* - The error shown if no error message is provided for a `withError`
*/
/**
* Creates a new `withInit` decorator
* @param {Options|function(): Options} getOptions Either a function or an object
*
* @example
* const Component = ({ title, doStuff }) => (
* <div>
* {title}
* <button onClick={doStuff}>Do stuff</button>
* </div>
* );
*
* withInit((withError, withLoader, compose) =>
* compose(
* withError('could not load'),
* withLoader,
* )(async () => {
* const data = await api.fetchData();
* return {
* title: data.title,
* doStuff: () =>
* compose(
* withError('could not do stuff'),
* withLoader,
* )(async () => {
* const newData = await api.postData(this.getProps().username);
* this.setProps({
* title: newData.title,
* });
* }),
* };
* })(Component);
*
*/
const createWithInit = (getOptions) => {
/**
* The resulting decorator
* @param {function(withLoader: function, withError: function, getProps: function, setProps: function, compose: function): Object}
*/
return load => (WrappedComponent) => {
const {
LoadingComponent,
handleError,
defaultError,
} = typeof getOptions === 'function' ? getOptions() : getOptions;
class WithInit extends Component {
constructor(...props) {
super(...props);
this.withLoader = this.withLoader.bind(this);
this.withError = this.withError.bind(this);
this.updateProps = this.updateProps.bind(this);
this.getProps = this.getProps.bind(this);
this.update = this.update.bind(this);
}
state = {
loading: false,
stateProps: {},
}
componentDidMount() {
this.update();
}
async componentWillUnmount() {
const { unload } = this.stateProps;
const run = async () => {
if (unload) {
await unload({
compose,
getProps: this.getProps,
withError: this.withError,
});
}
};
await run();
}
getProps() {
return {
...this.props,
...this.stateProps,
};
}
async withLoader(fn) {
let error;
this.setState({
loading: true,
});
try {
await fn();
} catch (err) {
error = err;
}
this.setState({
loading: false,
});
throw error;
}
withError(message = defaultError) {
return async (fn) => {
try {
await fn();
} catch (err) {
if (handleError) {
handleError(err, message, () => this.withError(fn, message));
}
}
};
}
updateProps(props) {
const { stateProps } = this.stateProps;
this.setState({
stateProps: {
...stateProps,
...props,
},
});
}
async update() {
const run = async () => {
if (load) {
const stateProps = await load({
compose,
getProps: this.getProps,
updateProps: this.updateProps,
withLoader: this.withLoader,
withError: this.withError,
}) || {};
this.setState({
stateProps,
});
}
};
await run();
}
render() {
const { loading, stateProps } = this.state;
return (
<Fragment>
{ loading && LoadingComponent && <LoadingComponent />}
<WrappedComponent
loading={loading}
retry={this.update}
{...this.props}
{...stateProps}
/>
</Fragment>
);
}
}
return WithInit;
};
};
export default createWithInit({
LoadingComponent: <div>Loading</div>,
handleError: (err, message, retry) => log(message, err, retry),
defaultError: 'Ups der er sket en fejl',
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment