|
1 | 1 | import React, { Component } from 'react';
|
2 | 2 | import './App.css';
|
3 | 3 |
|
4 |
| -const list = [ |
5 |
| - { |
6 |
| - title: 'React', |
7 |
| - url: 'https://facebook.github.io/react/', |
8 |
| - author: 'Jordan Walke', |
9 |
| - num_comments: 3, |
10 |
| - points: 4, |
11 |
| - objectID: 0, |
12 |
| - }, |
13 |
| - { |
14 |
| - title: 'Redux', |
15 |
| - url: 'https://github.com/reactjs/redux', |
16 |
| - author: 'Dan Abramov, Andrew Clark', |
17 |
| - num_comments: 2, |
18 |
| - points: 5, |
19 |
| - objectID: 1, |
20 |
| - }, |
21 |
| -]; |
22 |
| - |
23 |
| -const isSearched = (searchTerm) => (item) => |
24 |
| - !searchTerm || item.title.toLowerCase().includes(searchTerm.toLowerCase()); |
| 4 | +const DEFAULT_QUERY = 'redux'; |
| 5 | +const DEFAULT_PAGE = 0; |
| 6 | +const DEFAULT_HPP = '100'; |
| 7 | + |
| 8 | +const PATH_BASE = 'https://hn.algolia.com/api/v1'; |
| 9 | +const PATH_SEARCH = '/search'; |
| 10 | +const PARAM_SEARCH = 'query='; |
| 11 | +const PARAM_PAGE = 'page='; |
| 12 | +const PARAM_HPP = 'hitsPerPage='; |
25 | 13 |
|
26 | 14 | class App extends Component {
|
27 | 15 | constructor(props) {
|
28 | 16 | super(props);
|
29 | 17 |
|
30 | 18 | this.state = {
|
31 |
| - list, |
32 |
| - searchTerm: '', |
| 19 | + results: null, |
| 20 | + searchKey: '', |
| 21 | + searchTerm: DEFAULT_QUERY, |
33 | 22 | };
|
34 | 23 |
|
| 24 | + this.needsToSearchTopstories = this.needsToSearchTopstories.bind(this); |
| 25 | + this.setSearchTopstories = this.setSearchTopstories.bind(this); |
| 26 | + this.fetchSearchTopstories = this.fetchSearchTopstories.bind(this); |
35 | 27 | this.onSearchChange = this.onSearchChange.bind(this);
|
| 28 | + this.onSearchSubmit = this.onSearchSubmit.bind(this); |
36 | 29 | this.onDismiss = this.onDismiss.bind(this);
|
37 | 30 | }
|
38 | 31 |
|
| 32 | + needsToSearchTopstories(searchTerm) { |
| 33 | + return !this.state.results[searchTerm]; |
| 34 | + } |
| 35 | + |
| 36 | + setSearchTopstories(result) { |
| 37 | + const { hits, page } = result; |
| 38 | + const { searchKey, results } = this.state; |
| 39 | + |
| 40 | + const oldHits = results && results[searchKey] |
| 41 | + ? results[searchKey].hits |
| 42 | + : []; |
| 43 | + |
| 44 | + const updatedHits = [ |
| 45 | + ...oldHits, |
| 46 | + ...hits |
| 47 | + ]; |
| 48 | + |
| 49 | + this.setState({ |
| 50 | + results: { |
| 51 | + ...results, |
| 52 | + [searchKey]: { hits: updatedHits, page } |
| 53 | + } |
| 54 | + }); |
| 55 | + } |
| 56 | + |
| 57 | + fetchSearchTopstories(searchTerm, page) { |
| 58 | + fetch(`${PATH_BASE}${PATH_SEARCH}?${PARAM_SEARCH}${searchTerm}&${PARAM_PAGE}${page}&${PARAM_HPP}${DEFAULT_HPP}`) |
| 59 | + .then(response => response.json()) |
| 60 | + .then(result => this.setSearchTopstories(result)); |
| 61 | + } |
| 62 | + |
| 63 | + componentDidMount() { |
| 64 | + const { searchTerm } = this.state; |
| 65 | + this.setState({ searchKey: searchTerm }); |
| 66 | + this.fetchSearchTopstories(searchTerm, DEFAULT_PAGE); |
| 67 | + } |
| 68 | + |
39 | 69 | onSearchChange(event) {
|
40 | 70 | this.setState({ searchTerm: event.target.value });
|
41 | 71 | }
|
42 | 72 |
|
| 73 | + onSearchSubmit(event) { |
| 74 | + const { searchTerm } = this.state; |
| 75 | + this.setState({ searchKey: searchTerm }); |
| 76 | + |
| 77 | + if (this.needsToSearchTopstories(searchTerm)) { |
| 78 | + this.fetchSearchTopstories(searchTerm, DEFAULT_PAGE); |
| 79 | + } |
| 80 | + |
| 81 | + event.preventDefault(); |
| 82 | + } |
| 83 | + |
43 | 84 | onDismiss(id) {
|
| 85 | + const { searchKey, results } = this.state; |
| 86 | + const { hits, page } = results[searchKey]; |
| 87 | + |
44 | 88 | const isNotId = item => item.objectID !== id;
|
45 |
| - const updatedList = this.state.list.filter(isNotId); |
46 |
| - this.setState({ list: updatedList }); |
| 89 | + const updatedHits = hits.filter(isNotId); |
| 90 | + |
| 91 | + this.setState({ |
| 92 | + results: { |
| 93 | + ...results, |
| 94 | + [searchKey]: { hits: updatedHits, page } |
| 95 | + } |
| 96 | + }); |
47 | 97 | }
|
48 | 98 |
|
49 | 99 | render() {
|
50 |
| - const { searchTerm, list } = this.state; |
| 100 | + const { |
| 101 | + searchTerm, |
| 102 | + results, |
| 103 | + searchKey |
| 104 | + } = this.state; |
| 105 | + |
| 106 | + const page = ( |
| 107 | + results && |
| 108 | + results[searchKey] && |
| 109 | + results[searchKey].page |
| 110 | + ) || 0; |
| 111 | + |
| 112 | + const list = ( |
| 113 | + results && |
| 114 | + results[searchKey] && |
| 115 | + results[searchKey].hits |
| 116 | + ) || []; |
| 117 | + |
51 | 118 | return (
|
52 | 119 | <div className="page">
|
53 | 120 | <div className="interactions">
|
54 | 121 | <Search
|
55 | 122 | value={searchTerm}
|
56 | 123 | onChange={this.onSearchChange}
|
| 124 | + onSubmit={this.onSearchSubmit} |
57 | 125 | >
|
58 | 126 | Search
|
59 | 127 | </Search>
|
60 | 128 | </div>
|
61 | 129 | <Table
|
62 | 130 | list={list}
|
63 |
| - pattern={searchTerm} |
64 | 131 | onDismiss={this.onDismiss}
|
65 | 132 | />
|
| 133 | + <div className="interactions"> |
| 134 | + <Button onClick={() => this.fetchSearchTopstories(searchKey, page + 1)}> |
| 135 | + More |
| 136 | + </Button> |
| 137 | + </div> |
66 | 138 | </div>
|
67 | 139 | );
|
68 | 140 | }
|
69 | 141 | }
|
70 | 142 |
|
71 |
| -const Search = ({ value, onChange, children }) => |
72 |
| - <form> |
73 |
| - {children} <input |
| 143 | +const Search = ({ |
| 144 | + value, |
| 145 | + onChange, |
| 146 | + onSubmit, |
| 147 | + children |
| 148 | +}) => |
| 149 | + <form onSubmit={onSubmit}> |
| 150 | + <input |
74 | 151 | type="text"
|
75 | 152 | value={value}
|
76 | 153 | onChange={onChange}
|
77 | 154 | />
|
| 155 | + <button type="submit"> |
| 156 | + {children} |
| 157 | + </button> |
78 | 158 | </form>
|
79 | 159 |
|
80 |
| -const Table = ({ list, pattern, onDismiss }) => |
| 160 | +const Table = ({ list, onDismiss }) => |
81 | 161 | <div className="table">
|
82 |
| - { list.filter(isSearched(pattern)).map(item => |
| 162 | + { list.map(item => |
83 | 163 | <div key={item.objectID} className="table-row">
|
84 | 164 | <span style={{ width: '40%' }}>
|
85 | 165 | <a href={item.url}>{item.title}</a>
|
|
0 commit comments