The short answer is "no" because all the solutions here suck. You'd probably have better luck dealing with function components and useSelector
hooks.
When you map with a wrong key, your composed DevComp
will be a component which requires that a counter
prop be passed in when you call it so typescript gets that part right.
You can get the error to be raised by setting some of the generics on the connect
function, but this seems like a pain. The variables that we would set are the first, which is the props returned from mapStateToProps
, and the third, which is the props that you expect the component to be called with.
const DevComp = connect<DevCompProps, {}, {}>(mapStateToProps)(UnconnectedDevComp)
Likewise, you can get an error to be raised by setting the return type on mapStateToProps
to Partial<DevCompProps>
.
You would need to override some of the types in the react-redux
package in order to get that inference automatically. But that sucks too.
We want to make it such that the props returned from mapStateToProps
cannot have any keys which are not present in the component props.
You can't do it by manipulating the MapStateToProps
type. That has three variables, but none represent the type of the component to be mapped. What you need is for TStateProps
to be restricted.
export type MapStateToProps<TStateProps, TOwnProps, State = DefaultRootState> =
(state: State, ownProps: TOwnProps) => TStateProps;
Ultimately the issue is the two-step nature of the connect
HOC. The creation of the "connector" is separate from applying it to a component, so the connector cannot depend on that component.
It's perfectly fine to do this:
const myWrapper = connect(mapStateToProps);
and get the type
InferableComponentEnhancerWithProps<{ number: number; } & DispatchProp<AnyAction>, {}>
myWrapper
is a function which can wrap any component and will add a prop number
to it. It doesn't know anything about the type of component that it will be applied to and does not care whether that component actually wants a prop number
.
You would need to define your own function that combines the two steps into one function with two arguments so that the arguments can infer types from each other. I don't have a complete solution. connect
has 15 different overloads and this type signature only describes one. Also the implementation is an error, but the function typing does what you want.
const myConnect = <InnerProps, MappedProps extends Partial<InnerProps> , State = DefaultRootState>(
mapStateToProps: (state: State) => MappedProps, Component: ComponentType<InnerProps>
): ComponentType<Omit<InnerProps, keyof MappedProps>> => {
return connect(mapStateToProps)(Component);
}
export const DevComp = myConnect(mapStateToProps, UnconnectedDevComp)