Skip to content

Add more of the new features added in React 16.6#30054

Merged
johnnyreilly merged 21 commits intoDefinitelyTyped:masterfrom
Jessidhia:react-16-more
Nov 9, 2018
Merged

Add more of the new features added in React 16.6#30054
johnnyreilly merged 21 commits intoDefinitelyTyped:masterfrom
Jessidhia:react-16-more

Conversation

@Jessidhia
Copy link
Copy Markdown
Member

@Jessidhia Jessidhia commented Oct 26, 2018

Continues off #29990.

  • Use a meaningful title for the pull request. Include the name of the package modified.
  • Test the change in your own code. (Compile and run.)
  • Add or edit tests to reflect the change. (Run with npm test.)
  • Follow the advice from the readme.
  • Avoid common mistakes.
  • Run npm run lint package-name (or tsc if no tslint.json is present).

If changing an existing definition:

Comment thread types/react/index.d.ts
/**
* Not implemented yet, requires unstable_ConcurrentMode
*/
// maxDuration?: number
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I don't know whether to leave this in or not. The code to support it technically exists, but is unreachable without unstable_ConcurrentMode which we are not defining here.

Comment thread types/react/index.d.ts
* @deprecated
* https://reactjs.org/docs/legacy-context.html
*/
context: any;
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I removed this to make sure there's no magic any in this.context. That'll only happen when using legacy context; if using modern context it'll be the type of the value prop of this.constructor.contextType.Provider.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hm, it is strange, cause here https://reactjs.org/docs/context.html#classcontexttype we can see, React allows us to use just this.context, but it is not possible, if context will be deleted from types of Component.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

By the way, there is no anything like value in Component.contextType.Provider.

Copy link
Copy Markdown
Member Author

@Jessidhia Jessidhia Oct 30, 2018

Choose a reason for hiding this comment

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

The point is to force the user to declare it themselves because there is nothing better we can do other than any here, and having it already declared would allow people to access it even without a static contextType.

value is the value prop of the Provider exotic component. The actual type of context would be something like typeof this.constructor.contextType extends React.Context<infer T> ? T : never if this.constructor would be useful (its typeof is just Function).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hm, could you explain, why this code is not working:

class Component<P, S> {
    static contextType: Context<any>;
    ...
    ...
    context: typeof Component.contextType extends Context<infer T> ? T : never
}

Type of context is any always( But it works perfectly in my own component.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

That is because, by declaring it in the superclass, you don't have access to its type as assigned in the subclass. The type of the constructor itself is not generic so it just uses the type as declared, assuming Component is constructed as is and not through a subclass. (Note: despite how I phrased it, this still wouldn't work if Component was abstract)

This is the deficiency in TypeScript's class declaration language: it is possible to make an instance type generic, but not the constructor object itself generic.

There is a possible hack with constructible interfaces, though, if they're even accepted in an extends clause. It could be possible to just completely replace the class Component declaration with a pair of interfaces and a const. But... at what cost?.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thank you for explanation. I agree, what it is ok to get rid of context from typings.

Comment thread types/react/index.d.ts
*
* Note: its presence prevents any of the deprecated lifecycle methods from being invoked
*/
(error: any) => Partial<S> | null;
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I actually don't know if this is allowed to return null 🤔

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should the argument be of type Error?

Suggested change
(error: any) => Partial<S> | null;
(error: Error) => Partial<S> | null;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I kept it the same type as catch has.

try {
  ...
} catch (e) {
  // e will _always_ be `any`
  // giving it another type is a build error
}

You can also throw anything in javascript; even throw undefined.

Comment thread types/react/index.d.ts
): MemoExoticComponent<T>;

