Fix remove-forward-ref TypeScript output for React 19#348
Open
adamjcooper wants to merge 1 commit into
Open
Conversation
The transform had three problems with the ref prop type it emitted: 1. The ref prop was typed as `ref: React.RefObject<T>` -- required and RefObject-only. A forwardRef component accepts an optional ref that may be a ref object, a callback ref, or null, so every call site that didn't pass a ref (or passed a callback ref) became a type error. Emit `ref?: React.Ref<T>` instead, matching the type that ForwardRefExoticComponent exposed. 2. Props and ref types were only carried over when they were a simple type reference or type literal. Union types, intersections, and other type expressions (e.g. `forwardRef<HTMLButtonElement | HTMLAnchorElement, PropsWithChildren<Props>>`) caused the annotation to be dropped entirely, leaving an untyped destructure where every prop was inferred as any. Accept any TS type, parenthesizing unions and conditional types when placed in the emitted intersection. 3. The ref argument's annotation was only recognized in the qualified `React.ForwardedRef<SingleReference>` form. Recognize bare named imports (`ForwardedRef<T>`, `Ref<T>`, etc.) and non-reference element types such as unions. Fixes reactjs#336 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
de9484b to
3879a65
Compare
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.
Fixes #336
Running this codemod before it merges
jscodeshift can run the transform file straight from a checkout of this branch:
--parser=tsxis required (the transform doesn't export a parser). Addjsx,jsto--extensionsif you have untyped files, use--dry --printto preview, and run on a clean git tree so the changes are easy to review or discard.Problem
On TypeScript code,
remove-forward-refproduces output with type errors:RefObject-only (ref: React.RefObject<T>). AforwardRefcomponent's ref was optional and could also be a callback ref, so call sites that pass no ref — or a callback ref — no longer typecheck. This is the error reported in Refs required by react/19/remove-forward-ref #336.anyand breaking all consumers.React.ForwardedRef<SingleReference>. Bare named imports (ForwardedRef<T>) and union element types fell back tounknown.Fix
ref?: React.Ref<T>— optional, and accepting ref objects, callback refs, andnull, matching the ref prop ofForwardRefExoticComponent.(A | B) & { ref?: React.Ref<T> }.ForwardedRef<T>,Ref<T>, …) and union element types.Example:
Relationship to #338 and #339
@iamakulov's PRs address parts of this issue, and this PR covers both (apologies for the overlap — happy to coordinate however is easiest to review):
RefObject<T>, so callback refs still error.union-typesfixture outputButtonProps | LinkProps & { ref: ... }parses asButtonProps | (LinkProps & { ref: ... }), attaching the ref prop to only the last union member. It also still drops union ref element types (forwardRef<HTMLButtonElement | HTMLAnchorElement, Props>).Testing
ForwardedRefimport, and a union inside the ref argument's annotation. All 17 tests pass; lint is clean.ForwardedRef(e.g. customuseForwardedRefhooks), which a call-site transform can't know about.Notes for reviewers
React.Ref<T>assumes theReactnamespace is in scope — same as the previousReact.RefObject<T>output, so not a regression.ForwardedRef) are not pruned, as before; theref-arg-named-importfixture documents this.🤖 Generated with Claude Code, then manually reviewed and edited.