@zendesk/react-measure-timing-hooks
Version:
react hooks for measuring time to interactive and time to render of components
103 lines (90 loc) • 3.16 kB
text/typescript
import { useEffect, useRef } from 'react'
import { useOnComponentUnmount } from '../ErrorBoundary'
import { ensureTimestamp } from './ensureTimestamp'
import type { BeaconConfig, UseBeacon } from './hooksTypes'
import type { ComponentRenderSpan } from './spanTypes'
import type { TraceManager } from './TraceManager'
import type { RelationSchemasBase, RelationsOnASpan, Timestamp } from './types'
type MakeEntryInput<RelationSchemasT> = Omit<
ComponentRenderSpan<RelationSchemasT>,
'startTime'
> & {
startTime?: Timestamp
}
const makeEntry = <RelationSchemasT>(
inp: MakeEntryInput<RelationSchemasT>,
): ComponentRenderSpan<RelationSchemasT> => ({
...inp,
startTime: ensureTimestamp(inp.startTime),
})
/**
* The job of the beacon:
* emit component-render-start, component-render, component-unmount entries
*/
export const generateUseBeacon =
<
RelationSchemasT extends RelationSchemasBase<RelationSchemasT>,
RequiredAttributesT = {},
>(
traceManager: TraceManager<RelationSchemasT>,
): UseBeacon<RelationSchemasT, RequiredAttributesT> =>
(config: BeaconConfig<RelationSchemasT, RequiredAttributesT>) => {
const renderCountRef = useRef(0)
renderCountRef.current += 1
const { attributes, renderedOutput } = config
const status = config.error ? 'error' : 'ok'
const isIdle =
config.isIdle ??
(renderedOutput === 'content' || renderedOutput === 'error')
const relatedTo =
config.relatedTo as unknown as RelationsOnASpan<RelationSchemasT>
const renderStartTaskEntry = makeEntry<RelationSchemasT>({
...config,
relatedTo,
type: 'component-render-start',
duration: 0,
attributes,
status,
renderCount: renderCountRef.current,
isIdle,
})
traceManager.processSpan(renderStartTaskEntry)
const renderStartRef = useRef<Timestamp | undefined>()
renderStartRef.current = renderStartTaskEntry.startTime
// Beacon effect for tracking 'component-render'. This will fire after every render as it does not have any dependencies:
useEffect(() => {
traceManager.processSpan(
makeEntry<RelationSchemasT>({
...config,
relatedTo,
startTime: renderStartRef.current!,
type: 'component-render',
duration: performance.now() - renderStartRef.current!.now,
status,
attributes,
renderCount: renderCountRef.current,
isIdle,
}),
)
renderStartRef.current = undefined
})
// Beacon effect for tracking 'component-unmount' entries
useOnComponentUnmount(
(errorBoundaryMetadata) => {
const unmountEntry = makeEntry<RelationSchemasT>({
...config,
relatedTo,
type: 'component-unmount',
attributes,
error: errorBoundaryMetadata?.error,
errorInfo: errorBoundaryMetadata?.errorInfo,
duration: 0,
status: errorBoundaryMetadata?.error ? 'error' : 'ok',
renderCount: renderCountRef.current,
isIdle,
})
traceManager.processSpan(unmountEntry)
},
[config.name],
)
}