From 4424bfd2e54861e7061401ab8c0eae342b20daba Mon Sep 17 00:00:00 2001 From: eps1lon Date: Sun, 3 Oct 2021 13:00:56 +0200 Subject: [PATCH 01/29] [react] React 18 types --- types/react/index.d.ts | 13 ++++--------- types/react/test/index.ts | 12 +----------- types/react/test/tsx.tsx | 16 +++++----------- 3 files changed, 10 insertions(+), 31 deletions(-) diff --git a/types/react/index.d.ts b/types/react/index.d.ts index 958002af94c677..04c3f16d6e8f02 100644 --- a/types/react/index.d.ts +++ b/types/react/index.d.ts @@ -497,12 +497,7 @@ declare namespace React { forceUpdate(callback?: () => void): void; render(): ReactNode; - // React.Props is now deprecated, which means that the `children` - // property is not available on `P` by default, even though you can - // always pass children as variadic arguments to `createElement`. - // In the future, if we can define its call signature conditionally - // on the existence of `children` in `P`, then we should remove this. - readonly props: Readonly

& Readonly<{ children?: ReactNode | undefined }>; + readonly props: Readonly

; state: Readonly; /** * @deprecated @@ -548,7 +543,7 @@ declare namespace React { type FC

= FunctionComponent

; interface FunctionComponent

{ - (props: PropsWithChildren

, context?: any): ReactElement | null; + (props: P, context?: any): ReactElement | null; propTypes?: WeakValidationMap

| undefined; contextTypes?: ValidationMap | undefined; defaultProps?: Partial

| undefined; @@ -568,7 +563,7 @@ declare namespace React { type ForwardedRef = ((instance: T | null) => void) | MutableRefObject | null; interface ForwardRefRenderFunction { - (props: PropsWithChildren

, ref: ForwardedRef): ReactElement | null; + (props: P, ref: ForwardedRef): ReactElement | null; displayName?: string | undefined; // explicit rejected with `never` required due to // https://github.com/microsoft/TypeScript/issues/36826 @@ -862,7 +857,7 @@ declare namespace React { function memo

( Component: FunctionComponent

, - propsAreEqual?: (prevProps: Readonly>, nextProps: Readonly>) => boolean + propsAreEqual?: (prevProps: Readonly

, nextProps: Readonly

) => boolean ): NamedExoticComponent

; function memo>( Component: T, diff --git a/types/react/test/index.ts b/types/react/test/index.ts index bec90d264c33b1..da05b0601ecca9 100644 --- a/types/react/test/index.ts +++ b/types/react/test/index.ts @@ -227,16 +227,6 @@ LegacyStatelessComponent2.defaultProps = { foo: 42 }; -const FunctionComponent3: React.FunctionComponent = - // allows usage of props.children - // allows null return - props => props.foo ? DOM.div(null, props.foo, props.children) : null; - -const LegacyStatelessComponent3: React.SFC = - // allows usage of props.children - // allows null return - props => props.foo ? DOM.div(null, props.foo, props.children) : null; - // allows null as props const FunctionComponent4: React.FunctionComponent = props => null; @@ -768,7 +758,7 @@ declare var x: React.DOMElement<{ }, Element>; // React 16 should be able to render its children directly -class RenderChildren extends React.Component { +class RenderChildren extends React.Component<{ children?: React.ReactNode }> { render() { const { children } = this.props; return children !== undefined ? children : null; diff --git a/types/react/test/tsx.tsx b/types/react/test/tsx.tsx index 60b7884ffa4e40..9cb826edfecdb0 100644 --- a/types/react/test/tsx.tsx +++ b/types/react/test/tsx.tsx @@ -13,15 +13,9 @@ FunctionComponent.defaultProps = { }; ; ; - -const FunctionComponent2: React.FunctionComponent = ({ foo, children }) => { - return

{foo}{children}
; -}; -FunctionComponent2.displayName = "FunctionComponent4"; -FunctionComponent2.defaultProps = { - foo: 42 -}; -24; +// `FunctionComponent` has no `children` +// $ExpectError +24; const VoidFunctionComponent: React.VoidFunctionComponent = ({ foo }: SCProps) => { return
{foo}
; @@ -256,8 +250,8 @@ const Memoized4 = React.memo(React.forwardRef((props: {}, ref: React.Ref; const Memoized5 = React.memo<{ test: boolean }>( - prop => <>{prop.test && prop.children}, - (prevProps, nextProps) => nextProps.test ? prevProps.children === nextProps.children : prevProps.test + prop => <>{prop.test}, + (prevProps, nextProps) => nextProps.test === prevProps.test ); ; From c00f8f9b70c2dc4a74b43951a374436780ccc8de Mon Sep 17 00:00:00 2001 From: eps1lon Date: Sat, 9 Oct 2021 10:52:19 +0200 Subject: [PATCH 02/29] [react] Remove deprecated ReactType --- types/react/index.d.ts | 4 ---- types/react/test/tsx.tsx | 16 ++++++++-------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/types/react/index.d.ts b/types/react/index.d.ts index 04c3f16d6e8f02..8ff0a9c8fb7054 100644 --- a/types/react/index.d.ts +++ b/types/react/index.d.ts @@ -75,10 +75,6 @@ declare namespace React { [K in keyof JSX.IntrinsicElements]: P extends JSX.IntrinsicElements[K] ? K : never }[keyof JSX.IntrinsicElements] | ComponentType

; - /** - * @deprecated Please use `ElementType` - */ - type ReactType

= ElementType

; type ComponentType

= ComponentClass

| FunctionComponent

; type JSXElementConstructor

= diff --git a/types/react/test/tsx.tsx b/types/react/test/tsx.tsx index 9cb826edfecdb0..f2d56fce65a022 100644 --- a/types/react/test/tsx.tsx +++ b/types/react/test/tsx.tsx @@ -420,14 +420,14 @@ type ImgPropsWithoutRef = React.ComponentPropsWithoutRef<'img'>; // $ExpectType false type ImgPropsHasRef = 'ref' extends keyof ImgPropsWithoutRef ? true : false; -const HasClassName: React.ReactType<{ className?: string | undefined }> = 'a'; -const HasFoo: React.ReactType<{ foo: boolean }> = 'a'; // $ExpectError -const HasFoo2: React.ReactType<{ foo: boolean }> = (props: { foo: boolean }) => null; -const HasFoo3: React.ReactType<{ foo: boolean }> = (props: { foo: string }) => null; // $ExpectError -const HasHref: React.ReactType<{ href?: string | undefined }> = 'a'; -const HasHref2: React.ReactType<{ href?: string | undefined }> = 'div'; // $ExpectError +const HasClassName: React.ElementType<{ className?: string | undefined }> = 'a'; +const HasFoo: React.ElementType<{ foo: boolean }> = 'a'; // $ExpectError +const HasFoo2: React.ElementType<{ foo: boolean }> = (props: { foo: boolean }) => null; +const HasFoo3: React.ElementType<{ foo: boolean }> = (props: { foo: string }) => null; // $ExpectError +const HasHref: React.ElementType<{ href?: string | undefined }> = 'a'; +const HasHref2: React.ElementType<{ href?: string | undefined }> = 'div'; // $ExpectError -const CustomElement: React.ReactType = 'my-undeclared-element'; // $ExpectError +const CustomElement: React.ElementType = 'my-undeclared-element'; // $ExpectError // custom elements now need to be declared as intrinsic elements declare global { @@ -438,7 +438,7 @@ declare global { } } -const CustomElement2: React.ReactType = 'my-declared-element'; +const CustomElement2: React.ElementType = 'my-declared-element'; interface TestPropTypesProps { foo: string; From 98cf44bbd31ada5b35387ab2dd6418c10e52a563 Mon Sep 17 00:00:00 2001 From: eps1lon Date: Sat, 9 Oct 2021 11:28:10 +0200 Subject: [PATCH 03/29] [react] Remove deprecated SFCElement --- types/react/index.d.ts | 5 ----- types/react/test/index.ts | 6 ------ 2 files changed, 11 deletions(-) diff --git a/types/react/index.d.ts b/types/react/index.d.ts index 8ff0a9c8fb7054..daf4ec0faf71e3 100644 --- a/types/react/index.d.ts +++ b/types/react/index.d.ts @@ -151,11 +151,6 @@ declare namespace React { P = Pick, Exclude, 'key' | 'ref'>> > extends ReactElement> { } - /** - * @deprecated Please use `FunctionComponentElement` - */ - type SFCElement

= FunctionComponentElement

; - interface FunctionComponentElement

extends ReactElement> { ref?: ('ref' extends keyof P ? P extends { ref?: infer R | undefined } ? R : never : never) | undefined; } diff --git a/types/react/test/index.ts b/types/react/test/index.ts index da05b0601ecca9..a995dfb1269c70 100644 --- a/types/react/test/index.ts +++ b/types/react/test/index.ts @@ -247,8 +247,6 @@ const functionComponentFactoryElement: React.FunctionComponentElement = const legacyStatelessComponentFactory: React.SFCFactory = React.createFactory(FunctionComponent); -const legacyStatelessComponentFactoryElement: React.SFCElement = - legacyStatelessComponentFactory(props); const domFactory: React.DOMFactory, Element> = React.createFactory("div"); @@ -261,8 +259,6 @@ const elementNoState: React.CElement = React.crea const elementNullProps: React.CElement<{}, ModernComponentNoPropsAndState> = React.createElement(ModernComponentNoPropsAndState, null); const functionComponentElement: React.FunctionComponentElement = React.createElement(FunctionComponent, scProps); const functionComponentElementNullProps: React.FunctionComponentElement = React.createElement(FunctionComponent4, null); -const legacyStatelessComponentElement: React.SFCElement = React.createElement(FunctionComponent, scProps); -const legacyStatelessComponentElementNullProps: React.SFCElement = React.createElement(FunctionComponent4, null); const domElement: React.DOMElement, HTMLDivElement> = React.createElement("div"); const domElementNullProps = React.createElement("div", null); const htmlElement = React.createElement("input", { type: "text" }); @@ -304,8 +300,6 @@ const clonedElement3: React.CElement = }); const clonedfunctionComponentElement: React.FunctionComponentElement = React.cloneElement(functionComponentElement, { foo: 44 }); -const clonedlegacyStatelessComponentElement: React.SFCElement = - React.cloneElement(legacyStatelessComponentElement, { foo: 44 }); // Clone base DOMElement const clonedDOMElement: React.DOMElement, HTMLDivElement> = React.cloneElement(domElement, { From b19623fc368817725d28ef7a785d4aa19f87fa04 Mon Sep 17 00:00:00 2001 From: eps1lon Date: Sat, 9 Oct 2021 12:00:36 +0200 Subject: [PATCH 04/29] [react] Remove deprecated StatelessComponent --- types/react/index.d.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/types/react/index.d.ts b/types/react/index.d.ts index daf4ec0faf71e3..2dcdd10fc0300b 100644 --- a/types/react/index.d.ts +++ b/types/react/index.d.ts @@ -523,14 +523,6 @@ declare namespace React { */ type SFC

= FunctionComponent

; - /** - * @deprecated as of recent React versions, function components can no - * longer be considered 'stateless'. Please use `FunctionComponent` instead. - * - * @see [React Hooks](https://reactjs.org/docs/hooks-intro.html) - */ - type StatelessComponent

= FunctionComponent

; - type FC

= FunctionComponent

; interface FunctionComponent

