UNPKG

@zendesk/retrace

Version:

define and capture Product Operation Traces along with computed metrics with an optional friendly React beacon API

112 lines 4.86 kB
"use strict"; /** * Copyright Zendesk, Inc. * * Use of this source code is governed under the Apache License, Version 2.0 * found at http://www.apache.org/licenses/LICENSE-2.0. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.useTimingMeasurement = void 0; const react_1 = require("react"); const ErrorBoundary_1 = require("../ErrorBoundary"); const constants_1 = require("./constants"); const performanceMark_1 = require("./performanceMark"); /** * @description Internal hook that handles measuring React's lifecycle. * * What happens inside of the hook can seem a bit odd and non-standard React coding. * This is explained by what we're trying to do - measure the timing of a React lifecycle. * For instance, the state is kept in refs, which normally would not be a thing you want to do. * However in this case, we want to preserve a single instance of state in the component that renders this hook, * and refs are a great way to do this. * This is designed so that this hook itself doesn't change any of it's state, * and should never itself cause any additional re-renders. * We also do not want to persist certain state across renders. * ActionLog handles the long-lifecycle of the timed interaction. */ const useTimingMeasurement = ({ id, reportFn, isActive = true, shouldResetOnDependencyChangeFn, stage, actionLog, placement, onInternalError, error, onActionAddedCallback, }, restartWhenChanged) => { const timestampMark = (0, performanceMark_1.performanceMark)(`${id}/${placement}/render-start`); const lastStartTimeRef = (0, react_1.useRef)(timestampMark); lastStartTimeRef.current = timestampMark; actionLog.updateOptions({ id, reportFn, shouldResetOnDependencyChangeFn, onInternalError, onActionAddedCallback, }, placement); // this will fire when external deps have changed: // Note: we cannot use useEffect has we need this code to run during the render // and especially before we call actionLogRef.current.setActive. const lastExternalDeps = (0, react_1.useRef)(restartWhenChanged); const externalDepsHaveChanged = restartWhenChanged.some((value, i) => value !== lastExternalDeps.current[i]); if (externalDepsHaveChanged) { lastExternalDeps.current = restartWhenChanged; actionLog.onExternalDependenciesChange(restartWhenChanged, lastStartTimeRef.current, placement); } actionLog.setActive(isActive && stage !== constants_1.DEFAULT_STAGES.INACTIVE, placement); if (error) { if (actionLog.reportedErrors.has(error)) { // same error was previously reported, no need to re-report actionLog.disableReporting(); } else if (typeof error === 'object') { actionLog.reportedErrors.add(error); } actionLog.markStage({ stage: constants_1.DEFAULT_STAGES.ERROR, source: placement, metadata: { error, handled: true, }, renderEntry: timestampMark, }); } else if (stage) { actionLog.markStage({ stage, source: placement, renderEntry: timestampMark, }); } // this will fire after every render: (0, react_1.useEffect)(() => { if (!placement) { actionLog.onInternalError(new Error(`useTiming: '${id}' has a usage that does not define the 'placement' name. Please ensure every usage names its placement.`)); } actionLog.addSpan({ type: constants_1.ACTION_TYPE.RENDER, entry: Object.assign((0, performanceMark_1.performanceMeasure)(`${id}/${placement}/render`, lastStartTimeRef.current), { startMark: lastStartTimeRef.current }), source: placement, }); }); (0, react_1.useEffect)(() => () => { // last unmount, time to clean-up: actionLog.onBeaconRemoved(placement); }, [placement, actionLog]); (0, ErrorBoundary_1.useOnErrorBoundaryDidCatch)((errorMetadata) => { if (actionLog.reportedErrors.has(errorMetadata.error)) { // same error was previously reported, no need to re-report actionLog.disableReporting(); } else { try { actionLog.reportedErrors.add(errorMetadata.error); } catch { // we do our best to mark the error, but it's okay to ignore if it's frozen } } actionLog.markStage({ stage: constants_1.DEFAULT_STAGES.ERROR_BOUNDARY, source: placement, metadata: { ...errorMetadata, handled: true, }, }); }); }; exports.useTimingMeasurement = useTimingMeasurement; //# sourceMappingURL=useTimingMeasurement.js.map