useDebouncedDigitInput.ts
components/FeedbackSurvey/useDebouncedDigitInput.ts
83
Lines
2722
Bytes
1
Exports
2
Imports
10
Keywords
What this is
This page documents one file from the repository and includes its full source so you can read it without leaving the docs site.
Beginner explanation
This file is one piece of the larger system. Its name, directory, imports, and exports show where it fits. Start by reading the exports and related files first.
How it is used
Start from the exports list and related files. Those are the easiest clues for where this file fits into the system.
Expert explanation
Architecturally, this file intersects with ui-flow. It contains 83 lines, 2 detected imports, and 1 detected exports.
Important relationships
- components/FeedbackSurvey/FeedbackSurvey.tsx
- components/FeedbackSurvey/FeedbackSurveyView.tsx
- components/FeedbackSurvey/TranscriptSharePrompt.tsx
- components/FeedbackSurvey/submitTranscriptShare.ts
- components/FeedbackSurvey/useFeedbackSurvey.tsx
- components/FeedbackSurvey/useMemorySurvey.tsx
- components/FeedbackSurvey/usePostCompactSurvey.tsx
- components/FeedbackSurvey/useSurveyState.tsx
Detected exports
useDebouncedDigitInput
Keywords
currentdebouncerefinputvaluecallbacksrefuserefsetinputvalueisvaliddigitondigithastriggeredreflastchar
Detected imports
react../../utils/stringUtils.js
Source notes
This page embeds the full file contents. Small or leaf files are still indexed honestly instead of being over-explained.
Full source
import { useEffect, useRef } from 'react'
import { normalizeFullWidthDigits } from '../../utils/stringUtils.js'
// Delay before accepting a digit as a response, to prevent accidental
// submissions when users start messages with numbers (e.g., numbered lists).
// Short enough to feel instant for intentional presses, long enough to
// cancel when the user types more characters.
const DEFAULT_DEBOUNCE_MS = 400
/**
* Detects when the user types a single valid digit into the prompt input,
* debounces to avoid accidental submissions (e.g., "1. First item"),
* trims the digit from the input, and fires a callback.
*
* Used by survey components that accept numeric responses typed directly
* into the main prompt input.
*/
export function useDebouncedDigitInput<T extends string = string>({
inputValue,
setInputValue,
isValidDigit,
onDigit,
enabled = true,
once = false,
debounceMs = DEFAULT_DEBOUNCE_MS,
}: {
inputValue: string
setInputValue: (value: string) => void
isValidDigit: (char: string) => char is T
onDigit: (digit: T) => void
enabled?: boolean
once?: boolean
debounceMs?: number
}): void {
const initialInputValue = useRef(inputValue)
const hasTriggeredRef = useRef(false)
const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null)
// Latest-ref pattern so callers can pass inline callbacks without causing
// the effect to re-run (which would reset the debounce timer every render).
const callbacksRef = useRef({ setInputValue, isValidDigit, onDigit })
callbacksRef.current = { setInputValue, isValidDigit, onDigit }
useEffect(() => {
if (!enabled || (once && hasTriggeredRef.current)) {
return
}
if (debounceRef.current !== null) {
clearTimeout(debounceRef.current)
debounceRef.current = null
}
if (inputValue !== initialInputValue.current) {
const lastChar = normalizeFullWidthDigits(inputValue.slice(-1))
if (callbacksRef.current.isValidDigit(lastChar)) {
const trimmed = inputValue.slice(0, -1)
debounceRef.current = setTimeout(
(debounceRef, hasTriggeredRef, callbacksRef, trimmed, lastChar) => {
debounceRef.current = null
hasTriggeredRef.current = true
callbacksRef.current.setInputValue(trimmed)
callbacksRef.current.onDigit(lastChar)
},
debounceMs,
debounceRef,
hasTriggeredRef,
callbacksRef,
trimmed,
lastChar,
)
}
}
return () => {
if (debounceRef.current !== null) {
clearTimeout(debounceRef.current)
debounceRef.current = null
}
}
}, [inputValue, enabled, once, debounceMs])
}