Search code examples
javascriptreactjstypescriptreact-tsx

Why are these children as array not rendered in TS?


I have a dead simple component. Works perfectly in javascript.

const Test = (props: any) => <div>{props.children}</div>;

const Root: React.SFC<{}> = props => {
  return (
    <div className="Root">
      <h1>hello world.</h1>
      <Test>{[...Array(20)].map((_, index) => <h1>test{index}</h1>)}</Test>
    </div>
  );
};

export default Root;

But doesn't work in typescript. Why?

Both use same React version.

EDIT:

TypeScript: https://codepen.io/anon/pen/eKGoWo

JavaScript: https://codepen.io/anon/pen/GGMLOv


Solution

  • It works if you change it from that spread array map to

    <Test>{Array.from({length:20}, (_, index) => <h1 key={index}>test{index}</h1>)}</Test>
    

    (I also added key to that, since React complains once it starts working. :-) )

    Not working: https://codepen.io/anon/pen/XYeQzv?editors=1010

    Working: https://codepen.io/anon/pen/eKGoya?editors=1010

    It has to do with how TypeScript transpiles that spread notation. TypeScript is converting that [...Array(20)].map(/*...*/) to this:

    Array(5).slice().map(/*...*/)
    

    The problem with that is that Array(20) creates an array of length 20 with no entries in it. slice replicates that. map only visits entries that actually exist in the array, not gaps. But [...Array(20)] creates an array with 20 entries containing undefined, which map will visit:

    const a1 = [...Array(5)];
    console.log(0 in a1);   // true
    const a1m = a1.map((_, index) => index);
    console.log(0 in a1m);  // true
    console.log(a1m);       // 0, 1, 2, 3, 4
    
    const a2 = Array(5).slice();
    console.log(0 in a2);   // false
    const a2m = a2.map((_, index) => index);
    console.log(0 in a2m);  // false
    console.log(a2m);       // (gaps)
    Look in the real console (the snippet console gives the impression values exist in the array when they don't).

    Yury Tarabanko kindly found a bug report for it.