Refactor ElipsisText Component for Improved State Management

- Replaced refs with state variables for container and measure elements to enhance reactivity and maintainability.
- Updated layout effects to utilize new state variables, ensuring accurate measurement and truncation logic.
- Improved cleanup logic in useLayoutEffect to prevent memory leaks and ensure proper event handling for font loading and observers.
This commit is contained in:
Tom Butcher 2026-09-03 11:27:30 +01:00
parent cb49baaf93
commit 59f603d05d

View File

@ -31,42 +31,60 @@ const ElipsisText = ({ children, style, className, title, code, ...rest }) => {
const text = childrenToString(children) const text = childrenToString(children)
const { start, end } = splitMiddle(text) const { start, end } = splitMiddle(text)
const containerRef = useRef(null) const [containerEl, setContainerEl] = useState(null)
const measureRef = useRef(null) const [measureEl, setMeasureEl] = useState(null)
const truncatedRef = useRef(false) const truncatedRef = useRef(false)
const [truncated, setTruncated] = useState(false) const [truncated, setTruncated] = useState(false)
useLayoutEffect(() => { useLayoutEffect(() => {
const container = containerRef.current if (!containerEl || !measureEl) return
const measure = measureRef.current
if (!container || !measure) return let raf1 = 0
let raf2 = 0
let cancelled = false
const update = () => { const update = () => {
const available = container.getBoundingClientRect().width if (cancelled) return
const available = containerEl.getBoundingClientRect().width
if (available < 1) return if (available < 1) return
const textWidth = measure.getBoundingClientRect().width const textWidth = measureEl.getBoundingClientRect().width
const next = Boolean(end) && textWidth > available const next = Boolean(end) && textWidth > available
if (truncatedRef.current === next) return if (truncatedRef.current === next) return
truncatedRef.current = next truncatedRef.current = next
setTruncated(next) setTruncated(next)
} }
update() update()
const frame = requestAnimationFrame(update) raf1 = requestAnimationFrame(() => {
update()
raf2 = requestAnimationFrame(update)
})
const resizeObserver = new ResizeObserver(update) const resizeObserver = new ResizeObserver(update)
resizeObserver.observe(container) resizeObserver.observe(containerEl)
const intersectionObserver = new IntersectionObserver(update) const intersectionObserver = new IntersectionObserver(update)
intersectionObserver.observe(container) intersectionObserver.observe(containerEl)
const onFontsReady = () => {
if (!cancelled) update()
}
const fonts = typeof document !== 'undefined' ? document.fonts : null
fonts?.ready?.then(onFontsReady)
fonts?.addEventListener?.('loadingdone', onFontsReady)
return () => { return () => {
cancelAnimationFrame(frame) cancelled = true
cancelAnimationFrame(raf1)
cancelAnimationFrame(raf2)
resizeObserver.disconnect() resizeObserver.disconnect()
intersectionObserver.disconnect() intersectionObserver.disconnect()
fonts?.removeEventListener?.('loadingdone', onFontsReady)
} }
}, [text, end]) }, [containerEl, measureEl, text, end])
const showTruncated = truncated && Boolean(end) const showTruncated = truncated && Boolean(end)
const RootTag = code ? 'code' : 'span' const RootTag = code ? 'code' : 'span'
@ -74,9 +92,9 @@ const ElipsisText = ({ children, style, className, title, code, ...rest }) => {
const content = ( const content = (
<span <span
className={showTruncated ? 'elipsis-text is-truncated' : 'elipsis-text'} className={showTruncated ? 'elipsis-text is-truncated' : 'elipsis-text'}
ref={containerRef} ref={setContainerEl}
> >
<RootTag className='elipsis-text-measure' ref={measureRef}> <RootTag className='elipsis-text-measure' ref={setMeasureEl}>
{text} {text}
</RootTag> </RootTag>
<RootTag className='elipsis-text-full'>{text}</RootTag> <RootTag className='elipsis-text-full'>{text}</RootTag>
@ -96,7 +114,7 @@ const ElipsisText = ({ children, style, className, title, code, ...rest }) => {
className={['elipsis-text-wrapper', className].filter(Boolean).join(' ')} className={['elipsis-text-wrapper', className].filter(Boolean).join(' ')}
style={style} style={style}
> >
{truncated ? <Tooltip title={title ?? text}>{content}</Tooltip> : content} <Tooltip title={truncated ? (title ?? text) : null}>{content}</Tooltip>
</Text> </Text>
) )
} }