Skip to content

[react] [react-dom] Add support for rendering an array of elements#19363

Merged
johnnyreilly merged 7 commits intoDefinitelyTyped:masterfrom
cynecx:array_render
Oct 6, 2017
Merged

[react] [react-dom] Add support for rendering an array of elements#19363
johnnyreilly merged 7 commits intoDefinitelyTyped:masterfrom
cynecx:array_render

Conversation

@cynecx
Copy link
Copy Markdown
Contributor

@cynecx cynecx commented Aug 26, 2017

Changes:

  • Added more overloads to ReactDOM::render to support rendering an array of elements
    • Since typescript doesn't support variadic types, the array overloads use any instead of a deduced type
  • The callback function signature has been changed because react v16 doesn't actually pass any parameters anymore
  • Bumped the supported react-dom to v16

I am not quite sure if these changes are correct, so please be gentle on reviewing these changes ;)

@cynecx cynecx requested a review from johnnyreilly as a code owner August 26, 2017 17:47
@dt-bot
Copy link
Copy Markdown
Member

dt-bot commented Aug 26, 2017

types/react-dom/index.d.ts

to authors (@pspeter3 @vsiao @MartynasZilinskas AssureSign (account can't be detected) Microsoft (account can't be detected)). Could you review this PR?
👍 or 👎?

Checklist

  • pass the Travis CI test?

types/react/index.d.ts

to authors (@pspeter3 @vsiao @johnnyreilly @bbenezech @pzavolinsky @digiguru @ericanderson @morcerf @tkrotoff @DovydasNavickas @onigoetz @richseviora AssureSign (account can't be detected) Microsoft (account can't be detected)). Could you review this PR?
👍 or 👎?

Checklist

  • pass the Travis CI test?

@cynecx
Copy link
Copy Markdown
Contributor Author

cynecx commented Aug 26, 2017

Uhmm, I guess something is clearly wrong. Checking it out.

@typescript-bot
Copy link
Copy Markdown
Contributor

typescript-bot commented Aug 26, 2017

@cynecx Please fix the failures indicated in the Travis CI log.

@typescript-bot typescript-bot added Revision needed This PR needs code changes before it can be merged. and removed The Travis CI build failed Revision needed This PR needs code changes before it can be merged. labels Aug 26, 2017
@cynecx
Copy link
Copy Markdown
Contributor Author

cynecx commented Aug 26, 2017

It seems that this patch doesn't affect any of the occurring errors as I can reproduce them even without this patch. Would be great if someone could review this. Thank You.

@typescript-bot typescript-bot added The Travis CI build failed Revision needed This PR needs code changes before it can be merged. labels Aug 26, 2017
@cynecx cynecx closed this Aug 27, 2017
@cynecx cynecx reopened this Aug 28, 2017
Copy link
Copy Markdown
Contributor

@vsiao vsiao left a comment

Choose a reason for hiding this comment

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

I think the react-dom changes are incorrect. Core react changes seem fine, but perhaps missing changes to SFCs?

Comment thread types/react-dom/index.d.ts Outdated
element: SFCElement<P>,

export function render(
element: Array<DOMElement<any, 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.

can you link to any documentation of this API? I didn't know that it was possible to pass an array into ReactDOM.render.

Copy link
Copy Markdown
Contributor Author

@cynecx cynecx Aug 28, 2017

Choose a reason for hiding this comment

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

It's not yet documented but with React Fiber (v16) you can pass an array of elements to the render function.

See this test case: https://codesandbox.io/s/y310mjkp5v

The test case is using a beta release of react/react-dom (16.0.0-beta.5).

Copy link
Copy Markdown
Contributor Author

@cynecx cynecx Aug 28, 2017

Choose a reason for hiding this comment

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

Just as a code reference:

https://github.com/facebook/react/blob/master/src/renderers/shared/fiber/ReactChildFiber.js#L565

The parameter newChild is basically the element passed to the render function. This line in createChild checks whether the element parameter, which has been passed down to this function, is an array. If so, it deals with it respectively, in createFiberFromFragment, which indicates that React-Fiber (v16) supports passing an array of elements to the render function (react-dom).

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.

Thanks. Can you add type parameters for props, as in the other overloads?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Excuse me, but how would that work? Since we don't have something like "variadic parameter types" in typescript. Am I missing something?

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.

It could be Array<DOMElement<DOMAttributes<any>, any>>> for the lack of better specificity, to mirror the generic overload above that has DOMAttributes<T> as the props.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I agree. I've commited the change you suggested. Thanks.

@cynecx
Copy link
Copy Markdown
Contributor Author

cynecx commented Aug 28, 2017

Rebased the feature branch.

Comment thread types/react-dom/index.d.ts Outdated
element: Array<CElement<any, Component<any, ComponentState>>>,
container: Element | null,
callback?: () => void
): Component<any, ComponentState>;
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 this be an array return value?

