Issues with react and redux
We have a test case for common patterns with react redux and there are 3 differences between ts-go and tsc.
I've narrowed it down to the test cases that fail and created a repro:
https://github.com/lukeapage/ts-repro-1
just npm i and npm run test
link to test file: https://github.com/lukeapage/ts-repro-1/blob/main/test.tsx
The errors (please include them in your issues!)
test.tsx:58:3 - error TS2578: Unused '@ts-expect-error' directive.
58 // @ts-expect-error detects that the wrong type is used in map state to props (when no return type in mapStateToProps)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
test.tsx:77:15 - error TS2741: Property 'stringProp' is missing in type '{}' but required in type '{ key?: Key | null | undefined; ref?: Ref<ExampleComponent> | undefined; context?: Context<ReactReduxContextValue<any, UnknownAction> | null> | undefined; store?: Store<any, UnknownAction, unknown> | undefined; readonly stringProp: string; defaultBooleanProp?: boolean | undefined; }'.
77 const c2 = <ConnectedFn />;
~~~~~~~~~~~
test.tsx:14:3 - 'stringProp' is declared here.
14 stringProp: string;
~~~~~~~~~~
test.tsx:103:15 - error TS2741: Property 'stringProp' is missing in type '{}' but required in type '{ key?: Key | null | undefined; ref?: Ref<ExampleComponent> | undefined; context?: Context<ReactReduxContextValue<any, UnknownAction> | null> | undefined; store?: Store<any, UnknownAction, unknown> | undefined; readonly stringProp: string; defaultBooleanProp?: boolean | undefined; }'.
103 const c2 = <ConnectedFn />;
~~~~~~~~~~~
test.tsx:14:3 - 'stringProp' is declared here.
14 stringProp: string;
~~~~~~~~~~
Found 3 errors in the same file, starting at: test.tsx:58
Yet another issue related to type ordering. The connect function has the following first overload (which is the one chosen):
<TStateProps = {}, no_dispatch = {}, TOwnProps = {}, State = DefaultState>(mapStateToProps: MapStateToPropsParam<TStateProps, TOwnProps, State>): InferableComponentEnhancerWithProps<TStateProps & DispatchProp, TOwnProps>;
// ...
type MapStateToProps<TStateProps, TOwnProps, State> = (state: State, ownProps: TOwnProps) => TStateProps;
type MapStateToPropsFactory<TStateProps, TOwnProps, State> = (initialState: State, ownProps: TOwnProps) => MapStateToProps<TStateProps, TOwnProps, State>;
type MapStateToPropsParam<TStateProps, TOwnProps, State> = MapStateToPropsFactory<TStateProps, TOwnProps, State> | MapStateToProps<TStateProps, TOwnProps, State> | null | undefined;
The problem here is the MapStateToPropsParam union type which gets ordered differently by the old and new compilers. This means we infer to MapStateToPropsFactory and MapStateToProps in different orders and therefore get differently ordered candidates. And when we subsequently pick the "first" candidate, we get different outcomes.
You can trigger the error with the old compiler simply by reordering the union type:
type MapStateToPropsParam<TStateProps, TOwnProps, State> = MapStateToProps<TStateProps, TOwnProps, State> | MapStateToPropsFactory<TStateProps, TOwnProps, State> | null | undefined;
The reordering could also be triggered simply by code referencing MapToStateProps being checked before checking the connect call.
Ideally, the React declaration files would break the union parameter into separate overloads and list them in order of preference.
Thanks for the analysis! I’ve raised an issue in the react-redux github.