-
-
Notifications
You must be signed in to change notification settings - Fork 675
MentionWarnings [nfc]: Convert to function component. #4942
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
Conversation
src/compose/ComposeBox.js
Outdated
// but we need our `react-redux` wrapper to be aware of | ||
// `{ forwardRef: true }`, since we use that. | ||
// Here, Flow seems to infer the correct type based on usage. | ||
mentionWarnings = React.createRef(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately, none of these three worked (see errors alongside):
React.createRef<React$ElementRef<MentionWarnings>>()
Error ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ src/compose/ComposeBox.js:142:54
Cannot use MentionWarnings as a type. A name can be used as a type only if it refers to a type, interface, class, or
enum definition. To get the type of a non-class value, use typeof. [value-as-type]
139│ topicInputRef = React.createRef<$FlowFixMe>();
140│
141│ // Here, Flow seems to infer the correct type based on usage.
142│ mentionWarnings = React.createRef<React$ElementRef<MentionWarnings>>();
143│
144│ inputBlurTimeoutId: ?TimeoutID = null;
145│
React.createRef<MentionWarnings>()
(from Upgrade react-native-webview to latest, 11.6.4, on the path to the RN 64 upgrade. #4843 (comment))
Cannot use MentionWarnings as a type. A name can be used as a type only if it refers to a type, interface, class, or
enum definition. To get the type of a non-class value, use typeof. [value-as-type]
139│ topicInputRef = React.createRef<$FlowFixMe>();
140│
141│ // Here, Flow seems to infer the correct type based on usage.
142│ mentionWarnings = React.createRef<MentionWarnings>();
143│
144│ inputBlurTimeoutId: ?TimeoutID = null;
145│
React.createRef<typeof MentionWarnings>()
Error ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ src/compose/ComposeBox.js:304:37
Cannot call this.mentionWarnings.current?.handleMentionSubscribedCheck because property handleMentionSubscribedCheck is
missing in AbstractComponent [1]. [incompatible-use]
[1] 142│ mentionWarnings = React.createRef<typeof MentionWarnings>();
:
301│ if (lastWordPrefix === '@') {
302│ // https://github.com/eslint/eslint/issues/11045
303│ // eslint-disable-next-line no-unused-expressions
304│ this.mentionWarnings.current?.handleMentionSubscribedCheck(completion);
305│ }
306│ };
307│
Error ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ src/compose/ComposeBox.js:382:36
Cannot call this.mentionWarnings.current.clearMentionWarnings because property clearMentionWarnings is missing in
AbstractComponent [1]. [incompatible-use]
[1] 142│ mentionWarnings = React.createRef<typeof MentionWarnings>();
:
379│ this.setMessageInputValue('');
380│
381│ if (this.mentionWarnings.current) {
382│ this.mentionWarnings.current.clearMentionWarnings();
383│ }
384│
385│ dispatch(sendTypingStop(narrow));
Error ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ src/compose/ComposeBox.js:500:63
Cannot create MentionWarnings element because in property ref: [incompatible-type]
• Either object type [1] is incompatible with AbstractComponent [2] in property current.
• Or a call signature declaring the expected parameter / return type is missing in object type [3] but exists in
function type [4].
src/compose/ComposeBox.js
[2] 142│ mentionWarnings = React.createRef<typeof MentionWarnings>();
:
497│
498│ return (
499│ <View style={this.styles.wrapper}>
500│ <MentionWarnings narrow={narrow} stream={stream} ref={this.mentionWarnings} />
501│ <View style={[this.styles.autocompleteWrapper, { marginBottom: height }]}>
502│ <TopicAutocomplete
503│ isFocused={isTopicFocused}
/private/tmp/flow/flowlib_38b4cf0c/react.js
[4] 197│ | ((React$ElementRef<ElementType> | null) => mixed)
:
[3] 248│ ): {|current: null | T|};
src/compose/MentionWarnings.js
[1] 170│ const MentionWarnings: AbstractComponent<Props, ImperativeHandle> = forwardRef<
Found 3 errors
I found that the following would work, but I would appreciate hearing thoughts about it 🙂. I haven't yet been able to reconcile it with our findings in that linked discussion.
React.createRef<React$ElementRef<typeof MentionWarnings>>()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
React.createRef<React$ElementRef<typeof MentionWarnings>>()
Yeah, that looks correct to me.
In that discussion I wrote this:
Hmm, actually, now that I've reasoned through it more fully like that, I see that if my understanding of what's going on here is right, then
React$ElementRef<typeof Foo>
-- for anyFoo
we'll ever write -- is actually just a long way of spellingFoo
.
But that was based in part on this, which I now see is… incomplete!
Hmm, I guess
ref
can't be used on function components anyway.
What I really meant was that for a Foo
that is a class component-type, React$ElementRef<typeof Foo>
is actually just a long way of spelling Foo
.
As I now understand it, it is indeed true that ref
can't be used directly on a function component-type… but there are a bunch of other flavors of component type besides functions and classes, in particular the return values of forwardRef
.
More generally, I think the story is that React$ElementRef
takes anything that looks like React$AbstractComponent<Config, Instance>
, and it picks out that Instance
type. Here, we say right in the type annotation (new in this branch):
const MentionWarnings: AbstractComponent<Props, ImperativeHandle> = // …
so typeof MentionWarnings
is AbstractComponent<Props, ImperativeHandle>
;
so React$ElementRef<typeof MentionWarnings>
is ImperativeHandle
. Which is the type you want here, and the same as the type that Flow infers for it given just React.createRef()
with no type parameter. (It's using the fact that we pass this as the ref
pseudoprop to MentionWarnings
, down below.)
I started writing down some more notes on what I've worked out from studying these core-React types, and ended up expanding it into a new page in our docs tree:
https://github.com/zulip/zulip-mobile/blob/470ad571782501039369963eaf8d262802a2f07b/docs/background/react.md
See also where the Flow upstream docs describe ElementRef
, in a page I actually only just now found after writing that up. It doesn't cover what happens with forwardRef
. But I think it does explain what the major motivation for ElementRef
was, given that its behavior on class component-types and function component-types is so boring: it's for DOM element-types, where it maps 'div'
to HTMLDivElement
and 'input'
to HTMLInputElement
and so on.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Anyway, given that Flow successfully infers the type here just fine, and because spelling it out is kind of a mouthful, I'm happy to just leave it to be inferred.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for all that detail and the new doc! 😄
Anyway, given that Flow successfully infers the type here just fine, and because spelling it out is kind of a mouthful, I'm happy to just leave it to be inferred.
Now that I'm confident about how to spell it out, I think it might be better to do so. It seems to help Flow give better error messages.
If I do
- this.mentionWarnings.current.clearMentionWarnings();
+ this.mentionWarnings.current.clearMentionWarnings(123);
without spelling it out, then I get this error:
Error ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ src/compose/ComposeBox.js:500:63
Cannot create MentionWarnings element because a call signature declaring the expected parameter / return type is missing
in object type [1] but exists in function type [2] in property ref. [incompatible-type]
src/compose/ComposeBox.js
497│
498│ return (
499│ <View style={this.styles.wrapper}>
500│ <MentionWarnings narrow={narrow} stream={stream} ref={this.mentionWarnings} />
501│ <View style={[this.styles.autocompleteWrapper, { marginBottom: height }]}>
502│ <TopicAutocomplete
503│ isFocused={isTopicFocused}
/private/tmp/flow/flowlib_3f0b09e6/react.js
[2] 197│ | ((React$ElementRef<ElementType> | null) => mixed)
:
[1] 248│ ): {|current: null | T|};
But if I do that with spelling it out, I get this error:
Error ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ src/compose/ComposeBox.js:382:57
Cannot call this.mentionWarnings.current.clearMentionWarnings because no arguments are expected by function type [1].
[extra-arg]
src/compose/ComposeBox.js
379│ this.setMessageInputValue('');
380│
381│ if (this.mentionWarnings.current) {
382│ this.mentionWarnings.current.clearMentionWarnings(123);
383│ }
384│
385│ dispatch(sendTypingStop(narrow));
src/compose/MentionWarnings.js
[1] 37│ clearMentionWarnings(): void,
Which is more specific and nicer. 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm indeed -- good thought to check that!
That's a good reason to write down the annotation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @chrisbobbe ! Comments below. One is long, but the length is all background; all are simple to handle.
src/compose/ComposeBox.js
Outdated
// but we need our `react-redux` wrapper to be aware of | ||
// `{ forwardRef: true }`, since we use that. | ||
// Here, Flow seems to infer the correct type based on usage. | ||
mentionWarnings = React.createRef(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
React.createRef<React$ElementRef<typeof MentionWarnings>>()
Yeah, that looks correct to me.
In that discussion I wrote this:
Hmm, actually, now that I've reasoned through it more fully like that, I see that if my understanding of what's going on here is right, then
React$ElementRef<typeof Foo>
-- for anyFoo
we'll ever write -- is actually just a long way of spellingFoo
.
But that was based in part on this, which I now see is… incomplete!
Hmm, I guess
ref
can't be used on function components anyway.
What I really meant was that for a Foo
that is a class component-type, React$ElementRef<typeof Foo>
is actually just a long way of spelling Foo
.
As I now understand it, it is indeed true that ref
can't be used directly on a function component-type… but there are a bunch of other flavors of component type besides functions and classes, in particular the return values of forwardRef
.
More generally, I think the story is that React$ElementRef
takes anything that looks like React$AbstractComponent<Config, Instance>
, and it picks out that Instance
type. Here, we say right in the type annotation (new in this branch):
const MentionWarnings: AbstractComponent<Props, ImperativeHandle> = // …
so typeof MentionWarnings
is AbstractComponent<Props, ImperativeHandle>
;
so React$ElementRef<typeof MentionWarnings>
is ImperativeHandle
. Which is the type you want here, and the same as the type that Flow infers for it given just React.createRef()
with no type parameter. (It's using the fact that we pass this as the ref
pseudoprop to MentionWarnings
, down below.)
I started writing down some more notes on what I've worked out from studying these core-React types, and ended up expanding it into a new page in our docs tree:
https://github.com/zulip/zulip-mobile/blob/470ad571782501039369963eaf8d262802a2f07b/docs/background/react.md
See also where the Flow upstream docs describe ElementRef
, in a page I actually only just now found after writing that up. It doesn't cover what happens with forwardRef
. But I think it does explain what the major motivation for ElementRef
was, given that its behavior on class component-types and function component-types is so boring: it's for DOM element-types, where it maps 'div'
to HTMLDivElement
and 'input'
to HTMLInputElement
and so on.
src/compose/ComposeBox.js
Outdated
// but we need our `react-redux` wrapper to be aware of | ||
// `{ forwardRef: true }`, since we use that. | ||
// Here, Flow seems to infer the correct type based on usage. | ||
mentionWarnings = React.createRef(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Anyway, given that Flow successfully infers the type here just fine, and because spelling it out is kind of a mouthful, I'm happy to just leave it to be inferred.
An instance of zulip#4837. See the previous commit for more details, and see zulip#4942 (comment) for why we do React.createRef<React$ElementRef<typeof MentionWarnings>>() instead of putting something else for createRef's type parameter.
9b2e282
to
c25412a
Compare
Thanks for the review and that doc! Revision pushed. |
This one's a little unusual because of the design where imperative methods are exposed for a parent of MentionWarnings to call. Use `forwardRef` and `useImperativeHandle`, as the Hooks way to implement this design; see https://reactjs.org/docs/hooks-reference.html#useimperativehandle. Note that "forwardRef" already appears near the bottom of MentionWarnings.js. There, we're telling `connect` to forward the ref to the component wrapped by the HOC. That one will disappear in the next commit, when we stop using `connect`. But the second one will remain, since that's our ticket to allowing callers to take a `ref` to our function component; see the doc linked above, and https://reactjs.org/docs/refs-and-the-dom.html#refs-and-function-components.
An instance of zulip#4837. See the previous commit for more details, and see zulip#4942 (comment) for why we do React.createRef<React$ElementRef<typeof MentionWarnings>>() instead of putting something else for createRef's type parameter.
This gets us closer to switching to Flow's new "Types-First" mode; that's zulip#4907.
c25412a
to
e6428f3
Compare
Thanks! Merged. |
For annotating the default export of
MentionWarnings
, for #4907, I think it actually may be easier to first convert it to a function component and remove theconnect
call. This is a little surprising because it had been on my list of components that I thought would be pretty tricky to convert.I didn't find a way to get thorough type-checking of
MentionWarnings
's methods thatComposeBox
was calling on aref
, until I convertedMentionWarnings
to a function component. After this PR, we should get that type-checking, without having to manually repeat any types.I tested the component on my iPhone and it worked as I expected it to.