Reputation: 521
Foo.tsx
interface IFooProps{
userId:number
}
class Foo extends Component<IFooProps>{
render(){
return <p>foo...</p>
}
}
const mapStateToProps = (state: IState) => {
return {
userId: state.user.id,
}
}
const mapDispatchToProps = (dispatch: Dispatch) => {
return {}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(Foo)
Index.tsx
the following code occurred missing userId props error
// missing userId props error
<Foo />
props come from mapStateToProps
, do not need to pass again.
Upvotes: 3
Views: 911
Reputation: 2911
After a few years of working with Typescript and Redux I managed to find my ultimate solution for connect
. It's a little tricky
First, you need to use a module augmentation
feature from typescript. It allows you to extend existing typings. We would use it to extend a react-redux
typings.
You need to create a .ts
file somewhere in your project with the following code:
import { Component, ComponentClass } from "react-redux";
declare module "react-redux" {
export interface InferableComponentEnhancerWithProps<TInjectedProps, TNeedsProps> {
<P extends TInjectedProps>(component: Component<P>): ComponentClass<
Omit<P, keyof TInjectedProps> & TNeedsProps
> & {
WrappedComponent: Component<P>;
};
allProps: TInjectedProps & TNeedsProps;
}
}
Here we add to the existing InferableComponentEnhancerWithProps
interface a property allProps: TInjectedProps & TNeedsProps;
, the rest of the code is a rewrite of the existing interface declaration from the original typings.
After creating it typescript compiler should automatically pick up the new definition and now we are ready to go back to your files
Foo.tsx
// you only define props that you would like to pass from the parent component, not the props that are injected from redux state
interface IFooProps {
testProp: any;
}
// IConnectProps are defined at the bottom
class Foo extends Component<IConnectProps>{
render() {
this.props.testProps;
this.props.userId;
return <p>foo...</p>
}
}
const mapStateToProps = (state: IState, props: IFooProps) => {
return {
userId: state.user.id,
...props,
}
}
const mapDispatchToProps = (dispatch: Dispatch) => {
return {}
}
const connectCreator = connect(
mapStateToProps,
mapDispatchToProps
);
// here we use our new typings, thanks to them typescript automatically merge
// props that we declared in mapStateToProps so the IConnectProps
// looks like {
// userId: string;
// testProp: any;
// }
type IConnectProps = typeof connectCreator.allProps;
export default connectCreator(Foo);
Index.tsx
// typescript only tells you to pass a testProp
<Foo testProp={5} />
Upvotes: 0
Reputation: 2522
Your problem is a common problem people encounter when switch from JS to TS with React and using HOC like connect
with their component.
Basically, with your interface IFooProps
, you tell TS
that userId
is a required prop whenever the component is used and TS
doesn't know that actually, userId
will be taken care of / injected to the props through connect
.
There is couple of way to resolve the problem, the first is making userId
an optional attribute.
interface IFooProps{
userId?: number
}
This approach does have a downsize, as TS will ask yo to perform null/undefined check whenever you use userId
. However, it's the most simple one.
The other approach is to define another interface, in your example, can be conventionally named: IFooInjectedProps
. Your component will be like:
interface IFooProps {
// ....Whatever props that require by the component
}
// All injected props from the store
interface IFooInjectedProps extends IFooProps {
userId: number
}
class Foo extends Component<IFooProps> {
get injectedProps() {
// Cast this.props as injectedProps to bypass typechecking
return this.props as IFooInjectedProps;
}
render() {
// Get userId from the getter `injectedProps`
const { userId } = this.injectedProps;
return <p>foo...</p>
}
}
Upvotes: 4
Reputation: 2869
Because you said that "IFooProps" as type of props it is expecting you to send userId when you use it. set the type as any. You dont need to set the type of props when you using redux.
class Foo extends Component<any>{
render(){
return <p>foo...</p>
}
}
Upvotes: -1