Skip to content

EPBDS-16211 E2: seven stateful components, as TypeScript - #137

Merged
AlexSamBY merged 1 commit into
masterfrom
e2-ts-fifth-batch
Oct 1, 2026
Merged

AlexSamBY merged 1 commit into
masterfrom
e2-ts-fifth-batch

Conversation

@AlexSamBY

Copy link
Copy Markdown
Member

What changes

Seven stateful components become TypeScript:

  • ProgressBar, ProgressSteps and Counter;
  • Pagination, Select and Dropzone;
  • StandaloneTabs.

All seven are renamed with git mv. Git detects every rename (61–75% similarity), so their history follows.

The §9.3 step 6 hooks conversions among them are typed as they are:

  • their state objects get types;
  • the refs their timers read go through non-null assertions, since every render assigns the ref before any handler runs;
  • the props !== propsSeen derivations are untouched.
file notes
ProgressBar.tsx ProgressBarProps; the value may be a fraction or NaN, undefined or null, as documented
ProgressSteps.tsx ProgressStepsProps, ProgressStep
Counter.tsx CounterProps, CounterRender, and CounterFrame for the exported nextFrame
Pagination.tsx PaginationProps, PageChange
Select.tsx SelectProps, SelectOption
Dropzone.tsx DropzoneProps, and DropzoneHandle, the real handle with fileInputEl included
StandaloneTabs.tsx StandaloneTabsProps, StandaloneTab, and StandaloneTabsHandle for function content

Upload's last component cast, Dropzone's, is deleted. Its DropzoneHandle is re-exported from Dropzone.

A throwaway probe of seven wrong call sites failed on all seven, and its correct counterparts compiled:

  • a string value on ProgressBar;
  • a numeric step;
  • a Counter without end;
  • a Pagination without totalPages;
  • a Select without options;
  • an onDrop taking a string;
  • a tab without content.

What the checker surfaced

  • forwardRef's own type erases named props. It runs them through Omit<…, 'ref'>, which keeps nothing but the index signature of a type that has one. RowRef hit the same trap in EPBDS-16211 E2: the leaf components the first batch still cast, as TypeScript #134. Dropzone is therefore cast to the same component with its props kept.
  • InputNative's select branch hands Select the engine onChange, (value, name, event). That is the signature Select reports with. ForwardedProps said every branch overwrote the handler. The comment is corrected, and the call is a commented cast.
  • StandaloneTabs renders an object tab without an icon as it is. Its propTypes accept {text} alone, and React rejects rendering it. The component is demo-only, and NavTabs passes strings, so this is recorded and not fixed. The cast that keeps the JavaScript's assumption says so.
  • Two smaller casts:
    • renderFloat is still JavaScript, and its JSDoc types the element it returns as Object, so Counter's default render is cast.
    • Counter's className and style are declared by its propTypes and read by nothing. The type says so.

No runtime change

Every edit is a type, a cast or a comment.

dist/index.js shrank by 12 bytes, from 327,399 to 327,387, because Upload's const Dropzone = DropzoneJs alias is gone.

Measured

  • The corpus is identical to master. Over the 38 examples through the published entry: commits at mount 107, translate calls 1013, and 27 / 64 on an edit. The DOM is identical after mount and after an edit.
  • The TypeScript share grew. src/core + src/library hold 64 TypeScript files, 9,989 lines (was 57 and 8,830), against 93 JavaScript files, 10,111 lines (was 100 and 11,051). That is 49.7% of the lines, up from 44.4%.

Housekeeping

  • Six coverage thresholds follow the renamed files. They are in jest.config.js.
  • Two comments that named Pagination.js now name Pagination.tsx. One of them is in _semantic.less and survives compilation, so public/static/ui-render.built.css is rebuilt; it differs by that one line.
  • docs/SUPPORTED-PROPS.md needed no change, as docs:props:check confirms.
  • The plan records the batch. docs/UPGRADE-PLAN.md §9.6-E2 has the note.

Gates

All exited 0:

  • lint:js, lint:css, typecheck, typecheck:contract;
  • docs:props:check, docs:views:check, css:fixture:check;
  • test:env-flags, build;
  • test:coverage, thresholds included. Every touched file's coverage is unchanged, and the partial ones (Pagination, StandaloneTabs, InputNative, Upload) are partial on the same expressions as on master;
  • test:pack, test:pack:peers, test:types;
  • test:react16, test:react19, 2908 tests each;
  • test:e2e, 41 tests;
  • the manifest contract.

test:react17 took three runs, and none of them failed a test. Two runs lost a jest worker to SIGSEGV, the V8 crash of nodejs/node#62393 that this machine's Node 24.14 is known to hit:

  1. First run: the worker died in manifest.contract.test.js, and 2900 tests passed.
  2. Second run: the worker died in Dropdown.gate.test.js, a file this PR does not touch, and 2901 tests passed.
  3. Third run: clean, 2908 of 2908.

Both crashed suites also passed alone on the React 17 leg, 15 of 15. The three earlier React 17 runs this session, one per batch, were all clean.

In the demo on the dev server:

  • Pagination: a click on page 2 moved the current page and the table to rows 6 onwards.
  • Upload: it rendered its three Dropzones, each with a hidden file input carrying the right accept and multiple.
  • StandaloneTabs: the navigation switched to "Styles", and one Back returned to the examples page, so there is one history entry per click.
  • Console: no error.

🤖 Generated with Claude Code

ProgressBar, ProgressSteps, Counter, Pagination, Select, Dropzone and
StandaloneTabs become .tsx with exported prop types (git mv, so their
history follows). Upload's last component cast, Dropzone's, is
deleted, and DropzoneHandle is now the real handle.

forwardRef's own type runs props through Omit<..., 'ref'>, which keeps
only the index signature of a type that has one, so Dropzone is cast to
the same component with its props kept. Typing Select showed that
InputNative's select branch hands it the engine onChange; the
ForwardedProps comment said every branch overwrote it, and is
corrected. StandaloneTabs renders an object tab without an icon as it
is, which React rejects; demo-only, recorded and not fixed.

Measured: the 38-example corpus is identical to master (107 / 1013 /
27 / 64, same DOM); dist/index.js 327,399 -> 327,387 bytes (Upload's
Dropzone alias is gone); TypeScript is 49.7% of src/core + src/library
lines, was 44.4%.
@AlexSamBY
AlexSamBY merged commit 74c850f into master Oct 1, 2026
5 checks passed
@AlexSamBY
AlexSamBY deleted the e2-ts-fifth-batch branch October 1, 2026 08:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant