Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .changeset/perf-textinput-character-counter-updates.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
'@primer/react': patch
---
perf(TextInput): skip redundant character counter updates
40 changes: 35 additions & 5 deletions packages/react/src/TextInput/TextInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@ const TextInput = React.forwardRef<HTMLInputElement, TextInputProps>(
const [isOverLimit, setIsOverLimit] = useState<boolean>(false)
const [screenReaderMessage, setScreenReaderMessage] = useState<string>('')
const characterCounterRef = useRef<CharacterCounter | null>(null)
const lastCountedLengthRef = useRef<number | null>(null)
const lastCharacterCountRef = useRef<string>('')
const lastIsOverLimitRef = useRef<boolean>(false)
const lastScreenReaderMessageRef = useRef<string>('')

// this class is necessary to style FilterSearch, plz no touchy!
const wrapperClasses = clsx(className, 'TextInput-wrapper')
Expand Down Expand Up @@ -157,17 +161,33 @@ const TextInput = React.forwardRef<HTMLInputElement, TextInputProps>(
if (characterLimit) {
characterCounterRef.current = new CharacterCounter({
onCountUpdate: (count, overLimit, message) => {
setCharacterCount(message)
setIsOverLimit(overLimit)
if (message !== lastCharacterCountRef.current) {
lastCharacterCountRef.current = message
setCharacterCount(message)
}

if (overLimit !== lastIsOverLimitRef.current) {
lastIsOverLimitRef.current = overLimit
setIsOverLimit(overLimit)
}
},
onScreenReaderAnnounce: message => {
setScreenReaderMessage(message)
if (message !== lastScreenReaderMessageRef.current) {
lastScreenReaderMessageRef.current = message
setScreenReaderMessage(message)
}
},
})

lastCountedLengthRef.current = null

return () => {
characterCounterRef.current?.cleanup()
characterCounterRef.current = null
lastCountedLengthRef.current = null
lastCharacterCountRef.current = ''
lastIsOverLimitRef.current = false
lastScreenReaderMessageRef.current = ''
}
}
}, [characterLimit])
Expand All @@ -177,15 +197,25 @@ const TextInput = React.forwardRef<HTMLInputElement, TextInputProps>(
if (characterLimit && characterCounterRef.current) {
const currentValue =
value !== undefined ? String(value) : defaultValue !== undefined ? String(defaultValue) : ''
characterCounterRef.current.updateCharacterCount(currentValue.length, characterLimit)
const currentLength = currentValue.length

if (currentLength !== lastCountedLengthRef.current) {
lastCountedLengthRef.current = currentLength
characterCounterRef.current.updateCharacterCount(currentLength, characterLimit)
}
}
}, [value, defaultValue, characterLimit])

// Handle input change with character counter
const handleInputChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
if (characterLimit && characterCounterRef.current) {
characterCounterRef.current.updateCharacterCount(e.target.value.length, characterLimit)
const currentLength = e.target.value.length

if (currentLength !== lastCountedLengthRef.current) {
lastCountedLengthRef.current = currentLength
characterCounterRef.current.updateCharacterCount(currentLength, characterLimit)
}
}
onChange?.(e)
},
Expand Down
Loading