interface LazyExoticComponent<T extends ComponentType<any>> extends ExoticComponent<ComponentPropsWithRef<T>> {
readonly _result: T;
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I'm not really happy about this. Having an underscored value here looks like reaching into internals; but it's the only way to have access to T in LibraryManagedProps.

An alternative is to make up our own fictional prop name.

// fnc: (() => 'abc') as () => any,
// extraBool: false,
// reqNode: 'text_node' as React.ReactNode
// reqNode: 'text_node' as NonNullable<React.ReactNode>
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I fixed lints that were hit while I was editing this uncommented.

// // $ExpectType FunctionalComponentLibraryManagedAttributes
// type MemoFunctionalComponentLibraryManagedAttributes = JSX.LibraryManagedAttributes<typeof MemoFunctionalComponent, Props>;
// // $ExpectType FunctionalComponentLibraryManagedAttributes
// type LazyMemoFunctionalComponentLibraryManagedAttributes = JSX.LibraryManagedAttributes<typeof LazyMemoFunctionalComponent, Props>;
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Assuming I'm using $ExpectType correctly these all pass. It just doesn't actually work when the functional/exotic components are used in JSX.

Comment thread types/react/test/tsx.tsx
<LazyClassComponent hello='test'/>
<LazyClassComponent ref={ref => { if (ref) { ref.props.hello; } }} hello='test'/>
<LazyMemoized3 ref={ref => { if (ref) { ref.props.x; } }}/>
<LazyRefForwarding ref={memoized4Ref}/>
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

😇

image

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I couldn't figure out how refs work by reading mountLazyComponent in ReactFiberBeginWork.js; I actually thought they didn't work at all at first.

I did verify that they work at runtime, though (refs are set/called when the Suspense resolves).

@typescript-bot typescript-bot added Popular package This PR affects a popular package (as counted by NPM download counts). The Travis CI build failed labels Oct 26, 2018
@typescript-bot
Copy link
Copy Markdown
Contributor

typescript-bot commented Oct 26, 2018

@Kovensky Thank you for submitting this PR!

🔔 @johnnyreilly @bbenezech @pzavolinsky @digiguru @ericanderson @morcerf @tkrotoff @DovydasNavickas @onigoetz @theruther4d @guilhermehubner @ferdaber @jrakotoharisoa @pascaloliv @Hotell @franklixuefei @kaoDev @guntherjh @wasd171 @szabolcsx @kraenhansen @Stevearzh @mgoszcz2 @brandonhall @eps1lon - please review this PR in the next few days. Be sure to explicitly select Approve or Request Changes in the GitHub UI so I know what's going on.

If no reviewer appears after a week, a DefinitelyTyped maintainer will review the PR instead.

@typescript-bot
Copy link
Copy Markdown
Contributor

typescript-bot commented Oct 26, 2018

@Kovensky The Travis CI build failed! Please review the logs for more information.

Once you've pushed the fixes, the build will automatically re-run. Thanks!

@typescript-bot
Copy link
Copy Markdown
Contributor

typescript-bot commented Oct 27, 2018

@Kovensky The Travis CI build failed! Please review the logs for more information.

Once you've pushed the fixes, the build will automatically re-run. Thanks!

@Jessidhia
Copy link
Copy Markdown
Member Author

Issue I found while updating a codebase to React.lazy:

image

Apparently Readonly<any> is actually identical to {} and for some reason TS is expecting it to be covariant(?). Removing the Readonly is enough to get things to work.

I don't know whether to fix this here or in another PR.

@typescript-bot
Copy link
Copy Markdown
Contributor

@Kovensky The Travis CI build failed! Please review the logs for more information.

Once you've pushed the fixes, the build will automatically re-run. Thanks!

@typescript-bot
Copy link
Copy Markdown
Contributor

typescript-bot commented Oct 30, 2018

@Kovensky The Travis CI build failed! Please review the logs for more information.

Once you've pushed the fixes, the build will automatically re-run. Thanks!

Comment thread types/react/index.d.ts
children?: ReactNode

/** A fallback react tree to show when a Suspense child (like React.lazy) suspends */
fallback: NonNullable<ReactNode>|null
Copy link
Copy Markdown
Member Author

@Jessidhia Jessidhia Oct 30, 2018

Choose a reason for hiding this comment

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

React will throw at runtime if the Suspense suspends and the fallback is undefined, but null is ok.

Copy link
Copy Markdown
Contributor

@mikiest mikiest left a comment

Choose a reason for hiding this comment

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

Nice one!
Just a couple of minor comments about possibly avoidable anys.
Also, is it worth cleaning up some comments before merging?

Comment thread types/react/index.d.ts
*
* Note: its presence prevents any of the deprecated lifecycle methods from being invoked
*/
(error: any) => Partial<S> | null;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should the argument be of type Error?

Suggested change
(error: any) => Partial<S> | null;
(error: Error) => Partial<S> | null;

Comment thread types/react/index.d.ts

function forwardRef<T, P = {}>(Component: RefForwardingComponent<T, P>): ForwardRefExoticComponent<P & ClassAttributes<T>>;

type ComponentProps<T extends ComponentType<any>> =
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Do we need <any> here (and in the few other instances below)?
It looks like ComponentType generic will default to {} anyway, so maybe you could omit it?
See here:

type ComponentType<P = {}> = ComponentClass<P> | StatelessComponent<P>;

Copy link
Copy Markdown
Member Author

@Jessidhia Jessidhia Nov 1, 2018

Choose a reason for hiding this comment

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

Yeah, if there is no argument it'll be inferred as {} and this will actually cause the compiler to reject subtypes of ComponentType<{}> because {} will be a function or constructor argument's type.

It's the problem of (arg: { foo: string }) => null not being assignable to (arg: {}) => null, but being assignable to (arg: any) => null.

@typescript-bot typescript-bot added the Other Approved This PR was reviewed and signed-off by a community member. label Oct 31, 2018
@plievone
Copy link
Copy Markdown

plievone commented Nov 1, 2018

@Kovensky I have used the following React.lazy definition for a while in my types.d.ts:

interface ModuleLike<T> {
    default: T;
}
import * as React from 'react';
declare module 'react' {
    /**
     * Otherwise works, but 'show all usages' work only for prop names, not for component names, unfortunately
     */
    function lazy<T>(f: () => T): T extends Promise<infer U> ?
        (U extends ModuleLike<infer V> ? V : never) :
        never;
}

@Jessidhia
Copy link
Copy Markdown
Member Author

Rebased 🔥

@johnnyreilly
Copy link
Copy Markdown
Member

Nice! Just wait for ci to finish and we'll get this in too!

@johnnyreilly johnnyreilly merged commit 9039892 into DefinitelyTyped:master Nov 9, 2018
@johnnyreilly
Copy link
Copy Markdown
Member

Done!

@Jessidhia Jessidhia deleted the react-16-more branch November 9, 2018 06:50
@maciejw
Copy link
Copy Markdown

maciejw commented Nov 9, 2018

if @types/react has version 16.7.1 in npm then it should contain things from react 16.7.0-alpha.0 like maxDuration in React.Suspense witch is now commented out, what do you think?

when Suspense is defined as const

