diff --git a/striker-ui/hooks/useProtect.ts b/striker-ui/hooks/useProtect.ts new file mode 100644 index 00000000..660780c0 --- /dev/null +++ b/striker-ui/hooks/useProtect.ts @@ -0,0 +1,30 @@ +import { useEffect, useRef } from 'react'; + +// Allow any function as callback in the protect function. +// Could be used to wrap async callbacks to prevent them from running after +// component unmount. +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type AnyFunction = (...args: any[]) => any; + +type ProtectFunction = ( + fn: F, + ...args: Parameters +) => ReturnType; + +const useProtect = (): { protect: ProtectFunction } => { + const isComponentMountedRef = useRef(true); + + useEffect( + () => () => { + isComponentMountedRef.current = false; + }, + [], + ); + + return { + protect: (fn, ...args) => + isComponentMountedRef.current ? fn(...args) : undefined, + }; +}; + +export default useProtect; diff --git a/striker-ui/hooks/useProtectedState.ts b/striker-ui/hooks/useProtectedState.ts new file mode 100644 index 00000000..019fc9c0 --- /dev/null +++ b/striker-ui/hooks/useProtectedState.ts @@ -0,0 +1,25 @@ +import { Dispatch, SetStateAction, useState } from 'react'; + +type SetStateFunction = Dispatch>; + +type SetStateParameters = Parameters>; + +type SetStateReturnType = ReturnType>; + +const useProtectedState = ( + initialState: S | (() => S), + protect: ( + fn: SetStateFunction, + ...args: SetStateParameters + ) => SetStateReturnType, +): [S, SetStateFunction] => { + const [state, setState] = useState(initialState); + + return [ + state, + (...args: SetStateParameters): SetStateReturnType => + protect(setState, ...args), + ]; +}; + +export default useProtectedState;