1
1
import React , { Component } from 'react' ;
2
2
import axios from 'axios' ;
3
+ import { sortBy } from 'lodash' ;
4
+ import classNames from 'classnames' ;
3
5
import './App.css' ;
4
6
5
7
const DEFAULT_QUERY = 'redux' ;
@@ -11,6 +13,14 @@ const PARAM_SEARCH = 'query=';
11
13
const PARAM_PAGE = 'page=' ;
12
14
const PARAM_HPP = 'hitsPerPage=' ;
13
15
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
+
14
24
class App extends Component {
15
25
_isMounted = false ;
16
26
@@ -22,6 +32,9 @@ class App extends Component {
22
32
searchKey : '' ,
23
33
searchTerm : DEFAULT_QUERY ,
24
34
error : null ,
35
+ isLoading : false ,
36
+ sortKey : 'NONE' ,
37
+ isSortReverse : false ,
25
38
} ;
26
39
27
40
this . needsToSearchTopStories = this . needsToSearchTopStories . bind ( this ) ;
@@ -30,6 +43,7 @@ class App extends Component {
30
43
this . onSearchChange = this . onSearchChange . bind ( this ) ;
31
44
this . onSearchSubmit = this . onSearchSubmit . bind ( this ) ;
32
45
this . onDismiss = this . onDismiss . bind ( this ) ;
46
+ this . onSort = this . onSort . bind ( this ) ;
33
47
}
34
48
35
49
needsToSearchTopStories ( searchTerm ) {
@@ -53,11 +67,14 @@ class App extends Component {
53
67
results : {
54
68
...results ,
55
69
[ searchKey ] : { hits : updatedHits , page }
56
- }
70
+ } ,
71
+ isLoading : false
57
72
} ) ;
58
73
}
59
74
60
75
fetchSearchTopStories ( searchTerm , page = 0 ) {
76
+ this . setState ( { isLoading : true } ) ;
77
+
61
78
axios ( `${ PATH_BASE } ${ PATH_SEARCH } ?${ PARAM_SEARCH } ${ searchTerm } &${ PARAM_PAGE } ${ page } &${ PARAM_HPP } ${ DEFAULT_HPP } ` )
62
79
. then ( result => this . setSearchTopStories ( result . data ) )
63
80
. catch ( error => this . _isMounted && this . setState ( { error } ) ) ;
@@ -105,12 +122,20 @@ class App extends Component {
105
122
} ) ;
106
123
}
107
124
125
+ onSort ( sortKey ) {
126
+ const isSortReverse = this . state . sortKey === sortKey && ! this . state . isSortReverse ;
127
+ this . setState ( { sortKey, isSortReverse } ) ;
128
+ }
129
+
108
130
render ( ) {
109
131
const {
110
132
searchTerm,
111
133
results,
112
134
searchKey,
113
- error
135
+ error,
136
+ isLoading,
137
+ sortKey,
138
+ isSortReverse
114
139
} = this . state ;
115
140
116
141
const page = (
@@ -142,13 +167,18 @@ class App extends Component {
142
167
</ div >
143
168
: < Table
144
169
list = { list }
170
+ sortKey = { sortKey }
171
+ isSortReverse = { isSortReverse }
172
+ onSort = { this . onSort }
145
173
onDismiss = { this . onDismiss }
146
174
/>
147
175
}
148
176
< div className = "interactions" >
149
- < Button onClick = { ( ) => this . fetchSearchTopStories ( searchKey , page + 1 ) } >
177
+ < ButtonWithLoading
178
+ isLoading = { isLoading }
179
+ onClick = { ( ) => this . fetchSearchTopStories ( searchKey , page + 1 ) } >
150
180
More
151
- </ Button >
181
+ </ ButtonWithLoading >
152
182
</ div >
153
183
</ div >
154
184
) ;
@@ -172,33 +202,109 @@ const Search = ({
172
202
</ button >
173
203
</ form >
174
204
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" >
179
220
< 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 >
181
228
</ span >
182
229
< span style = { { width : '30%' } } >
183
- { item . author }
230
+ < Sort
231
+ sortKey = { 'AUTHOR' }
232
+ onSort = { onSort }
233
+ activeSortKey = { sortKey }
234
+ >
235
+ Author
236
+ </ Sort >
184
237
</ span >
185
238
< span style = { { width : '10%' } } >
186
- { item . num_comments }
239
+ < Sort
240
+ sortKey = { 'COMMENTS' }
241
+ onSort = { onSort }
242
+ activeSortKey = { sortKey }
243
+ >
244
+ Comments
245
+ </ Sort >
187
246
</ span >
188
247
< span style = { { width : '10%' } } >
189
- { item . points }
248
+ < Sort
249
+ sortKey = { 'POINTS' }
250
+ onSort = { onSort }
251
+ activeSortKey = { sortKey }
252
+ >
253
+ Points
254
+ </ Sort >
190
255
</ span >
191
256
< span style = { { width : '10%' } } >
192
- < Button
193
- onClick = { ( ) => onDismiss ( item . objectID ) }
194
- className = "button-inline"
195
- >
196
- Dismiss
197
- </ Button >
257
+ Archive
198
258
</ span >
199
259
</ 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
+ }
202
308
203
309
const Button = ( {
204
310
onClick,
@@ -213,6 +319,16 @@ const Button = ({
213
319
{ children }
214
320
</ button >
215
321
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
+
216
332
export {
217
333
Button ,
218
334
Search ,
0 commit comments