 const Suspense: ExoticComponent<{
    children?: ReactNode;

    /** A fallback react tree to show when a Suspense child (like React.lazy) suspends */
    fallback: NonNullable<ReactNode> | null;

    // I tried looking at the code but I have no idea what it does.
    // https://github.com/facebook/react/issues/13206#issuecomment-432489986
    /**
     * Not implemented yet, requires unstable_ConcurrentMode
     */
    // maxDuration?: number;
  }>;

its imposible to overwrite it, and make maxDuration visible.

making props as an interface would not prevent me to use interface declaration merging, when I want to add maxDuration in my project

Comment thread types/react/index.d.ts
}

function memo<P extends object>(
Component: SFC<P>,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Is this right? If I have a render prop component (where children might be a function), it's not being inferred as functional component.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Implicit children strike back

Again

🙄

I guess I have a New Years break project for a breaking change. Hopefully one I can get to compile.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I'm investigating it further. The problem may not be children, but something else (my current bet is functional components with custom generic types). I'll see if I can come up with a minimum reproduction.

Copy link
Copy Markdown
Member Author

@Jessidhia Jessidhia Dec 25, 2018

Choose a reason for hiding this comment

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

@sandersn @johnnyreilly Would it be possible to condition this breaking change on a specific TypeScript version through typesVersion?

I was thinking of being able to make the change incremental by only updating types on demand, when people update to, say, TS3.3. This would require kind of a "max supported TS version" on types that depend on @types/react.

I'm a bit afraid of it being a python3 problem, though, but the technical debt of @types/react is now nigh-unmanageable.

Yes, I could work around this problem in this specific instance by changing the input to a function instead of FunctionComponent, but it's pervasive and everywhere. A lot of things that currently work in the ecosystem (like function children) work because of ossified bugs in the types, not because of features. @types/react is due for a rewrite.

Copy link
Copy Markdown
Member Author

@Jessidhia Jessidhia Dec 25, 2018

Choose a reason for hiding this comment

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

@diegohaz ah, yes, that is the one big problem with mapped types and infer (and the lack of kinds?); they erase generics and collapse unions. If the props are either of those the output type may be funky.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

In another thread we decided it was time to update the types to expect TS 3

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Would it be possible to condition this breaking change on a specific TypeScript version through typesVersion?

I'm afraid I haven't played with typesVersion yet and so can't advise. That said, as @ericanderson mentioned there's been prior discussion about upgrading to TS 3. We should do that anyway. If anyone is thinking about submitting a PR that does that then they should expect ♥️ and 🤗

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Other Approved This PR was reviewed and signed-off by a community member. Popular package This PR affects a popular package (as counted by NPM download counts).

Projects

None yet

Development

Successfully merging this pull request may close these issues.

10 participants