{ From f800f4fa03a7fc7283f718b1af72fc5d072595af Mon Sep 17 00:00:00 2001 From: eps1lon Date: Sat, 9 Oct 2021 13:25:25 +0200 Subject: [PATCH 05/29] [react] Remove deprecated SFC --- types/react-dom/test-utils/index.d.ts | 4 ++-- types/react/index.d.ts | 8 -------- types/react/test/index.ts | 16 ---------------- 3 files changed, 2 insertions(+), 26 deletions(-) diff --git a/types/react-dom/test-utils/index.d.ts b/types/react-dom/test-utils/index.d.ts index 25bce6534a1a34..567dcf31c9ec3a 100644 --- a/types/react-dom/test-utils/index.d.ts +++ b/types/react-dom/test-utils/index.d.ts @@ -2,7 +2,7 @@ import { AbstractView, Component, ComponentClass, ReactElement, ReactInstance, ClassType, DOMElement, FunctionComponentElement, CElement, - ReactHTMLElement, DOMAttributes, SFC + ReactHTMLElement, DOMAttributes, FC } from 'react'; import * as ReactTestUtils from "."; @@ -194,7 +194,7 @@ export function isElementOfType

, T extends Element>( * Returns `true` if `element` is a React element whose type is of a React `componentClass`. */ export function isElementOfType

( - element: ReactElement, type: SFC

): element is FunctionComponentElement

; + element: ReactElement, type: FC

): element is FunctionComponentElement

; /** * Returns `true` if `element` is a React element whose type is of a React `componentClass`. */ diff --git a/types/react/index.d.ts b/types/react/index.d.ts index 2dcdd10fc0300b..3f96afcd4e3fa1 100644 --- a/types/react/index.d.ts +++ b/types/react/index.d.ts @@ -515,14 +515,6 @@ declare namespace React { // Class Interfaces // ---------------------------------------------------------------------- - /** - * @deprecated as of recent React versions, function components can no - * longer be considered 'stateless'. Please use `FunctionComponent` instead. - * - * @see [React Hooks](https://reactjs.org/docs/hooks-intro.html) - */ - type SFC

= FunctionComponent

; - type FC

= FunctionComponent

; interface FunctionComponent

{ diff --git a/types/react/test/index.ts b/types/react/test/index.ts index a995dfb1269c70..fba058d8a32a3b 100644 --- a/types/react/test/index.ts +++ b/types/react/test/index.ts @@ -219,14 +219,6 @@ FunctionComponent2.defaultProps = { foo: 42 }; -const LegacyStatelessComponent2: React.SFC = - // props is contextually typed - props => DOM.div(null, props.foo); -LegacyStatelessComponent2.displayName = "LegacyStatelessComponent2"; -LegacyStatelessComponent2.defaultProps = { - foo: 42 -}; - // allows null as props const FunctionComponent4: React.FunctionComponent = props => null; @@ -279,10 +271,6 @@ function foo3(child: React.ComponentClass<{ name: string }> | React.FunctionComp React.createElement(child, { name: "bar" }); } -function foo4(child: React.ComponentClass<{ name: string }> | React.SFC<{ name: string }> | string) { - React.createElement(child, { name: "bar" }); -} - // React.cloneElement const clonedElement: React.CElement = React.cloneElement(element, { foo: 43 }); @@ -770,10 +758,6 @@ React.createElement(Memoized2, { bar: 'string' }); const specialSfc1: React.ExoticComponent = Memoized1; const functionComponent: React.FunctionComponent = Memoized2; -const sfc: React.SFC = Memoized2; -// this $ExpectError is failing on TypeScript@next -// // $ExpectError Property '$$typeof' is missing in type -// const specialSfc2: React.SpecialSFC = props => null; const propsWithChildren: React.PropsWithChildren = { hello: "world", From f219c4da4239328f0d578ad7c877596435530637 Mon Sep 17 00:00:00 2001 From: eps1lon Date: Sun, 10 Oct 2021 12:03:12 +0200 Subject: [PATCH 06/29] [react] Remove deprecated RefForwardingComponent --- types/react/index.d.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/types/react/index.d.ts b/types/react/index.d.ts index 3f96afcd4e3fa1..ef7bbfc293da6a 100644 --- a/types/react/index.d.ts +++ b/types/react/index.d.ts @@ -552,12 +552,6 @@ declare namespace React { propTypes?: never | undefined; } - /** - * @deprecated Use ForwardRefRenderFunction. forwardRef doesn't accept a - * "real" component. - */ - interface RefForwardingComponent extends ForwardRefRenderFunction {} - interface ComponentClass

extends StaticLifecycle { new (props: P, context?: any): Component; propTypes?: WeakValidationMap

| undefined; From c112c30aa98c7d6858c084e1327d7a88aaff7215 Mon Sep 17 00:00:00 2001 From: eps1lon Date: Sun, 10 Oct 2021 12:15:20 +0200 Subject: [PATCH 07/29] [react] Remove deprecated Props --- types/react/index.d.ts | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/types/react/index.d.ts b/types/react/index.d.ts index ef7bbfc293da6a..41ca8e102d9ccf 100644 --- a/types/react/index.d.ts +++ b/types/react/index.d.ts @@ -1280,26 +1280,6 @@ declare namespace React { // Props / DOM Attributes // ---------------------------------------------------------------------- - /** - * @deprecated. This was used to allow clients to pass `ref` and `key` - * to `createElement`, which is no longer necessary due to intersection - * types. If you need to declare a props object before passing it to - * `createElement` or a factory, use `ClassAttributes`: - * - * ```ts - * var b: Button | null; - * var props: ButtonProps & ClassAttributes; +}); + +interface AppState { + name: string; + age: number; +} + +type AppActions = + | { type: "getOlder" } + | { type: "resetAge" }; + +function reducer(s: AppState, action: AppActions): AppState { + switch (action.type) { + case "getOlder": + return { ...s, age: s.age + 1 }; + case "resetAge": + return { ...s, age: 0 }; + } +} + +const initialState = { + name: "Daniel", + age: 26 +}; + +export function App() { + const [state, dispatch] = React.useReducer(reducer, initialState); + const birthdayRef = React.useRef>(null); + + React.useLayoutEffect(() => { + if (birthdayRef.current !== null) { + birthdayRef.current.fancyClick(); + } else { + // this looks redundant but it ensures the type actually has "null" in it instead of "never" + // $ExpectType null + birthdayRef.current; + } + }); + + return <> + + dispatch({ type: "getOlder" })}> + Birthday time! + + dispatch({ type: "resetAge" })}> + Let's start over. + + ; +} + +interface Context { + test: true; +} +const context = React.createContext({ test: true }); + +function useEveryHook(ref: React.Ref<{ id: number }>|undefined): () => boolean { + const value: Context = React.useContext(context); + const [, setState] = React.useState(() => 0); + // Bonus typescript@next version + // const [reducerState, dispatch] = React.useReducer(reducer, true as const, arg => arg && initialState); + // Compile error in typescript@3.0 but not in typescript@3.1. + // const [reducerState, dispatch] = React.useReducer(reducer, true as true, arg => arg && initialState); + const [reducerState, dispatch] = React.useReducer(reducer, true as true, (arg: true): AppState => arg && initialState); + + const [, simpleDispatch] = React.useReducer(v => v + 1, 0); + + // inline object, to (manually) check if autocomplete works + React.useReducer(reducer, { age: 42, name: 'The Answer' }); + + // Implicit any + // $ExpectError + const anyCallback = React.useCallback(value => { + // $ExpectType any + return value; + }, []); + // $ExpectType any + anyCallback({}); + // $ExpectType (value: string) => number + const typedCallback = React.useCallback((value: string) => { + return Number(value); + }, []); + // $ExpectType number + typedCallback("1"); + // Argument of type '{}' is not assignable to parameter of type 'string'. + // $ExpectError + typedCallback({}); + + // test useRef and its convenience overloads + // $ExpectType MutableRefObject + React.useRef(0); + + // these are not very useful (can't assign anything else to .current) + // but it's the only safe way to resolve them + // $ExpectType MutableRefObject + React.useRef(null); + // $ExpectType MutableRefObject + React.useRef(undefined); + + // |null convenience overload + // it should _not_ be mutable if the generic argument doesn't include null + // $ExpectType RefObject + React.useRef(null); + // but it should be mutable if it does (i.e. is not the convenience overload) + // $ExpectType MutableRefObject + React.useRef(null); + + // |undefined convenience overload + // with no contextual type or generic argument it should default to undefined only (not {} or unknown!) + // $ExpectType MutableRefObject + React.useRef(); + // $ExpectType MutableRefObject + React.useRef(); + // don't just accept a potential undefined if there is a generic argument + // $ExpectError + React.useRef(undefined); + // make sure once again there's no |undefined if the initial value doesn't either + // $ExpectType MutableRefObject + React.useRef(1); + // and also that it is not getting erased if the parameter is wider + // $ExpectType MutableRefObject + React.useRef(1); + + // should be contextually typed + const a: React.MutableRefObject = React.useRef(undefined); + const b: React.MutableRefObject = React.useRef(); + const c: React.MutableRefObject = React.useRef(null); + const d: React.RefObject = React.useRef(null); + + const id = React.useMemo(() => Math.random(), []); + React.useImperativeHandle(ref, () => ({ id }), [id]); + // was named like this in the first alpha, renamed before release + // $ExpectError + React.useImperativeMethods(ref, () => ({}), [id]); + + // make sure again this is not going to the |null convenience overload + // $ExpectType MutableRefObject + const didLayout = React.useRef(false); + + React.useLayoutEffect(() => { + setState(1); + setState(prevState => prevState - 1); + didLayout.current = true; + }, []); + React.useEffect(() => { + dispatch({ type: 'getOlder' }); + // $ExpectError + dispatch(); + + simpleDispatch(); + setState(reducerState.age); + }, []); + + // effects are only allowed to either be actually void or return actually void functions + React.useEffect(() => () => {}); + // indistinguishable + React.useEffect(() => () => undefined); + // $ExpectError + React.useEffect(() => null); + // $ExpectError + React.useEffect(() => Math.random() ? null : undefined); + // $ExpectError + React.useEffect(() => () => null); + // $ExpectError + React.useEffect(() => () => Math.random() ? null : undefined); + // $ExpectError + React.useEffect(() => async () => {}); + // $ExpectError + React.useEffect(async () => () => {}); + + React.useDebugValue(id, value => value.toFixed()); + React.useDebugValue(id); + + // allow passing an explicit undefined + React.useMemo(() => {}, undefined); + // but don't allow it to be missing + // $ExpectError + React.useMemo(() => {}); + + // useState convenience overload + // default to undefined only (not that useful, but type-safe -- no {} or unknown!) + // $ExpectType undefined + React.useState()[0]; + // $ExpectType number | undefined + React.useState()[0]; + // default overload + // $ExpectType number + React.useState(0)[0]; + // $ExpectType undefined + React.useState(undefined)[0]; + // make sure the generic argument does reject actual potentially undefined inputs + // $ExpectError + React.useState(undefined)[0]; + // make sure useState does not widen + const [toggle, setToggle] = React.useState(false); + // $ExpectType boolean + toggle; + // make sure setState accepts a function + setToggle(r => !r); + + // useReducer convenience overload + + return React.useCallback(() => didLayout.current, []); +} + +const UsesEveryHook = React.forwardRef( + function UsesEveryHook(props: {}, ref?: React.Ref<{ id: number }>) { + // $ExpectType boolean + useEveryHook(ref)(); + + return null; + } +); +const everyHookRef = React.createRef<{ id: number }>(); +; + + { + // $ExpectType { id: number; } | null + ref; + }}/>; diff --git a/types/react/v17/test/index.ts b/types/react/v17/test/index.ts new file mode 100644 index 00000000000000..fba058d8a32a3b --- /dev/null +++ b/types/react/v17/test/index.ts @@ -0,0 +1,825 @@ +import * as React from "react"; +import * as ReactDOM from "react-dom"; +import * as ReactDOMServer from "react-dom/server"; +import * as PropTypes from "prop-types"; +import createFragment = require("react-addons-create-fragment"); +import * as LinkedStateMixin from "react-addons-linked-state-mixin"; +import * as PureRenderMixin from "react-addons-pure-render-mixin"; +import shallowCompare = require("react-addons-shallow-compare"); +import update = require("react-addons-update"); +import createReactClass = require("create-react-class"); +import * as DOM from "react-dom-factories"; + +// NOTE: forward declarations for tests +declare function setInterval(...args: any[]): any; +declare function clearInterval(...args: any[]): any; +declare var console: Console; +interface Console { + log(...args: any[]): void; +} + +interface Props { + hello: string; + world?: string | null | undefined; + foo: number; +} + +interface State { + inputValue?: string | null | undefined; + seconds?: number | undefined; +} + +interface Snapshot { + baz: string; +} + +interface Context { + someValue?: string | null | undefined; +} + +interface ChildContext { + someOtherValue: string; +} + +interface MyComponent extends React.Component { + reset(): void; +} + +// use any for ClassAttribute type sine we're using string refs +const props: Props & React.ClassAttributes = { + key: 42, + ref: "myComponent42", + hello: "world", + foo: 42 +}; + +const scProps: SCProps = { + foo: 42 +}; + +declare const container: Element; + +// +// Top-Level API +// -------------------------------------------------------------------------- +{ + interface State { + inputValue: string; + seconds: number; + } + class SettingStateFromCtorComponent extends React.Component { + constructor(props: Props) { + super(props); + // $ExpectError + this.state = { + inputValue: 'hello' + }; + } + render() { return null; } + } + + class BadlyInitializedState extends React.Component { + // $ExpectError -> this throws error on TS 2.6 uncomment once TS requirement is TS >= 2.7 + // state = { + // secondz: 0, + // inputValuez: 'hello' + // }; + render() { return null; } + } + class BetterPropsAndStateChecksComponent extends React.Component { + render() { return null; } + componentDidMount() { + // $ExpectError -> this will be true in next BC release where state is gonna be `null | Readonly` + console.log(this.state.inputValue); + } + mutateState() { + // $ExpectError + this.state = { + inputValue: 'hello' + }; + + // Even if state is not set, this is allowed by React + this.setState({ inputValue: 'hello' }); + this.setState((prevState, props) => { + // $ExpectError + props = { foo: 'nope' }; + // $ExpectError + props.foo = 'nope'; + + return { inputValue: prevState.inputValue + ' foo' }; + }); + } + mutateProps() { + // $ExpectError + this.props = {}; + // $ExpectError + this.props = { + key: 42, + ref: "myComponent42", + hello: "world", + foo: 42 + }; + } + } +} + +class ModernComponent extends React.Component + implements MyComponent, React.ChildContextProvider { + static propTypes: React.ValidationMap = { + hello: PropTypes.string.isRequired, + world: PropTypes.string, + foo: PropTypes.number.isRequired + }; + + static contextTypes: React.ValidationMap = { + someValue: PropTypes.string + }; + + static childContextTypes: React.ValidationMap = { + someOtherValue: PropTypes.string.isRequired + }; + + context: Context = {}; + + getChildContext() { + return { + someOtherValue: "foo" + }; + } + + state = { + inputValue: this.context.someValue, + seconds: this.props.foo + }; + + reset() { + this._myComponent.reset(); + this.setState({ + inputValue: this.context.someValue, + seconds: this.props.foo + }); + } + + private readonly _myComponent: MyComponent; + private _input: HTMLInputElement | null; + + render() { + return DOM.div(null, + DOM.input({ + ref: input => this._input = input, + value: this.state.inputValue ? this.state.inputValue : undefined + }), + DOM.input({ + onChange: event => console.log(event.target) + })); + } + + shouldComponentUpdate(nextProps: Props, nextState: State, nextContext: any): boolean { + return shallowCompare(this, nextProps, nextState); + } + + getSnapshotBeforeUpdate(prevProps: Readonly) { + return { baz: `${prevProps.foo}baz` }; + } + + componentDidUpdate(prevProps: Readonly, prevState: Readonly, snapshot: Snapshot) { + return; + } +} + +class ModernComponentArrayRender extends React.Component { + render() { + return [DOM.h1({ key: "1" }, "1"), + DOM.h1({ key: "2" }, "2")]; + } +} + +class ModernComponentNoState extends React.Component { } +class ModernComponentNoPropsAndState extends React.Component { } + +interface SCProps { + foo?: number | undefined; +} + +function FunctionComponent(props: SCProps) { + return props.foo ? DOM.div(null, props.foo) : null; +} + +// tslint:disable-next-line:no-namespace +namespace FunctionComponent { + export const displayName = "FunctionComponent"; + export const defaultProps = { foo: 42 }; +} + +const FunctionComponent2: React.FunctionComponent = + // props is contextually typed + props => DOM.div(null, props.foo); +FunctionComponent2.displayName = "FunctionComponent2"; +FunctionComponent2.defaultProps = { + foo: 42 +}; + +// allows null as props +const FunctionComponent4: React.FunctionComponent = props => null; + +// undesired: Rejects `false` because of https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18051 +// leaving here to document limitation and inspect error message +const FunctionComponent5: React.FunctionComponent = () => false; // $ExpectError + +// React.createFactory +const factory: React.CFactory = + React.createFactory(ModernComponent); +const factoryElement: React.CElement = + factory(props); + +const functionComponentFactory: React.FunctionComponentFactory = + React.createFactory(FunctionComponent); +const functionComponentFactoryElement: React.FunctionComponentElement = + functionComponentFactory(props); + +const legacyStatelessComponentFactory: React.SFCFactory = + React.createFactory(FunctionComponent); + +const domFactory: React.DOMFactory, Element> = + React.createFactory("div"); +const domFactoryElement: React.DOMElement, Element> = + domFactory(); + +// React.createElement +const element: React.CElement = React.createElement(ModernComponent, props); +const elementNoState: React.CElement = React.createElement(ModernComponentNoState, props); +const elementNullProps: React.CElement<{}, ModernComponentNoPropsAndState> = React.createElement(ModernComponentNoPropsAndState, null); +const functionComponentElement: React.FunctionComponentElement = React.createElement(FunctionComponent, scProps); +const functionComponentElementNullProps: React.FunctionComponentElement = React.createElement(FunctionComponent4, null); +const domElement: React.DOMElement, HTMLDivElement> = React.createElement("div"); +const domElementNullProps = React.createElement("div", null); +const htmlElement = React.createElement("input", { type: "text" }); +const inputElementNullProps: React.DOMElement, HTMLInputElement> = React.createElement("input", null); +const svgElement = React.createElement("svg", { accentHeight: 12 }); +const svgElementNullProps = React.createElement("svg", null); +const fragmentElement: React.ReactElement<{}> = React.createElement(React.Fragment, {}, [React.createElement("div"), React.createElement("div")]); +const fragmentElementNullProps: React.ReactElement<{}> = React.createElement(React.Fragment, null, [React.createElement("div"), React.createElement("div")]); + +const customProps: React.HTMLProps = props; +const customDomElement = "my-element"; +const nonLiteralElement = React.createElement(customDomElement, customProps); +const customDomElementNullProps = React.createElement(customDomElement, null); + +// https://github.com/Microsoft/TypeScript/issues/15019 + +function foo3(child: React.ComponentClass<{ name: string }> | React.FunctionComponent<{ name: string }> | string) { + React.createElement(child, { name: "bar" }); +} + +// React.cloneElement +const clonedElement: React.CElement = React.cloneElement(element, { foo: 43 }); + +React.cloneElement(element, {}); +React.cloneElement(element, {}, null); + +const clonedElement2: React.CElement = + React.cloneElement(element, { + ref: c => c && c.reset() + }); +const clonedElement3: React.CElement = + React.cloneElement(element, { + key: "8eac7", + foo: 55 + }); +const clonedfunctionComponentElement: React.FunctionComponentElement = + React.cloneElement(functionComponentElement, { foo: 44 }); +// Clone base DOMElement +const clonedDOMElement: React.DOMElement, HTMLDivElement> = + React.cloneElement(domElement, { + className: "clonedDOMElement" + }); +// Clone ReactHTMLElement +const clonedHtmlElement: React.ReactHTMLElement = + React.cloneElement(htmlElement, { + className: "clonedHTMLElement" + }); +// Clone ReactSVGElement +const clonedSvgElement: React.ReactSVGElement = + React.cloneElement(svgElement, { + className: "clonedVGElement" + }); + +// React.render +const component: ModernComponent = ReactDOM.render(element, container); +const componentNullContainer: ModernComponent = ReactDOM.render(element, null); + +const componentElementOrNull: ModernComponent = ReactDOM.render(element, container); +const componentNoState: ModernComponentNoState = ReactDOM.render(elementNoState, container); +const componentNoStateElementOrNull: ModernComponentNoState = ReactDOM.render(elementNoState, container); +const domComponent: Element = ReactDOM.render(domElement, container); + +// Other Top-Level API +const unmounted: boolean = ReactDOM.unmountComponentAtNode(container); +const str: string = ReactDOMServer.renderToString(element); +const markup: string = ReactDOMServer.renderToStaticMarkup(element); +const notValid: boolean = React.isValidElement(props); // false +const isValid = React.isValidElement(element); // true +let domNode = ReactDOM.findDOMNode(component); +domNode = ReactDOM.findDOMNode(domNode as Element); +const fragmentType: React.ComponentType = React.Fragment; + +// +// React Elements +// -------------------------------------------------------------------------- + +const type: React.ComponentClass = element.type; +const elementProps: Props = element.props; +const key = element.key; + +// +// Component API +// -------------------------------------------------------------------------- + +// modern +const componentState: State = component.state; +component.setState({ inputValue: "!!!" }); +component.forceUpdate(); + +const myComponent = component as MyComponent; +myComponent.reset(); + +// +// Refs +// -------------------------------------------------------------------------- + +interface RCProps { } + +class RefComponent extends React.Component { + static create = React.createFactory(RefComponent); + refMethod() { + } +} + +let componentRef: RefComponent | null = new RefComponent({}); +RefComponent.create({ ref: "componentRef" }); +// type of c should be inferred +RefComponent.create({ ref: c => componentRef = c }); +componentRef.refMethod(); + +let domNodeRef: Element | null; +DOM.div({ ref: "domRef" }); +// type of node should be inferred +DOM.div({ ref: node => domNodeRef = node }); + +let inputNodeRef: HTMLInputElement | null; +DOM.input({ ref: node => inputNodeRef = node as HTMLInputElement }); + +interface ForwardingRefComponentProps { + hello: string; + world?: string | null | undefined; + foo: number; +} + +const ForwardingRefComponent = React.forwardRef((props: ForwardingRefComponentProps, ref: React.Ref) => { + return React.createElement(RefComponent, { ref }); +}); + +// Declaring forwardRef render function separately (not inline). +const ForwardRefRenderFunction = (props: ForwardingRefComponentProps, ref: React.ForwardedRef) => { + return React.createElement(RefComponent, { ref }); +}; +React.forwardRef(ForwardRefRenderFunction); + +const ForwardingRefComponentPropTypes: React.WeakValidationMap = {}; +ForwardingRefComponent.propTypes = ForwardingRefComponentPropTypes; + +// render function tests +// need the explicit type declaration for typescript < 3.1 +const ForwardRefRenderFunctionWithPropTypes: { (): null, propTypes?: {} | undefined } = () => null; +// Warning: forwardRef render functions do not support propTypes or defaultProps +// $ExpectError +React.forwardRef(ForwardRefRenderFunctionWithPropTypes); + +const ForwardRefRenderFunctionWithDefaultProps: { (): null, defaultProps?: {} | undefined } = () => null; +// Warning: forwardRef render functions do not support propTypes or defaultProps +// $ExpectError +React.forwardRef(ForwardRefRenderFunctionWithDefaultProps); + +function RefCarryingComponent() { + const ref = React.createRef(); + // Without the explicit type argument, TypeScript infers `{ref: React.RefObject}` + // from the second argument because both of the inferences generated by the first argument + // (both to the `P` in the call signature and the `P` in `defaultProps`) have low priority. + // Then we get a type error because `ForwardingRefComponent.defaultProps` has the wrong type. + // Can/should this be fixed somehow? + return React.createElement & ForwardingRefComponentProps>( + ForwardingRefComponent, + { + ref, + hello: 'there', + foo: 0, + }, + ); +} +const ForwardingRefComponent2 = React.forwardRef((props, ref) => { + return React.createElement('div', { + ref(e: HTMLDivElement) { + if (typeof ref === 'function') { + ref(e); + } else if (ref) { + ref.current = e; + } + } + }); +}); + +const MemoizedForwardingRefComponent = React.memo(ForwardingRefComponent); +const LazyComponent = React.lazy(() => Promise.resolve({ default: RefComponent })); + +type ClassComponentAsRef = React.ElementRef; // $ExpectType RefComponent +type FunctionComponentWithoutPropsAsRef = React.ElementRef; // $ExpectType never +type FunctionComponentWithPropsAsRef = React.ElementRef; // $ExpectType never +type HTMLIntrinsicAsRef = React.ElementRef<'div'>; // $ExpectType HTMLDivElement +type SVGIntrinsicAsRef = React.ElementRef<'svg'>; // $ExpectType SVGSVGElement +type ForwardingRefComponentAsRef = React.ElementRef; // $ExpectType RefComponent +type MemoizedForwardingRefComponentAsRef = React.ElementRef; // $ExpectType RefComponent +type LazyComponentAsRef = React.ElementRef; // $ExpectType RefComponent + +// +// Attributes +// -------------------------------------------------------------------------- + +const children: any[] = ["Hello world", [null], DOM.span(null)]; +const divStyle: React.CSSProperties = { // CSSProperties + flex: "1 1 main-size", + backgroundImage: "url('hello.png')" +}; +const htmlAttr: React.HTMLProps = { + key: 36, + ref: "htmlComponent", + children, + className: "test-attr", + style: divStyle, + slot: "HTMLComponent", + onClick: (event: React.MouseEvent<{}>) => { + event.preventDefault(); + event.stopPropagation(); + }, + onClickCapture: (event: React.MouseEvent<{}>) => { + event.preventDefault(); + event.stopPropagation(); + }, + onAnimationStart: event => { + const currentTarget: EventTarget & HTMLElement = event.currentTarget; + }, + onBlur: (event: React.FocusEvent) => { + const { + // $ExpectType (EventTarget & Element) | null + relatedTarget, + // $ExpectType EventTarget & Element + target + } = event; + }, + onFocus: (event: React.FocusEvent) => { + const { + // $ExpectType (EventTarget & Element) | null + relatedTarget, + // $ExpectType EventTarget & Element + target + } = event; + }, + dangerouslySetInnerHTML: { + __html: "STRONG" + }, + unselectable: 'on', + 'aria-atomic': false, + 'aria-checked': 'true', + 'aria-colcount': 7, + 'aria-label': 'test', + 'aria-relevant': 'additions removals' +}; +DOM.div(htmlAttr); +DOM.span(htmlAttr); +DOM.input(htmlAttr); + +DOM.svg({ + viewBox: "0 0 48 48", + xmlns: "http://www.w3.org/2000/svg" +}, + DOM.rect({ + className: 'foobar', + id: 'foo', + color: 'black', + x: 22, + y: 10, + width: 4, + height: 28, + strokeDasharray: '30%', + strokeDashoffset: '20%' + }), + DOM.rect({ + x: 10, + y: 22, + width: 28, + height: 4, + strokeDasharray: 30, + strokeDashoffset: 20 + }), + DOM.path({ + d: "M0,0V3H3V0ZM1,1V2H2V1Z", + fill: "#999999", + fillRule: "evenodd" + }) +); + +// +// React.Children +// -------------------------------------------------------------------------- + +const mappedChildrenArray: number[] = + React.Children.map(children, (child: any) => 42); +const childrenArray: Array> = children; +const mappedChildrenArrayWithKnownChildren: number[] = + React.Children.map(childrenArray, (child) => child.props.p); +React.Children.forEach(children, (child) => { }); +const nChildren: number = React.Children.count(children); +let onlyChild: React.ReactElement = React.Children.only(DOM.div()); // ok +onlyChild = React.Children.only([null, [[["Hallo"], true]], false]); // error +const childrenToArray: Array> = React.Children.toArray(children); + +declare const numberChildren: number[]; +declare const nodeChildren: React.ReactNode; +declare const elementChildren: JSX.Element[]; +declare const mixedChildren: Array; +declare const singlePluralChildren: JSX.Element | JSX.Element[]; +declare const renderPropsChildren: () => JSX.Element; + +// $ExpectType null +const mappedChildrenArray0 = React.Children.map(null, num => num); +// $ExpectType undefined +const mappedChildrenArray1 = React.Children.map(undefined, num => num); +// $ExpectType number[] +const mappedChildrenArray2 = React.Children.map(numberChildren, num => num); +// $ExpectType Element[] +const mappedChildrenArray3 = React.Children.map(elementChildren, element => element); +// $ExpectType (string | Element)[] +const mappedChildrenArray4 = React.Children.map(mixedChildren, elementOrString => elementOrString); +// 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 +// $ExpectError +const mappedChildrenArray7 = React.Children.map(nodeChildren, node => node).map; + +// +// Example from http://facebook.github.io/react/ +// -------------------------------------------------------------------------- + +interface TimerState { + secondsElapsed: number; +} +class Timer extends React.Component<{}, TimerState> { + state = { + secondsElapsed: 0 + }; + private _interval: number; + tick() { + this.setState((prevState, props) => ({ + secondsElapsed: prevState.secondsElapsed + 1 + })); + } + componentDidMount() { + this._interval = setInterval(() => this.tick(), 1000); + } + componentWillUnmount() { + clearInterval(this._interval); + } + render() { + return DOM.div( + null, + "Seconds Elapsed: ", + this.state.secondsElapsed + ); + } +} +ReactDOM.render(React.createElement(Timer), container); + +// +// createFragment addon +// -------------------------------------------------------------------------- +createFragment({ + a: DOM.div(), + b: ["a", false, React.createElement("span")] +}); + +// +// LinkedStateMixin addon +// -------------------------------------------------------------------------- +createReactClass({ + mixins: [LinkedStateMixin], + getInitialState() { + return { + isChecked: false, + message: "hello!" + }; + }, + render() { + return DOM.div(null, + DOM.input({ + type: "checkbox", + checkedLink: this.linkState("isChecked") + }), + DOM.input({ + type: "text", + valueLink: this.linkState("message") + }) + ); + } +}); + +// +// PureRenderMixin addon +// -------------------------------------------------------------------------- +createReactClass({ + mixins: [PureRenderMixin], + render() { return DOM.div(null); } +}); + +// +// update addon +// -------------------------------------------------------------------------- +{ + // These are copied from https://facebook.github.io/react/docs/update.html + const initialArray = [1, 2, 3]; + const newArray = update(initialArray, { $push: [4] }); // => [1, 2, 3, 4] + + const collection = [1, 2, { a: [12, 17, 15] }]; + const newCollection = update(collection, { 2: { a: { $splice: [[1, 1, 13, 14]] } } }); + // => [1, 2, {a: [12, 13, 14, 15]}] + + const obj = { a: 5, b: 3 }; + const newObj = update(obj, { + b: { + $apply: (x) => x * 2 + } + }); + // => {a: 5, b: 6} + const newObj2 = update(obj, { b: { $set: obj.b * 2 } }); + + const objShallow = { a: 5, b: 3 }; + const newObjShallow = update(obj, { $merge: { b: 6, c: 7 } }); // => {a: 5, b: 6, c: 7} +} + +// +// Events +// -------------------------------------------------------------------------- +function eventHandler(e: T) {} + +function handler(e: React.MouseEvent) { + eventHandler(e); +} + +const keyboardExtendsUI: React.UIEventHandler = (e: React.KeyboardEvent) => {}; + +// +// The SyntheticEvent.target.value should be accessible for onChange +// -------------------------------------------------------------------------- +class SyntheticEventTargetValue extends React.Component<{}, { value: string }> { + state: { value: string }; + constructor(props: {}) { + super(props); + this.state = { value: 'a' }; + } + render() { + return DOM.textarea({ + value: this.state.value, + onChange: e => { + const target: HTMLTextAreaElement = e.target; + } + }); + } +} + +DOM.input({ + onChange: event => { + // `event.target` is guaranteed to be HTMLInputElement + const target: HTMLInputElement = event.target; + } +}); + +// A ChangeEvent is a valid FormEvent (maintain compatibility with existing +// event handlers) + +type InputChangeEvent = React.ChangeEvent; +type InputFormEvent = React.FormEvent; +const changeEvent: InputChangeEvent = undefined as any; +const formEvent: InputFormEvent = changeEvent; + +// defaultProps should be optional of props +{ + interface ComponentProps { + prop1: string; + prop2: string; + prop3?: string | undefined; + } + class ComponentWithDefaultProps extends React.Component { + static defaultProps = { + prop3: "default value", + }; + } + const VariableWithAClass: React.ComponentClass = ComponentWithDefaultProps; +} + +// complex React.DOMElement type +declare var x: React.DOMElement<{ + className: string; + style: { + height: string; + overflowY: "auto"; + transition: string; + }; +}, Element>; + +// React 16 should be able to render its children directly +class RenderChildren extends React.Component<{ children?: React.ReactNode }> { + render() { + const { children } = this.props; + return children !== undefined ? children : null; + } +} + +const Memoized1 = React.memo(function Foo(props: { foo: string }) { return null; }); +React.createElement(Memoized1, { foo: 'string' }); + +const Memoized2 = React.memo<{ bar: string }>( + function Bar(props: { bar: string }) { return null; }, + (prevProps, nextProps) => prevProps.bar === nextProps.bar +); +React.createElement(Memoized2, { bar: 'string' }); + +const specialSfc1: React.ExoticComponent = Memoized1; +const functionComponent: React.FunctionComponent = Memoized2; + +const propsWithChildren: React.PropsWithChildren = { + hello: "world", + foo: 42, + children: React.createElement(functionComponent), +}; + +type UnionProps = + | ({ type: 'single'; value?: number } & React.RefAttributes) + | ({ type: 'multiple'; value?: number[] } & React.RefAttributes); + +// $ExpectError +const propsWithoutRef: React.PropsWithoutRef = { + type: 'single', + value: [2], +}; + +// JSXElemenConstructor vs Component assignability +{ + interface ExactProps { + value: 'A' | 'B'; + } + interface NarrowerProps { + value: 'A'; + } + interface WiderProps { + value: 'A' | 'B' | 'C'; + } + + // We don't actually care about the concrete type of `Wrapper` i.e. + // we don't care about the value created by `new Wrapper()`. + // We only care about the props we can pass to the component. + let Wrapper: React.JSXElementConstructor; + // $ExpectError + Wrapper = class Narrower extends React.Component {}; + // $ExpectError + Wrapper = (props: NarrowerProps) => null; + Wrapper = class Exact extends React.Component {}; + Wrapper = (props: ExactProps) => null; + Wrapper = class Wider extends React.Component {}; + Wrapper = (props: WiderProps) => null; + + React.createElement(Wrapper, { value: 'A' }); + React.createElement(Wrapper, { value: 'B' }); + // $ExpectError + React.createElement(Wrapper, { value: 'C' }); +} + +// ComponentPropsWithRef and JSXElementConstructor +{ + interface Props { + value: string; + } + type InferredProps = React.ComponentPropsWithRef>; + const props: Props = { + value: 'inferred', + // $ExpectError + notImplemented: 5 + }; + const inferredProps: InferredProps = { + value: 'inferred', + // $ExpectError + notImplemented: 5 + }; +} diff --git a/types/react/v17/test/managedAttributes.tsx b/types/react/v17/test/managedAttributes.tsx new file mode 100644 index 00000000000000..494545c2100da4 --- /dev/null +++ b/types/react/v17/test/managedAttributes.tsx @@ -0,0 +1,276 @@ +interface LeaveMeAloneDtslint { foo: string; } +// // Re-enable when we move @types/react to TS 3.0 + +// import * as React from 'react'; +// import * as PropTypes from 'prop-types'; + +// interface Props { +// bool?: boolean; +// fnc: () => any; +// node?: React.ReactNode; +// num?: number; +// reqNode: NonNullable; +// str: string; +// } + +// const propTypes = { +// bool: PropTypes.bool, +// fnc: PropTypes.func.isRequired, +// node: PropTypes.node, +// num: PropTypes.number, +// str: PropTypes.string.isRequired, +// extraStr: PropTypes.string.isRequired, +// extraNum: PropTypes.number +// }; + +// const defaultProps = { +// fnc: (() => 'abc') as () => any, +// extraBool: false, +// reqNode: 'text_node' as NonNullable +// }; + +// class AnnotatedPropTypesAndDefaultProps extends React.Component { +// static propTypes = propTypes; +// static defaultProps = defaultProps; +// } + +// const annotatedPropTypesAndDefaultPropsTests = [ +// // $ExpectError +// , // str and extraStr are required +// , +// // $ExpectError +// , // num type mismatch +// } +// num={0} +// reqNode={} +// str='abc' +// /> +// ]; + +// class UnannotatedPropTypesAndDefaultProps extends React.Component { +// static propTypes = propTypes; +// static defaultProps = defaultProps; +// } + +// const unannotatedPropTypesAndDefaultPropsTests = [ +// // $ExpectError +// , // stra and extraStr are required +// , +// // $ExpectError +// , // extraBool type mismatch +// } +// num={0} +// reqNode={} +// str='abc' +// /> +// ]; + +// class AnnotatedPropTypes extends React.Component { +// static propTypes = propTypes; +// } + +// const annotatedPropTypesTests = [ +// // $ExpectError +// , // str, extraStr, reqNode and fnc are required +// } />, +// // $ExpectError +// } extraBool={false} />, // extraBool doesn't exist +// // $ExpectError +// } num='abc' />, // num type mismatch +// } +// num={0} +// reqNode={} +// str='abc' +// /> +// ]; + +// class UnannotatedPropTypes extends React.Component { +// static propTypes = propTypes; +// } + +// const unannotatedPropTypesTests = [ +// // $ExpectError +// , // str, extraStr and fnc are required +// , +// // $ExpectError +// } />, // reqNode doesn't exist +// // $ExpectError +// } num='abc' />, // num type mismatch +// } +// num={0} +// str='abc' +// /> +// ]; + +// class AnnotatedDefaultProps extends React.Component { +// static defaultProps = defaultProps; +// } + +// const annotatedDefaultPropsTests = [ +// // $ExpectError +// , // str is required +// , +// } />, +// // $ExpectError +// { }} />, // str type mismatch +// +// ]; + +// class UnannotatedDefaultProps extends React.Component { +// static defaultProps = defaultProps; +// } + +// const unannotatedDefaultPropsTests = [ +// , +// } +// /> +// ]; + +// class ComponentWithNoDefaultProps extends React.Component {} + +// function FunctionalComponent(props: Props) { return <>{props.reqNode} } +// FunctionalComponent.defaultProps = defaultProps; + +// const functionalComponentTests = [ +// // $ExpectError +// , +// // This is possibly a bug/limitation of TS +// // Even if JSX.LibraryManagedAttributes returns the correct type, it doesn't seem to work with non-classes +// // This also doesn't work with things typed React.SFC

because defaultProps will always be Partial

+// // $ExpectError +// +// ]; + +// const MemoFunctionalComponent = React.memo(FunctionalComponent); +// const MemoAnnotatedDefaultProps = React.memo(AnnotatedDefaultProps); +// const LazyMemoFunctionalComponent = React.lazy(async () => ({ default: MemoFunctionalComponent })); +// const LazyMemoAnnotatedDefaultProps = React.lazy(async () => ({ default: MemoAnnotatedDefaultProps })); + +// const memoTests = [ +// // $ExpectError +// , +// // $ExpectError won't work as long as FunctionalComponent doesn't work either +// , +// // $ExpectError +// , +// , +// // $ExpectError this doesn't work despite JSX.LibraryManagedAttributes returning the correct type +// , +// // $ExpectError won't work as long as FunctionalComponent doesn't work either +// , +// // $ExpectError +// , +// // $ExpectError this doesn't work despite JSX.LibraryManagedAttributes returning the correct type +// +// ]; + +// type AnnotatedDefaultPropsLibraryManagedAttributes = JSX.LibraryManagedAttributes; +// // $ExpectType AnnotatedDefaultPropsLibraryManagedAttributes +// type FunctionalComponentLibraryManagedAttributes = JSX.LibraryManagedAttributes; +// // $ExpectType FunctionalComponentLibraryManagedAttributes +// type MemoFunctionalComponentLibraryManagedAttributes = JSX.LibraryManagedAttributes; +// // $ExpectType FunctionalComponentLibraryManagedAttributes +// type LazyMemoFunctionalComponentLibraryManagedAttributes = JSX.LibraryManagedAttributes; + +// const ForwardRef = React.forwardRef((props: Props, ref: React.Ref) => ( +// +// )); +// ForwardRef.defaultProps = defaultProps; + +// const forwardRefTests = [ +// // $ExpectError +// , +// } +// str='' +// />, +// // same bug as MemoFunctionalComponent and React.SFC-typed things +// // $ExpectError the type of ForwardRef.defaultProps stays Partial

anyway even if assigned +// +// ]; + +// const weakComponentPropTypes = { +// foo: PropTypes.string, +// bar: PropTypes.bool.isRequired +// }; +// interface WeakComponentProps1 { +// foo: any; +// bar: number; +// } +// interface WeakComponentProps2 { +// foo: string; +// bar: any; +// } +// interface WeakComponentProps3 { +// foo: any; +// bar: any; +// } + +// // $ExpectType true +// type weakComponentTest1 = JSX.LibraryManagedAttributes<{ propTypes: typeof weakComponentPropTypes }, any> extends { +// foo?: string | null +// bar: boolean +// } ? true : false; +// // $ExpectType true +// type weakComponentTest2 = JSX.LibraryManagedAttributes<{ propTypes: typeof weakComponentPropTypes }, WeakComponentProps1> extends { +// foo?: string | null +// bar: number +// } ? true : false; +// // $ExpectType true +// type weakComponentTest3 = JSX.LibraryManagedAttributes<{ propTypes: typeof weakComponentPropTypes }, WeakComponentProps2> extends { +// foo: string +// bar: boolean +// } ? true : false; + +// // $ExpectError +// const weakComponentOptionalityTest1: JSX.LibraryManagedAttributes<{ propTypes: typeof weakComponentPropTypes }, WeakComponentProps3> = { foo: '' }; +// const weakComponentOptionalityTest2: JSX.LibraryManagedAttributes<{ propTypes: typeof weakComponentPropTypes }, WeakComponentProps3> = { bar: true }; + +// interface IndexedComponentProps { +// [K: string]: boolean; +// } +// interface WeakIndexedComponentProps { +// [K: string]: any; +// } + +// const weakComponentIndexedTest1: JSX.LibraryManagedAttributes<{ propTypes: typeof weakComponentPropTypes }, IndexedComponentProps> = { }; +// // $ExpectError +// const weakComponentIndexedTest2: JSX.LibraryManagedAttributes<{ propTypes: typeof weakComponentPropTypes }, IndexedComponentProps> = { foo: '' }; +// const weakComponentIndexedTest3: JSX.LibraryManagedAttributes<{ propTypes: typeof weakComponentPropTypes }, WeakIndexedComponentProps> = { foo: '' }; +// const weakComponentIndexedTest4: JSX.LibraryManagedAttributes<{ propTypes: typeof weakComponentPropTypes }, WeakIndexedComponentProps> = { foo: 4 }; + +// const optionalUnionPropTest: JSX.LibraryManagedAttributes<{ propTypes: {} }, { optional?: string } | { optional?: number }> = {}; diff --git a/types/react/v17/test/tsx.tsx b/types/react/v17/test/tsx.tsx new file mode 100644 index 00000000000000..75a933c5354cb7 --- /dev/null +++ b/types/react/v17/test/tsx.tsx @@ -0,0 +1,521 @@ +import PropTypes = require("prop-types"); +import React = require("react"); + +interface SCProps { + foo?: number | undefined; +} +const FunctionComponent: React.FunctionComponent = ({ foo }: SCProps) => { + return

{foo}
; +}; +FunctionComponent.displayName = "FunctionComponent3"; +FunctionComponent.defaultProps = { + foo: 42 +}; +; +; +// `FunctionComponent` has no `children` +// $ExpectError +24; + +const VoidFunctionComponent: React.VoidFunctionComponent = ({ foo }: SCProps) => { + return
{foo}
; +}; +VoidFunctionComponent.displayName = "VoidFunctionComponent1"; +VoidFunctionComponent.defaultProps = { + foo: 42 +}; +; + +// $ExpectError +const VoidFunctionComponent2: React.VoidFunctionComponent = ({ foo, children }) => { + return
{foo}{children}
; +}; +VoidFunctionComponent2.displayName = "VoidFunctionComponent2"; +VoidFunctionComponent2.defaultProps = { + foo: 42 +}; +24; // $ExpectError + +// svg sanity check + + + + Hello, world! + + +
Hello again!
+ +; + +// React-specific Attributes +
+ foo +
; + +// WAI-ARIA 1.1 Attributes +
+ bar +
; + +// button type attribute +; +; +; +; // $ExpectError +; // $ExpectError + +interface Props { + hello: string; +} +interface State { + foobar: string; +} +class ComponentWithPropsAndState extends React.Component { +} +; + +class ComponentWithoutState extends React.Component { +} +; + +class ComponentWithoutPropsAndState extends React.Component { +} +; + +const FunctionComponentWithoutProps: React.FunctionComponent = (props) => { + return
; +}; +; + +// React.createContext +const ContextWithRenderProps = React.createContext('defaultValue'); + +// unstable APIs should not be part of the typings +// $ExpectError +const ContextUsingUnstableObservedBits = React.createContext(undefined, (previous, next) => 7); +// $ExpectError + + {(value: unknown) => null} +; + +// Fragments +
+ + + Child 1 + Child 2 + + + Child 3 + Child 4 + + +
; + +// Strict Mode +
+ +
+ +
; + +// Below tests that setState() works properly for both regular and callback modes +class SetStateTest extends React.Component<{}, { foo: boolean, bar: boolean }> { + handleSomething = () => { + this.setState({ foo: '' }); // $ExpectError + this.setState({ foo: true }); + this.setState({ foo: true, bar: true }); + this.setState({}); + this.setState(null); + this.setState({ foo: true, foo2: true }); // $ExpectError + this.setState(() => ({ foo: '' })); // $ExpectError + this.setState(() => ({ foo: true })); + this.setState(() => ({ foo: true, bar: true })); + this.setState(() => ({ foo: true, foo2: true })); // $ExpectError + this.setState(() => ({ foo: '', foo2: true })); // $ExpectError + this.setState(() => ({ })); // ok! + this.setState({ foo: true, bar: undefined}); // $ExpectError + this.setState(prevState => (prevState.bar ? { bar: false } : null)); + } +} + +// Below tests that extended types for state work +export abstract class SetStateTestForExtendsState extends React.Component { + foo() { + this.setState({ baseProp: 'foobar' }); + } +} + +// Below tests that & generic still works +// This is invalid because 'S' may specify a different type for `baseProp`. +// export abstract class SetStateTestForAndedState extends React.Component { +// foo() { +// this.setState({ baseProp: 'foobar' }); +// } +// } + +interface NewProps { foo: string; } +interface NewState { bar: string; } + +class ComponentWithNewLifecycles extends React.Component { + static getDerivedStateFromProps: React.GetDerivedStateFromProps = (nextProps) => { + return { bar: `${nextProps.foo}bar` }; + } + + state = { + bar: 'foo' + }; + + getSnapshotBeforeUpdate(prevProps: Readonly) { + return { baz: `${prevProps.foo}baz` }; + } + + componentDidUpdate(prevProps: Readonly, prevState: Readonly, snapshot: { baz: string }) { + return; + } + + render() { + return this.state.bar; + } +} +; + +class PureComponentWithNewLifecycles extends React.PureComponent { + static getDerivedStateFromProps: React.GetDerivedStateFromProps = (nextProps) => { + return { bar: `${nextProps.foo}bar` }; + } + + state = { + bar: 'foo' + }; + + getSnapshotBeforeUpdate(prevProps: Readonly) { + return { baz: `${prevProps.foo}baz` }; + } + + componentDidUpdate(prevProps: Readonly, prevState: Readonly, snapshot: { baz: string }) { + return; + } + + render() { + return this.state.bar; + } +} +; + +class ComponentWithLargeState extends React.Component<{}, Record<'a'|'b'|'c', string>> { + static getDerivedStateFromProps: React.GetDerivedStateFromProps<{}, Record<'a'|'b'|'c', string>> = () => { + return { a: 'a' }; + } +} +const AssignedComponentWithLargeState: React.ComponentClass = ComponentWithLargeState; + +const componentWithBadLifecycle = new (class extends React.Component<{}, {}, number> {})({}); +componentWithBadLifecycle.getSnapshotBeforeUpdate = () => { // $ExpectError + return 'number'; +}; +componentWithBadLifecycle.componentDidUpdate = (prevProps: {}, prevState: {}, snapshot?: string) => { // $ExpectError + return; +}; + +const Memoized1 = React.memo(function Foo(props: { foo: string }) { return null; }); +; + +const Memoized2 = React.memo( + function Bar(props: { bar: string }) { return null; }, + (prevProps, nextProps) => prevProps.bar === nextProps.bar +); +; + +const Memoized3 = React.memo(class Test extends React.Component<{ x?: string | undefined }> {}); + { if (ref) { ref.props.x; } }}/>; + +const memoized4Ref = React.createRef(); +const Memoized4 = React.memo(React.forwardRef((props: {}, ref: React.Ref) =>
)); +; + +const Memoized5 = React.memo<{ test: boolean }>( + prop => <>{prop.test}, + (prevProps, nextProps) => nextProps.test === prevProps.test +); + +; + +const Memoized6: React.NamedExoticComponent = React.memo(props => null); +; +// $ExpectError +; + +// NOTE: this test _requires_ TypeScript 3.1 +// It is passing, for what it's worth. +// const Memoized7 = React.memo((() => { +// function HasDefaultProps(props: { test: boolean }) { return null; } +// HasDefaultProps.defaultProps = { +// test: true +// }; +// return HasDefaultProps; +// })()); +// // $ExpectType boolean +// Memoized7.type.defaultProps.test; + +const LazyClassComponent = React.lazy(async () => ({ default: ComponentWithPropsAndState })); +const LazyMemoized3 = React.lazy(async () => ({ default: Memoized3 })); +const LazyRefForwarding = React.lazy(async () => ({ default: Memoized4 })); + +}> + + { if (ref) { ref.props.hello; } }} hello='test'/> + { if (ref) { ref.props.x; } }}/> + +; + +; +// $ExpectError +; + +// unstable API should not be part of the typings +// $ExpectError +; + +class LegacyContext extends React.Component { + static contextTypes = { foo: PropTypes.node.isRequired }; + + render() { + // $ExpectType unknown + this.context; + return (this.context as any).foo; + } +} + +class LegacyContextAnnotated extends React.Component { + static contextTypes = { foo: PropTypes.node.isRequired }; + context: { foo: React.ReactNode } = { foo: {} as React.ReactNode }; + + render() { + // $ExpectType ReactNode + this.context.foo; + return this.context.foo; + } +} + +class NewContext extends React.Component { + static contextType = ContextWithRenderProps; + context: React.ContextType = ""; + + render() { + // $ExpectType string + this.context; + return this.context; + } +} + +const ForwardRef = React.forwardRef((props: JSX.IntrinsicElements['div'], ref?: React.Ref) =>
); +const ForwardRef2 = React.forwardRef((props: React.ComponentProps, ref?: React.Ref) => ); +const divFnRef = (ref: HTMLDivElement|null) => { /* empty */ }; +const divRef = React.createRef(); + +; +; +; // $ExpectError +; +; +; // $ExpectError + +const htmlElementFnRef = (instance: HTMLElement | null) => {}; +const htmlElementRef = React.createRef(); +
; +
; +// `current` is nullable +const unsoundDivFnRef = (current: HTMLDivElement) => {}; +declare const unsoundDivObjectRef: { current: HTMLDivElement }; +// `current` is nullable but type-checks +// this is consistent with ref objects +// If this ever not type-checks, `
; +
; + +const newContextRef = React.createRef(); +; +; + +const ForwardNewContext = React.forwardRef((_props: {}, ref?: React.Ref) => ); +; +; // $ExpectError + +const ForwardRef3 = React.forwardRef( + (props: JSX.IntrinsicElements['div'] & Pick, ref?: React.Ref) => +
+); + +; +; + +const { Profiler } = React; + +// 'id' is missing +; // $ExpectError +// 'onRender' is missing +; // $ExpectError +// 'number' is not assignable to 'string' +; // $ExpectError + + { + const message = `${id} ${phase} took ${actualDuration.toFixed(2)}s actual, ${baseDuration.toFixed(2)}s base`; + + const commitMessage = `commit started ${startTime.toFixed(2)} within ${commitTime}`; + + const interactionsSummary = Array.from(interactions) + .map(interaction => { + return `${interaction.id}: '${interaction.name}' started at ${interaction.timestamp.toFixed(2)}`; + }) + .join("\n"); + const interactionMessage = `there were ${interactions.size} interactions:\n${interactionsSummary}`; + }} +> +
+; + +type ImgProps = React.ComponentProps<'img'>; +const imgProps: ImgProps = {}; +// the order of the strings in the union seems to vary +// with the typescript version, so test assignment instead +imgProps.decoding = 'async'; +imgProps.decoding = 'auto'; +imgProps.decoding = 'sync'; +imgProps.loading = 'eager'; +imgProps.loading = 'lazy'; +// $ExpectError +imgProps.loading = 'nonsense'; +// $ExpectError +imgProps.decoding = 'nonsense'; +type ImgPropsWithRef = React.ComponentPropsWithRef<'img'>; +// $ExpectType ((instance: HTMLImageElement | null) => void) | RefObject | null | undefined +type ImgPropsWithRefRef = ImgPropsWithRef['ref']; +type ImgPropsWithoutRef = React.ComponentPropsWithoutRef<'img'>; +// $ExpectType false +type ImgPropsHasRef = 'ref' extends keyof ImgPropsWithoutRef ? true : false; + +const HasClassName: React.ElementType<{ className?: string | undefined }> = 'a'; +const HasFoo: React.ElementType<{ foo: boolean }> = 'a'; // $ExpectError +const HasFoo2: React.ElementType<{ foo: boolean }> = (props: { foo: boolean }) => null; +const HasFoo3: React.ElementType<{ foo: boolean }> = (props: { foo: string }) => null; // $ExpectError +const HasHref: React.ElementType<{ href?: string | undefined }> = 'a'; +const HasHref2: React.ElementType<{ href?: string | undefined }> = 'div'; // $ExpectError + +const CustomElement: React.ElementType = 'my-undeclared-element'; // $ExpectError + +// custom elements now need to be declared as intrinsic elements +declare global { + namespace JSX { + interface IntrinsicElements { + 'my-declared-element': {}; + } + } +} + +const CustomElement2: React.ElementType = 'my-declared-element'; + +interface TestPropTypesProps { + foo: string; +} +interface TestPropTypesProps1 { + foo?: string | undefined; +} +interface TestPropTypesProps2 { + foo: string | null; +} +interface TestPropTypesProps3 { + foo?: string | null | undefined; +} +const testPropTypes = { + foo: PropTypes.string +}; +type DeclaredPropTypes

