Version:
Redux 4 + React Redux 5.0.20 + typesafe-actions 2.0.2+ types/react-redux 6.0.0
For example:
todoAction:
const load = createAction("[Todo] Load Todo Item");
Container:
export interface Props {
load: () => void;
}
const TodoContainer = ({load}: Props) => {
return (
<div>
<button onClick={load}>load item</button>
</div>
);
};
export default connect(null, {
load: todoAction.load
})(TodoContainer);
Error:
[ts]
Argument of type '({ load }: Props) => Element' is not assignable to parameter of type 'ComponentType<Shared<{ items: Todo[];
loading: boolean; } & { load: () => { type: "[Todo] Load To...'.
Type '({ load }: Props) => Element' is not assignable to type 'StatelessComponent<Shared<{ items: Todo[]; loading: boolean; } & { load: () => { type: "[Todo] Lo...'.
Types of parameters '__0' and 'props' are incompatible.
Type 'Shared<{ items: Todo[]; loading: boolean; } & { load: () => { type: "[Todo] Load Todo Item"; }; }...' is not assignable to type 'Props'.
Property 'load' is optional in type 'Shared<{ items: Todo[]; loading: boolean; } & { load: () => { type: "[Todo] Load Todo Item"; }; }...' but required in type 'Props'.
It works without any issue with types/react-redux 5.0.20, but when I upgrade to 6 it shows this error.
I think it is something related to the type Shared
I got a similar problem with @types/react-redux 6.0.0 when working on a HOC. The problem consisted if I used the generic type for base Component. I reverted to @types/react-redux 5.0.20 and it worked fine.
interface UnwrappedProps {
records: Record[]
}
interface WithRecordsProps {
dataset: string
}
export const withRecords = <P extends UnwrappedProps>(Component: React.ComponentType<P>) => {
class ComponentWithRecords extends React.Component<P & WithRecordsProps, {}> {
public render() {
return (
<Component records={this.props.records} {...this.props} />
)
}
}
const getDatasetSelector = (state: StateStruct, props: P & WithRecordsProps) =>
getDataset(state.datasets, props.dataset)
const getRecordsSelector = (state: StateStruct, props: P & WithRecordsProps) =>
state.records
const makeGetRecordsSelector = () => createSelector(
[getDatasetSelector, getRecordsSelector],
(dataset, records) => dataset.records.map((record) => getRecord(records, record)),
)
const makeMapStateToProps = () => {
return (state: StateStruct, props: P & WithRecordsProps) => {
return {
records: makeGetRecordsSelector()(state, props),
}
}
}
return connect<UnwrappedProps>(makeMapStateToProps)(ComponentWithRecords)
}
Got error:
Argument of type 'typeof ComponentWithRecords' is not assignable to parameter of type 'ComponentType<Shared<UnwrappedProps & DispatchProp<AnyAction>, P &
WithRecordsProps>>'.
Type 'typeof ComponentWithRecords' is not assignable to type 'StatelessComponent<Shared<UnwrappedProps & DispatchProp<AnyAction>, P & WithRecordsProps>>'.
Type 'typeof ComponentWithRecords' provides no match for the signature '(props: Shared<UnwrappedProps & DispatchProp<AnyAction>, P & WithRecordsProps> & { children?: ReactNode; }, context?: any): ReactElement<any> | null'.
I have no idea why they have v6. I think someone from them need to give the reason.
Same with me
src/stateful-navigator.tsx:81:35 - error TS2345: Argument of type '({ navigation, dispatch }: INavigatorProps) => Element' is not assignable to parameter of type 'ComponentType<Shared<{ navigation: NavigationState; } & DispatchProp<AnyAction>, INavigatorProps>>'.
Type '({ navigation, dispatch }: INavigatorProps) => Element' is not assignable to type 'StatelessComponent<Shared<{ navigation: NavigationState; } & DispatchProp<AnyAction>, INavigatorP...'.
Types of parameters '__0' and 'props' are incompatible.
Type 'Shared<{ navigation: NavigationState; } & DispatchProp<AnyAction>, INavigatorProps> & { children?...' is not assignable to type 'INavigatorProps'.
Types of property 'navigation' are incompatible.
Type 'NavigationState | undefined' is not assignable to type 'NavigationState'.
Type 'undefined' is not assignable to type 'NavigationState'.
81 return connect(mapStateToProps)(statefulNavigator);
This was working before without any issues.
cc: @Kallikrein @ryym @sheetalkamat
Hi,
I'm sorry some features are provoking bugs, can you please help me and provide a light repo reproducing your issue ?
If you can't I will investigate anyway, only later.
@changLiuUNSW
@yordis
I've bootstrapped this : https://github.com/Kallikrein/debug-react-redux-typings
Can you add something to break this so I can help you fix ?
@Kallikrein
Here you are:
https://github.com/changLiuUNSW/React-Scaffold
Check TodoContainer.tsx
@changLiuUNSW I just tried.
I think Shared<>is working just fine, it may be inferring better than it was before and highlighting what was previously false negatives.
It seems your typings are not matching :
interface Props {
items: Todo[];
loading: boolean;
load: () => void;
}
And your load is in fact:
interface DispatchProps {
load: () => {
type: TodoActionTypes.Load
}
}
If think the error lies in
type MapDispatchToProps<TDispatchProps, TOwnProps> =
MapDispatchToPropsFunction<TDispatchProps, TOwnProps> | TDispatchProps;
Where when mapDispatchToProps is an object (it's my first time using it), it incorrectly infers injected props as the object itself.
Instead, it should do a transform, something along the lines of:
type ObjectDispatch<TDispatchProps> = Record<keyof TDispatchProps, () => void>;
type MapDispatchToProps<TDispatchProps, TOwnProps> =
| MapDispatchToPropsFunction<TDispatchProps, TOwnProps>
| ObjectDispatch<TDispatchProps>;
I tried this fix by locally editing typings in your example and it's working fine.
Before I submit this fix and check it's not inducing any kind of regression, I would like your opinion on the return of these object mapDispatchToProps properties : once wrapped, do we always have a void return, or maybe something else ? (like a promise)
@suppayami your issue is missing some parts, can you dump me a file without imported typings or a repo reproducing your issue ?
I faked some typings and the following :
return connect(makeMapStateToProps)(ComponentWithRecords);
Went flawlessly. Do you really need to provide the generics to connect ? It is inferring perfectly with HOF mapStateToProps in my small mockup...
@yordis I have a feeling your various troubles may be totally unrelated, maybe it would be better to open separate issues with reproducible examples.
Don't forget to tag me, I have trouble following the overwhelming flow of issue on this repo and only subscribed to tagged notifications...
@Kallikrein Thanks for you reply.
For now, I have done following workaround for TodoContainer to eliminate the error:
interface DispatchProps {
load: () => void;
}
interface PropsState {
items: Todo[];
loading: boolean;
}
const TodoContainer = ({ loading, load, items }: DispatchProps & PropsState) => {
return (
<div>
<Button primary={true} onClick={load}>
load item
</Button>
{loading && <p>loading...</p>}
{!loading && <TodoList items={items} />}
</div>
);
};
const mapStateToProps = (state: RootState) => {
return {
items: getTodos(state),
loading: getTodoLoading(state)
};
};
export default connect<PropsState, DispatchProps>(mapStateToProps, {
load: todoActions.load
})(TodoContainer);
I am not 100% why above workaround is working.
once wrapped, do we always have a void return, or maybe something else ? (like a promise)
Return promise is quite normal scenario especially for Redux-Thunk
It works because you are casting your connect. It can be dangerous so I would advise against it.
The best quick fix I would advise you is to replace your object mapStateToProps with a classic function
interface DispatchProps {
load: () => void;
}
const mapDispatchToProps: MapDispatchToPropsFunction<DispatchProps, any> = dispatch => ({
load: () => dispatch(todoActions.load())
})
export default connect(mapStateToProps, mapDispatchToProps)(TodoContainer)
You are just doing explicitely what the shortcut react-redux object sugar does for you.
This lib is a real pain, it is highly polymorphic argument number, argument types, etc, it's a horrible design "脿 la js"...
@Kallikrein Thanks for your reply.
I have tried, this also works:
interface DispatchProps {
load: () => void;
}
interface PropsState {
items: Todo[];
loading: boolean;
}
const TodoContainer = ({ loading, load, items }: DispatchProps & PropsState) => {
return (
<div>
<Button primary={true} onClick={load}>
load item
</Button>
{loading && <p>loading...</p>}
{!loading && <TodoList items={items} />}
</div>
);
};
const mapStateToProps = (state: RootState) => {
return {
items: getTodos(state),
loading: getTodoLoading(state)
};
};
export default connect(mapStateToProps, {
load: todoActions.load
})(TodoContainer);
@changLiuUNSW One last advice
Type your lambda components as React.StatelessComponent, it provides children in case you need it.
const TodoContainer: React.StatelessComponent<DispatchProps & StateProps> = ({ loading, load, items }) => (
<div>
<Button primary={true} onClick={load}> load item </Button>
{loading ? <p>loading...</p> : <TodoList items={items} />}
</div>
);
@Kallikrein
I have done future investigation, I believe you are right. It infers better than before and highlight what was previously false negatives.
Because I have tested, my load is actually not void but actually return action object:
load: () => {
type: TodoActionTypes.Load
}
// console.log (load());
// { "type": "[TODO] Todo Load" }
I have done the fix to use mapDispatchToProps and does not return action object:
const mapDispatchToProps = (dispatch: Dispatch<RootAction>) => {
return {
// DON'T, because this still return action object
// load: () => dispatch(searchActions.search(input));
load: () => {
dispatch(searchActions.search(input));
}
};
};
Most helpful comment
I got a similar problem with @types/react-redux 6.0.0 when working on a HOC. The problem consisted if I used the generic type for base Component. I reverted to @types/react-redux 5.0.20 and it worked fine.
Got error: