Last active
April 14, 2023 13:35
-
-
Save composite/2ec11abe33d4a6e6041e998433fe218e to your computer and use it in GitHub Desktop.
Just React hook utilities.
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
import { | |
type ComponentProps, | |
createElement, | |
type ElementType, | |
type ForwardedRef, | |
forwardRef, | |
type MutableRefObject, | |
type ReactDOM, | |
type ReactElement, | |
type Ref, | |
type RefCallback, | |
type RefObject | |
} from "react"; | |
export type ValidComponent = Parameters<typeof createElement>[0]; | |
export type AsProp<C extends ValidComponent> = { as?: C }; | |
export type RefProp<C extends ValidComponent> = { ref?: DynamicForwardedRef<C> }; | |
export type DynamicComponent<T extends ValidComponent> = T extends keyof ReactDOM ? ElementType<T> : T; | |
export type DynamicBaseProps<C extends ValidComponent> = (C extends ReactElement<infer P> ? P : C extends keyof ReactDOM ? ComponentProps<C> : Record<string, any>) | |
export type DynamicProps<C extends ValidComponent, P = DynamicBaseProps<C>> = { [K in keyof P]: P[K] } & AsProp<C> | |
export type DynamicForwardedRef<T extends ValidComponent> = ForwardedRef<DynamicComponent<T>> | |
export type DynamicRef<T extends ValidComponent> = Ref<DynamicComponent<T>> | |
export type DynamicMutableRefObject<T extends ValidComponent> = MutableRefObject<DynamicComponent<T>> | |
export type DynamicRefCallback<T extends ValidComponent> = RefCallback<DynamicComponent<T>> | |
export type DynamicRefObject<T extends ValidComponent> = RefObject<DynamicComponent<T>> | |
const Dynamic = <T extends ValidComponent>({ as, children, ...others }: DynamicProps<T>, ref: DynamicForwardedRef<T>) => createElement(as ?? 'div', { ...others, ref } as any, children) | |
/** | |
* The react dynamic component solution. similar to Vue's `<Component>` and solid's `<Dynamic>`. | |
* Usage: | |
* ```jsx | |
* <Dynamic as="input" type="text" /> | |
* <Dynamic as={YourComponent} yourProp={foo}>Your child content</Dynamic> | |
* ``` | |
* @param props Desired component's props. even children, ref included. | |
* @param props.as (required) A string tagname for plain HTML element or Component object or function for your component. | |
* @return Your desired component. | |
*/ | |
export default forwardRef(Dynamic) as <T extends ValidComponent = 'div'>( | |
props: DynamicProps<T> & RefProp<T> | |
) => ReturnType<typeof Dynamic> |
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
/** | |
* The utility for assign `ref`. | |
* Usage: | |
* ```jsx | |
* <div ref={div => assignRef(myRef, div)}>div content</div> | |
* ``` | |
* @param ref A ref callback or object. | |
* @param to A ref target for binding. | |
* @return assigned `ref` will returned. | |
*/ | |
export const assignRef = <T, R extends Ref<T>>(ref: R, to: T): R => { | |
// const isFunction = o => o instanceof Function || typeof o === 'function' | |
isFunction(ref) ? ref(to) : ref && ((ref as MutableRefObject<T>).current = to) | |
return ref | |
} | |
/** | |
* `ref` for multiple `ref`s. | |
* Usage: | |
* ```jsx | |
* <div ref={mergeRef(ref1, ref2)}>div content</div> | |
* ``` | |
* @param refs The `ref`s to binding. | |
* @return The `RefCallback` to bind. | |
*/ | |
export const mergeRef = <T,R extends Ref<T>>(...refs: R[]): RefCallback<T> => (ref: T) => refs.forEach(r => assignRef(r, ref)) |
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
/** | |
* A Ref callback for normal or lazy behavior DOM effect. | |
* Usage: (same as `useRef`) | |
* ```jsx | |
* const myEvent = () => alert('hello!') | |
* const myRef = useRefCallback( | |
* node => node.addEventListener('click', myEvent), | |
* node => node.removeEventListener('click', myEvent) | |
* ) | |
* | |
* <div ref={myRef}>div content</div> | |
* ``` | |
* @param init callback when ref bound. | |
* @param dispose callback when unbound ref. | |
* @return A `RefCallback` with `.current` for access `ref` object anytime. | |
*/ | |
export const useRefCallback = <T, R extends Ref<T>>(init: (ref: T) => void, dispose?: (ref: T) => void): RefCallback<T> & RefObject<T> => { | |
const ref = useRef<T>() | |
const callback = useCallback((node: T) => { | |
ref.current && dispose && dispose(ref.current) | |
node && init && init(node) | |
ref.current = node! | |
}, []) | |
return Object.assign(callback, { get current() { return ref.current } }) as RefCallback<T> & RefObject<T> | |
} |
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
/** | |
* Hook setter to tracked by `Promise`. | |
* Usage: | |
* ```js | |
* const [state, setState] = useState(0) | |
* const setStatePromise = useSetStatePromise(setState, [state]) | |
* | |
* const handleSubmit = async (e: Event) => { | |
* setState(1) | |
* const reponse1 = await fetch('/path/to/server', { method: 'POST', body: `state=${state}` }) // unchanged state '0' will provided. | |
* await setStatePromise(2) | |
* const reponse2 = await fetch('/path/to/server', { method: 'POST', body: `state=${state}` }) // changed state '2' will provided. | |
* } | |
* ``` | |
* @param setter The setter to hook | |
* @param deps track to getters. `[]` will not tracked. | |
* @return The void promise that wait until state changed. | |
*/ | |
export const useSetStatePromise = <T>(setter: Dispatch<SetStateAction<T>>, deps: DependencyList) => { | |
const ref = useRef<(a?: unknown) => void>() | |
useEffect(() => { | |
ref.current?.() | |
}, deps) | |
return (o: SetStateAction<T>): Promise<void> => { | |
setter(o) | |
return new Promise(ok => ref.current = ok).then(() => void (ref.current = undefined)) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment