Skip to content

Instantly share code, notes, and snippets.

@mattmccray
Last active June 6, 2021 16:25
Show Gist options
  • Save mattmccray/edf1fc92be37285e73100c3a79ed8ef7 to your computer and use it in GitHub Desktop.
Save mattmccray/edf1fc92be37285e73100c3a79ed8ef7 to your computer and use it in GitHub Desktop.
import { useEffect, useMemo, useRef } from "preact/hooks"
type PropsChangeMode = 'notify' | 'recycle' | 'smart'
interface IControllerClass<T, P> {
new(props: P): T
dispose?: () => void
propsDidChange?: (props: P) => void
}
/**
* Creates a controller, which is a class that has the optional methods: `dispose()` and `propsDidChange(newProps: T)`.
*
* @param klass Controller class to instantiate
* @param props Props to send to the controller
* @param deps Dependencies to track - Behavior when changes are detected are dictated by `recycle`
* @param changeMode if `notify`, changes to deps will call the `propsDidChange()` method on the current instance. If `recycle`, it disposes of the current instance and creates a new one. If `smart` (the default), it will look at the controller and if a `propsDidChange` method is found, mode will be notify, otherwise it will recycle.
*/
export function useController<T, P>(klass: IControllerClass<T, P>, props: P, deps: any[] = [], changeMode: PropsChangeMode = 'smart'): T {
const instance = useRef<T | null>(null)
useMemo(() => {
if (shouldRecycle(changeMode, klass)) {
disposeController(instance)
instance.current = new klass(props)
}
else {
if (!instance.current) {
instance.current = new klass(props)
}
else {
notifyController(instance, props)
}
}
return instance.current
}, deps)
useEffect(() => () => disposeController(instance), []) // Dispose on dismount
return instance.current as T
}
const disposeController = (ref: any) => ref?.current?.dispose?.()
const notifyController = (ref: any, props: any) => ref?.current?.propsDidChange?.(props)
const hasNotificationMethod = <T, P>(klass: IControllerClass<T, P>) => (
'propsDidChange' in klass.prototype ||
'propsDidChange' in klass
)
function shouldRecycle<T, P>(mode: PropsChangeMode, klass: IControllerClass<T, P>): boolean {
if (mode == 'recycle') return true
if (mode == 'notify') return false
return hasNotificationMethod(klass) ? false : true
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment