Skip to content

Commit

Permalink
Update React libdefs for Fiber
Browse files Browse the repository at this point in the history
Summary:
React Fiber lets you return more types from your render method. Specifically,
you can return an array of elements, or strings, etc.

This change updates the React libdefs in a few ways.

1. Define a React$Node<T> type, which represents all valid return types
2. Update React.render and like to accept React$Node<T>
   (note that createElement still returns a React$Element)
3. Exports a non-parameterized Node type from the react module

I also updated some of the hardcoded logic in Flow that deals with these types.
Specifically, in the createElement custom fun, we check that a stateless
functional component is correct by "calling" it with an expected return type.
That expected return type is now `React$Node<Config>`.

The non-parameterized exports give folks a way of importing types without
depending on the globals. I used this in the tests in a few places.

In projects that use a lot of React, I recommend creating an alias:

```
type ReactNode = React$Node<any>
```

and using that as the return types of render() methods. If you are doing
something like a HOC, or otherwise need to use the parameterized version, you
are free to do so, but people should use the non-parameterized versions
otherwise.

Note that one could equivalently write something like this in every module:

```
import {Node as ReactNode} from 'react';
```

But I think that's too cumbersome to actually do in practice, if you have lots
of React in your codebase, so just make a global alias.

Reviewed By: spicyj

Differential Revision: D5296482

fbshipit-source-id: 80c09dc22015dc1e53743a352091c26eb1aa6050
  • Loading branch information
samwgoldman authored and facebook-github-bot committed Jun 22, 2017
1 parent f11cba5 commit 770a206
Show file tree
Hide file tree
Showing 20 changed files with 332 additions and 190 deletions.
22 changes: 15 additions & 7 deletions lib/react.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@
*/
/* React */

type React$Node<Config> =
| React$Element<Config>
| string
| number
| Iterable<React$Node<Config>>;

