You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Second use of type guard produces different result #52232
importReactfrom'react';constchildArray=[]asanyasReact.ReactNode[];childArray.forEach(child=>{constchildren=React.isValidElement(child)&&child.props.children;// child.props is of type "unknown" error. If we swap this line with// the one above it, the error moves to the other line of code. Only// the line where we read child.props does it correctly type the value.// Multiple reads of child.props on the first line also work fine.constpropsValue=React.isValidElement(child)&&child.props.value;});// Wrapping the reads with one call to isValidElement will type correctlychildArray.forEach(child=>{if(React.isValidElement(child)){// no errors hereconstchildren=child.props.children;constpropsValue=child.props.value;}});childArray.forEach(child=>{constisValid=React.isValidElement(child);if(isValid){constchildren=child.props.children;constpropsValue=child.props.value;// after reading the isValid value a second time, child.props is // again unknown}elseif(isValid){constchildren=child.props.children;constpropsValue=child.props.value;}});
🙁 Actual behavior
All examples fail after the second read of a type guard on props, whether re-running the function or just reading the variable.
🙂 Expected behavior
Should behave consistently no matter if this is the first or second read of the type guard.
Change the .d.ts, function isValidElement<T = any> to preserve the old behavior with zero candidates. Basically the generic default here and on ReactElement should really match
Change the .d.ts to add an overload function isValidElement<T = any>(object: ReactNode<T>) so that a zero-candidate inference doesn't occur
Do something different in choosing what to do in UDTG behavior
Part of this issue is an effect of #50044. With that PR we consistently favor the asserted type in a type predicate narrowing, meaning that ReactElement<any> narrows to ReactElement<unknown>, which makes a property access like obj.props.baz an error. That is working as intended.
There isn't an error on the first property access is because the ReactNode type includes SomeReactElementWithFoo in a union type that isn't subject to subtype reduction (explicitly written union types aren't subtype reduced an thus may contain redundant constituents). Following the first isValidElement(obj) check, the type of obj is narrowed to ReactElement<unknown> | SomeReactElementWithFoo, and the property access succeeds because SomeReactElementWithFoo is still in the type. But control flow analysis then performs subtype reduction of the two possible control flows and we end up eliminating SomeReactElementWithFoo. Thereafter, the property access becomes an error.
So, overall this is working as intended, though I'll admit it's subtle.
Change the .d.ts, function isValidElement<T = any> to preserve the old behavior with zero candidates. Basically the generic default here and on ReactElement should really match
I'm not quite willing to sign on to "intended" (lol) but given that the UDTG definition is extremely suspect I think we can call this "best we can do for now". Hopefully one day we can solve the any/unknown subtyping dilemma.
Activity
RyanCavanaugh commentedon Jan 14, 2023
😱
Anyone interested in trying to produce a repro that doesn't need the entire React typings?
tjjfvi commentedon Jan 14, 2023
Here you go!
RyanCavanaugh commentedon Jan 14, 2023
Stellar, thank you!
RyanCavanaugh commentedon Jan 14, 2023
typescript-bot commentedon Jan 14, 2023
The change between origin/release-4.7 and origin/release-4.8 occurred at a4507c9.
RyanCavanaugh commentedon Jan 14, 2023
Root cause is #50044, cc Anders Hejlsberg (@ahejlsberg)
Possible fixes:
function isValidElement<T = any>to preserve the old behavior with zero candidates. Basically the generic default here and onReactElementshould really matchfunction isValidElement<T = any>(object: ReactNode<T>)so that a zero-candidate inference doesn't occurahejlsberg commentedon Jan 17, 2023
Part of this issue is an effect of #50044. With that PR we consistently favor the asserted type in a type predicate narrowing, meaning that
ReactElement<any>narrows toReactElement<unknown>, which makes a property access likeobj.props.bazan error. That is working as intended.There isn't an error on the first property access is because the
ReactNodetype includesSomeReactElementWithFooin a union type that isn't subject to subtype reduction (explicitly written union types aren't subtype reduced an thus may contain redundant constituents). Following the firstisValidElement(obj)check, the type ofobjis narrowed toReactElement<unknown> | SomeReactElementWithFoo, and the property access succeeds becauseSomeReactElementWithFoois still in the type. But control flow analysis then performs subtype reduction of the two possible control flows and we end up eliminatingSomeReactElementWithFoo. Thereafter, the property access becomes an error.So, overall this is working as intended, though I'll admit it's subtle.
ahejlsberg commentedon Jan 17, 2023
BTW, I'll second Ryan Cavanaugh (@RyanCavanaugh)'s suggested fix:
jasonaden commentedon Jan 19, 2023
Thanks for checking into this. I'll PR against React library to land a fix.
RyanCavanaugh commentedon Jan 19, 2023
I'm not quite willing to sign on to "intended" (lol) but given that the UDTG definition is extremely suspect I think we can call this "best we can do for now". Hopefully one day we can solve the any/unknown subtyping dilemma.