Skip to content

fix: annotate props with named react type imports - #131

Merged
thomasbeaudry merged 1 commit into
mainfrom
fix/aggregated-declarations-name-react
Oct 30, 2026
Merged

thomasbeaudry merged 1 commit into
mainfrom
fix/aggregated-declarations-name-react

Conversation

@thomasbeaudry

@thomasbeaudry thomasbeaudry commented Oct 30, 2026 •

Copy link
Copy Markdown
Collaborator

#130 was incomplete. It gave every component file that names React in a prop annotation its own import type React from 'react', which fixed that file's own declaration — but a component assembled with Object.assign re-emits its members' annotations textually into its own .d.ts, without carrying their imports. So dist/components/Tabs/Tabs.d.ts still read:

export declare const Tabs: (({ ref, ...props }: React.ComponentProps<...>) => ...) & { ... };

with no React in scope. Six aggregates were left that way after 6.19.1: Tabs, Select, Sheet, Table, HoverCard and DropdownMenu.

The fix

Import each type by name instead of reaching through the namespace. The declaration emitter then writes import("react").ComponentProps<...>, which is self-contained and needs no local binding to survive being re-emitted anywhere:

-import type React from 'react';
+import type { ComponentProps } from 'react';

-export const TabsRoot = ({ ref, ...props }: React.ComponentProps<typeof Root>) => {
+export const TabsRoot = ({ ref, ...props }: ComponentProps<typeof Root>) => {

94 files. The namespace import stays only where React is used as a value — React.useState, React.useRef, React.useEffect in ComboBox and FieldGroupDescription — since those never reach a declaration.

This also makes the class of bug structurally impossible rather than fixed case by case: the next Object.assign aggregate cannot reintroduce it.

Why it matters downstream

A consumer without @types/react has no React UMD global, so the unresolved name silently degrades every prop on these six components to any (skipLibCheck hides the error in the declaration itself). In Open Data Capture that surfaced as 18 TS7006 errors across 7 files on 6.19.1.

Verification

  • dist declarations naming a bare React.: 6 → 0
  • pnpm lint clean, pnpm build clean, 226/226 tests pass
  • Packed this branch as a tarball and installed it into Open Data Capture over 6.19.1: tsc --noEmit in apps/web goes from 18 TS7006 errors to 0

🤖 Generated with Claude Code

`import type React from 'react'` fixed the declarations of each component's
own file, but a component aggregated with `Object.assign` re-emits its
members' annotations textually, so `Tabs.d.ts` names `React` without
importing it. Six aggregates still leaked a bare `React.` into `dist`.

Importing each type by name makes the emitter write `import("react").X`
everywhere, which no local binding has to exist for. Only the value uses of
the namespace (`React.useState` and friends) keep `import * as React`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@thomasbeaudry
thomasbeaudry merged commit 053f6b6 into main Oct 30, 2026
1 check passed
@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 6.19.2 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant