@@ -66,14 +66,14 @@ const DataSource = {
66
66
};
67
67
/** type aliases just to deduplicate */
68
68
type DataType = typeof DataSource ;
69
- type TODO_ANY = any ;
69
+ // type TODO_ANY = any;
70
70
71
71
/** utility types we use */
72
72
type Omit <T , K extends keyof T > = Pick <T , Exclude <keyof T , K >>;
73
- type Optionalize <T extends K , K > = Omit <T , keyof K >;
73
+ // type Optionalize<T extends K, K> = Omit<T, keyof K>;
74
74
75
75
/** Rewritten Components from the React docs that just uses injected data prop */
76
- function CommentList({ data }: WithSubscriptionProps ) {
76
+ function CommentList({ data }: WithDataProps < typeof comments > ) {
77
77
return (
78
78
<div >
79
79
{ data .map ((comment : CommentType ) => (
@@ -82,13 +82,13 @@ function CommentList({ data }: WithSubscriptionProps) {
82
82
</div >
83
83
);
84
84
}
85
- interface BlogPostProps extends WithSubscriptionProps {
85
+ interface BlogPostProps extends WithDataProps < string > {
86
86
id: number ;
87
87
// children: ReactNode;
88
88
}
89
89
function BlogPost({ data , id }: BlogPostProps ) {
90
90
return (
91
- <div >
91
+ <div key = { id } >
92
92
<TextBlock text = { data } />;
93
93
</div >
94
94
);
@@ -100,44 +100,55 @@ function BlogPost({ data, id }: BlogPostProps) {
100
100
Example HOC from React Docs translated to TypeScript
101
101
102
102
``` tsx
103
- // // ACTUAL HOC
104
- interface WithSubscriptionProps {
105
- data: TODO_ANY ;
103
+ // these are the props to be injected by the HOC
104
+ interface WithDataProps < T > {
105
+ data: T ; // data is generic
106
106
}
107
- /** This function takes a component... */
108
- function withSubscription<
109
- T extends WithSubscriptionProps = WithSubscriptionProps
110
- >(
111
- WrappedComponent : React .ComponentType <T >,
112
- selectData : (DataSource : DataType , props : TODO_ANY ) => TODO_ANY
107
+ // T is the type of data
108
+ // P is the props of the wrapped component that is inferred
109
+ // C is the actual interface of the wrapped component (used to grab defaultProps from it)
110
+ export function withSubscription<T , P extends WithDataProps <T >, C >(
111
+ // this type allows us to infer P, but grab the type of WrappedComponent separately without it interfering with the inference of P
112
+ WrappedComponent : JSXElementConstructor <P > & C ,
113
+ // selectData is a functor for T
114
+ // props is Readonly because it's readonly inside of the class
115
+ selectData : (
116
+ dataSource : typeof DataSource ,
117
+ props : Readonly <JSX .LibraryManagedAttributes <C , Omit <P , ' data' >>>
118
+ ) => T
113
119
) {
114
- // ...and returns another component...
115
- return class extends React .Component <Optionalize <T , WithSubscriptionProps >> {
116
- state = {
117
- data: selectData (DataSource , this .props )
118
- };
119
-
120
- componentDidMount() {
121
- // ... that takes care of the subscription...
122
- DataSource .addChangeListener (this .handleChange );
120
+ // the magic is here: JSX.LibraryManagedAttributes will take the type of WrapedComponent and resolve its default props
121
+ // against the props of WithData, which is just the original P type with 'data' removed from its requirements
122
+ type Props = JSX .LibraryManagedAttributes <C , Omit <P , ' data' >>;
123
+ type State = {
124
+ data: T ;
125
+ };
126
+ return class WithData extends Component <Props , State > {
127
+ constructor (props : Props ) {
128
+ super (props );
129
+ this .handleChange = this .handleChange .bind (this );
130
+ this .state = {
131
+ data: selectData (DataSource , props )
132
+ };
123
133
}
124
134
125
- componentWillUnmount() {
135
+ componentDidMount = () => DataSource .addChangeListener (this .handleChange );
136
+
137
+ componentWillUnmount = () =>
126
138
DataSource .removeChangeListener (this .handleChange );
127
- }
128
139
129
- handleChange = () => {
140
+ handleChange = () =>
130
141
this .setState ({
131
142
data: selectData (DataSource , this .props )
132
143
});
133
- };
134
144
135
145
render() {
136
- // ... and renders the wrapped component with the fresh data!
137
- // Notice that we pass through any additional props
138
- return <WrappedComponent data = { this .state .data } { ... this .props as T } />;
146
+ // the typing for spreading this.props is... very complex. best way right now is to just type it as any
147
+ // data will still be typechecked
148
+ return <WrappedComponent data = { this .state .data } { ... this .props as any } />;
139
149
}
140
150
};
151
+ // return WithData;
141
152
}
142
153
143
154
/** HOC usage with Components */
@@ -148,7 +159,7 @@ export const CommentListWithSubscription = withSubscription(
148
159
149
160
export const BlogPostWithSubscription = withSubscription (
150
161
BlogPost ,
151
- (DataSource : DataType , props : React . ComponentProps < typeof BlogPost >) =>
162
+ (DataSource : DataType , props : Omit < BlogPostProps , ' data ' >) =>
152
163
DataSource .getBlogPost (props .id )
153
164
);
154
165
```
0 commit comments