Skip to content

Commit 3cf2a8f

Browse files
author
sw-yx
committed
add hoc
1 parent dfde467 commit 3cf2a8f

File tree

1 file changed

+153
-0
lines changed

1 file changed

+153
-0
lines changed

HOC.md

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
# HOC Cheatsheet
2+
3+
**This HOC Cheatsheet** compiles all available knowledge for writing Higher Order Components with React and TypeScript.
4+
5+
- We will map closely to [the official docs on HOCs](https://reactjs.org/docs/higher-order-components.html) initially
6+
- While hooks exist, many libraries and codebases still have a need to type HOCs.
7+
- Render props may be considered in future
8+
- The goal is to write HOCs that offer type safety while not getting in the way.
9+
10+
---
11+
12+
### HOC Cheatsheet Table of Contents
13+
14+
<details>
15+
16+
<summary><b>Expand Table of Contents</b></summary>
17+
18+
- [Section 0: Prerequisites](#section-0-prerequisites)
19+
</details>
20+
21+
# Section 1: React HOC docs in TypeScript
22+
23+
In this first section we refer closely to [the React docs on HOCs](https://reactjs.org/docs/higher-order-components.html) and offer direct TypeScript parallels.
24+
25+
## Example: Use HOCs For Cross-Cutting Concerns
26+
27+
<details>
28+
29+
<summary>
30+
<b>Misc variables reference in the example below</b>
31+
</summary>
32+
33+
```tsx
34+
/** dummy child components that take anything */
35+
const Comment = (_: any) => null;
36+
const TextBlock = Comment;
37+
38+
/** dummy Data */
39+
type CommentType = { text: string; id: number };
40+
const comments: CommentType[] = [
41+
{
42+
text: 'comment1',
43+
id: 1
44+
},
45+
{
46+
text: 'comment2',
47+
id: 2
48+
}
49+
];
50+
const blog = 'blogpost';
51+
52+
/** mock data source */
53+
const DataSource = {
54+
addChangeListener(e: Function) {
55+
// do something
56+
},
57+
removeChangeListener(e: Function) {
58+
// do something
59+
},
60+
getComments() {
61+
return comments;
62+
},
63+
getBlogPost(id: number) {
64+
return blog;
65+
}
66+
};
67+
/** type aliases just to deduplicate */
68+
type DataType = typeof DataSource;
69+
type TODO_ANY = any;
70+
71+
/** utility types we use */
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>;
74+
75+
/** Rewritten Components from the React docs that just uses injected data prop */
76+
function CommentList({ data }: WithSubscriptionProps) {
77+
return (
78+
<div>
79+
{data.map((comment: CommentType) => (
80+
<Comment comment={comment} key={comment.id} />
81+
))}
82+
</div>
83+
);
84+
}
85+
interface BlogPostProps extends WithSubscriptionProps {
86+
id: number;
87+
// children: ReactNode;
88+
}
89+
function BlogPost({ data, id }: BlogPostProps) {
90+
return (
91+
<div>
92+
<TextBlock text={data} />;
93+
</div>
94+
);
95+
}
96+
```
97+
98+
</details>
99+
100+
```tsx
101+
// // ACTUAL HOC
102+
interface WithSubscriptionProps {
103+
data: TODO_ANY;
104+
}
105+
/** This function takes a component... */
106+
function withSubscription<
107+
T extends WithSubscriptionProps = WithSubscriptionProps
108+
>(
109+
WrappedComponent: React.ComponentType<T>,
110+
selectData: (DataSource: DataType, props: TODO_ANY) => TODO_ANY
111+
) {
112+
// ...and returns another component...
113+
return class extends React.Component<Optionalize<T, WithSubscriptionProps>> {
114+
state = {
115+
data: selectData(DataSource, this.props)
116+
};
117+
118+
componentDidMount() {
119+
// ... that takes care of the subscription...
120+
DataSource.addChangeListener(this.handleChange);
121+
}
122+
123+
componentWillUnmount() {
124+
DataSource.removeChangeListener(this.handleChange);
125+
}
126+
127+
handleChange = () => {
128+
this.setState({
129+
data: selectData(DataSource, this.props)
130+
});
131+
};
132+
133+
render() {
134+
// ... and renders the wrapped component with the fresh data!
135+
// Notice that we pass through any additional props
136+
// return <WrappedComponent data={this.state.data} {...this.props} />;
137+
return <WrappedComponent data={this.state.data} {...this.props as T} />;
138+
}
139+
};
140+
}
141+
142+
/** HOC usage with Components */
143+
export const CommentListWithSubscription = withSubscription(
144+
CommentList,
145+
(DataSource: DataType) => DataSource.getComments()
146+
);
147+
148+
export const BlogPostWithSubscription = withSubscription(
149+
BlogPost,
150+
(DataSource: DataType, props: React.ComponentProps<typeof BlogPost>) =>
151+
DataSource.getBlogPost(props.id)
152+
);
153+
```

0 commit comments

Comments
 (0)