44 lines
1.7 KiB
JavaScript
44 lines
1.7 KiB
JavaScript
'use client';
|
|
|
|
import { SafeReact } from "./safeReact.mjs";
|
|
import { useRefWithInit } from "./useRefWithInit.mjs";
|
|
const useInsertionEffect = SafeReact.useInsertionEffect;
|
|
const useSafeInsertionEffect =
|
|
// React 17 doesn't have useInsertionEffect.
|
|
useInsertionEffect &&
|
|
// Preact replaces useInsertionEffect with useLayoutEffect and fires too late.
|
|
useInsertionEffect !== SafeReact.useLayoutEffect ? useInsertionEffect : fn => fn();
|
|
/**
|
|
* Stabilizes the function passed so it's always the same between renders.
|
|
*
|
|
* The function becomes non-reactive to any values it captures.
|
|
* It can safely be passed as a dependency of `React.useMemo` and `React.useEffect` without re-triggering them if its captured values change.
|
|
*
|
|
* The function must only be called inside effects and event handlers, never during render (which throws an error).
|
|
*
|
|
* This hook is a more permissive version of React 19.2's `React.useEffectEvent` in that it can be passed through contexts and called in event handler props, not just effects.
|
|
*/
|
|
export function useStableCallback(callback) {
|
|
const stable = useRefWithInit(createStableCallback).current;
|
|
stable.next = callback;
|
|
useSafeInsertionEffect(stable.effect);
|
|
return stable.trampoline;
|
|
}
|
|
function createStableCallback() {
|
|
const stable = {
|
|
next: undefined,
|
|
callback: assertNotCalled,
|
|
trampoline: (...args) => stable.callback?.(...args),
|
|
effect: () => {
|
|
stable.callback = stable.next;
|
|
}
|
|
};
|
|
return stable;
|
|
}
|
|
function assertNotCalled() {
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
// TODO: fix mui/no-guarded-throw
|
|
// eslint-disable-next-line mui/no-guarded-throw
|
|
throw /* minify-error-disabled */new Error('Base UI: Cannot call an event handler while rendering.');
|
|
}
|
|
} |