| 1 | import React, {PureComponent, startTransition} from 'react'; |
| 2 | import {createRoot} from 'react-dom/client'; |
| 3 | import _ from 'lodash'; |
| 4 | import Charts from './Charts'; |
| 5 | import Clock from './Clock'; |
| 6 | import './index.css'; |
| 7 | |
| 8 | let cachedData = new Map(); |
| 9 | |
| 10 | class App extends PureComponent { |
| 11 | state = { |
| 12 | value: '', |
| 13 | strategy: 'sync', |
| 14 | showDemo: true, |
| 15 | showClock: false, |
| 16 | }; |
| 17 | |
| 18 | // Random data for the chart |
| 19 | getStreamData(input) { |
| 20 | if (cachedData.has(input)) { |
| 21 | return cachedData.get(input); |
| 22 | } |
| 23 | const multiplier = input.length !== 0 ? input.length : 1; |
| 24 | const complexity = |
| 25 | (parseInt(window.location.search.slice(1), 10) / 100) * 25 || 25; |
| 26 | const data = _.range(5).map(t => |
| 27 | _.range(complexity * multiplier).map((j, i) => { |
| 28 | return { |
| 29 | x: j, |
| 30 | y: (t + 1) * _.random(0, 255), |
| 31 | }; |
| 32 | }) |
| 33 | ); |
| 34 | cachedData.set(input, data); |
| 35 | return data; |
| 36 | } |
| 37 | |
| 38 | componentDidMount() { |
| 39 | window.addEventListener('keydown', e => { |
| 40 | if (e.key.toLowerCase() === '?') { |
| 41 | e.preventDefault(); |
| 42 | this.setState(state => ({ |
| 43 | showClock: !state.showClock, |
| 44 | })); |
| 45 | } |
| 46 | }); |
| 47 | } |
| 48 | |
| 49 | handleChartClick = e => { |
| 50 | if (this.state.showDemo) { |
| 51 | if (e.shiftKey) { |
| 52 | this.setState({showDemo: false}); |
| 53 | } |
| 54 | return; |
| 55 | } |
| 56 | if (this.state.strategy !== 'async') { |
| 57 | this.setState(state => ({ |
| 58 | showDemo: !state.showDemo, |
| 59 | })); |
| 60 | return; |
| 61 | } |
| 62 | if (this._ignoreClick) { |
| 63 | return; |
| 64 | } |
| 65 | this._ignoreClick = true; |
| 66 | |
| 67 | startTransition(() => { |
| 68 | this.setState({showDemo: true}, () => { |
| 69 | this._ignoreClick = false; |
| 70 | }); |
| 71 | }); |
| 72 | }; |
| 73 | |
| 74 | debouncedHandleChange = _.debounce(value => { |
| 75 | if (this.state.strategy === 'debounced') { |
| 76 | this.setState({value: value}); |
| 77 | } |
| 78 | }, 1000); |
| 79 | |
| 80 | renderOption(strategy, label) { |
| 81 | const {strategy: currentStrategy} = this.state; |
| 82 | return ( |
| 83 | <label className={strategy === currentStrategy ? 'selected' : null}> |
| 84 | <input |
| 85 | type="radio" |
| 86 | checked={strategy === currentStrategy} |
| 87 | onChange={() => this.setState({strategy})} |
| 88 | /> |
| 89 | {label} |
| 90 | </label> |
| 91 | ); |
| 92 | } |
| 93 | |
| 94 | handleChange = e => { |
| 95 | const value = e.target.value; |
| 96 | const {strategy} = this.state; |
| 97 | switch (strategy) { |
| 98 | case 'sync': |
| 99 | this.setState({value}); |
| 100 | break; |
| 101 | case 'debounced': |
| 102 | this.debouncedHandleChange(value); |
| 103 | break; |
| 104 | case 'async': |
| 105 | // TODO: useTransition hook instead. |
| 106 | startTransition(() => { |
| 107 | this.setState({value}); |
| 108 | }); |
| 109 | break; |
| 110 | default: |
| 111 | break; |
| 112 | } |
| 113 | }; |
| 114 | |
| 115 | render() { |
| 116 | const {showClock} = this.state; |
| 117 | const data = this.getStreamData(this.state.value); |
| 118 | return ( |
| 119 | <div className="container"> |
| 120 | <div className="rendering"> |
| 121 | {this.renderOption('sync', 'Synchronous')} |
| 122 | {this.renderOption('debounced', 'Debounced')} |
| 123 | {this.renderOption('async', 'Concurrent')} |
| 124 | </div> |
| 125 | <input |
| 126 | className={'input ' + this.state.strategy} |
| 127 | placeholder="longer input → more components and DOM nodes" |
| 128 | defaultValue={this.state.input} |
| 129 | onChange={this.handleChange} |
| 130 | /> |
| 131 | <div className="demo" onClick={this.handleChartClick}> |
| 132 | {this.state.showDemo && ( |
| 133 | <Charts data={data} onClick={this.handleChartClick} /> |
| 134 | )} |
| 135 | <div style={{display: showClock ? 'block' : 'none'}}> |
| 136 | <Clock /> |
| 137 | </div> |
| 138 | </div> |
| 139 | </div> |
| 140 | ); |
| 141 | } |
| 142 | } |
| 143 | |
| 144 | const container = document.getElementById('root'); |
| 145 | const root = createRoot(container); |
| 146 | root.render(<App />); |