Created
September 14, 2018 03:33
-
-
Save killtheliterate/fa5d9c37d69b64c8a51073c6bf8441d1 to your computer and use it in GitHub Desktop.
This file contains 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
type Omit<T, K> = Pick<T, Exclude<keyof T, K>> | |
type Subtract<T, K> = Omit<T, keyof K> | |
export type EffOptions = { | |
ErrorComponent?: React.ComponentType<any> | |
LoadingComponent?: React.ComponentType<any> | |
log?: (...args: any[]) => void | |
} | |
export type EffProps = { | |
effect: () => Promise<any> | |
shouldEffectKey: string | |
} | |
export type EffState = { | |
isErred: boolean | |
isResolved: boolean | |
prevKey?: string | |
effResult?: any | |
} | |
export type EffInjectedProps = { | |
effResult?: any // We can't know | |
} | |
export function withEffect ({ ErrorComponent, LoadingComponent, log }: EffOptions = {}) { | |
return function <P> (CC: React.ComponentType<P & EffInjectedProps>) { | |
const IsEff: React.SFC<Partial<P>> = (props) => LoadingComponent | |
? (<LoadingComponent {...props} />) | |
: (<Loader />) | |
const IsErr: React.SFC<Partial<P>> = (props) => ErrorComponent | |
? (<ErrorComponent {...props} />) | |
: (<ErrorMessage />) | |
return class Eff extends React.Component<Subtract<P, EffInjectedProps> & EffProps, EffState> { | |
static displayName = `withEffect(${getDisplayName(CC)})` | |
// @see: https://github.com/Microsoft/TypeScript/pull/13288 | |
// @see: https://github.com/Microsoft/TypeScript/issues/10727 | |
// | |
// Using the {a, b, ...restProps} form below doesn't enforce that props | |
// are passed unless the component is called as <jsx />. Since this will | |
// often be composed with things like `connect`, that's no good. These | |
// seems "good enough" until the aforementioned PR lands. | |
static propTypes = { | |
effect: PropTypes.func.isRequired, | |
shouldEffectKey: PropTypes.string.isRequired | |
} | |
_resolvingKey: string | void = undefined | |
state = { | |
effResult: undefined, | |
isErred: false, | |
isResolved: false, | |
prevKey: undefined | |
} | |
constructor (props: P & EffProps) { | |
super(props) | |
this._effect = this._effect.bind(this) | |
} | |
static getDerivedStateFromProps (nextProps: P & EffProps, prevState: EffState) { | |
if (nextProps.shouldEffectKey !== prevState.prevKey) { | |
return { | |
isErred: false, | |
isResolved: false, | |
prevKey: nextProps.shouldEffectKey | |
} | |
} | |
return null | |
} | |
componentDidMount () { | |
this._effect(this.props.shouldEffectKey) | |
} | |
componentWillUnmount () { | |
this._resolvingKey = undefined | |
} | |
componentDidUpdate () { | |
if (this.state.isResolved === false) { | |
this._effect(this.props.shouldEffectKey) | |
} | |
} | |
render () { | |
const { effect, shouldEffectKey, ...restProps } = this.props as EffProps | |
if (this.state.isErred) { | |
return <IsErr {...restProps} /> | |
} | |
if (!this.state.isResolved) { | |
return <IsEff {...restProps} /> | |
} | |
return <CC { ...restProps } effResult={this.state.effResult} /> | |
} | |
// --------------------------------------------------------------------- | |
_effect (key: EffProps['shouldEffectKey']) { | |
if (key === this._resolvingKey) { | |
return // already resolving | |
} | |
this._resolvingKey = key | |
this.props.effect() | |
.then( | |
(result) => { | |
if (key === this._resolvingKey) { | |
this.setState(() => ({ | |
effResult: result, | |
isResolved: true | |
})) | |
} | |
}, | |
(err: Error) => { | |
if (key === this._resolvingKey) { | |
if (log) { | |
log(err) | |
} | |
this.setState(() => ({ isErred: true, isResolved: true })) | |
} | |
} | |
) | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment