Repository navigation
await Promise.all fails to resolve due to optional chaining in JSX #52475
Copy link
Copy link
Closed
Labels
Milestone
Description
Bug Report - await Promise.all fails to resolve due to optional chaining inside JSX
I am sorry if this doesn't end up being a TypeScript bug, I'm just not able to replicate this without React at the moment. This feels more like a TypeScript issue? So I'll let you guys decide.
🔎 Search Terms
await Promise.all toString JSX ReactNode
🕗 Version & Regression Information
- This is a type checking error
- Broken on 4.9.4, works with version 4.6.2
- I am only able to replicate this with React 17.0.2, not react 18
⏯ Repo Link
Sorry, I have to use a repo for this. It's a bit of a setup to create the conditions for this.
https://github.com/Fish1/bughunter
💻 Code
/src/pages/HomePage.tsx
import { ReactNode } from "react";
import MyList from "../component/MyList";
function HomePage() {
async function myUnusedFunction() {
const getDataFetch1 = Promise.resolve(["hello", "world"])
const getDataFetch2 = Promise.resolve([{ id: 1 }, { id: 2 }])
const [data1, data2] = await Promise.all([
getDataFetch1, getDataFetch2
]);
// *******************************
// TypeScript thinks data2 is a Promise<{ id: number; }[]>
const x = data2.map((data) => {
return data.id.toString();
});
// *******************************
// TypeScript thinks data1 is a Promise<string[]>
const y = data1.map((data) => {
return data.toString();
});
return { x, y };
}
const myNodeArray: ReactNode[] = [
<div>hello</div>,
<div>world</div>,
"hello",
"sup",
]
return (
<div>
<h1>Home Page</h1>
<MyList columnsCurrent={myNodeArray}/>
</div>
);
}
export default HomePage;/src/component/MyList.tsx
import { ReactNode } from "react";
export interface ListThingProps {
columnsCurrent: ReactNode[],
}
function MyList(props: ListThingProps) {
const { columnsCurrent } = props;
return (
<div>
{
columnsCurrent.map((column, index) => {
/**
* *******************************************************************
* This variable existing will cause Promise.all() to not resolve its promises.
* *******************************************************************
*/
const brokenString = column?.toString();
return (
<div key={index}>
{/* remove {column} here to fix the Promise.all() error */}
{column}
</div>
)
})
}
</div>
);
}
export default MyList;output of npx tsc
src/pages/HomePage.tsx:14:23 - error TS2339: Property 'map' does not exist on type 'Promise<{ id: number; }[]>'.
14 const x = data2.map((data) => {
~~~
src/pages/HomePage.tsx:14:23
14 const x = data2.map((data) => {
~~~
Did you forget to use 'await'?
src/pages/HomePage.tsx:14:28 - error TS7006: Parameter 'data' implicitly has an 'any' type.
14 const x = data2.map((data) => {
~~~~
src/pages/HomePage.tsx:18:23 - error TS2339: Property 'map' does not exist on type 'Promise<string[]>'.
18 const y = data1.map((data) => {
~~~
src/pages/HomePage.tsx:18:23
18 const y = data1.map((data) => {
~~~
Did you forget to use 'await'?
src/pages/HomePage.tsx:18:28 - error TS7006: Parameter 'data' implicitly has an 'any' type.
18 const y = data1.map((data) => {
~~~~
Found 4 errors in the same file, starting at: src/pages/HomePage.tsx:14
🙁 Actual behavior
await Promise.all doesn't resolve the promises.
🙂 Expected behavior
I expect Promise.all() to resolve it's types correctly regardless of a variable in another file.
Strange Solutions that I have come up with
- Comment out the brokenString variable in /src/component/MyList.tsx
- Uncomment the FixPromiseError() function in src/pages/DummyPage.tsx
- Remove {column} from inside the div in /src/component/MyList.tsx
Here is a link to the TypeScript community discord, and the thread with a lots more testing.
https://discord.com/channels/508357248330760243/1068268321998372964
Video of error (incase text description wasn't clear)
Video.webm
Reactions are currently unavailable
Activity
Fish1 commentedon Jan 28, 2023
Here is a demonstration of another "fix".
This resolves all the issues with promise.all, It seemed like typescript didn't have the type definitions of the Promise library?
All fixed :D

Fish1 commentedon Jan 28, 2023
This error seems to be caused by the optional chaining of the ReactNode type.
The optional chain can exit if it has an if statement guard, and it is fine. It's only an issue when the code is depending on the optional chain.
Also, I can only seem to get this error when working with the ReactNode type in react.
[-]await Promise.all fails to resolve due a toString call inside JSX[/-][+]await Promise.all fails to resolve due a optional chaining in JSX[/+][-]await Promise.all fails to resolve due a optional chaining in JSX[/-][+]await Promise.all fails to resolve due to optional chaining in JSX[/+]Fish1 commentedon Jan 28, 2023
Another test case: the optional chaining works fine, as long as you use the variable inside the JSX return of the map. And you don't use the column variable in any other pointless expression.
column?.toString()(with or without the if guard) (with or without the optional chaining)Fish1 commentedon Jan 28, 2023
Test Case: setting "allowJS": false inside tsconfig.json
If you set "allowJS": false in tsconfig.json, then VSCode will not detect the error. But TSC will continue to detect it.
Fish1 commentedon Jan 30, 2023
Test Case: Updating to React 18 fixes the issue. But I noticed the spot where they changed the ReactFragment type, and reverted it back to what was in React 17. This caused to issue to reappear.
@types/react/index.d.ts

Here I commented out the React18 ReactFragment, and brought back the React17 ReactFragment. And the issue shows up.
@types/react/index.d.ts

I then deleted the {} type from the Fragment, and the issue is resolved.
RyanCavanaugh commentedon Jan 30, 2023
Minimal repro I have so far:
Trying to simplify further but this is very, very delicate
RyanCavanaugh commentedon Jan 30, 2023
OK, so it's just these two files
listed in that order in tsconfig, with these dependencies:
I'm incredibly curious what's going on, but would really want the react dependency stubbed out into something that still repros this before investigating further. This bug, whatever it is, is very hard to hit.
Fish1 commentedon Jan 31, 2023
Ryan Cavanaugh (@RyanCavanaugh) Thank you so much! I think I was able to remove the React dependency. It is pushed to the "minimal" branch of my GitHub repo.
Branch: https://github.com/Fish1/bughunter/tree/minimal
/main.tsx
/MyList.tsx
Fish1 commentedon Jan 31, 2023
Was able to take reduce the issue a little bit more. We can simply remove the map from the MyList file.
/main.ts
/MyType.ts
I also went and reset the tsconfig to what is produced by TSC init.
{ "compilerOptions": { "target": "es2016", "module": "commonjs", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "strict": true, "skipLibCheck": true, "noEmit": true } }Fish1 commentedon Jan 31, 2023
Test Case: It appears to be that what matters is two expressions of myVar being used, where the first is a conditional chain.
Fish1 commentedon Jan 31, 2023
Was able to reproduce it in a single file, and have it in the playground.
https://www.typescriptlang.org/play?#code/C4TwDgpgBAsiAq5oF4oG8C+UA+UB2ArgDZE5QF4AmEAZgJZ4SUDcAUKwMYD2eAzsFAC2IAGoBDAE4AuWAiRRUhElDG9ZiSG1bDxEgPwA6YFwAyXDmKIQAysAkMA5mx2StqkHg5QaFDsDo8QiAAqoS8TABivv48ABQAlOisUFDcfAI0EMAcABYAjApQAAoSXIJ04QYSELxcRABuELEA2gDkORAkXK0ANFCtAO5cEkSUrQC68WwpafxQzZRiwGJ544ViA2J0AiVlFRAGlkQtmdn5k9NQi8t5BlZ4DsA5bBisQA
Seem like it was the mapping that wasn't allowing us to repro it in a single file.

jakebailey commentedon Feb 1, 2023
Since this reproduces, I did a bisect which points to #49119.
jakebailey commentedon Feb 1, 2023
The test, FWIW:
RyanCavanaugh commentedon Feb 1, 2023
Jacob Enders (@Fish1) big thanks for helping get to a short repro and Mateusz Burzyński (@Andarist) for the fast fix!
Fish1 commentedon Feb 1, 2023
Amazing work! Thanks everyone!
Andarist commentedon Feb 2, 2023
It was so bizarre that I couldn't resist debugging this 😅