use-debounce#useThrottledCallback TypeScript Examples
The following examples show how to use
use-debounce#useThrottledCallback.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: FunnelBarGraph.tsx From posthog-foss with MIT License | 5 votes |
function AverageTimeInspector({
onClick,
disabled,
averageTime,
aggregationTargetLabel,
}: AverageTimeInspectorProps): JSX.Element {
// Inspector button which automatically shows/hides the info text.
const wrapperRef = useRef<HTMLDivElement | null>(null)
const infoTextRef = useRef<HTMLDivElement | null>(null)
const buttonRef = useRef<HTMLDivElement | null>(null)
const [infoTextVisible, setInfoTextVisible] = useState(true)
function decideTextVisible(): void {
// Show/hide label position based on whether both items fit horizontally
const wrapperWidth = wrapperRef.current?.clientWidth ?? null
const infoTextWidth = infoTextRef.current?.offsetWidth ?? null
const buttonWidth = buttonRef.current?.offsetWidth ?? null
if (wrapperWidth !== null && infoTextWidth !== null && buttonWidth !== null) {
if (infoTextWidth + buttonWidth <= wrapperWidth) {
setInfoTextVisible(true)
return
}
}
setInfoTextVisible(false)
}
useEffect(() => {
decideTextVisible()
}, [])
useResizeObserver({
onResize: useThrottledCallback(decideTextVisible, 200),
ref: wrapperRef,
})
return (
<div ref={wrapperRef}>
<span
ref={infoTextRef}
className="text-muted-alt"
style={{ paddingRight: 4, display: 'inline-block', visibility: infoTextVisible ? undefined : 'hidden' }}
>
Average time:
</span>
<ValueInspectorButton
innerRef={buttonRef}
style={{ paddingLeft: 0, paddingRight: 0 }}
onClick={onClick}
disabled={disabled}
title={`Average of time elapsed for each ${aggregationTargetLabel.singular} between completing this step and starting the next one.`}
>
{humanFriendlyDuration(averageTime, 2)}
</ValueInspectorButton>
</div>
)
}
Example #2
Source File: PropertyValue.tsx From posthog-foss with MIT License | 4 votes |
export function PropertyValue({
propertyKey,
type,
endpoint = undefined,
placeholder = undefined,
style = {},
bordered = true,
onSet,
value,
operator,
outerOptions = undefined,
autoFocus = false,
allowCustom = true,
}: PropertyValueProps): JSX.Element {
const isMultiSelect = operator && isOperatorMulti(operator)
const [input, setInput] = useState(isMultiSelect ? '' : toString(value))
const [shouldBlur, setShouldBlur] = useState(false)
const [options, setOptions] = useState({} as Record<string, Option>)
const autoCompleteRef = useRef<HTMLElement>(null)
const { formatForDisplay } = useValues(propertyDefinitionsModel)
// update the input field if passed a new `value` prop
useEffect(() => {
if (!value) {
setInput('')
} else if (value !== input) {
const valueObject = options[propertyKey]?.values?.find((v) => v.id === value)
if (valueObject) {
setInput(toString(valueObject.name))
}
}
}, [value])
const loadPropertyValues = useThrottledCallback((newInput) => {
if (type === 'cohort') {
return
}
const key = propertyKey.split('__')[0]
setOptions({ ...options, [propertyKey]: { ...options[propertyKey], status: 'loading' } })
if (outerOptions) {
setOptions({
...options,
[propertyKey]: {
values: [...Array.from(new Set(outerOptions))],
status: 'loaded',
},
})
} else {
api.get(endpoint || 'api/' + type + '/values/?key=' + key + (newInput ? '&value=' + newInput : '')).then(
(propValues: PropValue[]) => {
setOptions({
...options,
[propertyKey]: {
values: [...Array.from(new Set(propValues))],
status: 'loaded',
},
})
}
)
}
}, 300)
function setValue(newValue: PropertyValueProps['value']): void {
onSet(newValue)
if (isMultiSelect) {
setInput('')
}
}
useEffect(() => {
loadPropertyValues('')
}, [propertyKey])
useEffect(() => {
if (input === '' && shouldBlur) {
;(document.activeElement as HTMLElement)?.blur()
setShouldBlur(false)
}
}, [input, shouldBlur])
const displayOptions = (options[propertyKey]?.values || []).filter(
(option) => input === '' || matchesLowerCase(input, toString(option?.name))
)
const validationError = operator ? getValidationError(operator, value) : null
const commonInputProps = {
style: { width: '100%', ...style },
onSearch: (newInput: string) => {
setInput(newInput)
if (!Object.keys(options).includes(newInput) && !(operator && isOperatorFlag(operator))) {
loadPropertyValues(newInput)
}
},
['data-attr']: 'prop-val',
dropdownMatchSelectWidth: 350,
bordered,
placeholder,
allowClear: Boolean(value),
onKeyDown: (e: React.KeyboardEvent) => {
if (e.key === 'Escape') {
setInput('')
setShouldBlur(true)
return
}
if (!isMultiSelect && e.key === 'Enter') {
// We have not explicitly selected a dropdown item by pressing the up/down keys; or the ref is unavailable
if (
!autoCompleteRef.current ||
autoCompleteRef.current?.querySelectorAll?.('.ant-select-item-option-active')?.length === 0
) {
setValue(input)
}
}
},
handleBlur: () => {
if (input != '') {
if (Array.isArray(value) && !value.includes(input)) {
setValue([...value, ...[input]])
} else if (!Array.isArray(value)) {
setValue(input)
}
setInput('')
}
},
}
const dayJSMightParse = (
candidateDateTimeValue: string | number | (string | number)[] | null | undefined
): candidateDateTimeValue is string | number | undefined =>
['string', 'number'].includes(typeof candidateDateTimeValue)
return (
<>
{isMultiSelect ? (
<SelectGradientOverflow
loading={options[propertyKey]?.status === 'loading'}
propertyKey={propertyKey}
{...commonInputProps}
autoFocus={autoFocus}
value={value === null ? [] : value}
mode="multiple"
showSearch
onChange={(val, payload) => {
if (Array.isArray(payload) && payload.length > 0) {
setValue(val)
} else if (payload instanceof Option) {
setValue(payload?.value ?? [])
} else {
setValue([])
}
}}
>
{input && !displayOptions.some(({ name }) => input.toLowerCase() === toString(name).toLowerCase()) && (
<Select.Option key="specify-value" value={input} className="ph-no-capture">
Specify: {formatForDisplay(propertyKey, input)}
</Select.Option>
)}
{displayOptions.map(({ name: _name }, index) => {
const name = toString(_name)
return (
<Select.Option
key={name}
value={name}
data-attr={'prop-val-' + index}
className="ph-no-capture"
title={name}
>
{name === '' ? <i>(empty string)</i> : formatForDisplay(propertyKey, name)}
</Select.Option>
)
})}
</SelectGradientOverflow>
) : operator && isOperatorDate(operator) ? (
<>
<DatePicker
{...commonInputProps}
inputReadOnly={true}
className={'filter-date-picker'}
dropdownClassName={'filter-date-picker-dropdown'}
format="YYYY-MM-DD HH:mm:ss"
showTime={true}
showNow={false}
value={dayJSMightParse(value) ? dayjs(value) : null}
onOk={(selectedDate) => {
setValue(selectedDate.format('YYYY-MM-DD HH:MM:ss'))
}}
getPopupContainer={(trigger: Element | null) => {
const container = trigger?.parentElement?.parentElement?.parentElement
return container ?? document.body
}}
/>
</>
) : (
<AutoComplete
{...commonInputProps}
autoFocus={autoFocus}
value={input}
onClear={() => {
setInput('')
setValue('')
}}
onChange={(val) => {
setInput(toString(val))
}}
onSelect={(val, option) => {
setInput(option.title)
setValue(toString(val))
}}
onKeyDown={(e) => {
if (e.key === 'Enter') {
setInput(toString(input))
setValue(toString(input))
}
}}
ref={autoCompleteRef}
>
{[
...(input && allowCustom && !displayOptions.some(({ name }) => input === toString(name))
? [
<AutoComplete.Option key="@@@specify-value" value={input} className="ph-no-capture">
Specify: {input}
</AutoComplete.Option>,
]
: []),
...displayOptions.map(({ name: _name, id }, index) => {
const name = toString(_name)
return (
<AutoComplete.Option
key={id ? toString(id) : name}
value={id ? toString(id) : name}
data-attr={'prop-val-' + index}
className="ph-no-capture"
title={name}
>
{name}
</AutoComplete.Option>
)
}),
]}
</AutoComplete>
)}
{validationError && <p className="text-danger">{validationError}</p>}
</>
)
}
Example #3
Source File: FunnelBarGraph.tsx From posthog-foss with MIT License | 4 votes |
function Bar({
percentage,
name,
onBarClick,
disabled,
isBreakdown = false,
breakdownIndex,
breakdownMaxIndex,
breakdownSumPercentage,
popoverTitle = null,
popoverMetrics = [],
aggregationTargetLabel,
}: BarProps): JSX.Element {
const barRef = useRef<HTMLDivElement | null>(null)
const labelRef = useRef<HTMLDivElement | null>(null)
const [labelPosition, setLabelPosition] = useState<LabelPosition>('inside')
const [labelVisible, setLabelVisible] = useState(true)
const LABEL_POSITION_OFFSET = 8 // Defined here and in SCSS
const { insightProps } = useValues(insightLogic)
const { clickhouseFeaturesEnabled } = useValues(funnelLogic(insightProps))
const cursorType = clickhouseFeaturesEnabled && !disabled ? 'pointer' : ''
const hasBreakdownSum = isBreakdown && typeof breakdownSumPercentage === 'number'
const shouldShowLabel = !isBreakdown || (hasBreakdownSum && labelVisible)
function decideLabelPosition(): void {
if (hasBreakdownSum) {
// Label is always outside for breakdowns, but don't show if it doesn't fit in the wrapper
setLabelPosition('outside')
const barWidth = barRef.current?.clientWidth ?? null
const barOffset = barRef.current?.offsetLeft ?? null
const wrapperWidth = barRef.current?.parentElement?.clientWidth ?? null
const labelWidth = labelRef.current?.clientWidth ?? null
if (barWidth !== null && barOffset !== null && wrapperWidth !== null && labelWidth !== null) {
if (wrapperWidth - (barWidth + barOffset) < labelWidth + LABEL_POSITION_OFFSET * 2) {
setLabelVisible(false)
} else {
setLabelVisible(true)
}
}
return
}
// Place label inside or outside bar, based on whether it fits
const barWidth = barRef.current?.clientWidth ?? null
const labelWidth = labelRef.current?.clientWidth ?? null
if (barWidth !== null && labelWidth !== null) {
if (labelWidth + LABEL_POSITION_OFFSET * 2 > barWidth) {
setLabelPosition('outside')
return
}
}
setLabelPosition('inside')
}
useResizeObserver({
onResize: useThrottledCallback(decideLabelPosition, 200),
ref: barRef,
})
return (
<Popover
trigger="hover"
placement="right"
content={
<LEGACY_InsightTooltip altTitle={popoverTitle}>
{popoverMetrics.map(({ title, value, visible }, index) =>
visible !== false ? <MetricRow key={index} title={title} value={value} /> : null
)}
</LEGACY_InsightTooltip>
}
>
<div
ref={barRef}
className={`funnel-bar ${getSeriesPositionName(breakdownIndex, breakdownMaxIndex)}`}
style={{
flex: `${percentage} 1 0`,
cursor: cursorType,
backgroundColor: getSeriesColor(breakdownIndex),
}}
onClick={() => {
if (clickhouseFeaturesEnabled && !disabled && onBarClick) {
onBarClick()
}
}}
>
{shouldShowLabel && (
<div
ref={labelRef}
className={`funnel-bar-percentage ${labelPosition}`}
title={
name ? `${capitalizeFirstLetter(aggregationTargetLabel.plural)} who did ${name}` : undefined
}
role="progressbar"
aria-valuemin={0}
aria-valuemax={100}
aria-valuenow={(breakdownSumPercentage ?? percentage) * 100}
>
{formatDisplayPercentage(breakdownSumPercentage ?? percentage)}%
</div>
)}
</div>
</Popover>
)
}