= Required['propTypes'], undefined>>; +// $ExpectType false +type propTypesTest = typeof testPropTypes extends DeclaredPropTypes ? true : false; +// $ExpectType true +type propTypesTest1 = typeof testPropTypes extends DeclaredPropTypes ? true : false; +// $ExpectType true +type propTypesTest2 = typeof testPropTypes extends DeclaredPropTypes ? true : false; +// $ExpectType true +type propTypesTest3 = typeof testPropTypes extends DeclaredPropTypes ? true : false; +function CustomSelect(props: { + children: ReadonlyArray< + React.ReactElement< + React.ComponentPropsWithoutRef + > + >; + }): JSX.Element { + return ( +

+
    {props.children}
+ +
+ ); +} +function CustomSelectOption(props: { + value: string; + children: React.ReactNode; +}): JSX.Element { + return
  • {props.children}
  • ; +} +function Example() { + return ( + + One + Two + + ); +} + +function reactNodeTests() { + function *createChildren() { + yield
    one
    ; + yield
    two
    ; + } + +
    {Object.freeze([
    one
    ,
    two
    ])}
    ; +
    {new Set([
    one
    ,
    two
    ])}
    ; + // TODO: This warns at runtime so we should probably reject it as well +
    + { + new Map([ + ['one',
    one
    ], + ['two',
    two
    ], + ]) + } +
    ; +
    {createChildren()}
    ; +} diff --git a/types/react/v17/tsconfig.json b/types/react/v17/tsconfig.json new file mode 100644 index 00000000000000..7e96f14a8af628 --- /dev/null +++ b/types/react/v17/tsconfig.json @@ -0,0 +1,34 @@ +{ + "files": [ + "index.d.ts", + "test/index.ts", + "test/tsx.tsx", + "test/cssProperties.tsx", + "test/elementAttributes.tsx", + "test/managedAttributes.tsx", + "test/hooks.tsx" + ], + "compilerOptions": { + "module": "commonjs", + "lib": [ + "es6" + ], + "paths": { + "react": ["react/v17"], + "react-dom": ["react-dom/v17"], + "react-dom/*": ["react-dom/v17/*"] + }, + "noImplicitAny": true, + "noImplicitThis": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "baseUrl": "../../", + "typeRoots": [ + "../../" + ], + "types": [], + "noEmit": true, + "forceConsistentCasingInFileNames": true, + "jsx": "preserve" + } +} diff --git a/types/react/v17/tslint.json b/types/react/v17/tslint.json new file mode 100644 index 00000000000000..a77da0b0c4ae6b --- /dev/null +++ b/types/react/v17/tslint.json @@ -0,0 +1,11 @@ +{ + "extends": "@definitelytyped/dtslint/dt.json", + "rules": { + "dt-header": false, + "no-empty-interface": false, + "no-object-literal-type-assertion": false, + "no-unnecessary-generics": false, + "strict-export-declare-modifiers": false, + "void-return": false + } +} From 11ba977dd1463981111c9ba3f6ef9b9522ef601e Mon Sep 17 00:00:00 2001 From: eps1lon Date: Tue, 29 Mar 2022 19:15:48 +0200 Subject: [PATCH 19/29] Update peer deps for packages incompatible with v18 https://github.com/bvaughn/react-virtualized/blob/2e962d8f8aebc22cb1168a8d147bcaa1d27aa326/package.json#L150 react-router-navigation-core technically supports React 18 (https://github.com/winoteam/react-router-navigation/blob/v1.0.0-rc.5/packages/react-router-navigation-core/package.json#L16). This is almost certainly not correct though considering the package is archived. --- types/react-router-navigation-core/tsconfig.json | 3 +++ types/react-router-navigation/tsconfig.json | 3 +++ types/react-virtualized/tsconfig.json | 5 +++++ 3 files changed, 11 insertions(+) diff --git a/types/react-router-navigation-core/tsconfig.json b/types/react-router-navigation-core/tsconfig.json index a9ffa87aba8b33..736cca68f14a14 100644 --- a/types/react-router-navigation-core/tsconfig.json +++ b/types/react-router-navigation-core/tsconfig.json @@ -6,6 +6,9 @@ ], "types": [], "paths": { + "react": [ + "react/v17" + ], "react-navigation": [ "react-navigation/v1" ], diff --git a/types/react-router-navigation/tsconfig.json b/types/react-router-navigation/tsconfig.json index 6b5ea552697d3e..18af7908c08b54 100644 --- a/types/react-router-navigation/tsconfig.json +++ b/types/react-router-navigation/tsconfig.json @@ -6,6 +6,9 @@ ], "types": [], "paths": { + "react": [ + "react/v17" + ], "react-navigation": [ "react-navigation/v1" ], diff --git a/types/react-virtualized/tsconfig.json b/types/react-virtualized/tsconfig.json index 22318cc53804d0..a2a4fa9e2ed65c 100644 --- a/types/react-virtualized/tsconfig.json +++ b/types/react-virtualized/tsconfig.json @@ -5,6 +5,11 @@ "es6", "dom" ], + "paths": { + "react": [ + "react/v17" + ] + }, "jsx": "react", "baseUrl": "../", "typeRoots": [ From f78a3bb907fc287ded4ff9f40826584a45bf201e Mon Sep 17 00:00:00 2001 From: eps1lon Date: Tue, 29 Mar 2022 19:28:31 +0200 Subject: [PATCH 20/29] Major version is now 18 --- types/react-dom/index.d.ts | 6 +----- types/react-dom/v17/index.d.ts | 8 -------- types/react/index.d.ts | 6 +----- types/react/v17/OTHER_FILES.txt | 2 -- types/react/v17/index.d.ts | 8 -------- 5 files changed, 2 insertions(+), 28 deletions(-) diff --git a/types/react-dom/index.d.ts b/types/react-dom/index.d.ts index d9a3f7db9aba20..98f799af98434a 100644 --- a/types/react-dom/index.d.ts +++ b/types/react-dom/index.d.ts @@ -1,4 +1,4 @@ -// Type definitions for React (react-dom) 17.0 +// Type definitions for React (react-dom) 18.0 // Project: https://reactjs.org // Definitions by: Asana // AssureSign @@ -10,10 +10,6 @@ // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped // TypeScript Version: 2.8 -// NOTE: Users of the upcoming React 18 release should add a reference -// to 'react-dom/next' in their project. See next.d.ts's top comment -// for reference and documentation on how exactly to do it. - // NOTE: Users of the `experimental` builds of React should add a reference // to 'react-dom/experimental' in their project. See experimental.d.ts's top comment // for reference and documentation on how exactly to do it. diff --git a/types/react-dom/v17/index.d.ts b/types/react-dom/v17/index.d.ts index d9a3f7db9aba20..83e2570656568d 100644 --- a/types/react-dom/v17/index.d.ts +++ b/types/react-dom/v17/index.d.ts @@ -10,14 +10,6 @@ // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped // TypeScript Version: 2.8 -// NOTE: Users of the upcoming React 18 release should add a reference -// to 'react-dom/next' in their project. See next.d.ts's top comment -// for reference and documentation on how exactly to do it. - -// NOTE: Users of the `experimental` builds of React should add a reference -// to 'react-dom/experimental' in their project. See experimental.d.ts's top comment -// for reference and documentation on how exactly to do it. - export as namespace ReactDOM; import { diff --git a/types/react/index.d.ts b/types/react/index.d.ts index 2f296f6920c45b..fc8e76ce6e44cb 100644 --- a/types/react/index.d.ts +++ b/types/react/index.d.ts @@ -1,4 +1,4 @@ -// Type definitions for React 17.0 +// Type definitions for React 18.0 // Project: http://facebook.github.io/react/ // Definitions by: Asana // AssureSign @@ -29,10 +29,6 @@ // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped // TypeScript Version: 2.8 -// NOTE: Users of the upcoming React 18 release should add a reference -// to 'react/next' in their project. See next.d.ts's top comment -// for reference and documentation on how exactly to do it. - // NOTE: Users of the `experimental` builds of React should add a reference // to 'react/experimental' in their project. See experimental.d.ts's top comment // for reference and documentation on how exactly to do it. diff --git a/types/react/v17/OTHER_FILES.txt b/types/react/v17/OTHER_FILES.txt index a4d354fcba50c4..d830e68845ac36 100644 --- a/types/react/v17/OTHER_FILES.txt +++ b/types/react/v17/OTHER_FILES.txt @@ -1,4 +1,2 @@ -next.d.ts -experimental.d.ts jsx-dev-runtime.d.ts jsx-runtime.d.ts diff --git a/types/react/v17/index.d.ts b/types/react/v17/index.d.ts index 2f296f6920c45b..ab6ba26e3b05c5 100644 --- a/types/react/v17/index.d.ts +++ b/types/react/v17/index.d.ts @@ -29,14 +29,6 @@ // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped // TypeScript Version: 2.8 -// NOTE: Users of the upcoming React 18 release should add a reference -// to 'react/next' in their project. See next.d.ts's top comment -// for reference and documentation on how exactly to do it. - -// NOTE: Users of the `experimental` builds of React should add a reference -// to 'react/experimental' in their project. See experimental.d.ts's top comment -// for reference and documentation on how exactly to do it. - /// import * as CSS from 'csstype'; From c95f0bc194f1b1f811466bf03e1552cbab397a37 Mon Sep 17 00:00:00 2001 From: eps1lon Date: Tue, 29 Mar 2022 19:45:51 +0200 Subject: [PATCH 21/29] [react-dom] Update types for v18 --- types/react-dom/node-stream/index.d.ts | 18 ------------ types/react-dom/test/next-tests.tsx | 27 ----------------- types/react-dom/test/react-dom-tests.tsx | 37 +++++++++++++++++------- 3 files changed, 26 insertions(+), 56 deletions(-) delete mode 100644 types/react-dom/node-stream/index.d.ts diff --git a/types/react-dom/node-stream/index.d.ts b/types/react-dom/node-stream/index.d.ts deleted file mode 100644 index 480392aaa4c83a..00000000000000 --- a/types/react-dom/node-stream/index.d.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { ReactElement } from 'react'; - -/** - * Render a ReactElement to its initial HTML. This should only be used on the - * server. - * See https://facebook.github.io/react/docs/react-dom-stream.html#rendertostream - */ -export function renderToStream(element: ReactElement): any; - -/** - * Similar to renderToStream, except this doesn't create extra DOM attributes - * such as data-react-id that React uses internally. - * See https://facebook.github.io/react/docs/react-dom-stream.html#rendertostaticstream - */ -export function renderToStaticStream(element: ReactElement): any; -export const version: string; - -export as namespace ReactDOMNodeStream; diff --git a/types/react-dom/test/next-tests.tsx b/types/react-dom/test/next-tests.tsx index e94ad090fa74d5..ce82938506e415 100644 --- a/types/react-dom/test/next-tests.tsx +++ b/types/react-dom/test/next-tests.tsx @@ -1,28 +1 @@ /// -import React = require('react'); -import ReactDOMClient = require('react-dom/client'); - -function createRoot() { - const root = ReactDOMClient.createRoot(document.documentElement); - - root.render(
    initial render
    ); - - // only makes sense for `hydrateRoot` - // $ExpectError - ReactDOMClient.createRoot(document); -} - -function hydrateRoot() { - const hydrateable = ReactDOMClient.hydrateRoot(document,
    initial render
    , { - identifierPrefix: 'react-18-app', - onRecoverableError: error => { - console.error(error); - }, - }); - hydrateable.render(
    render update
    ); - ReactDOMClient.hydrateRoot(document, { - // Forgot `initialChildren` - // $ExpectError - identifierPrefix: 'react-18-app', - }); -} diff --git a/types/react-dom/test/react-dom-tests.tsx b/types/react-dom/test/react-dom-tests.tsx index 2ec52495aa930a..99b454c9457f38 100644 --- a/types/react-dom/test/react-dom-tests.tsx +++ b/types/react-dom/test/react-dom-tests.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import * as ReactDOM from 'react-dom'; +import * as ReactDOMClient from 'react-dom/client'; import * as ReactDOMServer from 'react-dom/server'; -import * as ReactDOMNodeStream from 'react-dom/node-stream'; import * as ReactTestUtils from 'react-dom/test-utils'; declare function describe(desc: string, f: () => void): void; @@ -91,16 +91,6 @@ describe('ReactDOMServer', () => { }); }); -describe('ReactDOMNodeStream', () => { - it('renderToStream', () => { - const content: any = ReactDOMNodeStream.renderToStream(React.createElement('div')); - }); - - it('renderToStaticStream', () => { - const content: any = ReactDOMNodeStream.renderToStaticStream(React.createElement('div')); - }); -}); - describe('React dom test utils', () => { it('Simulate', () => { const element = document.createElement('div'); @@ -237,3 +227,28 @@ describe('React dom test utils', () => { }); }); }); + +function createRoot() { + const root = ReactDOMClient.createRoot(document.documentElement); + + root.render(
    initial render
    ); + + // only makes sense for `hydrateRoot` + // $ExpectError + ReactDOMClient.createRoot(document); +} + +function hydrateRoot() { + const hydrateable = ReactDOMClient.hydrateRoot(document,
    initial render
    , { + identifierPrefix: 'react-18-app', + onRecoverableError: error => { + console.error(error); + }, + }); + hydrateable.render(
    render update
    ); + ReactDOMClient.hydrateRoot(document, { + // Forgot `initialChildren` + // $ExpectError + identifierPrefix: 'react-18-app', + }); +} From 1a6448c89cac19a2f1bf4c8149c76fce5a498441 Mon Sep 17 00:00:00 2001 From: eps1lon Date: Tue, 29 Mar 2022 19:50:53 +0200 Subject: [PATCH 22/29] Move createMutableSource to experimental --- types/react/experimental.d.ts | 16 ++++++++++++++++ types/react/next.d.ts | 27 --------------------------- types/react/test/experimental.tsx | 23 +++++++++++++++++++++++ types/react/test/next.tsx | 24 ------------------------ 4 files changed, 39 insertions(+), 51 deletions(-) diff --git a/types/react/experimental.d.ts b/types/react/experimental.d.ts index 33a6c69f2097a5..b83451c662915e 100644 --- a/types/react/experimental.d.ts +++ b/types/react/experimental.d.ts @@ -93,4 +93,20 @@ declare module '.' { * @see https://reactjs.org/docs/concurrent-mode-patterns.html#suspenselist */ export const SuspenseList: ExoticComponent; + + /** + * this should be an internal type + */ + interface MutableSource { + _source: T; + } + + export type MutableSourceSubscribe = (source: T, callback: () => void) => () => void; + + // TODO: This may not be intentionally part of the experimental release considering useMutableSource is no longer available + /** + * @param source A source could be anything as long as they can be subscribed to and have a "version". + * @param getVersion A function returns a value which will change whenever part of the source changes. + */ + export function unstable_createMutableSource(source: T, getVersion: () => any): MutableSource; } diff --git a/types/react/next.d.ts b/types/react/next.d.ts index c66cdc38a04b6d..68ad78f3396611 100644 --- a/types/react/next.d.ts +++ b/types/react/next.d.ts @@ -98,33 +98,6 @@ declare module '.' { export function useId(): string; - /** - * this should be an internal type - */ - interface MutableSource { - _source: T; - } - - export type MutableSourceSubscribe = (source: T, callback: () => void) => () => void; - - /** - * @param source A source could be anything as long as they can be subscribed to and have a "version". - * @param getVersion A function returns a value which will change whenever part of the source changes. - */ - export function unstable_createMutableSource(source: T, getVersion: () => any): MutableSource; - - /** - * useMutableSource() enables React components to safely and efficiently read from a mutable external source in Concurrent Mode. - * The API will detect mutations that occur during a render to avoid tearing - * and it will automatically schedule updates when the source is mutated. - * @param MutableSource - * @param getSnapshot - * @param subscribe - * - * @see https://github.com/reactjs/rfcs/blob/master/text/0147-use-mutable-source.md - */ - export function unstable_useMutableSource(MutableSource: MutableSource, getSnapshot: (source: T) => TResult, subscribe: MutableSourceSubscribe): TResult; - /** * @param effect Imperative function that can return a cleanup function * @param deps If present, effect will only activate if the values in the list change. diff --git a/types/react/test/experimental.tsx b/types/react/test/experimental.tsx index 615d1f87815366..6395a59975caf3 100644 --- a/types/react/test/experimental.tsx +++ b/types/react/test/experimental.tsx @@ -22,3 +22,26 @@ import React = require('react'); A B ; + +// We need these Window interfaces to compile +interface Window { + location: { + href: string; + pathname: string; + }; + addEventListener(type: string, callback: () => void): void; + removeEventListener(type: string, callback: () => void): void; +} + +const noop = () => {}; + +const window: Window = { location: { href: '', pathname: '' }, addEventListener: noop, removeEventListener: noop }; + +const locationSource = React.unstable_createMutableSource(window, () => window.location.href); + +const getSnapshot = (window: Window) => window.location.pathname; + +const subscribe: React.MutableSourceSubscribe = (window, callback) => { + window.addEventListener("popstate", callback); + return () => window.removeEventListener("popstate", callback); +}; diff --git a/types/react/test/next.tsx b/types/react/test/next.tsx index 8e366af1004f63..f70848be9fba28 100644 --- a/types/react/test/next.tsx +++ b/types/react/test/next.tsx @@ -3,28 +3,6 @@ import React = require('react'); const { useSyncExternalStore } = React; -// We need these Window interfaces to compile -interface Window { - location: { - href: string; - pathname: string; - }; - addEventListener(type: string, callback: () => void): void; - removeEventListener(type: string, callback: () => void): void; -} - -const noop = () => {}; - -const window: Window = { location: { href: '', pathname: '' }, addEventListener: noop, removeEventListener: noop }; - -const locationSource = React.unstable_createMutableSource(window, () => window.location.href); - -const getSnapshot = (window: Window) => window.location.pathname; - -const subscribe: React.MutableSourceSubscribe = (window, callback) => { - window.addEventListener("popstate", callback); - return () => window.removeEventListener("popstate", callback); -}; function useExperimentalHooks() { const [toggle, setToggle] = React.useState(false); @@ -50,8 +28,6 @@ function useExperimentalHooks() { // $ExpectType () => string const deferredConstructible = React.useDeferredValue(Constructible); - // $ExpectType string - const pathName = React.unstable_useMutableSource(locationSource, getSnapshot, subscribe); React.useInsertionEffect(() => {}); React.useInsertionEffect(() => {}, []); From e4696f356848a2fbd1da17c95956d16937e16382 Mon Sep 17 00:00:00 2001 From: eps1lon Date: Tue, 29 Mar 2022 19:53:43 +0200 Subject: [PATCH 23/29] Move unstable_expectedLoadTime to experimental --- types/react/experimental.d.ts | 9 +++++++++ types/react/next.d.ts | 9 --------- types/react/test/experimental.tsx | 14 ++++++++++++++ types/react/test/next.tsx | 14 -------------- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/types/react/experimental.d.ts b/types/react/experimental.d.ts index b83451c662915e..2a620c688f0476 100644 --- a/types/react/experimental.d.ts +++ b/types/react/experimental.d.ts @@ -39,6 +39,15 @@ import React = require('./next'); export {}; declare module '.' { + export interface SuspenseProps { + /** + * The presence of this prop indicates that the content is computationally expensive to render. + * In other words, the tree is CPU bound and not I/O bound (e.g. due to fetching data). + * @see {@link https://github.com/facebook/react/pull/19936} + */ + unstable_expectedLoadTime?: number | undefined; + } + export type SuspenseListRevealOrder = 'forwards' | 'backwards' | 'together'; export type SuspenseListTailMode = 'collapsed' | 'hidden'; diff --git a/types/react/next.d.ts b/types/react/next.d.ts index 68ad78f3396611..e764c67c6fa591 100644 --- a/types/react/next.d.ts +++ b/types/react/next.d.ts @@ -33,15 +33,6 @@ declare const UNDEFINED_VOID_ONLY: unique symbol; type VoidOrUndefinedOnly = void | { [UNDEFINED_VOID_ONLY]: never }; declare module '.' { - export interface SuspenseProps { - /** - * The presence of this prop indicates that the content is computationally expensive to render. - * In other words, the tree is CPU bound and not I/O bound (e.g. due to fetching data). - * @see {@link https://github.com/facebook/react/pull/19936} - */ - unstable_expectedLoadTime?: number | undefined; - } - // must be synchronous export type TransitionFunction = () => VoidOrUndefinedOnly; // strange definition to allow vscode to show documentation on the invocation diff --git a/types/react/test/experimental.tsx b/types/react/test/experimental.tsx index 6395a59975caf3..8c2f434da7318c 100644 --- a/types/react/test/experimental.tsx +++ b/types/react/test/experimental.tsx @@ -2,6 +2,20 @@ import React = require('react'); +function suspenseTest() { + function DisplayData() { + return null; + } + + function FlameChart() { + return ( + + + + ); + } +} + // Unsupported `revealOrder` triggers a runtime warning // $ExpectError diff --git a/types/react/test/next.tsx b/types/react/test/next.tsx index f70848be9fba28..a0f808a78ca517 100644 --- a/types/react/test/next.tsx +++ b/types/react/test/next.tsx @@ -66,20 +66,6 @@ function startTransitionTest() { React.startTransition(async () => {}); } -function suspenseTest() { - function DisplayData() { - return null; - } - - function FlameChart() { - return ( - - - - ); - } -} - function Dialog() { const id = React.useId(); const nameId = `${id}-name`; From fc646631d7babad9c985c06e2958661c6ac63874 Mon Sep 17 00:00:00 2001 From: eps1lon Date: Tue, 29 Mar 2022 20:00:16 +0200 Subject: [PATCH 24/29] Move /next into main entrypoint --- types/react/index.d.ts | 86 ++++++++++++++++++++++-- types/react/next.d.ts | 88 +------------------------ types/react/test/hooks.tsx | 115 ++++++++++++++++++++++++++++++++ types/react/test/next.tsx | 132 ------------------------------------- types/react/test/tsx.tsx | 1 - 5 files changed, 197 insertions(+), 225 deletions(-) diff --git a/types/react/index.d.ts b/types/react/index.d.ts index fc8e76ce6e44cb..b668622a2c62b8 100644 --- a/types/react/index.d.ts +++ b/types/react/index.d.ts @@ -56,6 +56,7 @@ type Booleanish = boolean | 'true' | 'false'; declare const UNDEFINED_VOID_ONLY: unique symbol; // Destructors are only allowed to return void. type Destructor = () => void | { [UNDEFINED_VOID_ONLY]: never }; +type VoidOrUndefinedOnly = void | { [UNDEFINED_VOID_ONLY]: never }; // tslint:disable-next-line:export-just-namespace export = React; @@ -383,16 +384,10 @@ declare namespace React { interface SuspenseProps { children?: ReactNode | undefined; - // TODO(react18): `fallback?: ReactNode;` /** A fallback react tree to show when a Suspense child (like React.lazy) suspends */ - fallback: NonNullable|null; + fallback?: ReactNode; } - // TODO(react18): Updated JSDoc to reflect that Suspense works on the server. - /** - * This feature is not yet available for server-side rendering. - * Suspense support will be added in a later release. - */ const Suspense: ExoticComponent; const version: string; @@ -1092,6 +1087,83 @@ declare namespace React { // it's just the function name without the "use" prefix. function useDebugValue(value: T, format?: (value: T) => any): void; + // must be synchronous + export type TransitionFunction = () => VoidOrUndefinedOnly; + // strange definition to allow vscode to show documentation on the invocation + export interface TransitionStartFunction { + /** + * State updates caused inside the callback are allowed to be deferred. + * + * **If some state update causes a component to suspend, that state update should be wrapped in a transition.** + * + * @param callback A _synchronous_ function which causes state updates that can be deferred. + */ + (callback: TransitionFunction): void; + } + + /** + * Returns a deferred version of the value that may “lag behind” it for at most `timeoutMs`. + * + * This is commonly used to keep the interface responsive when you have something that renders immediately + * based on user input and something that needs to wait for a data fetch. + * + * A good example of this is a text input. + * + * @param value The value that is going to be deferred + * + * @see https://reactjs.org/docs/concurrent-mode-reference.html#usedeferredvalue + */ + export function useDeferredValue(value: T): T; + + /** + * Allows components to avoid undesirable loading states by waiting for content to load + * before transitioning to the next screen. It also allows components to defer slower, + * data fetching updates until subsequent renders so that more crucial updates can be + * rendered immediately. + * + * The `useTransition` hook returns two values in an array. + * + * The first is a boolean, React’s way of informing us whether we’re waiting for the transition to finish. + * The second is a function that takes a callback. We can use it to tell React which state we want to defer. + * + * **If some state update causes a component to suspend, that state update should be wrapped in a transition.** + * + * @param config An optional object with `timeoutMs` + * + * @see https://reactjs.org/docs/concurrent-mode-reference.html#usetransition + */ + export function useTransition(): [boolean, TransitionStartFunction]; + + /** + * Similar to `useTransition` but allows uses where hooks are not available. + * + * @param callback A _synchronous_ function which causes state updates that can be deferred. + */ + export function startTransition(scope: TransitionFunction): void; + + export function useId(): string; + + /** + * @param effect Imperative function that can return a cleanup function + * @param deps If present, effect will only activate if the values in the list change. + * + * @see https://github.com/facebook/react/pull/21913 + */ + export function useInsertionEffect(effect: EffectCallback, deps?: DependencyList): void; + + /** + * @param subscribe + * @param getSnapshot + * + * @see https://github.com/reactwg/react-18/discussions/86 + */ + // keep in sync with `useSyncExternalStore` from `use-sync-external-store` + export function useSyncExternalStore( + subscribe: (onStoreChange: () => void) => () => void, + getSnapshot: () => Snapshot, + getServerSnapshot?: () => Snapshot, + ): Snapshot; + // // Event System // ---------------------------------------------------------------------- diff --git a/types/react/next.d.ts b/types/react/next.d.ts index e764c67c6fa591..abb81df8bf54db 100644 --- a/types/react/next.d.ts +++ b/types/react/next.d.ts @@ -1,7 +1,5 @@ /** - * These are types for things that are present in the upcoming React 18 release. - * - * Once React 18 is released they can just be moved to the main index file. + * These are types for things that are present in the React `next` release channel. * * To load the types declared here in an actual project, there are three ways. The easiest one, * if your `tsconfig.json` already has a `"types"` array in the `"compilerOptions"` section, @@ -23,90 +21,10 @@ * Either the import or the reference only needs to appear once, anywhere in the project. */ -// See https://github.com/facebook/react/blob/master/packages/react/src/React.js to see how the exports are declared, +// See https://github.com/facebook/react/blob/main/packages/react/src/React.js to see how the exports are declared, import React = require('.'); export {}; -declare const UNDEFINED_VOID_ONLY: unique symbol; -type VoidOrUndefinedOnly = void | { [UNDEFINED_VOID_ONLY]: never }; - -declare module '.' { - // must be synchronous - export type TransitionFunction = () => VoidOrUndefinedOnly; - // strange definition to allow vscode to show documentation on the invocation - export interface TransitionStartFunction { - /** - * State updates caused inside the callback are allowed to be deferred. - * - * **If some state update causes a component to suspend, that state update should be wrapped in a transition.** - * - * @param callback A _synchronous_ function which causes state updates that can be deferred. - */ - (callback: TransitionFunction): void; - } - - /** - * Returns a deferred version of the value that may “lag behind” it for at most `timeoutMs`. - * - * This is commonly used to keep the interface responsive when you have something that renders immediately - * based on user input and something that needs to wait for a data fetch. - * - * A good example of this is a text input. - * - * @param value The value that is going to be deferred - * - * @see https://reactjs.org/docs/concurrent-mode-reference.html#usedeferredvalue - */ - export function useDeferredValue(value: T): T; - - /** - * Allows components to avoid undesirable loading states by waiting for content to load - * before transitioning to the next screen. It also allows components to defer slower, - * data fetching updates until subsequent renders so that more crucial updates can be - * rendered immediately. - * - * The `useTransition` hook returns two values in an array. - * - * The first is a boolean, React’s way of informing us whether we’re waiting for the transition to finish. - * The second is a function that takes a callback. We can use it to tell React which state we want to defer. - * - * **If some state update causes a component to suspend, that state update should be wrapped in a transition.** - * - * @param config An optional object with `timeoutMs` - * - * @see https://reactjs.org/docs/concurrent-mode-reference.html#usetransition - */ - export function useTransition(): [boolean, TransitionStartFunction]; - - /** - * Similar to `useTransition` but allows uses where hooks are not available. - * - * @param callback A _synchronous_ function which causes state updates that can be deferred. - */ - export function startTransition(scope: TransitionFunction): void; - - export function useId(): string; - - /** - * @param effect Imperative function that can return a cleanup function - * @param deps If present, effect will only activate if the values in the list change. - * - * @see https://github.com/facebook/react/pull/21913 - */ - export function useInsertionEffect(effect: EffectCallback, deps?: DependencyList): void; - - /** - * @param subscribe - * @param getSnapshot - * - * @see https://github.com/reactwg/react-18/discussions/86 - */ - // keep in sync with `useSyncExternalStore` from `use-sync-external-store` - export function useSyncExternalStore( - subscribe: (onStoreChange: () => void) => () => void, - getSnapshot: () => Snapshot, - getServerSnapshot?: () => Snapshot, - ): Snapshot; -} +declare module '.' {} diff --git a/types/react/test/hooks.tsx b/types/react/test/hooks.tsx index 88c32eb34d3f8e..62b1a75d61cd65 100644 --- a/types/react/test/hooks.tsx +++ b/types/react/test/hooks.tsx @@ -1,5 +1,7 @@ import * as React from "react"; +const {useSyncExternalStore} = React; + interface PersonProps { name: string; age: number; @@ -252,3 +254,116 @@ const everyHookRef = React.createRef<{ id: number }>(); // $ExpectType { id: number; } | null ref; }}/>; + +function useExperimentalHooks() { + const [toggle, setToggle] = React.useState(false); + + const [done, startTransition] = React.useTransition(); + // $ExpectType boolean + done; + + // $ExpectType boolean + const deferredToggle = React.useDeferredValue(toggle); + + const [func] = React.useState(() => () => 0); + + // $ExpectType () => number + func; + // $ExpectType () => number + const deferredFunc = React.useDeferredValue(func); + + class Constructor {} + // $ExpectType typeof Constructor + const deferredConstructor = React.useDeferredValue(Constructor); + + // $ExpectType () => string + const deferredConstructible = React.useDeferredValue(Constructible); + + React.useInsertionEffect(() => {}); + React.useInsertionEffect(() => {}, []); + React.useInsertionEffect(() => { + return () => {}; + }, [toggle]); + + return () => { + startTransition(() => { + setToggle(toggle => !toggle); + }); + + // The function must be synchronous, even if it can start an asynchronous update + // it's no different from an useEffect callback in this respect + // $ExpectError + startTransition(async () => {}); + + // Unlike Effect callbacks, though, there is no possible destructor to return + // $ExpectError + startTransition(() => () => {}); + }; + + function Constructible() { + return ''; + } +} + +function startTransitionTest() { + function transitionToPage(page: string) {} + + React.startTransition(() => { + transitionToPage('/'); + }); + + // $ExpectError + React.startTransition(async () => {}); +} + +function Dialog() { + const id = React.useId(); + const nameId = `${id}-name`; + const descriptionId = `${id}-description`; + + return ( +
    +

    Name

    +

    Description

    +
    + ); +} + +// keep in sync with `use-sync-external-store-tests.ts` +interface Store { + getState(): State; + getServerState(): State; + subscribe(onStoreChange: () => void): () => void; +} + +declare const numberStore: Store; +function useVersion(): number { + return useSyncExternalStore(numberStore.subscribe, numberStore.getState); +} + +function useStoreWrong() { + useSyncExternalStore( + // no unsubscribe returned + // $ExpectError + () => { + return null; + }, + () => 1, + ); + + // `string` is not assignable to `number` + // $ExpectError + const version: number = useSyncExternalStore( + () => () => {}, + () => '1', + ); +} + +declare const objectStore: Store<{ version: { major: number; minor: number }; users: string[] }>; +function useUsers(): string[] { + return useSyncExternalStore( + objectStore.subscribe, + () => objectStore.getState().users, + () => objectStore.getServerState().users, + ); +} diff --git a/types/react/test/next.tsx b/types/react/test/next.tsx index a0f808a78ca517..ce82938506e415 100644 --- a/types/react/test/next.tsx +++ b/types/react/test/next.tsx @@ -1,133 +1 @@ /// - -import React = require('react'); - -const { useSyncExternalStore } = React; - -function useExperimentalHooks() { - const [toggle, setToggle] = React.useState(false); - - const [done, startTransition] = React.useTransition(); - // $ExpectType boolean - done; - - // $ExpectType boolean - const deferredToggle = React.useDeferredValue(toggle); - - const [func] = React.useState(() => () => 0); - - // $ExpectType () => number - func; - // $ExpectType () => number - const deferredFunc = React.useDeferredValue(func); - - class Constructor {} - // $ExpectType typeof Constructor - const deferredConstructor = React.useDeferredValue(Constructor); - - // $ExpectType () => string - const deferredConstructible = React.useDeferredValue(Constructible); - - - React.useInsertionEffect(() => {}); - React.useInsertionEffect(() => {}, []); - React.useInsertionEffect(() => { - return () => {}; - }, [toggle]); - - return () => { - startTransition(() => { - setToggle(toggle => !toggle); - }); - - // The function must be synchronous, even if it can start an asynchronous update - // it's no different from an useEffect callback in this respect - // $ExpectError - startTransition(async () => {}); - - // Unlike Effect callbacks, though, there is no possible destructor to return - // $ExpectError - startTransition(() => () => {}); - }; - - function Constructible() { - return ''; - } -} - -function startTransitionTest() { - function transitionToPage(page: string) {} - - React.startTransition(() => { - transitionToPage('/'); - }); - - // $ExpectError - React.startTransition(async () => {}); -} - -function Dialog() { - const id = React.useId(); - const nameId = `${id}-name`; - const descriptionId = `${id}-description`; - - return ( -
    -

    Name

    -

    Description

    -
    - ); -} - -function SuspenseTest() { - // TODO(react18): Should not error. - // `fallback` is optional in React 18 - // $ExpectError - ; - // TODO(react18): Should not error. - // `fallback` is optional in React 18. - // $ExpectError - ; - // Workaround. - // TODO(react18): Remove. - ; -} - -// keep in sync with `use-sync-external-store-tests.ts` -interface Store { - getState(): State; - getServerState(): State; - subscribe(onStoreChange: () => void): () => void; -} - -declare const numberStore: Store; -function useVersion(): number { - return useSyncExternalStore(numberStore.subscribe, numberStore.getState); -} - -function useStoreWrong() { - useSyncExternalStore( - // no unsubscribe returned - // $ExpectError - () => { - return null; - }, - () => 1, - ); - - // `string` is not assignable to `number` - // $ExpectError - const version: number = useSyncExternalStore( - () => () => {}, - () => '1', - ); -} - -declare const objectStore: Store<{ version: { major: number; minor: number }; users: string[] }>; -function useUsers(): string[] { - return useSyncExternalStore( - objectStore.subscribe, - () => objectStore.getState().users, - () => objectStore.getServerState().users, - ); -} diff --git a/types/react/test/tsx.tsx b/types/react/test/tsx.tsx index 75a933c5354cb7..3904ee96c7e8af 100644 --- a/types/react/test/tsx.tsx +++ b/types/react/test/tsx.tsx @@ -285,7 +285,6 @@ const LazyRefForwarding = React.lazy(async () => ({ default: Memoized4 })); ; ; -// $ExpectError ; // unstable API should not be part of the typings From 5ccad4132a6cadd29acca9b476c00e416b67b972 Mon Sep 17 00:00:00 2001 From: eps1lon Date: Tue, 29 Mar 2022 20:15:24 +0200 Subject: [PATCH 25/29] [styled-components] Fix TS 4.7 compat issues --- types/styled-components/index.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/types/styled-components/index.d.ts b/types/styled-components/index.d.ts index 6ba73d81f9b26e..b4141ca6284840 100644 --- a/types/styled-components/index.d.ts +++ b/types/styled-components/index.d.ts @@ -105,11 +105,11 @@ export type InterpolationFunction

    = (props: P) => Interpolation

    ; type Attrs, T> = ((props: ThemedStyledProps) => A) | A; -export type ThemedGlobalStyledClassProps = WithOptionalTheme & { +export type ThemedGlobalStyledClassProps

    = WithOptionalTheme & { suppressMultiMountWarning?: boolean | undefined; }; -export interface GlobalStyleComponent extends React.ComponentClass> {} +export interface GlobalStyleComponent

    extends React.ComponentClass> {} // remove the call signature from StyledComponent so Interpolation can still infer InterpolationFunction type StyledComponentInterpolation = From 92c2313772dd3d3eb9c55aa01deb503eca149a70 Mon Sep 17 00:00:00 2001 From: eps1lon Date: Tue, 29 Mar 2022 22:04:00 +0200 Subject: [PATCH 26/29] [meteor] Fix TS 4.7 compat issues --- types/meteor/mongo.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/types/meteor/mongo.d.ts b/types/meteor/mongo.d.ts index 82f7107d1b2b48..6b042fc77a1463 100644 --- a/types/meteor/mongo.d.ts +++ b/types/meteor/mongo.d.ts @@ -174,7 +174,7 @@ declare module 'meteor/mongo' { * Constructor for a Collection * @param name The name of the collection. If null, creates an unmanaged (unsynchronized) local collection. */ - new ( + new ( name: string | null, options?: { /** @@ -199,7 +199,7 @@ declare module 'meteor/mongo' { }, ): Collection; } - interface Collection { + interface Collection { allow = undefined>(options: { insert?: ((userId: string, doc: DispatchTransform) => boolean) | undefined; update?: From 18712b8f6d6b13801a61bd8d8d4a0103195e9d69 Mon Sep 17 00:00:00 2001 From: eps1lon Date: Wed, 30 Mar 2022 13:41:34 +0200 Subject: [PATCH 27/29] [react-virtualized-select] Use same types as runtime peer dependencies https://github.com/bvaughn/react-virtualized-select/blob/3.1.3/package.json#L128 --- types/react-virtualized-select/tsconfig.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/types/react-virtualized-select/tsconfig.json b/types/react-virtualized-select/tsconfig.json index ed27d2d5c5014b..e17f1768f42c6d 100644 --- a/types/react-virtualized-select/tsconfig.json +++ b/types/react-virtualized-select/tsconfig.json @@ -5,6 +5,9 @@ "es6", "dom" ], + "paths": { + "react": ["react/v16"] + }, "jsx": "react", "noImplicitAny": true, "noImplicitThis": true, From 93ac4146c8998e03ceb95ecfffd776ecc9976b63 Mon Sep 17 00:00:00 2001 From: eps1lon Date: Wed, 30 Mar 2022 13:51:50 +0200 Subject: [PATCH 28/29] work around definitions-parser limitation --- types/react-virtualized-select/tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/react-virtualized-select/tsconfig.json b/types/react-virtualized-select/tsconfig.json index e17f1768f42c6d..7b12b3ca71e06e 100644 --- a/types/react-virtualized-select/tsconfig.json +++ b/types/react-virtualized-select/tsconfig.json @@ -6,7 +6,7 @@ "dom" ], "paths": { - "react": ["react/v16"] + "react": ["react/v17"] }, "jsx": "react", "noImplicitAny": true, From 184ad9c8dc9c39f4edea93b5fa2b62ef7cb32e98 Mon Sep 17 00:00:00 2001 From: eps1lon Date: Wed, 30 Mar 2022 14:09:22 +0200 Subject: [PATCH 29/29] Use typings from 208180734633b7839fc4aaa336137b6d96f54359 for v17 --- types/react/v17/index.d.ts | 78 +++++++++++++++++++++++++++++----- types/react/v17/test/hooks.tsx | 5 ++- types/react/v17/test/index.ts | 34 ++++++++++++++- types/react/v17/test/tsx.tsx | 38 ++++++++++------- 4 files changed, 125 insertions(+), 30 deletions(-) diff --git a/types/react/v17/index.d.ts b/types/react/v17/index.d.ts index ab6ba26e3b05c5..3b1e7ebbf24e0c 100644 --- a/types/react/v17/index.d.ts +++ b/types/react/v17/index.d.ts @@ -67,6 +67,10 @@ declare namespace React { [K in keyof JSX.IntrinsicElements]: P extends JSX.IntrinsicElements[K] ? K : never }[keyof JSX.IntrinsicElements] | ComponentType

    ; + /** + * @deprecated Please use `ElementType` + */ + type ReactType

    = ElementType

    ; type ComponentType

    = ComponentClass

    | FunctionComponent

    ; type JSXElementConstructor

    = @@ -143,6 +147,11 @@ declare namespace React { P = Pick, Exclude, 'key' | 'ref'>> > extends ReactElement> { } + /** + * @deprecated Please use `FunctionComponentElement` + */ + type SFCElement

    = FunctionComponentElement

    ; + interface FunctionComponentElement

    extends ReactElement> { ref?: ('ref' extends keyof P ? P extends { ref?: infer R | undefined } ? R : never : never) | undefined; } @@ -220,7 +229,7 @@ declare namespace React { * @deprecated Use either `ReactNode[]` if you need an array or `Iterable` if its passed to a host component. */ interface ReactNodeArray extends ReadonlyArray {} - type ReactFragment = Iterable; + type ReactFragment = {} | Iterable; type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined; // @@ -459,7 +468,8 @@ declare namespace React { * * @see https://reactjs.org/docs/context.html */ - context: unknown; + // TODO (TypeScript 3.0): unknown + context: any; constructor(props: Readonly

    | P); /** @@ -479,7 +489,12 @@ declare namespace React { forceUpdate(callback?: () => void): void; render(): ReactNode; - readonly props: Readonly

    ; + // React.Props is now deprecated, which means that the `children` + // property is not available on `P` by default, even though you can + // always pass children as variadic arguments to `createElement`. + // In the future, if we can define its call signature conditionally + // on the existence of `children` in `P`, then we should remove this. + readonly props: Readonly

    & Readonly<{ children?: ReactNode | undefined }>; state: Readonly; /** * @deprecated @@ -506,10 +521,26 @@ declare namespace React { // Class Interfaces // ---------------------------------------------------------------------- + /** + * @deprecated as of recent React versions, function components can no + * longer be considered 'stateless'. Please use `FunctionComponent` instead. + * + * @see [React Hooks](https://reactjs.org/docs/hooks-intro.html) + */ + type SFC

    = FunctionComponent

    ; + + /** + * @deprecated as of recent React versions, function components can no + * longer be considered 'stateless'. Please use `FunctionComponent` instead. + * + * @see [React Hooks](https://reactjs.org/docs/hooks-intro.html) + */ + type StatelessComponent

    = FunctionComponent

    ; + type FC

    = FunctionComponent

    ; interface FunctionComponent

    { - (props: P, context?: any): ReactElement | null; + (props: PropsWithChildren

    , context?: any): ReactElement | null; propTypes?: WeakValidationMap

    | undefined; contextTypes?: ValidationMap | undefined; defaultProps?: Partial

    | undefined; @@ -529,7 +560,7 @@ declare namespace React { type ForwardedRef = ((instance: T | null) => void) | MutableRefObject | null; interface ForwardRefRenderFunction { - (props: P, ref: ForwardedRef): ReactElement | null; + (props: PropsWithChildren

    , ref: ForwardedRef): ReactElement | null; displayName?: string | undefined; // explicit rejected with `never` required due to // https://github.com/microsoft/TypeScript/issues/36826 @@ -543,6 +574,12 @@ declare namespace React { propTypes?: never | undefined; } + /** + * @deprecated Use ForwardRefRenderFunction. forwardRef doesn't accept a + * "real" component. + */ + interface RefForwardingComponent extends ForwardRefRenderFunction {} + interface ComponentClass

    extends StaticLifecycle { new (props: P, context?: any): Component; propTypes?: WeakValidationMap

    | undefined; @@ -817,7 +854,7 @@ declare namespace React { function memo

    ( Component: FunctionComponent

    , - propsAreEqual?: (prevProps: Readonly

    , nextProps: Readonly

    ) => boolean + propsAreEqual?: (prevProps: Readonly>, nextProps: Readonly>) => boolean ): NamedExoticComponent

    ; function memo>( Component: T, @@ -856,7 +893,8 @@ declare namespace React { // The identity check is done with the SameValue algorithm (Object.is), which is stricter than === type ReducerStateWithoutAction> = R extends ReducerWithoutAction ? S : never; - type DependencyList = ReadonlyArray; + // TODO (TypeScript 3.0): ReadonlyArray + type DependencyList = ReadonlyArray; // NOTE: callbacks are _only_ allowed to return either void, or a destructor. type EffectCallback = () => (void | Destructor); @@ -1063,10 +1101,8 @@ declare namespace React { * @version 16.8.0 * @see https://reactjs.org/docs/hooks-reference.html#usecallback */ - // A specific function type would not trigger implicit any. - // See https://github.com/DefinitelyTyped/DefinitelyTyped/issues/52873#issuecomment-845806435 for a comparison between `Function` and more specific types. - // tslint:disable-next-line ban-types - function useCallback(callback: T, deps: DependencyList): T; + // TODO (TypeScript 3.0): unknown> + function useCallback any>(callback: T, deps: DependencyList): T; /** * `useMemo` will only recompute the memoized value when one of the `deps` has changed. * @@ -1272,6 +1308,26 @@ declare namespace React { // Props / DOM Attributes // ---------------------------------------------------------------------- + /** + * @deprecated. This was used to allow clients to pass `ref` and `key` + * to `createElement`, which is no longer necessary due to intersection + * types. If you need to declare a props object before passing it to + * `createElement` or a factory, use `ClassAttributes`: + * + * ```ts + * var b: Button | null; + * var props: ButtonProps & ClassAttributes