Skip to content

Commit 4ca37ea

Browse files
committed
chapter 5
1 parent 7eae32b commit 4ca37ea

File tree

5 files changed

+296
-28
lines changed

5 files changed

+296
-28
lines changed

package-lock.json

+5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
"private": true,
55
"dependencies": {
66
"axios": "^0.17.1",
7+
"classnames": "^2.2.5",
8+
"lodash": "^4.17.5",
79
"react": "^16.2.0",
810
"react-dom": "^16.2.0",
911
"react-scripts": "1.1.1"

src/App.js

+136-20
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import React, { Component } from 'react';
22
import axios from 'axios';
3+
import { sortBy } from 'lodash';
4+
import classNames from 'classnames';
35
import './App.css';
46

57
const DEFAULT_QUERY = 'redux';
@@ -11,6 +13,14 @@ const PARAM_SEARCH = 'query=';
1113
const PARAM_PAGE = 'page=';
1214
const PARAM_HPP = 'hitsPerPage=';
1315

16+
const SORTS = {
17+
NONE: list => list,
18+
TITLE: list => sortBy(list, 'title'),
19+
AUTHOR: list => sortBy(list, 'author'),
20+
COMMENTS: list => sortBy(list, 'num_comments').reverse(),
21+
POINTS: list => sortBy(list, 'points').reverse(),
22+
};
23+
1424
class App extends Component {
1525
_isMounted = false;
1626

@@ -22,6 +32,9 @@ class App extends Component {
2232
searchKey: '',
2333
searchTerm: DEFAULT_QUERY,
2434
error: null,
35+
isLoading: false,
36+
sortKey: 'NONE',
37+
isSortReverse: false,
2538
};
2639

2740
this.needsToSearchTopStories = this.needsToSearchTopStories.bind(this);
@@ -30,6 +43,7 @@ class App extends Component {
3043
this.onSearchChange = this.onSearchChange.bind(this);
3144
this.onSearchSubmit = this.onSearchSubmit.bind(this);
3245
this.onDismiss = this.onDismiss.bind(this);
46+
this.onSort = this.onSort.bind(this);
3347
}
3448

3549
needsToSearchTopStories(searchTerm) {
@@ -53,11 +67,14 @@ class App extends Component {
5367
results: {
5468
...results,
5569
[searchKey]: { hits: updatedHits, page }
56-
}
70+
},
71+
isLoading: false
5772
});
5873
}
5974

6075
fetchSearchTopStories(searchTerm, page = 0) {
76+
this.setState({ isLoading: true });
77+
6178
axios(`${PATH_BASE}${PATH_SEARCH}?${PARAM_SEARCH}${searchTerm}&${PARAM_PAGE}${page}&${PARAM_HPP}${DEFAULT_HPP}`)
6279
.then(result => this.setSearchTopStories(result.data))
6380
.catch(error => this._isMounted && this.setState({ error }));
@@ -105,12 +122,20 @@ class App extends Component {
105122
});
106123
}
107124

