Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Stronger types for React DOM #6727

Closed
wants to merge 12 commits into from

Conversation

motiz88
Copy link
Contributor

@motiz88 motiz88 commented Aug 11, 2018

Provides stronger prop type definitions for React DOM's JSX intrinsics.

This will be an ongoing project for a little while as the scope is ambitious. I'm opening this WIP PR for early feedback on the overall direction as well as for help from fellow interested developers. Pull requests to this branch welcome!

Will eventually close #3773 and close #5865.

Includes #6728 (SyntheticPointerEvent definition).

Implementation checklist

  • Global HTML attributes (except for events)
    • autoCapitalize (missed this in the initial pass)
  • Global HTML events
  • Global SVG attributes and events
  • Element-specific attributes (and events, if that's a thing)
  • Precise type for style prop (a whole other can of worms 😅)
  • Disallow children and dangerouslySetInnerHTML on void element tags
  • Accurate instance types (for refs and Event.currentTarget)
  • More? Let's discuss in this PR

Useful references

Design choices / constraints

(I am open to discussing all of these!)

Leave unknown props typed as any

There is no way we can enumerate all acceptable/standard props of DOM intrinsics. If we ever got a Flow feature allowing regexes for keys, we could express open-ended namespaces like data-* attributes, custom attributes etc more precisely, and then maybe revisit this - with the goal of detecting more classes of errors, such as typos in prop names, or usage of attributes on elements that don't support them.

For now the typing philosophy here is: be reasonably strict with the types of known attributes, but lenient otherwise. This means we can add some value with these types, but not quite match the DX of using composite components... yet.

  • Open question: Should these be any or mixed? The latter would seem to be more useful in scenarios other than passing attributes into elements (say, defining a custom component type that takes React.ElementProps<button>)

Type numeric attributes as number

React will not warn if you pass a string (be it "1" or "foo") into what's meant to be a numeric attribute, e.g. tabIndex. However, I'm leaning towards the stricter tabIndex?: number in such cases. It's a judgement call, though, and an argument can be made for number | string. I've created the ReactDOM$Number type alias as a reminder to come back to this after some discussion.

Impose some case-sensitivity on enumerated attributes

In an ideal world we'd have a Flow builtin, perhaps called $CaseInsensitive, that makes the following possible:

const valid: $CaseInsensitive<"foo" | "bar"> = "Foo"; // OK
const invalid: $CaseInsensitive<"foo" | "bar"> = "baz"; // Error

(EDIT: See separate issue about $CaseInsensitive - #6743)

With such a hypothetical builtin (and I'm not the right person to build it - as I'm not fluent in OCaml by any stretch), we could model enumerated attributes correctly, as case-insensitive enums.

Here, rather than reverting to string for such attributes (or manually adding all letter case permutations of values), I've arbitrarily chosen to support what I think are common/natural-enough spellings.

  • For enumerated values made of single words (true, false) - support only the all-lowercase version.
  • For enumerated values representing acronyms (ltr, rtl) - support all-lowercase alongside all-uppercase (LTR, RTL).
  • For enumerated values representing multiple joined words (contentEditable) - support camel-case alongside all-lowercase (contenteditable).
  • For enumerated values in kebab-case (family-name, street-address) - only support the lowercase version.

I am totally hoping that a Flow internals expert can come along and just give us $CaseInsensitive 😄 In the meantime I've introduced the $TODO$CaseInsensitive type as a placeholder.

Forbid the value "" in all kinds of boolean/enumerated props

See also #6727 (comment), facebook/react#13400, facebook/react#13404.

There are a few classes of DOM attributes that are "boolean", "booleanish" (a term from the React source) or "enumerated"; in HTML, these generally admit "" as a valid value meaning "true". However, for some props, React DOM processes "" as a falsy JS value, omitting the attribute. This inconsistency, along with the fact that there is always a less-ambiguous way to specify "true" (foobar={true} or foobar="foobar") or an equivalent, is why I chose to model these props with union types that do not include "" - reducing the chance of users stumbling across surprising behaviour.

This is a first pass to start surfacing the architectural and practical
issues involved in typing React DOM more precisely.

In this commit:
- No event props yet!
- React global attributes like dangerouslySetInnerHTML
- Shared HTML/SVG attributes
- HTML global attributes
- A handful of SVG global attributes
- Lots of things marked TODO
- Some design choices described in comments

"Booleanish string" as a term is lifted from the React DOM source.
This is based on facebook/react#12507 which is
seemingly missing a couple of fields from the official spec.
Consequently this will need an update once React supports all fields.
facebook/react#13374 has been merged so the next
React DOM release will have these fields.
This also refactors the main set of JSX intrinsics declarations to
support typing Event.currentTarget with each element's instance type.
@motiz88
Copy link
Contributor Author

motiz88 commented Aug 14, 2018

UPDATE: This is now described as a design choice in the main comment: #6727 (comment)


In light of the issue outlined in facebook/react#13400, I'm currently thinking of removing "" as an allowed value from all kinds of enumerated attributes (ReactDOM$Boolean, ReactDOM$BooleanishString, and others like translate?: $TODO$CaseInsensitive<"" | "yes" | "no">), as there are simply too many subtly different behaviours to keep track of.

  • In a DOM boolean prop - modeled by ReactDOM$Boolean - React treats "" as falsy, while HTML specifies it as one of the allowed truthy values.
  • In DOM "booleanish string" props - ReactDOM$BooleanishString - and other enums where "" is allowed, React treats it as truthy (as does HTML).

As with typing numeric properties as number, disallowing "" is an "editorial" decision to be stricter in order to reduce ambiguity and the potential for bugs. I'd be happier to do this if React were to add a corresponding warning or documentation note discouraging the use of "" (or string values in general) in DOM boolean props.

@mrkev
Copy link
Contributor

mrkev commented Aug 15, 2018

Oh, nice. Some comments:

  • It might be helpful to do this work throughout multiple PRs, and keep track of progress in a "master issue?

  • In an ideal world we'd have a Flow builtin, perhaps called $CaseInsensitive. Interesting idea, and can definitely see cases such as this in which it would be useful. Making an issue to track the addition said utility and liking it back here for context would be helpful to keep track of it.

  • I'm a little worried about making stuff more restrictive, since it could introduce errors to people's pre-existing builds. Up for discussion.

  • Digging the pointer event's definitions 👏!

@motiz88
Copy link
Contributor Author

motiz88 commented Aug 15, 2018

@mrkev:

It might be helpful to do this work throughout multiple PRs, and keep track of progress in a "master issue?

Yes in principle, I'm just trying to get to a point where I can see the overall "shape" of this work & see what the shippable milestones are along the way. Happy to get your thoughts on that. Also I didn't know what to expect in terms of a timeline for review/release etc (I'm glad to have your comments this early!), and as you see I'm still discovering some issues as I dive into specs and React's implementation thereof.

Things like modelling style can definitely be broken out, but in my mind the ideal is to roll all of this up into one coherent release of Flow - "the one where we got typechecking for React DOM" (and had to adjust a little bit 😄). One clear transition might be easier to document and educate users about.

@motiz88
Copy link
Contributor Author

motiz88 commented Sep 1, 2018

This is still in progress, just had to take a break and focus on other things recently.

There is an angle here that I think ties into the React team's longer-term plans for React DOM (facebook/react#13525). If ultimately we can model both today's React DOM and the future "React Fire" (which will include breaking API changes, e.g. potentially classNameclass and others), and the types are accurate and strict enough, then Flow can play a useful role in helping the ecosystem migrate safely, especially for use cases not covered by non-type-aware codemod tools. The ideal starting point, well in advance of when React starts warning/deprecating APIs or committing to specific plans, would IMHO be a good typechecking experience for React DOM code written today.

@nmote nmote added the Library definitions Issues or pull requests about core library definitions label Jan 3, 2019
@goodmind
Copy link
Contributor

@motiz88 are you still working on this PR?

@villesau
Copy link
Contributor

villesau commented Mar 9, 2019

@jbrown215 would there be parts in this that could be used from this pull request?? This is needed for improved Flowgen React support.

@jbrown215
Copy link
Contributor

jbrown215 commented Mar 11, 2019

@villesau: Which parts do you need? I expect that @motiz88 does not have time to continue his work on this, but I'm happy to review other PRs to the libdefs that you need.

@villesau
Copy link
Contributor

I need as much as possible 😅This is the beef: joarwilk/flowgen#76 (comment)

I'll try to pick up the parts that are needed the most.

@goodmind
Copy link
Contributor

Yeah, actually part that is needed is not implemented here
Element-specific attributes (and events, if that's a thing)

Also style property can be typed with this https://github.com/frenic/csstype

villesau pushed a commit to villesau/flow that referenced this pull request Mar 18, 2019
villesau pushed a commit to villesau/flow that referenced this pull request Mar 18, 2019
villesau pushed a commit to villesau/flow that referenced this pull request May 7, 2019
@goodmind goodmind added the Superseded PRs that solve the same issue as a "superseding" PR that already landed label Jul 27, 2019
@FezVrasta
Copy link
Contributor

Is there any chance to get this revived?

@gkz
Copy link
Member

gkz commented Aug 18, 2024

Subsumed by #7569

@gkz gkz closed this Aug 18, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed Library definitions Issues or pull requests about core library definitions react Superseded PRs that solve the same issue as a "superseding" PR that already landed
Projects
None yet
Development

Successfully merging this pull request may close these issues.

React DOM (e.g <div />) not type checked [question] Type Props for basic JSX elements
9 participants