Skip to content

Commit 2aec7a4

Browse files
author
sw-yx
committed
fix HOC example
1 parent 169cdf4 commit 2aec7a4

File tree

1 file changed

+42
-31
lines changed

1 file changed

+42
-31
lines changed

HOC.md

Lines changed: 42 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -66,14 +66,14 @@ const DataSource = {
6666
};
6767
/** type aliases just to deduplicate */
6868
type DataType = typeof DataSource;
69-
type TODO_ANY = any;
69+
// type TODO_ANY = any;
7070

7171
/** utility types we use */
7272
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>;
7474

7575
/** Rewritten Components from the React docs that just uses injected data prop */
76-
function CommentList({ data }: WithSubscriptionProps) {
76+
function CommentList({ data }: WithDataProps<typeof comments>) {
7777
return (
7878
<div>
7979
{data.map((comment: CommentType) => (
@@ -82,13 +82,13 @@ function CommentList({ data }: WithSubscriptionProps) {
8282
</div>
8383
);
8484
}
85-
interface BlogPostProps extends WithSubscriptionProps {
85+
interface BlogPostProps extends WithDataProps<string> {
8686
id: number;
8787
// children: ReactNode;
8888
}
8989
function BlogPost({ data, id }: BlogPostProps) {
9090
return (
91-
<div>
91+
<div key={id}>
9292
<TextBlock text={data} />;
9393
</div>
9494
);
@@ -100,44 +100,55 @@ function BlogPost({ data, id }: BlogPostProps) {
100100
Example HOC from React Docs translated to TypeScript
101101

102102
```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
106106
}
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
113119
) {
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+
};
123133
}
124134

125-
componentWillUnmount() {
135+
componentDidMount = () => DataSource.addChangeListener(this.handleChange);
136+
137+
componentWillUnmount = () =>
126138
DataSource.removeChangeListener(this.handleChange);
127-
}
128139

129-
handleChange = () => {
140+
handleChange = () =>
130141
this.setState({
131142
data: selectData(DataSource, this.props)
132143
});
133-
};
134144

135145
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} />;
139149
}
140150
};
151+
// return WithData;
141152
}
142153

143154
/** HOC usage with Components */
@@ -148,7 +159,7 @@ export const CommentListWithSubscription = withSubscription(
148159

149160
export const BlogPostWithSubscription = withSubscription(
150161
BlogPost,
151-
(DataSource: DataType, props: React.ComponentProps<typeof BlogPost>) =>
162+
(DataSource: DataType, props: Omit<BlogPostProps, 'data'>) =>
152163
DataSource.getBlogPost(props.id)
153164
);
154165
```

0 commit comments

Comments
 (0)