Last active
June 6, 2021 16:25
-
-
Save mattmccray/edf1fc92be37285e73100c3a79ed8ef7 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 { 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