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

[react] Port ElementRef utility type from Flow #43201

Merged
merged 10 commits into from Mar 20, 2020
36 changes: 36 additions & 0 deletions types/react/index.d.ts
Expand Up @@ -90,6 +90,42 @@ declare namespace React {
type RefCallback<T> = { bivarianceHack(instance: T | null): void }["bivarianceHack"];
type Ref<T> = RefCallback<T> | RefObject<T> | null;
type LegacyRef<T> = string | Ref<T>;
/**
* Gets the instance type for a React element. The instance will be different for various component types:
*
* - React class components will be the class instance. So if you had `class Foo extends React.Component<{}> {}`
* and used `React.ElementRef<typeof Foo>` then the type would be the instance of `Foo`.
* - React stateless functional components do not have a backing instance and so `React.ElementRef<typeof Bar>`
* (when `Bar` is `function Bar() {}`) will give you the `undefined` type.
* - JSX intrinsics like `div` will give you their DOM instance. For `React.ElementRef<'div'>` that would be
* `HTMLDivElement`. For `React.ElementRef<'input'>` that would be `HTMLInputElement`.
* - React stateless functional components that forward a `ref` will give you the `ElementRef` of the forwarded
* to component.
*
* `C` must be the type _of_ a React component so you need to use typeof as in React.ElementRef<typeof MyComponent>.
*
* @todo In Flow, this works a little different with forwarded refs and the `AbstractComponent` that
* `React.forwardRef()` returns.
*/
type ElementRef<
C extends
| ForwardRefExoticComponent<any>
| { new (props: any): Component<any> }
| ((props: any, context?: any) => ReactElement | null)
| keyof JSX.IntrinsicElements
Copy link
Contributor

Choose a reason for hiding this comment

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

How about for readability:

type ElementCType = ComponentClass<any> | FunctionComponent<any> | keyof JSX.IntrinsicElements;
type ElementRef<C extends ElementCType> = ...

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I didn’t want to add intermediate types into this namespace, but if people agree then I’m definitely not against it.

Copy link
Contributor

Choose a reason for hiding this comment

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

This already exists JSXElementConstructor.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I tried it, but didn’t feel like readability improved by using a type alias in this first part but then use different types below. I think if there were very narrowly defined type aliases for these then that might be helpful, but that would introduce types that don’t exist upstream and so I’d like an emphatic “yes please” before I do that.

> = C extends ForwardRefExoticComponent<infer FP>
? FP extends RefAttributes<infer FC>
? FC
: never
: C extends { new (props: any): Component<any> }
? InstanceType<C>
: C extends ((props: any, context?: any) => ReactElement | null)
? undefined
: C extends keyof JSX.IntrinsicElements
? JSX.IntrinsicElements[C] extends DOMAttributes<infer E>
alloy marked this conversation as resolved.
Show resolved Hide resolved
? E
: never
: never;

type ComponentState = any;

Expand Down
12 changes: 12 additions & 0 deletions types/react/test/index.ts
Expand Up @@ -440,6 +440,18 @@ function RefCarryingComponent() {
);
}

const MemoizedForwardingRefComponent = React.memo(ForwardingRefComponent);
const LazyComponent = React.lazy(() => Promise.resolve({ default: RefComponent }));

type ClassComponentAsRef = React.ElementRef<typeof RefComponent>; // $ExpectType RefComponent
type FunctionComponentWithoutPropsAsRef = React.ElementRef<typeof RefCarryingComponent>; // $ExpectType undefined
type FunctionComponentWithPropsAsRef = React.ElementRef<typeof FunctionComponent>; // $ExpectType undefined
type HTMLIntrinsicAsRef = React.ElementRef<'div'>; // $ExpectType HTMLDivElement
type SVGIntrinsicAsRef = React.ElementRef<'svg'>; // $ExpectType SVGSVGElement
type ForwardingRefComponentAsRef = React.ElementRef<typeof ForwardingRefComponent>; // $ExpectType RefComponent
type MemoizedForwardingRefComponentAsRef = React.ElementRef<typeof MemoizedForwardingRefComponent>; // $ExpectType RefComponent
type LazyComponentAsRef = React.ElementRef<typeof LazyComponent>; // $ExpectType RefComponent

//
// Attributes
// --------------------------------------------------------------------------
Expand Down