125+
onSort(sortKey) {
126+
const isSortReverse = this.state.sortKey === sortKey && !this.state.isSortReverse;
127+
this.setState({ sortKey, isSortReverse });
128+
}
129+
108130
render() {
109131
const {
110132
searchTerm,
111133
results,
112134
searchKey,
113-
error
135+
error,
136+
isLoading,
137+
sortKey,
138+
isSortReverse
114139
} = this.state;
115140

116141
const page = (
@@ -142,13 +167,18 @@ class App extends Component {
142167
</div>
143168
: <Table
144169
list={list}
170+
sortKey={sortKey}
171+
isSortReverse={isSortReverse}
172+
onSort={this.onSort}
145173
onDismiss={this.onDismiss}
146174
/>
147175
}
148176
<div className="interactions">
149-
<Button onClick={() => this.fetchSearchTopStories(searchKey, page + 1)}>
177+
<ButtonWithLoading
178+
isLoading={isLoading}
179+
onClick={() => this.fetchSearchTopStories(searchKey, page + 1)}>
150180
More
151-
</Button>
181+
</ButtonWithLoading>
152182
</div>
153183
</div>
154184
);
@@ -172,33 +202,109 @@ const Search = ({
172202
</button>
173203
</form>
174204

175-
const Table = ({ list, onDismiss }) =>
176-
<div className="table">
177-
{list.map(item =>
178-
<div key={item.objectID} className="table-row">
205+
const Table = ({
206+
list,
207+
sortKey,
208+
isSortReverse,
209+
onSort,
210+
onDismiss
211+
}) => {
212+
const sortedList = SORTS[sortKey](list);
213+
const reverseSortedList = isSortReverse
214+
? sortedList.reverse()
215+
: sortedList;
216+
217+
return(
218+
<div className="table">
219+
<div className="table-header">
179220
<span style={{ width: '40%' }}>
180-
<a href={item.url}>{item.title}</a>
221+
<Sort
222+
sortKey={'TITLE'}
223+
onSort={onSort}
224+
activeSortKey={sortKey}
225+
>
226+
Title
227+
</Sort>
181228
</span>
182229
<span style={{ width: '30%' }}>
183-
{item.author}
230+
<Sort
231+
sortKey={'AUTHOR'}
232+
onSort={onSort}
233+
activeSortKey={sortKey}
234+
>
235+
Author
236+
</Sort>
184237
</span>
185238
<span style={{ width: '10%' }}>
186-
{item.num_comments}
239+
<Sort
240+
sortKey={'COMMENTS'}
241+
onSort={onSort}
242+
activeSortKey={sortKey}
243+
>
244+
Comments
245+
</Sort>
187246
</span>
188247
<span style={{ width: '10%' }}>
189-
{item.points}
248+
<Sort
249+
sortKey={'POINTS'}
250+
onSort={onSort}
251+
activeSortKey={sortKey}
252+
>
253+
Points
254+
</Sort>
190255
</span>
191256
<span style={{ width: '10%' }}>
192-
<Button
193-
onClick={() => onDismiss(item.objectID)}
194-
className="button-inline"
195-
>
196-
Dismiss
197-
</Button>
257+
Archive
198258
</span>
199259
</div>
200-
)}
201-
</div>
260+
{reverseSortedList.map(item =>
261+
<div key={item.objectID} className="table-row">
262+
<span style={{ width: '40%' }}>
263+
<a href={item.url}>{item.title}</a>
264+
</span>
265+
<span style={{ width: '30%' }}>
266+
{item.author}
267+
</span>
268+
<span style={{ width: '10%' }}>
269+
{item.num_comments}
270+
</span>
271+
<span style={{ width: '10%' }}>
272+
{item.points}
273+
</span>
274+
<span style={{ width: '10%' }}>
275+
<Button
276+
onClick={() => onDismiss(item.objectID)}
277+
className="button-inline"
278+
>
279+
Dismiss
280+
</Button>
281+
</span>
282+
</div>
283+
)}
284+
</div>
285+
);
286+
}
287+
288+
const Sort = ({
289+
sortKey,
290+
activeSortKey,
291+
onSort,
292+
children
293+
}) => {
294+
const sortClass = classNames(
295+
'button-inline',
296+
{ 'button-active': sortKey === activeSortKey }
297+
);
298+
299+
return (
300+
<Button
301+
onClick={() => onSort(sortKey)}
302+
className={sortClass}
303+
>
304+
{children}
305+
</Button>
306+
);
307+
}
202308

203309
const Button = ({
204310
onClick,
@@ -213,6 +319,16 @@ const Button = ({
213319
{children}
214320
</button>
215321

322+
const Loading = () =>
323+
<div>Loading ...</div>
324+
325+
const withLoading = (Component) => ({ isLoading, ...rest }) =>
326+
isLoading
327+
? <Loading />
328+
: <Component { ...rest } />
329+
330+
const ButtonWithLoading = withLoading(Button);
331+
216332
export {
217333
Button,
218334
Search,

src/App.test.js

+2
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ describe('Table', () => {
6666
{ title: '1', author: '1', num_comments: 1, points: 2, objectID: 'y' },
6767
{ title: '2', author: '2', num_comments: 1, points: 2, objectID: 'z' },
6868
],
69+
sortKey: 'TITLE',
70+
isSortReverse: false,
6971
};
7072

7173
it('renders without crashing', () => {

0 commit comments

Comments
 (0)