|
| 1 | +import { useCallback, useRef } from 'react'; |
| 2 | + |
| 3 | +export type StableCallback<A extends unknown[], R> = (...args: A) => R; |
| 4 | + |
| 5 | +/** |
| 6 | + * A utility hook implementing a stable callback. It takes in an unstable method that |
| 7 | + * is supposed to be invoked somewhere deeper in the DOM tree without making it |
| 8 | + * change its reference every time the parent component rerenders. It will also return |
| 9 | + * the value of the callback if it does return one. |
| 10 | + * A common use-case would be having a function whose invocation depends on state |
| 11 | + * somewhere high up in the DOM tree and wanting to use the same function deeper |
| 12 | + * down, for example in a leaf node and simply using useCallback results in |
| 13 | + * cascading dependency hell. If we wrap it in useStableCallback, we would be able |
| 14 | + * to: |
| 15 | + * - Use the same function as a dependency of another hook (since it is stable) |
| 16 | + * - Still invoke it and get the latest state |
| 17 | + * |
| 18 | + * **Caveats:** |
| 19 | + * - Never wrap a function that is supposed to return a React.ReactElement in |
| 20 | + * useStableCallback, since React will not know that the DOM needs to be updated |
| 21 | + * whenever the callback value changes (for example, renderItem from FlatList must |
| 22 | + * never be wrapped in this hook) |
| 23 | + * - Always prefer using a standard useCallback/stable function wherever possible |
| 24 | + * (the purpose of useStableCallback is to bridge the gap between top level contexts |
| 25 | + * and cascading rereders in downstream components - **not** as an escape hatch) |
| 26 | + * @param callback - the callback we want to stabilize |
| 27 | + */ |
| 28 | +export const useStableCallback = <A extends unknown[], R>( |
| 29 | + callback: StableCallback<A, R>, |
| 30 | +): StableCallback<A, R> => { |
| 31 | + const ref = useRef(callback); |
| 32 | + ref.current = callback; |
| 33 | + |
| 34 | + return useCallback<StableCallback<A, R>>((...args) => ref.current(...args), []); |
| 35 | +}; |
0 commit comments