Skip to content

Commit

Permalink
[react] Port ElementRef utility type from Flow (#43201)
Browse files Browse the repository at this point in the history
* [react] Port ElementRef utility type from Flow

https://flow.org/en/docs/react/types/#toc-react-elementref

* [react] Use least amount of required typing

* [react] Support forwarded refs with ElementRef

* [react] Use correct function component return type

* [react] Add ElementRef test for memoized ref forwarding component

* [react] Add ElementRef test for lazy component

* [react] Match function components with props

* [react] Fix test-case by moving type around

* [react] Fix flaky test-case

* [recharts] Remove deprecated CSS properties

These have been removed from `lib.dom.d.ts`:
https://github.com/microsoft/TypeScript/pull/37464/files#r395692943
  • Loading branch information
alloy committed Mar 20, 2020
1 parent 1b0bb5a commit 67fdf14
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 5 deletions.
40 changes: 38 additions & 2 deletions types/react/index.d.ts
Expand Up @@ -82,17 +82,53 @@ declare namespace React {
| ((props: P) => ReactElement | null)
| (new (props: P) => Component<P, any>);

type Key = string | number;

interface RefObject<T> {
readonly current: T | null;
}
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
> = 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>
? E
: never
: never;

type ComponentState = any;

type Key = string | number;

/**
* @internal You shouldn't need to use this type since you never see these attributes
* inside your component or have to validate them.
Expand Down
17 changes: 16 additions & 1 deletion 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 Expand Up @@ -542,8 +554,11 @@ const mappedChildrenArray2 = React.Children.map(numberChildren, num => num);
const mappedChildrenArray3 = React.Children.map(elementChildren, element => element);
// $ExpectType (string | Element)[]
const mappedChildrenArray4 = React.Children.map(mixedChildren, elementOrString => elementOrString);
// $ExpectType Key[]
// This test uses a conditional type because otherwise it gets flaky and can resolve to either Key or ReactText, both
// of which are aliases for `string | number`.
const mappedChildrenArray5 = React.Children.map(singlePluralChildren, element => element.key);
// $ExpectType true
type mappedChildrenArray5Type = typeof mappedChildrenArray5 extends React.Key[] ? true : false;
// $ExpectType string[]
const mappedChildrenArray6 = React.Children.map(renderPropsChildren, element => element.name);
// The return type may not be an array
Expand Down
4 changes: 2 additions & 2 deletions types/recharts/index.d.ts
Expand Up @@ -62,9 +62,9 @@ export type ReferenceLinePosition = 'start' | 'middle' | 'end';
export type PickedCSSStyleDeclarationKeys =
'alignmentBaseline' | 'baselineShift' | 'clip' | 'clipPath' | 'clipRule' | 'color' |
'colorInterpolationFilters' | 'cursor' | 'direction' | 'display' | 'dominantBaseline' |
'enableBackground' | 'fill' | 'fillRule' | 'filter' | 'floodColor' |
'fill' | 'fillRule' | 'filter' | 'floodColor' |
'floodOpacity' | 'font' | 'fontFamily' | 'fontStretch' | 'fontStyle' | 'fontVariant' |
'glyphOrientationHorizontal' | 'glyphOrientationVertical' | 'letterSpacing' | 'lightingColor' |
'glyphOrientationVertical' | 'letterSpacing' | 'lightingColor' |
'markerEnd' | 'markerMid' | 'markerStart' | 'mask' | 'overflow' | 'pointerEvents' |
'stopColor' | 'strokeDasharray' | 'strokeLinecap' | 'strokeLinejoin' | 'textAnchor' |
'textDecoration' | 'unicodeBidi' | 'visibility' | 'writingMode' | 'transform';
Expand Down

0 comments on commit 67fdf14

Please sign in to comment.