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

Add React.Suspense component, remove React.useMutationEffect hook #7213

Closed
wants to merge 5 commits into from

Conversation

bvaughn
Copy link
Contributor

@bvaughn bvaughn commented Nov 27, 2018

Resolves #7219

  • 2fdcda5: Removed React.useMutationEffect hook (Remove useMutationEffect react#14336) and updated tests. This hook was only ever exposed in an alpha release of 16.7 and so it should be safe to remove in a non-backwards-compatible way.
  • dabf49e: Add React.Suspense component and tests. (No final public documentation exists for Suspense yet– although we're working on it. For the time being, tests like this can serve as a reference.)
  • 8df05bd: Fixed invalid useCallback definition and tests.
  • 063b022: Fixed invalid React.lazy signature to reflect that a Promise with a "default" property should be returned (rather than a React component).

@bvaughn
Copy link
Contributor Author

bvaughn commented Nov 27, 2018

The type-at-pos test kept crashing for me locally but I don't think that's related to this PR.

Copy link
Contributor

@facebook-github-bot facebook-github-bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jbrown215 has imported this pull request. If you are a Facebook employee, you can view this diff on Phabricator.

@@ -250,6 +250,12 @@ declare module react {
declare export var ConcurrentMode: ({children: ?React$Node}) => React$Node; // 16.7+
declare export var StrictMode: ({children: ?React$Node}) => React$Node;

This comment was marked as resolved.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. I'm ok with adding that fix to this PR or doing it in a separate one.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bvaughn actually brought up a good point below, it's not quite a ComponentType. It will be an AbstractComponent, though!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's leave it for now?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, sounds good

lib/react.js Outdated
@@ -250,6 +250,12 @@ declare module react {
declare export var ConcurrentMode: ({children: ?React$Node}) => React$Node; // 16.7+
declare export var StrictMode: ({children: ?React$Node}) => React$Node;

declare export var Suspense: React$ComponentType<{
children?: ?React$Node,
fallback: React$Node,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fallback is optional (for nested Suspense components); missing, undefined, and null should all typecheck I think

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah thanks! I thought it was a required prop.

children?: ?React$Node,
fallback: React$Node,
maxDuration?: number
}>; // 16.6+

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this imply that this is a class? I guess that isn't technically correct since it's one of those internal special symbols like Fragment but maybe good enough for now.

Should it use the same hack as Fragments and pretend to be a functional component? That way maybe refs are invalid maybe?

Copy link
Contributor Author

@bvaughn bvaughn Nov 27, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it use the same hack as Fragments and pretend to be a functional component? That way maybe refs are invalid maybe?

Then people might think they can call it as a function, which would error? (Doesn't seem like that would obviously be better than this.)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ComponentType is the type alias that (currently) represents function components and class components.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there an alias for something that more accurately represents StrictMode, Suspense, etc?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not yet, but AbstractComponent will fulfill that role!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then people might think they can call it as a function, which would error? (Doesn't seem like that would obviously be better than this.)

If the instance type is void, it's more precise to use the function hack. Since we're already using that hack today, and since it will be fixed as part of my work on AbstractComponent, I think we should use the function representation for now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok~

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I must be missing something, but changing it to a function declaration seems break the validation.

For example, this test fails, as I would expect.

But this one and this one do not.

Copy link
Contributor Author

@bvaughn bvaughn Nov 27, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling Suspense directly (as a plain function) e.g. Suspense({ maxDuration: 'abc' }) properly catches the error, but wrapping it in a createElement call e.g. <Suspense maxDuration="abc" /> does not.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -250,6 +250,12 @@ declare module react {
declare export var ConcurrentMode: ({children: ?React$Node}) => React$Node; // 16.7+
declare export var StrictMode: ({children: ?React$Node}) => React$Node;

declare export var Suspense: React$ComponentType<{
children?: ?React$Node,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this needed?
IIUC, this says that it may or may not have children, and they have to be react nodes. I think that's the default.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

whoa, that seems like a bug – composite components should only take children if it's explicitly specified (it's only host components like 'div' that always take React$Node children)

Copy link
Contributor Author

@bvaughn bvaughn Nov 27, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on the context Provider and Consumer types above, I assumed an explicit children prop was needed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, children is the part of our react model that I'm still not totally familiar with. I'm glad we're having this conversation because it's forcing me to look into it more.

@sophiebits: I'll have to do some more investigation. Requiring an explicit children prop would be an extremely disruptive change at this point.

@bvaughn: I just took a look at our react docs to see what we say about children types. It looks like we have conflicting advice here and here. I think the ?React.Node bit is necessary, but I'm unclear about whether or not the ?children part is also necessary. I'll have to look into it more, but either way I'm ok with what you have here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sophiebits: Width subtyping allows extra properties to be passed into an object, so if the Props type is not exact then we allow children even if they are not specified explicitly. On the other hand, if the Props type is exact then we don't allow the extra properties.

Mystery solved!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for posterity: React's treatment of children:

  1. React.createElement takes varargs that become props.children (JSX children syntax turns into this)
  2. host component ('div' etc) children prop needs to be react nodes and React recurses into them
  3. composite component children are not special in any way; by convention people often use it to contain React nodes then render <div>{props.children}</div> or similar, but they can also be other data types (eg: the "render props" pattern often has children be a function)

@bvaughn
Copy link
Contributor Author

bvaughn commented Nov 27, 2018

I pushed a change to make Suspense "fallback" prop optional but I didn't change the component type yet because it looks broken.

@jbrown215
Copy link
Contributor

jbrown215 commented Nov 28, 2018

Yea, that looks like a bug to me.

The only downside I see with ComponentType is that we won't correctly check the ref pseudo-prop. Other than that, it's just a strange representation. I'd rather not block people from using it just because it's strange and has unsafe ref typing. I'm ok with this as is.

AbstractComponent is far enough along that I don't think this problem will be around for long.

Copy link
Contributor

@facebook-github-bot facebook-github-bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jbrown215 has imported this pull request. If you are a Facebook employee, you can view this diff on Phabricator.

@bvaughn
Copy link
Contributor Author

bvaughn commented Nov 28, 2018

Sweet! Thanks for the udpate!

@jbrown215
Copy link
Contributor

@bvaughn: I think the tests need to be re-recorded again.

@bvaughn
Copy link
Contributor Author

bvaughn commented Nov 28, 2018

Yeah, I noticed. I guess something landed in master that caused conflicts.

I'm in the process of rebasing and rerecording now.

@bvaughn
Copy link
Contributor Author

bvaughn commented Nov 28, 2018

I noticed something wrong with the useCallback def that I'm going to go ahead and toss in here too.

@bvaughn
Copy link
Contributor Author

bvaughn commented Nov 28, 2018

Okay. I think I fixed the bad useCallback definition (and tests) via e33b4b5. Another round of review, pls!

Edit not sure why CI is failing:

=== ERROR while compiling core_kernel.v0.11.1 ================================#
context 2.0.0 | win32/x86_64 | ocaml-variants.4.05.0+mingw64c | git+https:/fdopen/opam-repository-mingw.git#opam2

@bvaughn
Copy link
Contributor Author

bvaughn commented Nov 29, 2018

Okay. useCallback updated (again) as well as lazy. Let's see if CI is happier.

Copy link
Contributor

@facebook-github-bot facebook-github-bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jbrown215 has imported this pull request. If you are a Facebook employee, you can view this diff on Phabricator.

@TrySound
Copy link
Contributor

Can useRef current be not nullable since it has default value?

@jbrown215
Copy link
Contributor

The default value isn't required :(. This is something Brian and I did discuss too

@bvaughn
Copy link
Contributor Author

bvaughn commented Nov 29, 2018

FB internal lint failure because of the lack of a newline at the end of the lazy test :( so I rebased once more and fixed that along the way.

Copy link
Contributor

@facebook-github-bot facebook-github-bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bvaughn has imported this pull request. If you are a Facebook employee, you can view this diff on Phabricator.

gabelevi pushed a commit that referenced this pull request Dec 4, 2018
Summary:
Resolves #7219

- [x] 2fdcda5: Removed `React.useMutationEffect` hook (facebook/react/pull/14336) and updated tests. This hook was only ever exposed in an alpha release of 16.7 and so it should be safe to remove in a non-backwards-compatible way.
- [x] dabf49e: Add `React.Suspense` component and tests. (No final public documentation exists for `Suspense` yet– although we're working on it. For the time being, [tests like this](https:/facebook/react/blob/master/packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js) can serve as a reference.)
- [x] 8df05bd: Fixed invalid `useCallback` definition and tests.
- [x] 063b022: Fixed invalid `React.lazy` signature to reflect that a `Promise` with a "default" property should be returned (rather than a React component).
Pull Request resolved: #7213

Reviewed By: jbrown215

Differential Revision: D13222880

Pulled By: bvaughn

fbshipit-source-id: 9e1b48ace984051928c4ab92fa61182ad70f7119
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

React.lazy has an incorrect type definition
7 participants