Copy link
Copy Markdown
Contributor Author

@cynecx cynecx Aug 28, 2017

Choose a reason for hiding this comment

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

I don't think so. It seems that React-Fiber does not return the whole array which is somewhat strange. According to my tests, it only returns the first element of the array.

Copy link
Copy Markdown
Member

@Jessidhia Jessidhia Sep 27, 2017

Choose a reason for hiding this comment

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

Using render's return value is itself deprecated (it's planned to become fully async in the future). Function refs should be used instead.

I'd be in favor of declaring this as void even though it's de facto wrong.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I would leave this as it is for now. When it's finally changed we can reflect the changes afterwards. It may be deprecated but some users might still use this in their code. I guess it's better this way.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

At least adding a comment with a deprecated tag should be enough in my opinion.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Where would you like to add them? Marking the whole render function as deprecated doesn't feel right (Using jsdoc's @deprecated). Or do you think a simple comment would be sufficient?

Copy link
Copy Markdown
Collaborator

@MartynasZilinskas MartynasZilinskas Sep 29, 2017

Choose a reason for hiding this comment

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

I thought something with function overload. But other than that, simple comment should be enough.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I've pushed a new commit which adds a deprecated comment.

@cynecx
Copy link
Copy Markdown
Contributor Author

cynecx commented Aug 28, 2017

It seems that there is an issue in react-router/test/InheritingRoute.tsx. The problem here is that the return type of the render function (JSX.Element | JSX.Element[]) is not compatible with the paramter type of the cloneElement function signature as it only accepts JSX.Element.

I am not sure how to fix this issue. We can either assume that the render function in the class Route from react-router always returns a JSX.Element or we can modify the test.

We could modify the Route class like the following:

export class Route<T extends RouteProps = RouteProps> extends React.Component<T> {
  render(): JSX.Element | null | false;
}

Should this go in a new PR?

@richseviora
Copy link
Copy Markdown
Contributor

@cynecx I'm thinking it's probably best to update the broken tests (react-router and other types as necessary) and then flag their contributors about the change for their approval.

@typescript-bot typescript-bot added the Abandoned This PR had no activity for a long time, and is considered abandoned label Sep 4, 2017
@cynecx
Copy link
Copy Markdown
Contributor Author

cynecx commented Sep 5, 2017

Rebased on master. I am working on fixing the tests now.

@cynecx
Copy link
Copy Markdown
Contributor Author

cynecx commented Sep 5, 2017

Seems good now. There is just one error which I am certain that this PR didn't affect:

Error in redux-form v6
Command failed: node /home/travis/build/DefinitelyTyped/DefinitelyTyped/node_modules/dtslint/bin/index.js --noLint
Unexpected compiler option noImplicitReturns

@cynecx
Copy link
Copy Markdown
Contributor Author

cynecx commented Sep 5, 2017

Flagging some users for approval: @prakarshpandey, @Andy-MS, @mtpc, @tkrotoff, @MartynasZilinskas

@circlingthesun
Copy link
Copy Markdown

Any update on this?

@typescript-bot typescript-bot added the Popular package This PR affects a popular package (as counted by NPM download counts). label Sep 7, 2017
@cynecx
Copy link
Copy Markdown
Contributor Author

cynecx commented Sep 7, 2017

@circlingthesun Well, I am still currently waiting for approval :)

@ghost
Copy link
Copy Markdown

ghost commented Sep 8, 2017

Hi @cynecx, tests should be fixed if you merge in the latest master.

@cynecx
Copy link
Copy Markdown
Contributor Author

cynecx commented Sep 8, 2017

@Andy-MS Okay. I am not sure how this PR caused that last one but I'll fix it :)

@johnnyreilly johnnyreilly merged commit 0b21b7d into DefinitelyTyped:master Oct 6, 2017
@johnnyreilly
Copy link
Copy Markdown
Member

Thanks all!

@plantain-00
Copy link
Copy Markdown
Contributor

It seems it breaks a simple case:

import * as React from "react";
import * as ReactDOM from "react-dom";

class Main extends React.Component<{}, {}> {}

