Recap
This chapter will dug a deeper at state management in React than using React's local state. It expanded on the best practices, how to apply them, and why you could consider using a third-party state management library.
We'll cover the following...
Your src/App.js should look like the following by now:
import React, { Component } from 'react'; import { sortBy } from 'lodash'; import classNames from 'classnames'; require('./App.css'); const DEFAULT_QUERY = 'redux'; const DEFAULT_HPP = '100'; const PATH_BASE = 'https://hn.algolia.com/api/v1'; const PATH_SEARCH = '/search'; const PARAM_SEARCH = 'query='; const PARAM_PAGE = 'page='; const SORTS = { NONE: list => list, TITLE: list => sortBy(list, 'title'), AUTHOR: list => sortBy(list, 'author'), COMMENTS: list => sortBy(list, 'num_comments').reverse(), POINTS: list => sortBy(list, 'points').reverse(), }; const updateSearchTopstoriesState = (hits, page) => (prevState) => { const { searchKey, results } = prevState; const oldHits = results && results[searchKey] ? results[searchKey].hits : []; const updatedHits = [ ...oldHits, ...hits ]; return { results: { ...results, [searchKey]: { hits: updatedHits, page } }, isLoading: false }; }; class App extends Component { constructor(props) { super(props); this.state = { results: null, searchKey: '', searchTerm: DEFAULT_QUERY, error: null, isLoading: false, }; this.needsToSearchTopstories = this.needsToSearchTopstories.bind(this); this.setSearchTopstories = this.setSearchTopstories.bind(this); this.fetchSearchTopstories = this.fetchSearchTopstories.bind(this); this.onSearchChange = this.onSearchChange.bind(this); this.onSearchSubmit = this.onSearchSubmit.bind(this); this.onDismiss = this.onDismiss.bind(this); } needsToSearchTopstories(searchTerm) { return !this.state.results[searchTerm]; } setSearchTopstories(result) { const { hits, page } = result; this.setState(updateSearchTopstoriesState(hits, page)); } fetchSearchTopstories(searchTerm, page = 0) { this.setState({ isLoading: true }); fetch(`${PATH_BASE}${PATH_SEARCH}?${PARAM_SEARCH}${searchTerm}&${PARAM_PAGE}${page}`) .then(response => response.json()) .then(result => this.setSearchTopstories(result)) .catch(e => this.setState({ error: e })); } componentDidMount() { const { searchTerm } = this.state; this.setState({ searchKey: searchTerm }); this.fetchSearchTopstories(searchTerm); } onSearchChange(event) { this.setState({ searchTerm: event.target.value }); } onSearchSubmit(event) { const { searchTerm } = this.state; this.setState({ searchKey: searchTerm }); if (this.needsToSearchTopstories(searchTerm)) { this.fetchSearchTopstories(searchTerm); } event.preventDefault(); } onDismiss(id) { const { searchKey, results } = this.state; const { hits, page } = results[searchKey]; const isNotId = item => item.objectID !== id; const updatedHits = hits.filter(isNotId); this.setState({ results: { ...results, [searchKey]: { hits: updatedHits, page } } }); } render() { const { searchTerm, results, searchKey, error, isLoading } = this.state; const page = ( results && results[searchKey] && results[searchKey].page ) || 0; const list = ( results && results[searchKey] && results[searchKey].hits ) || []; return ( <div className="page"> <div className="interactions"> <Search value={searchTerm} onChange={this.onSearchChange} onSubmit={this.onSearchSubmit} > Search </Search> </div> { error ? <div className="interactions"> <p>Something went wrong.</p> </div> : <Table list={list} onDismiss={this.onDismiss} /> } <div className="interactions"> <ButtonWithLoading isLoading={isLoading} onClick={() => this.fetchSearchTopstories(searchKey, page + 1)}> More </ButtonWithLoading> </div> </div> ); } } const Search = ({ value, onChange, onSubmit, children }) => <form onSubmit={onSubmit}> <input type="text" value={value} onChange={onChange} /> <button type="submit"> {children} </button> </form> class Table extends Component { constructor(props) { super(props); this.state = { sortKey: 'NONE', isSortReverse: false, }; this.onSort = this.onSort.bind(this); } onSort(sortKey) { const isSortReverse = this.state.sortKey === sortKey && !this.state.isSortReverse; this.setState({ sortKey, isSortReverse }); } render() { const { list, onDismiss } = this.props; const { sortKey, isSortReverse, } = this.state; const sortedList = SORTS[sortKey](list); const reverseSortedList = isSortReverse ? sortedList.reverse() : sortedList; return( <div className="table"> <div className="table-header"> <span style={{ width: '40%' }}> <Sort sortKey={'TITLE'} onSort={this.onSort} activeSortKey={sortKey} > Title </Sort> </span> <span style={{ width: '30%' }}> <Sort sortKey={'AUTHOR'} onSort={this.onSort} activeSortKey={sortKey} > Author </Sort> </span> <span style={{ width: '10%' }}> <Sort sortKey={'COMMENTS'} onSort={this.onSort} activeSortKey={sortKey} > Comments </Sort> </span> <span style={{ width: '10%' }}> <Sort sortKey={'POINTS'} onSort={this.onSort} activeSortKey={sortKey} > Points </Sort> </span> <span style={{ width: '10%' }}> Archive </span> </div> { reverseSortedList.map(item => <div key={item.objectID} className="table-row"> <span style={{ width: '40%' }}> <a href={item.url}>{item.title}</a> </span> <span style={{ width: '30%' }}> {item.author} </span> <span style={{ width: '10%' }}> {item.num_comments} </span> <span style={{ width: '10%' }}> {item.points} </span> <span style={{ width: '10%' }}> <Button onClick={() => onDismiss(item.objectID)} className="button-inline" > Dismiss </Button> </span> </div> )} </div> ); } } const Button = ({ onClick, className = '', children }) => <button onClick={onClick} className={className} type="button" > {children} </button> const Loading = () => <div>Loading ...</div> const withLoading = (Component) => ({ isLoading, ...rest }) => isLoading ? <Loading /> : <Component { ...rest } /> const ButtonWithLoading = withLoading(Button); const Sort = ({ sortKey, activeSortKey, onSort, children }) => { const sortClass = classNames( 'button-inline', { 'button-active': sortKey === activeSortKey } ); return ( <Button onClick={() => onSort(sortKey)} className={sortClass} > {children} </Button> ); } export default App; export { Button, Search, Table, };
You have learned advanced state ...