Last active
March 15, 2018 22:46
-
-
Save morten-olsen/e59492abcdb441eeec7136abf05944ad to your computer and use it in GitHub Desktop.
This file contains hidden or 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
| 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