forked from mzabriskie/react-workshop
-
Notifications
You must be signed in to change notification settings - Fork 25
Expand file tree
/
Copy path10-hoc.js
More file actions
171 lines (161 loc) · 4.88 KB
/
10-hoc.js
File metadata and controls
171 lines (161 loc) · 4.88 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import axios from 'axios'
// State and imperative code are two of the things that makes building applications more difficult.
// And we haven't even started talking about context yet!
// The more components we can have that have no state and completely declarative the better.
// Doing this makes things easier to test and maintain.
//
// So we're going to refactor our last component to follow a pattern called "Higher Order Components"
//
// This is essentially a function that accepts a component and returns a new one with that manages
// the state and renders the original component with the state as props. (react-redux follows this
// pattern, as did react-router until recently)
//
// Example:
//
// ```
// // this is the component that people use (so you expose this)
// const UserProfileContainer = fetchDataComponent(UserProfile)
//
// function fetchDataComponent(Comp) {
// return class FetchUserProfile extends Component {
// state = {user: {}}
// static propTypes = {
// username: PropTypes.number.isRequired,
// fetch: PropTypes.func,
// }
// static defaultProps = { fetch: axios.get } // doing this allows you to pass a mock version as a prop
//
// componentDidMount() {
// this.props.fetch(`/users/${this.props.username}`)
// .then(
// ({data: user}) => this.setState({user}),
// // should add an error handler here :)
// )
// }
//
// render() {
// // we're spreading the state of repos, loading, and error as props to the Comp
// // we're forwarding the props given to this component to the child Comp
// return <Comp {...this.state} {...this.props} />
// }
// }
// }
//
// function UserProfile({user}) {
// return (
// <div>
// <div>First name: {user.firstName}</div>
// <div>Last name: {user.lastName}</div>
// <div>Email address: {user.emailAddress}</div>
// </div>
// )
// }
//
// UserProfile.propTypes = {
// user: PropTypes.shape({
// firstName: PropTypes.string.isRequired,
// lastName: PropTypes.string.isRequired,
// emailAddress: PropTypes.string.isRequired,
// }).isRequired,
// }
// ```
//
// Exercise:
//
// Refactor the original component to use the render callback pattern
// Create a function that accepts a component class and returns a new component class
// put all the state related stuff in this new component
// and render the given component in the new component's render method
class RepoListContainer extends Component {
static propTypes = {
username: PropTypes.string.isRequired,
fetch: PropTypes.func,
}
static defaultProps = {fetch: axios.get}
state = {repos: null, loading: false, error: null}
componentDidMount() {
this.fetchRepos()
}
fetchRepos() {
this.setState({repos: null, loading: true, error: null})
this.props
.fetch(
`https://api.github.com/users/${this.props
.username}/repos?per_page=100&sort=pushed`,
)
.then(
({data: repos}) => this.setState({repos, error: null, loading: false}),
error => this.setState({repos: null, error, loading: false}),
)
}
render() {
const {repos, loading, error} = this.state
const {username} = this.props
return (
<div>
{!loading ? null : <div>Loading...</div>}
{!error ? null : (
<div>
Error loading info for <code>{username}</code>
<pre>{JSON.stringify(error, null, 2)}</pre>
</div>
)}
{!repos ? null : <RepoList username={username} repos={repos} />}
</div>
)
}
}
function RepoList({username, repos}) {
return (
<div>
<h1>{username}'s repos</h1>
<ul style={{textAlign: 'left'}}>
{repos.map(repo => {
return <li key={repo.id}>{repo.name}</li>
})}
</ul>
</div>
)
}
RepoList.propTypes = {
username: PropTypes.string.isRequired,
repos: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
}),
).isRequired,
}
export const Example = () => (
<RepoListContainer username="kentcdodds" fetch={mockFetch} />
)
// This is for you. Merry Christmas 🎅 🎄 🎁
function mockFetch() {
const delay = 0 // set this to `Number.MAX_VALUE` test the loading state
const sendError = false // set this to `true` to test out the error state
return new Promise((resolve, reject) => {
setTimeout(() => {
if (sendError) {
reject({
message: 'Something went wrong',
status: 500,
})
} else {
resolve({
data: [
{id: 12345, name: 'turbo-sniffle'},
{id: 54321, name: 'ubiquitous-succotash'},
{id: 43234, name: 'solid-waffle'},
],
})
}
}, delay)
})
}
export default RepoListContainer
/*
eslint
no-unused-vars: 0,
*/