Skip to content

fix: add stable merged refs - #5266

Open
nynexman4464 wants to merge 1 commit into
mainfrom
fix/stabilize-text-refs
Open

fix: add stable merged refs#5266
nynexman4464 wants to merge 1 commit into
mainfrom
fix/stabilize-text-refs

Conversation

@nynexman4464

@nynexman4464 nynexman4464 commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Stack

This is 1 of 3 in GitHub stack #5270:

  1. fix: add stable merged refs #5266 — this PR: add useMergedRefs and fix Text/Heading
  2. fix: stabilize merged refs across core #5267: migrate every remaining core and lab callsite
  3. lint: forbid inline mergeRefs calls #5269: enforce the safe pattern with ESLint

Review and land in this order.

Problem

Text and Heading currently build a merged callback ref inline:

ref={mergeRefs(ref, truncation.ref, textRef)}

That creates a different callback ref on every render. React therefore detaches the previous ref and attaches the new one even though the DOM element did not change.

For these components, detaching reaches useTruncation with null. Its cleanup path calls setIsTruncated(false) and setFullText(''). Those state updates happen during React's commit phase on every parent rerender, including when maxLines is 0.

User impact

A standalone React 19.2 reproduction has a deliberately temporary consumer loop: while simulated async data is pending, it returns a fresh empty-array fallback; after 100 ms a timer supplies stable data and stops the loop.

Child rendered inside the loop Result
Plain <span> The timer runs, data stabilizes, and the page recovers
Astryx <Text> React throws error #185 and unmounts the root before the timer runs

The reproduction uses only React, React DOM, @astryxdesign/core, and esbuild—no framework or application providers. It fails in development and production builds and in every stable Astryx version tested from 0.0.15 through 0.4.5.

The consumer still owns its unstable fallback bug. Astryx should not add commit-phase state updates that turn that temporary loop into an immediate fatal error.

Regression from the predecessor implementation

The predecessor text component kept its merged ref stable with useCallback. Astryx replaced that with an inline mergeRefs(...) call, losing the stable identity.

Fix

This PR adds a public hook:

const mergedRef = useMergedRefs(ref, truncation.ref, textRef);

useMergedRefs memoizes the combined callback and only changes it when an input ref changes. Text and Heading use it as the proof-of-concept and outage fix.

The standalone production reproduction was rerun against the packed package:

Build Result
Stock 0.4.5 React #185; root unmounted
This PR Timer runs; page remains mounted
Stock restored React #185 again

Why the stack is split

This PR stays narrow: it introduces the safe API and fixes the two components implicated by the reproduction. #5267 performs the broad mechanical migration separately. #5269 enables enforcement only after the repository is clean.

Fixes #5264.

Test plan

  • regression tests fail before the fix because forwarded refs receive element → null → element on rerender
  • pnpm vitest run packages/core/src/hooks/useMergedRefs.test.tsx packages/core/src/Text/Text.test.tsx packages/core/src/Heading/Heading.test.tsx
  • pnpm -F @astryxdesign/core build
  • pnpm lint
  • standalone production reproduction with the packed package, including stock/fixed/restored controls

@vercel

vercel Bot commented Aug 20, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
astryx Ready Ready Preview Aug 20, 2026 10:35pm

Request Review

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Aug 20, 2026
@github-actions

github-actions Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

PR Analysis Report

📚 Storybook Preview

View Storybook for this PR
GitHub Pages may take up to a minute to hydrate after deploy.

🧪 Sandbox Preview

View Sandbox for this PR
GitHub Pages may take up to a minute to hydrate after deploy.

Modified Components

Heading (@astryxdesign/core) · View in Storybook
Metric Before After Delta
Bundle Size (ESM) N/A N/A N/A
Lines of Code N/A 152 -
Complexity N/A High (23) -
Text (@astryxdesign/core) · View in Storybook
Metric Before After Delta
Bundle Size (ESM) N/A N/A N/A
Lines of Code N/A 521 -
Complexity N/A High (25) -

Bundle Size Summary

Package Size (ESM) Size (CJS) Gzipped
@astryxdesign/core N/A 4.8KB 1.2KB

Accessibility Audit

Status: 2 accessibility violation(s) found — 2 serious.

Heading - 1 issue(s)
  • 🟠 serious: Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds
    • Rule: color-contrast · Affects 1/16 stories · Learn more
    • WCAG: 1.4.3 (Level AA)
Text - 1 issue(s)
  • 🟠 serious: Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds
    • Rule: color-contrast · Affects 1/23 stories · Learn more
    • WCAG: 1.4.3 (Level AA)

Generated by PR Enrichment workflow | Storybook | Sandbox | View full report

github-actions Bot added a commit that referenced this pull request Aug 20, 2026
@nynexman4464 nynexman4464 changed the title fix: keep Text and Heading refs stable across rerenders fix: add stable merged refs Aug 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Text ref churn turns temporary update loops into React error #185

2 participants