Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
@skava/state / src / observerWithObservableProps.ts
Size: Mime:
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
}