fix: stale onError in memo/forwardRef on React 19.2, getConnectedDataURL() on ECharts 6.1 - #577
Merged
Merged
Conversation
React 19.2.x refreshes useEffectEvent callbacks only on plain function component fibers; ForwardRef (11) and SimpleMemo (15) fibers `break` without updating ref.impl (react-dom 19.2.7 commit phase). 19.3.0 fixed it (react/react#34831) and the fix was never backported. With the peer range at react ^19.2.0, `memo(EChart)` or a memoized component that calls useEcharts kept routing effect-time errors to the first render's onError. Effect-time errors now read the latest onError from a ref synced in a layout effect: useChartCore reuses latestRef, useResizeObserver gets its own onErrorRef, and both report through a shared reportEffectError next to routeImperativeError. The library no longer uses useEffectEvent, so docs drop it as the reason for the 19.2 floor (the peer range itself is unchanged). New tests render the hook inside memo() and forwardRef components and swap onError before failing option sync, auto-resize and cleanup. With react/react-dom temporarily at 19.2.7 both fail on the old code and pass with this change; the whole unit project (282 tests) also passes on 19.2.7. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VAs1BuCUj8VWLQvBSaxqWM
ECharts 6.1.0's getConnectedDataURL dereferences `opts.type` with no
`opts = opts || {}` guard (getDataURL has one). The fix is on the
apache/echarts release branch (#21736, 2026-09-14) but not on npm, so
the documented `getConnectedDataURL()` threw a TypeError that the hook
routed to onError or rethrew.
The hook now passes `opts ?? {}`. A new browser test calls both
getDataURL() and getConnectedDataURL() without options on a real chart:
it fails with "Cannot read properties of undefined (reading 'type')"
before this change and passes after; the unit test asserts the `{}`
default and that explicit options still pass through.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VAs1BuCUj8VWLQvBSaxqWM
Docs, checked against the upstream sources: - CLAUDE.md said minimumReleaseAgeExcludePrune "does not reliably remove matured entries". The pnpm docs (settings/dependency-resolution.md) define it as dropping only entries the freshly written lockfile no longer resolves; it never prunes by age. Reword so manual pruning reads as the designed workflow, not a workaround. The pnpm-workspace.yaml comment also listed only add/update/remove; it runs on install and dedupe too. - CLAUDE.md's tooling line now notes that the pre-commit hook's `vp staged` needs Node 22.22.1+ on 22.x, as CONTRIBUTING.md and the Vite+ commit-hooks guide already say. Config with no behaviour change: - .gitignore: drop `.vitest-attachments/`; Vitest 5 writes attachments under `.vitest/`, which is already ignored. - vite.config.ts: drop `clearMocks: true` and both inline `extends: true`; Vitest 5 defaults clearMocks to true and inline projects inherit the root config unless `extends` is false (both read from the bundled vitest sources). - tsconfig.app.json: drop "DOM.Iterable"; in TypeScript 7 that lib file is an empty stub whose contents moved into "DOM". peerDependencyRules.allowedVersions stays: `vp migrate` rewrites it on every toolchain upgrade, so removing it would only churn. Validation: `vp check`, `vp exec tsc -b`, unit tests with coverage (282 passed, 100%), browser project (9 passed), `vp pack` (attw + publint clean, two React Compiler caches), `vp build`, size-limit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VAs1BuCUj8VWLQvBSaxqWM
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #577 +/- ##
=========================================
Coverage 100.00% 100.00%
=========================================
Files 19 19
Lines 574 574
Branches 146 143 -3
=========================================
Hits 574 574 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR comes from re-reading upstream docs and sources (React, ECharts, Vite+/Vitest, TypeScript 7, pnpm). Two of the findings are real bugs: each one was reproduced before fixing and verified after. The PR also corrects docs and config.
1.
onErrorgoes stale insidememo()/forwardRefon React 19.2 (patch)useEffectEventcallbacks on plain function-component fibers.ForwardRefandSimpleMemofibersbreakwithout updating them (react-dom 19.2.7, commit phase).useEffectEventreads latest values inforwardRefandmemo()Components react/react#34831, but the fix was never backported to 19.2.x.react ^19.2.0. So withmemo(EChart), or a memoized component that callsuseEcharts, effect-time errors kept going to theonErrorfrom the first render.onErrorfrom a ref that is synced in a layout effect.useChartCorereuseslatestRef;useResizeObservergets its own ref.reportEffectError.useEffectEvent, so the docs no longer cite it as the reason for the 19.2 floor. The peer range itself is unchanged.memo()andforwardRef, swaponError, then make option sync, auto-resize and cleanup fail.2.
getConnectedDataURL()without options throws on ECharts 6.1.0 (patch)opts.typewithout defaultingopts.getDataURLdoes have a default.optsargument ofechartsInstance.getConnectedDataURLoptional apache/echarts#21736) is on thereleasebranch only; it hasn't been published to npm.opts ?? {}.getDataURL()andgetConnectedDataURL()without options on a real chart.Cannot read properties of undefined (reading 'type'); after the fix it passes.3. Docs and config corrections (no behaviour change)
minimumReleaseAgeExcludePrune"does not reliably remove matured entries". The pnpm docs define it as dropping only entries the lockfile no longer resolves; it never prunes by age, so removing matured entries by hand is the designed workflow. Thepnpm-workspace.yamlcomment is corrected the same way (the prune also runs oninstallanddedupe).vp stagedneeds Node 22.22.1+ on 22.x, matching CONTRIBUTING.md and the Vite+ commit-hooks guide..vitest-attachments/in.gitignore: Vitest 5 writes to.vitest/, which is already ignored.clearMocks: trueand both inlineextends: trueinvite.config.ts: these are Vitest 5 defaults, checked in the bundled sources.DOM.Iterableintsconfig.app.json: it is an empty stub in TypeScript 7.peerDependencyRules.allowedVersions:vp migraterewrites it on every toolchain upgrade, so removing it would only cause churn.Validation
vp checkandvp exec tsc -bpass.vp pack: attw and publint are clean; there are still two React Compiler caches.vp buildand size-limit pass (6.88 KB / 1.51 KB / 227 B).🤖 Generated with Claude Code
https://claude.ai/code/session_01VAs1BuCUj8VWLQvBSaxqWM
Generated by Claude Code