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 { start, end } = splitMiddle(text)
const containerRef = useRef(null)
const measureRef = useRef(null)
const [containerEl, setContainerEl] = useState(null)
const [measureEl, setMeasureEl] = useState(null)
const truncatedRef = useRef(false)
const [truncated, setTruncated] = useState(false)
useLayoutEffect(() => {
const container = containerRef.current
const measure = measureRef.current
if (!container || !measure) return
if (!containerEl || !measureEl) return
let raf1 = 0
let raf2 = 0
let cancelled = false
const update = () => {
const available = container.getBoundingClientRect().width
if (cancelled) return
const available = containerEl.getBoundingClientRect().width
if (available < 1) return
const textWidth = measure.getBoundingClientRect().width
const textWidth = measureEl.getBoundingClientRect().width
const next = Boolean(end) && textWidth > available
if (truncatedRef.current === next) return
truncatedRef.current = next
setTruncated(next)
}
update()
const frame = requestAnimationFrame(update)
raf1 = requestAnimationFrame(() => {
update()
raf2 = requestAnimationFrame(update)
})
const resizeObserver = new ResizeObserver(update)
resizeObserver.observe(container)
resizeObserver.observe(containerEl)
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 () => {
cancelAnimationFrame(frame)
cancelled = true
cancelAnimationFrame(raf1)
cancelAnimationFrame(raf2)
resizeObserver.disconnect()
intersectionObserver.disconnect()
fonts?.removeEventListener?.('loadingdone', onFontsReady)
}
}, [text, end])
}, [containerEl, measureEl, text, end])
const showTruncated = truncated && Boolean(end)
const RootTag = code ? 'code' : 'span'
@ -74,9 +92,9 @@ const ElipsisText = ({ children, style, className, title, code, ...rest }) => {
const content = (
<span
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}
</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(' ')}
style={style}
>
{truncated ? <Tooltip title={title ?? text}>{content}</Tooltip> : content}
<Tooltip title={truncated ? (title ?? text) : null}>{content}</Tooltip>
</Text>
)
}