Skip to content

Commit 765c644

Browse files
authored
Merge pull request #4 from ReactBangalore/exercise/component-patterns
HOC - slides, exercise and solution
2 parents 467fe9f + ad42f5f commit 765c644

File tree

5 files changed

+279
-0
lines changed

5 files changed

+279
-0
lines changed

exercises/src/exercises/hoc.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import React from "react";
2+
3+
/**
4+
* Utility to calculate the display name of a React Component.
5+
*/
6+
const getDisplayName = WrappedComponent => {
7+
return WrappedComponent.displayName || WrappedComponent.name || "Component";
8+
};
9+
10+
/**
11+
* HOC
12+
*/
13+
const logProps = WrappedComponent => {};
14+
15+
/**
16+
* Display current count from props
17+
*/
18+
class ChildComponent extends React.Component {}
19+
20+
/**
21+
* Enhance the child Component
22+
*/
23+
const WithLog = logProps(ChildComponent);
24+
25+
/**
26+
* A React class with counter, which increments on press of a button.
27+
* It passes the current count to its child
28+
*/
29+
class Counter extends React.Component {}
30+
31+
export default Counter;

exercises/src/exercises/render-props.js

Whitespace-only changes.

slides/componentpatterns.md

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
### Higher Order Components
2+
3+
Nitish Kumar
4+
[@nitishk88](https://twitter.com/nitishk88)
5+
6+
---
7+
8+
### Introduction
9+
10+
A higher-order component(HOC) is a technique in React for reusing component logic.
11+
A HOC is a pure function that takes a compoenent and returns a new component.
12+
13+
```js
14+
const EnhancedComponent = higherOrderComponent(WrappedComponent);
15+
```
16+
17+
---
18+
19+
### Concept and Usage
20+
21+
HOCs should be used for cross-cutting concerns.
22+
23+
* Patterns that repeat can be identified, abstracted and shared across multiple components.
24+
* A function can be written that takes a component, applies the re-usable logic and returns a new
25+
component with original and additional calculated props.
26+
* HOC neither modifies the input component, nor does it use inheritance to copy its behavior. Rather,
27+
it composes the original component by wrapping it in a container component.
28+
29+
---
30+
31+
### Convention - Pass unrelated props through to the Wrapped Component
32+
33+
To avoid altering the contract of a component, HOCs should pass through props that are
34+
unrelated to its specific concern.
35+
36+
---
37+
38+
```js
39+
render() {
40+
// Filter out extra props that are specific to this HOC and shouldn't be
41+
// passed through
42+
const { extraProp, ...passThroughProps } = this.props;
43+
44+
// Inject props into the wrapped component. These are usually state values or
45+
// instance methods.
46+
const injectedProp = someStateOrInstanceMethod;
47+
48+
// Pass props to wrapped component
49+
return (
50+
<WrappedComponent
51+
injectedProp={injectedProp}
52+
{...passThroughProps}
53+
/>
54+
);
55+
}
56+
```
57+
58+
---
59+
60+
### Convention - Design HOC for maximum composability
61+
62+
HOCs can be designed to accept multiple arguments with the wrapped component.
63+
64+
```js
65+
const NavbarWithRouter = withRouter(Navbar);
66+
const CommentWithRelay = Relay.createContainer(Comment, config);
67+
```
68+
69+
Single-argument HOCs have the signature `Component => Component`. These kind of functions are really
70+
easy to compose together.
71+
72+
```js
73+
compose(f, g, h); // same as (...args) => f(g(h(...args)))
74+
```
75+
---
76+
77+
In case where a HOC requires argument(s) other than the wrapped component, a higher-order function that returns a HOC can be utilized.
78+
79+
```js
80+
// connect is a function that returns another function
81+
const enhance = connect(commentListSelector, commentListActions);
82+
// The returned function is a HOC, which returns a component that is connected
83+
// to the Redux store
84+
const ConnectedComment = enhance(CommentList);
85+
```
86+
87+
Function composition becomes feasible again in this case.
88+
89+
```js
90+
const enhance = compose(
91+
// These are both single-argument HOCs
92+
withRouter,
93+
connect(commentSelector)
94+
);
95+
const EnhancedComponent = enhance(WrappedComponent);
96+
```
97+
98+
---
99+
100+
### Convention - Wrap the Display Name for Easy Debugging
101+
102+
Containers created by HOCs show up in the React Developer Tools like any other component.
103+
A display name that depicts the usage of HOC can be used to ease debugging.
104+
105+
---
106+
107+
```js
108+
const withSubscription = WrappedComponent => {
109+
class WithSubscription extends React.Component {
110+
/* ... */
111+
}
112+
WithSubscription.displayName = `WithSubscription(${getDisplayName(
113+
WrappedComponent
114+
)})`;
115+
return WithSubscription;
116+
};
117+
118+
const getDisplayName = WrappedComponent => {
119+
return WrappedComponent.displayName || WrappedComponent.name || "Component";
120+
};
121+
```
122+
123+
---
124+
125+
### Caveats - Applying HOCs Inside the render Method
126+
127+
```js
128+
render() {
129+
// A new version of EnhancedComponent is created on every render
130+
// EnhancedComponent1 !== EnhancedComponent2
131+
const EnhancedComponent = enhance(MyComponent);
132+
// That causes the entire subtree to unmount/remount each time!
133+
return <EnhancedComponent />;
134+
}
135+
```
136+
137+
---
138+
139+
* Instead, apply HOCs outside the component definition so that the resulting component is created only once.
140+
Its identity will be consistent across renders in that case.
141+
142+
* In cases where you need to apply a HOC dynamically, you can also do it inside a component’s lifecycle methods or its constructor.
143+
144+
---
145+
146+
### Caveats - Static Methods Must be Copied Over
147+
148+
```js
149+
const enhance = WrappedComponent => {
150+
class Enhance extends React.Component {
151+
/*...*/
152+
}
153+
// Must know exactly which method(s) to copy :(
154+
Enhance.staticMethod = WrappedComponent.staticMethod;
155+
return Enhance;
156+
};
157+
```
158+
---
159+
160+
Use `hoist-non-react-statics` to automatically copy all non-React static methods:
161+
162+
```js
163+
import hoistNonReactStatic from "hoist-non-react-statics";
164+
const enhance = WrappedComponent => {
165+
class Enhance extends React.Component {
166+
/*...*/
167+
}
168+
hoistNonReactStatic(Enhance, WrappedComponent);
169+
return Enhance;
170+
};
171+
```
172+
173+
---
174+
175+
## Exercise
176+
177+
* Create a HOC that logs the Wrapped Component's old props and new props to console.
178+
* It should be displayed as ```LogProps``` in the React Dev Tools.

solutions/src/exercises/hoc.js

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import React from "react";
2+
3+
/**
4+
* Utility to calculate the display name of a React Component.
5+
*/
6+
const getDisplayName = WrappedComponent => {
7+
return WrappedComponent.displayName || WrappedComponent.name || "Component";
8+
};
9+
10+
/**
11+
* HOC
12+
*/
13+
const logProps = WrappedComponent => {
14+
class LogProps extends React.Component {
15+
componentDidUpdate = prevProps => {
16+
console.group("props logger");
17+
console.log("old props:", prevProps);
18+
console.log("new props", this.props);
19+
console.groupEnd("props logger");
20+
};
21+
render() {
22+
return <WrappedComponent {...this.props} />;
23+
}
24+
}
25+
26+
LogProps.displayName = `LogProps(${getDisplayName(WrappedComponent)})`;
27+
return LogProps;
28+
};
29+
30+
/**
31+
* Display current count from props
32+
*/
33+
class ChildComponent extends React.Component {
34+
render() {
35+
return <div>{this.props.count}</div>;
36+
}
37+
}
38+
39+
/**
40+
* Enhance the child Component
41+
*/
42+
const WithLog = logProps(ChildComponent);
43+
44+
/**
45+
* A React class with counter, which increments on press of a button.
46+
* It passes the current count to its child
47+
*/
48+
class Coounter extends React.Component {
49+
constructor(props) {
50+
super(props);
51+
this.state = {
52+
count: 0
53+
};
54+
}
55+
56+
increment = () => {
57+
this.setState(state => ({ count: state.count + 1 }));
58+
};
59+
60+
render() {
61+
return (
62+
<div>
63+
<button onClick={this.increment}>+</button>
64+
<WithLog count={this.state.count} />
65+
</div>
66+
);
67+
}
68+
}
69+
70+
export default Coounter;

solutions/src/exercises/render-props.js

Whitespace-only changes.

0 commit comments

Comments
 (0)