Type-safe props, events, and refs for react-strict-dom#504
Open
yaminyassin wants to merge 4 commits into
Open
Conversation
workflow: benchmarks/sizeComparison of minified (terser) and compressed (brotli) size results, measured in bytes. Smaller is better.
|
workflow: benchmarks/perf (native)Comparison of performance test results, measured in operations per second. Larger is better.
|
added 4 commits
June 17, 2026 15:49
The event props on the Strict* prop types were all $FlowFixMe, so handlers got no checking and authors got no autocomplete. Add StrictReactDOMEvents with the event shapes the native factories actually build (change, input, key, click, image load/error) and a StrictOpaqueEventHandler for the pass-through handlers whose runtime shape is platform-specific. Wire those through StrictReactDOMProps and the button/image/input/select/ textarea prop types, and re-export the payload types from the native and web entrypoints so consumers can annotate their own handlers.
Type the props the native modules build and drop the suppressions that were hiding the gaps. The prop mutation that the factories do (role defaults, display:block emulation, the hidden polyfill) moves out of the hook bodies into plain helpers so it can write to the caller-owned nativeProps without tripping react-rule-hook-mutation. Route HostInstance through the renderer.native type boundary rather than importing it from 'react-native' in each module, matching how the runtime already funnels RN access through the local wrapper. Two suppressions are left in place on purpose: the skew check in useStyleTransition and the provideInheritableStyle comparison both sit on top of latent behavior bugs, so the fixes (and the type cleanup that goes with them) land in a separate PR rather than riding along with a types-only change.
The default style table cast every entry with $FlowFixMe[incompatible-type]; the types line up now, so the casts are gone. For the debug-style object that stylex.create does not generate, use a small local DebugCompiledStyle type instead of an unclear-type cast. The one suppression left on validateStrictProps stays, with a note on why: typing the argument precisely would block the in-place delete of invalid keys that the function relies on.
Now that the props are typed, pin the behaviour down. refs-and-props covers the host-element ref types and the props that should be accepted; expected-errors locks in the misuse Flow has to reject, so a future loosening of the types fails here instead of slipping through. Extend html-types-match with the new event payload types.
d1c714d to
4391699
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.
Summary
This change replaces the
$FlowFixMesuppressions with real types, moves the prop-building mutation out of the hook bodies so it stops fighting the React hook rules, and pins the result down with Flow tests.This is a types-only change. It does not alter runtime behavior, so it can land on its own and be reviewed as such.
Motivation
$FlowFixMeis a checked-in promise to come back later. For the event props it had two costs:onClick={(e) => e.tpye}typed clean.or read the source.
The props are the public surface of the library. They are the right place to spend the type budget.
What changed
Event payloads are typed. A new
StrictReactDOMEventsmodule holds the event shapes the native factories actually construct:change,input, key,click, and imageload/error. Handlers whose runtime shape is defined by the platform (most pointer, mouse, touch, and clipboard events) use a singleStrictOpaqueEventHandlerinstead of$FlowFixMe. The payload types are re-exported from the native and web entry points so consumers can annotate their own handlers.Prop mutation moved out of the hooks. The native factories build their
nativePropsby mutating an object (role defaults, thedisplay:blockemulation, thehiddenpolyfill). Doing that inside a hook body needed areact-rule-hook-mutationsuppression. That logic now lives in plain helpers (applyViewProps,applyHtmlProps) that take thenativePropsand write to it directly, so the suppressions are gone.HostInstanceroutes through the type boundary.types/renderer.native.jsnow exportsHostInstancefromreact-nativeHow this improves DX
onChangehands them.(event: StrictChangeEvent) => voidand have it stay in sync with the library.Type safety in practice
A typo in a payload field is a Flow error where it is written, not a runtime
undefined:The click payload carries the modifier and position fields it actually has, so reaching past them is caught:
Key handlers get a stable
keyfield across platforms:Annotating a handler defined outside JSX uses the exported payload types, which stay in sync with the library:
A handler whose signature does not match the prop is rejected, so a web payload shape cannot be assumed on a strict element:
The opaque handlers still pass the event through, but
unknownforces a check before use rather than handing backany:Compat Table
✅means the native runtime wires the handler today;⚪means the type exists and the event passes through, but no native module wires it yet.onChange,onInput,onClick,onKeyDown,onLoad,onErrortype+target.value, a stable key payload, or a normalized image load/error shape.onPointer*(Down/Up/Move/Enter/Leave/Over/Out/Cancel),onGotPointerCapture,onLostPointerCaptureonTouchStart/End/Move/CancelonMouseDown/Up/Enter/Leave/Over/OutonBlur,onFocusonScroll(UIEvent),onSelectionChange(FormEvent)onAuxClick,onContextMenu,onMouseMove,onCopy,onCut,onPaste,onWheel,onKeyUp,onBeforeInput,onInvalid,onSelect,onFullscreenChange,onFullscreenError,onFocusIn,onFocusOutNotes for reviewers
A few calls I would like a second opinion on:
StrictChangeEvent,StrictClickEvent...) are new public API. If the names or shapes are wrong, now is the time to change them.⚪re-synthesized and pass-through handlers get concrete payload types now, or stay opaque until a native implementation wires each one?