/**
* Base class of ES6 React classes, modeled as a polymorphic class whose type
* parameters are DefaultProps, Props, State.
Expand All @@ -30,7 +36,7 @@ declare class React$Component<DefaultProps, Props, State> {
// lifecycle methods

constructor(props?: Props, context?: any): void;
render(): ?React$Element<any>;
render(): ?React$Node<any>;
componentWillMount(): void;
componentDidMount(component?: any): void;
componentWillReceiveProps(nextProps: Props, nextContext?: any): void;
Expand Down Expand Up @@ -211,15 +217,15 @@ declare module react {
): null | Element | Text;

declare function render<Config>(
element: React$Element<Config>,
element: React$Node<Config>,
container: any
): React$Component<$DefaultPropsOf<Config>, $PropsOf<Config>, any>;

declare function renderToString(
element: React$Element<any>
element: React$Node<any>
): string;
declare function renderToStaticMarkup(
element: React$Element<any>
element: React$Node<any>
): string;

declare function unmountComponentAtNode(container: any): boolean;
Expand All @@ -230,6 +236,8 @@ declare module react {
declare var Component: typeof React$Component;
declare var PureComponent: typeof React$PureComponent;
declare var Element: typeof React$Element;

declare type Node = React$Node<any>;
}

// TODO Delete this once https://github.com/facebook/react/pull/3031 lands
Expand All @@ -246,7 +254,7 @@ declare module 'react-dom' {
): null | Element | Text;

declare function render<Config>(
element: React$Element<Config>,
element: React$Node<Config>,
container: any,
callback?: () => void
): React$Component<$DefaultPropsOf<Config>, $PropsOf<Config>, any>;
Expand Down Expand Up @@ -274,10 +282,10 @@ declare module 'react-dom' {
// 0.15 comes out and removes them.
declare module 'react-dom/server' {
declare function renderToString(
element: React$Element<any>
element: React$Node<any>
): string;
declare function renderToStaticMarkup(
element: React$Element<any>
element: React$Node<any>
): string;
}

Expand Down
2 changes: 1 addition & 1 deletion src/typing/react_kit.ml
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ let run cx trace reason_op l u
rec_flow_t cx trace (l, react_class)
| DefT (_, FunT _) ->
let return_t =
get_builtin_typeapp cx ~trace elem_reason "React$Element"
get_builtin_typeapp cx ~trace elem_reason "React$Node"
[Locationless.AnyT.t]
in
let return_t = DefT (elem_reason, MaybeT return_t) in
Expand Down
2 changes: 1 addition & 1 deletion tests/facebook_fbt_none/main.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// @flow
var React = require('react');
(<fbt />: React.Element<*>);
(<fbt />: React$Element<*>);
(<fbt />: number); // Error: ReactElement ~> number
106 changes: 77 additions & 29 deletions tests/jsx_intrinsics.builtin/jsx_intrinsics.builtin.exp
Original file line number Diff line number Diff line change
@@ -1,34 +1,82 @@
main.js:12
12: var b: React.Element<{prop1: string}> = <CustomComponent prop="asdf" />; // Error: Props<{prop}> ~> Props<{prop1}>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ React element `CustomComponent`
12: var b: React.Element<{prop1: string}> = <CustomComponent prop="asdf" />; // Error: Props<{prop}> ~> Props<{prop1}>
^^^^^^^^^^^^^^^ property `prop1`. Property not found in
12: var b: React.Element<{prop1: string}> = <CustomComponent prop="asdf" />; // Error: Props<{prop}> ~> Props<{prop1}>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ props of React element `CustomComponent`

main.js:12
12: var b: React.Element<{prop1: string}> = <CustomComponent prop="asdf" />; // Error: Props<{prop}> ~> Props<{prop1}>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ React element `CustomComponent`
12: var b: React.Element<{prop1: string}> = <CustomComponent prop="asdf" />; // Error: Props<{prop}> ~> Props<{prop1}>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ property `prop`. Property not found in
12: var b: React.Element<{prop1: string}> = <CustomComponent prop="asdf" />; // Error: Props<{prop}> ~> Props<{prop1}>
^^^^^^^^^^^^^^^ object type

main.js:19
19: var d: React.Element<{doesntmatch: string}> = <div not_a_real_attr="asdf" />;
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ React element `div`
19: var d: React.Element<{doesntmatch: string}> = <div not_a_real_attr="asdf" />;
^^^^^^^^^^^^^^^^^^^^^ property `doesntmatch`. Property not found in
19: var d: React.Element<{doesntmatch: string}> = <div not_a_real_attr="asdf" />;
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ props of React element `div`
12: var b: React$Node<{prop1: string}> = <CustomComponent prop="asdf" />; // Error: Props<{prop}> ~> Props<{prop1}>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ React element `CustomComponent`
12: var b: React$Node<{prop1: string}> = <CustomComponent prop="asdf" />; // Error: Props<{prop}> ~> Props<{prop1}>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ React$Element. This type is incompatible with
12: var b: React$Node<{prop1: string}> = <CustomComponent prop="asdf" />; // Error: Props<{prop}> ~> Props<{prop1}>
^^^^^^^^^^^^^^^^^^^^^^^^^^^ union: type application of class `React$Element` | string | number | type application of identifier `Iterable`
Member 1:
13: | React$Element<Config>
^^^^^^^^^^^^^^^^^^^^^ type application of class `React$Element`. See lib: <BUILTINS>/react.js:13
Error:
12: var b: React$Node<{prop1: string}> = <CustomComponent prop="asdf" />; // Error: Props<{prop}> ~> Props<{prop1}>
^^^^^^^^^^^^^^^ property `prop1`. Property not found in
12: var b: React$Node<{prop1: string}> = <CustomComponent prop="asdf" />; // Error: Props<{prop}> ~> Props<{prop1}>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ props of React element `CustomComponent`
Member 2:
14: | string
^^^^^^ string. See lib: <BUILTINS>/react.js:14
Error:
12: var b: React$Node<{prop1: string}> = <CustomComponent prop="asdf" />; // Error: Props<{prop}> ~> Props<{prop1}>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ React$Element. This type is incompatible with
14: | string
^^^^^^ string. See lib: <BUILTINS>/react.js:14
Member 3:
15: | number
^^^^^^ number. See lib: <BUILTINS>/react.js:15
Error:
12: var b: React$Node<{prop1: string}> = <CustomComponent prop="asdf" />; // Error: Props<{prop}> ~> Props<{prop1}>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ React$Element. This type is incompatible with
15: | number
^^^^^^ number. See lib: <BUILTINS>/react.js:15
Member 4:
16: | Iterable<React$Node<Config>>;
^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type application of identifier `Iterable`. See lib: <BUILTINS>/react.js:16
Error:
16: | Iterable<React$Node<Config>>;
^^^^^^^^^^^^^^^^^^^^^^^^^^^^ property `@@iterator` of $Iterable. Property not found in. See lib: <BUILTINS>/react.js:16
12: var b: React$Node<{prop1: string}> = <CustomComponent prop="asdf" />; // Error: Props<{prop}> ~> Props<{prop1}>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ React$Element

main.js:19
19: var d: React.Element<{doesntmatch: string}> = <div not_a_real_attr="asdf" />;
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ React element `div`
19: var d: React.Element<{doesntmatch: string}> = <div not_a_real_attr="asdf" />;
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ property `not_a_real_attr`. Property not found in
19: var d: React.Element<{doesntmatch: string}> = <div not_a_real_attr="asdf" />;
^^^^^^^^^^^^^^^^^^^^^ object type
19: var d: React$Node<{doesntmatch: string}> = <div not_a_real_attr="asdf" />;
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ React element `div`
19: var d: React$Node<{doesntmatch: string}> = <div not_a_real_attr="asdf" />;
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ React$Element. This type is incompatible with
19: var d: React$Node<{doesntmatch: string}> = <div not_a_real_attr="asdf" />;
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ union: type application of class `React$Element` | string | number | type application of identifier `Iterable`
Member 1:
13: | React$Element<Config>
^^^^^^^^^^^^^^^^^^^^^ type application of class `React$Element`. See lib: <BUILTINS>/react.js:13
Error:
19: var d: React$Node<{doesntmatch: string}> = <div not_a_real_attr="asdf" />;
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ property `not_a_real_attr`. Property not found in
19: var d: React$Node<{doesntmatch: string}> = <div not_a_real_attr="asdf" />;
^^^^^^^^^^^^^^^^^^^^^ object type
Member 2:
14: | string
^^^^^^ string. See lib: <BUILTINS>/react.js:14
Error:
19: var d: React$Node<{doesntmatch: string}> = <div not_a_real_attr="asdf" />;
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ React$Element. This type is incompatible with
14: | string
^^^^^^ string. See lib: <BUILTINS>/react.js:14
Member 3:
15: | number
^^^^^^ number. See lib: <BUILTINS>/react.js:15
Error:
19: var d: React$Node<{doesntmatch: string}> = <div not_a_real_attr="asdf" />;
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ React$Element. This type is incompatible with
15: | number
^^^^^^ number. See lib: <BUILTINS>/react.js:15
Member 4:
16: | Iterable<React$Node<Config>>;
^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type application of identifier `Iterable`. See lib: <BUILTINS>/react.js:16
Error:
16: | Iterable<React$Node<Config>>;
^^^^^^^^^^^^^^^^^^^^^^^^^^^^ property `@@iterator` of $Iterable. Property not found in. See lib: <BUILTINS>/react.js:16
19: var d: React$Node<{doesntmatch: string}> = <div not_a_real_attr="asdf" />;
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ React$Element


Found 4 errors
Found 2 errors
10 changes: 5 additions & 5 deletions tests/jsx_intrinsics.builtin/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ class CustomComponent extends React.Component {
};
}

var a: React.Element<{prop: string}> = <CustomComponent prop="asdf" />;
var b: React.Element<{prop1: string}> = <CustomComponent prop="asdf" />; // Error: Props<{prop}> ~> Props<{prop1}>
var a: React$Node<{prop: string}> = <CustomComponent prop="asdf" />;
var b: React$Node<{prop1: string}> = <CustomComponent prop="asdf" />; // Error: Props<{prop}> ~> Props<{prop1}>

// Since intrinsics are typed as `any` out of the box, we can pass any
// attributes to intrinsics!
var c: React.Element<any> = <div not_a_real_attr="asdf" />;
var c: React$Node<any> = <div not_a_real_attr="asdf" />;
// However, we don't allow such elements to be viewed as React elements with
// different attributes.
var d: React.Element<{doesntmatch: string}> = <div not_a_real_attr="asdf" />;
var d: React$Node<{doesntmatch: string}> = <div not_a_real_attr="asdf" />;
// No error as long as expectations are consistent, though.
var e: React.Element<{not_a_real_attr: string}> = <div not_a_real_attr="asdf" />;
var e: React$Node<{not_a_real_attr: string}> = <div not_a_real_attr="asdf" />;
Loading

0 comments on commit 770a206

Please sign in to comment.