UNPKG

react-admit-one

Version:

An admit-one ticket for your React components

86 lines (85 loc) 3.66 kB
import React, { useContext, useEffect, useRef } from 'react'; import captureComponentStack from './utils/captureComponentStack'; import getErrorMessage from './utils/getErrorMessage'; import getDisplayName from './utils/getDisplayName'; import useLazyValue from './utils/useLazyValue'; import { AdmitOneBoundaryContext } from './AdmitOneBoundary'; var GLOBAL_INSTANCE_TRACKER = new Map(); var noop = function () { }; var defaultOptions = { onMount: noop, onUnmount: noop, onRestrictedMount: noop, persistTrace: false, ignoreBoundary: false, }; function admitOne(WrappedComponent, options) { var opts = Object.assign({}, defaultOptions, options); function WithAdmitOne(props, ref) { var boundary = useContext(AdmitOneBoundaryContext); var mountedInstancesMap = GLOBAL_INSTANCE_TRACKER; var withinBoundary = false; if (boundary && !opts.ignoreBoundary) { mountedInstancesMap = boundary; withinBoundary = true; } /** * Holds reference to the element at the current render. This is used to * prevent effects from firing on every re-render when the element is used * as a dependency */ var elementRef = useRef(); elementRef.current = React.createElement(WrappedComponent, Object.assign({}, props, { ref: ref })); var renderAllowed = useLazyValue(function () { var element = elementRef.current; var mountedInstance = mountedInstancesMap.get(WrappedComponent); if (mountedInstance) { if (process.env.NODE_ENV === 'development') { var errorMessage = getErrorMessage(element, mountedInstance, withinBoundary); console.error(errorMessage); } return false; } mountedInstance = { element: element, }; if (process.env.NODE_ENV === 'development') { // Capturing stacktrace at mount time (as opposed to when needed) // because the stack may change after the first render, and this stack // will be needed to know exactly how the component was first rendered mountedInstance.stackTrace = captureComponentStack(element); } mountedInstancesMap.set(WrappedComponent, mountedInstance); return true; }); useEffect(function () { var mountedElement = elementRef.current; if (renderAllowed) { opts.onMount(mountedElement); } else { opts.onRestrictedMount(mountedElement); } // Since the component wasn't allowed to render, there is nothing to // clean up or do when the HOC unmounts if (!renderAllowed) { return; } return function () { opts.onUnmount(elementRef.current); var mountedInstance = mountedInstancesMap.get(WrappedComponent); delete mountedInstance.element; if (!opts.persistTrace) { mountedInstancesMap.delete(WrappedComponent); } }; }, []); // eslint-disable-line react-hooks/exhaustive-deps return renderAllowed ? elementRef.current : null; } if (process.env.NODE_ENV === 'development') { var wrappedDisplayName = getDisplayName(WrappedComponent); WithAdmitOne.displayName = "AdmitOne(" + wrappedDisplayName + ")"; } return React.forwardRef(WithAdmitOne); } export { admitOne };