@ryo-98/react-api-bridge
Version:
A registry for component imperative api
84 lines (83 loc) • 3.29 kB
JavaScript
import { useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef } from "react";
import { getIsMulti, useFinalContextValue, getApiDesc, getResolverOrCreateWhenMissing } from "../../core/index.js";
import { useUniqueElementRef } from "../../utils/useUniqueElementRef.js";
import { useHookId } from "../../utils/useHookId.js";
import { tryInvoke } from "../../utils/tryInvoke.js";
import { appendToMappedSet } from "../../utils/mappedSet.js";
function useRegister(apiBridge, name, init, deps, hookOptions) {
const isMulti = getIsMulti(name, apiBridge.bridgeOptions);
const contextValue = useFinalContextValue(hookOptions, apiBridge.BridgeContext);
const { apiNList } = useMemo(() => getApiDesc(name, contextValue.bridge, apiBridge.bridgeOptions), [name, contextValue]);
const apiRef = useUniqueElementRef(apiNList);
const hookId = useHookId();
const localApiRef = useRef(null);
const isOwnerRef = useRef(false);
const hasWarnedRef = useRef(false);
useImperativeHandle(isMulti ? apiRef : localApiRef, () => {
return init();
}, deps);
useLayoutEffect(() => {
if (isMulti) return;
const entry = contextValue.bridge[name];
if (!entry) return;
if (entry.ownerId === void 0 || entry.ownerId === hookId) {
entry.ownerId = hookId;
isOwnerRef.current = true;
} else {
isOwnerRef.current = false;
if (!hasWarnedRef.current) {
hasWarnedRef.current = true;
console.warn(
`[react-api-bridge] useRegister: the API "${String(name)}" is already registered by another component (only the first registered component can provide it in non-multi mode). The new registration is ignored to avoid overriding the existing API. If multiple registrations on the same field are intended, enable "isMulti: true" in the bridge options for this field.`
);
}
}
return () => {
if (isOwnerRef.current) {
if (entry.ownerId === hookId) {
entry.ownerId = void 0;
}
apiNList.current = null;
isOwnerRef.current = false;
}
};
}, []);
useLayoutEffect(() => {
if (isMulti) return;
if (isOwnerRef.current) {
apiNList.current = localApiRef.current;
}
});
useEffect(() => {
let clearFns = [];
const {
bucket
} = getResolverOrCreateWhenMissing(apiNList, contextValue, true, apiBridge.pendingResolverMap);
bucket.initialResolver?.resolve(apiNList);
bucket.waiters.forEach((resolver) => {
resolver.resolve(apiNList);
});
bucket.waiters.clear();
const callbacks = apiBridge.cacheInitCbMap.get(apiNList);
callbacks?.forEach((initInfo, hookId2) => {
if (apiBridge.initializedOnInitMap.get(apiRef)?.has(hookId2)) return;
initInfo.touchedRefs.add(apiRef);
clearFns.push(appendToMappedSet(apiBridge.initializedOnInitMap, apiRef, hookId2));
const onInit = initInfo.onInit;
if (isMulti) {
const _assertedOnInit = onInit;
clearFns.push(_assertedOnInit(apiRef, apiNList));
} else {
const _assertedOnInit = onInit;
clearFns.push(_assertedOnInit(apiRef));
}
});
return () => {
clearFns.forEach(tryInvoke);
apiBridge.initializedOnInitMap.delete(apiRef);
};
}, []);
}
export {
useRegister
};