ReactDOM.render(<Main />, document.getElementById("container")); // <- error here
error TS2345: Argument of type 'Element' is not assignable to parameter of type 'ReactElement<any>[]'.
  Property 'length' is missing in type 'Element'.

@cynecx
Copy link
Copy Markdown
Contributor Author

cynecx commented Oct 7, 2017

@plantain-00 I am not able to reproduce this. Your simple case compiles just fine here. What version of typescript are you currently using?

@plantain-00
Copy link
Copy Markdown
Contributor

plantain-00 commented Oct 7, 2017

@cynecx

"@types/react": "16.0.10",
"@types/react-dom": "16.0.1",
"typescript": "2.5.3",

wait for a while.
update: it's npm's problem, there is a node_modules/@types/react/* in @types/react-dom

@johnnyreilly
Copy link
Copy Markdown
Member

@plantain-00 could you put together a minimal repro repo to share please? That will help things move quicker

@circlingthesun
Copy link
Copy Markdown

Based on @plantain-00's observation, I managed to work around this by adding the following to my package.json (I'm using yarn).

"resolutions": {
    "@types/react": "16.0.10",
    "@types/react-dom": "16.0.1",
}

@plantain-00
Copy link
Copy Markdown
Contributor

it's OK now, there is a node_modules/@types/react-dom/node_modules/@types/react/* in node_modules/@types/react-dom, reinstalling them works

@tkrotoff
Copy link
Copy Markdown
Contributor

tkrotoff commented Oct 9, 2017

This does not work:

  render() {
    return this.props.children;
  }

Question: why do we have ReactNode and JSX.Element? Could they be merged?

@MartynasZilinskas
Copy link
Copy Markdown
Collaborator

MartynasZilinskas commented Oct 9, 2017

@tkrotoff Since when we can return undefined in render()? You can only return null in render method if I remember correctly. Unless it was changed in newest React v16 that if children are absent it will return null instead of undefined.

@tkrotoff
Copy link
Copy Markdown
Contributor

tkrotoff commented Oct 9, 2017

@MartynasZilinskas You're right, see https://codepen.io/tkrotoff/pen/yzKKdB

Still there is a problem:

  render() {
    const { children } = this.props;
    return children !== undefined ? children : null;
  }
Types of property 'render' are incompatible.
  Type '() => string | number | boolean | {} | ReactElement<any> | (string | number | boolean | any[] | R...' is not assignable to type '() => string | number | false | Element | Element[] | null'.
    Type 'string | number | boolean | {} | ReactElement<any> | (string | number | boolean | any[] | ReactEl...' is not assignable to type 'string | number | false | Element | Element[] | null'.
      Type 'true' is not assignable to type 'string | number | false | Element | Element[] | null'.

@wcj3
Copy link
Copy Markdown

wcj3 commented Oct 10, 2017

Has the above fix been added for stateless components? I'm getting an error when attempting to implement one that returns an array of elements

@patsissons
Copy link
Copy Markdown
Contributor

why was the generic version of findDOMNode removed?

export function findDOMNode<E extends Element>(instance: ReactInstance): E;

(tagging @cynecx)

@vsiao
Copy link
Copy Markdown
Contributor

vsiao commented Oct 10, 2017

@patsissons it was removed because of a lint failure. Does it suffice to convert usages like so?

before:

ReactDOM.findDOMNode<HTMLInputElement>(inst)

after:

<HTMLInputElement>ReactDOM.findDOMNode(inst)

@patsissons
Copy link
Copy Markdown
Contributor

patsissons commented Oct 10, 2017

yea i am doing (basically) this as a temporary patch. what is the type safe way of finding a dom node of a specific type (i.e., my use case is that i am finding a dom node that will then be focused, so i need the type to be derived from HTMLElement)? I don't particularly enjoy using direct cast (or the as operator in my case, since it is in a tsx file).

I guess the argument is that the previous typing was no more type safe than a direct cast, in which case I understand why the change was made.

@DovydasNavickas
Copy link
Copy Markdown
Contributor

@patsissons, you will NOT get a type-safe result from a function ReactDOM.findDOMNode, because TypeScript's type system exists only during compile time and you are calling a function that was defined in JS world (react-dom library).

What you CAN do is have a type-gate for checking if an element.focus != null && typeof element.focus === "function". This way you can create and use your own Focusable interface for that and have a type-safety this way.

@vsiao
Copy link
Copy Markdown
Contributor

vsiao commented Oct 11, 2017

@patsissons I think the typecast is equivalent to what you were doing before. As @DovydasNavickas mentioned there's no guarantee that findDOMNode will return the exact subtype of Element that you expect.

@patsissons
Copy link
Copy Markdown
Contributor

patsissons commented Oct 11, 2017

I have to imagine that the equivalence of findDOMNode<T>(elem) would be identical to findDOMNode(elem) as T. I was using the generic call with a semantic guarantee that it would return a sane dom node. But I now see that syntactically there ought to be no difference since the cast is just happening through generic parameterization rather than direct casting. Since i was already sanitizing the dom node anyways, it really doesn't matter other than requiring a direct cast which i try to avoid but in this case it is literally unavoidable (unless i create a type guard function to recast back to HTMLElement).

patsissons added a commit to marinels/webrx-react that referenced this pull request Oct 11, 2017
patsissons added a commit to marinels/webrx-react that referenced this pull request Oct 11, 2017
@niba
Copy link
Copy Markdown

niba commented Oct 13, 2017

@cynecx I think you forgot about functional components. The following code doesn't work

const Test: React.StatelessComponent<any> = () => [
  <div />,
  <div />,
]

and the error

[ts]
Type '() => Element[]' is not assignable to type 'StatelessComponent'.
Type 'Element[]' is not assignable to type 'ReactElement | null'.
Type 'Element[]' is not assignable to type 'ReactElement'.
Property 'type' is missing in type 'Element[]'.

@cynecx
Copy link
Copy Markdown
Contributor Author

cynecx commented Oct 13, 2017

@niba Seems so. Looks like a trivial change to me. I can open a PR later.

@niba
Copy link
Copy Markdown

niba commented Oct 17, 2017

@cynecx I have been trying to add this on my own but it doesn't look like a trivial change. Simple changing the return type of StatalessComponent function breaks JSX
from (props: P & { children?: ReactNode }, context?: any): ReactElement<any> | null;
to (props: P & { children?: ReactNode }, context?: any): ReactElement<any>[] | ReactElement<any>| null;

with this change, the below definition of functional component started working

const StatelessComponent: React.SFC<SCProps> = ({ foo }: SCProps) => {
  return [
    <div>a</div>,
    <div> aa</div>
  ];
};

but now it's impossible to call it from JSX. <StatelessComponent />; produces

[ts]
JSX element type 'ReactElement | ReactElement[] | null' is not a constructor function for JSX elements.
Type 'null' is not assignable to type 'ElementClass'.
[ts] JSX element class does not support attributes because it does not have a 'props' property.

I don't see any constructor function in ReactElement and I don't know how JSX is processed by typescript. Do you have any idea what is wrong?

@cynecx
Copy link
Copy Markdown
Contributor Author

cynecx commented Oct 17, 2017

@niba You are right, this doesn't look trivial. I am also not sure what's wrong here (I am not too familiar with this code) but I will try investigating this.

@vsiao
Copy link
Copy Markdown
Contributor

vsiao commented Oct 18, 2017

I don't have much time to look into this, but there's some useful background in microsoft/TypeScript#5478 about how SFC+TSX works. cc @RyanCavanaugh

@notitatall
Copy link
Copy Markdown

notitatall commented Oct 31, 2017

I get an error when I try to return an array that contains a null element from render(). Simplified example:

class ReturnArrayWithNullElement extends React.Component<Props, {}> {

    ...

    render() {
        return [
            <div key={1}>Element 1</div>,
            <div key={2}>Element 2</div>,
            this.props.displayThird ? <div key={3}>Element 3</div> : null
        ]
    }
}

Maybe this isn't an idiomatic way of conditionally rendering an element inside of an array (still becoming acquainted with Fiber), but React deals with this how I would expect. TypeScript throws the following error:

error TS2415: Class 'ReturnArrayWithNullElement' incorrectly extends base class 'Component<Props, {}>'.
  Types of property 'render' are incompatible.
    Type '() => (Element | null)[]' is not assignable to type '() => string | number | false | Element | Element[] | ReactPortal | null'.
      Type '(Element | null)[]' is not assignable to type 'string | number | false | Element | Element[] | ReactPortal | null'.
        Type '(Element | null)[]' is not assignable to type 'ReactPortal'.

@vsiao
Copy link
Copy Markdown
Contributor

vsiao commented Oct 31, 2017

@notitatall the return type of render needs to be merged with the ReactNode type. For the time being you can probably cast to an appropriate type to unblock yourself.

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

Labels

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.