Repository URL to install this package:
|
Version:
3.0.2 ▾
|
import { isFunction } from 'exotic'
import { autorun, computed } from 'xmobx/mobx'
import { observer } from 'xmobx/mobx-react'
import { ComponentClass } from 'react'
export type ComponentWithUpdateState<PropTypes> = ComponentClass<PropTypes> & {
/**
* @todo is required but decorator is not nice with this
*/
updateState?: () => void
}
// import { toComponentName } from '@skava/identifier'
export const toComponentName = (Target: ComponentClass) =>
Target.displayName || Target.constructor.name
export const updatingRegistry = new WeakSet()
/**
* @IDEA: can I get properties that were passed into that type as generic out? props
* @alias updateForProps
*/
export function observerWithObservableProps<PropTypes>(
Target: ComponentWithUpdateState<PropTypes>
): ComponentWithUpdateState<PropTypes> {
@observer
class Decorated extends Target {
static defaultProps = Target.defaultProps
static displayName =
'ObservableProps(' + toComponentName(Target as ComponentClass<any>) + ')'
disposer?: () => void
/**
* @todo @@perf can probably do this in `componentDidUpdate` as well (removed `isMounting` since it was not updating 1st call)
* @see https://mobx.js.org/refguide/autorun.html
* @note without `@computed` it returned 2x as often
* @note without `updatingRegistry` it ran 3x as often
*/
componentDidMount() {
/**
* @description this destructures props, triggering them as being accessed
*/
const observableProps = computed(() => ({ ...this.props }))
// so we do not update state on first mount
let isMounting = true
this.disposer = autorun(() => {
const props = observableProps.get()
if (updatingRegistry.has(props)) {
// console.info('[updateForProps]{autorun} already had')
} else {
// console.info('[updateForProps]{autorun} updating')
updatingRegistry.add(props)
/**
* @note usually for perf we would use else here
* but it is already too deep
*
* @todo split this out cleanly, currently depends on isMounting
* for skipping first mount
*
* @todo enable this, currently commented out to avoid pre-optimization
*/
// if (isMounting) {
// // console.info('[updateForProps]{autorun} skipped because mounting')
// return
// }
const inherited = this as any
inherited.updateState(props)
}
})
isMounting = false
if (isFunction(super.componentDidMount)) {
super.componentDidMount()
}
}
componentWillUnmount() {
if (isFunction(this.disposer)) {
this.disposer()
}
if (isFunction(super.componentWillUnmount)) {
super.componentWillUnmount()
}
}
}
return Decorated
}