react-admit-one
Version:
An admit-one ticket for your React components
86 lines (85 loc) • 3.66 kB
JavaScript
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 };