152 lines
3.7 KiB
JavaScript
152 lines
3.7 KiB
JavaScript
import React, {PureComponent} from 'react';
|
|
import {flushSync, createRoot} from 'react-dom';
|
|
import Scheduler from 'scheduler';
|
|
import _ from 'lodash';
|
|
import Charts from './Charts';
|
|
import Clock from './Clock';
|
|
import './index.css';
|
|
|
|
let cachedData = new Map();
|
|
|
|
class App extends PureComponent {
|
|
state = {
|
|
value: '',
|
|
strategy: 'sync',
|
|
showDemo: true,
|
|
showClock: false,
|
|
};
|
|
|
|
// Random data for the chart
|
|
getStreamData(input) {
|
|
if (cachedData.has(input)) {
|
|
return cachedData.get(input);
|
|
}
|
|
const multiplier = input.length !== 0 ? input.length : 1;
|
|
const complexity =
|
|
(parseInt(window.location.search.substring(1), 10) / 100) * 25 || 25;
|
|
const data = _.range(5).map(t =>
|
|
_.range(complexity * multiplier).map((j, i) => {
|
|
return {
|
|
x: j,
|
|
y: (t + 1) * _.random(0, 255),
|
|
};
|
|
})
|
|
);
|
|
cachedData.set(input, data);
|
|
return data;
|
|
}
|
|
|
|
componentDidMount() {
|
|
window.addEventListener('keydown', e => {
|
|
if (e.key.toLowerCase() === '?') {
|
|
e.preventDefault();
|
|
this.setState(state => ({
|
|
showClock: !state.showClock,
|
|
}));
|
|
}
|
|
});
|
|
}
|
|
|
|
handleChartClick = e => {
|
|
if (this.state.showDemo) {
|
|
if (e.shiftKey) {
|
|
this.setState({showDemo: false});
|
|
}
|
|
return;
|
|
}
|
|
if (this.state.strategy !== 'async') {
|
|
flushSync(() => {
|
|
this.setState(state => ({
|
|
showDemo: !state.showDemo,
|
|
}));
|
|
});
|
|
return;
|
|
}
|
|
if (this._ignoreClick) {
|
|
return;
|
|
}
|
|
this._ignoreClick = true;
|
|
|
|
Scheduler.unstable_next(() => {
|
|
this.setState({showDemo: true}, () => {
|
|
this._ignoreClick = false;
|
|
});
|
|
});
|
|
};
|
|
|
|
debouncedHandleChange = _.debounce(value => {
|
|
if (this.state.strategy === 'debounced') {
|
|
flushSync(() => {
|
|
this.setState({value: value});
|
|
});
|
|
}
|
|
}, 1000);
|
|
|
|
renderOption(strategy, label) {
|
|
const {strategy: currentStrategy} = this.state;
|
|
return (
|
|
<label className={strategy === currentStrategy ? 'selected' : null}>
|
|
<input
|
|
type="radio"
|
|
checked={strategy === currentStrategy}
|
|
onChange={() => this.setState({strategy})}
|
|
/>
|
|
{label}
|
|
</label>
|
|
);
|
|
}
|
|
|
|
handleChange = e => {
|
|
const value = e.target.value;
|
|
const {strategy} = this.state;
|
|
switch (strategy) {
|
|
case 'sync':
|
|
this.setState({value});
|
|
break;
|
|
case 'debounced':
|
|
this.debouncedHandleChange(value);
|
|
break;
|
|
case 'async':
|
|
// TODO: useTransition hook instead.
|
|
setTimeout(() => {
|
|
this.setState({value});
|
|
}, 0);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
};
|
|
|
|
render() {
|
|
const {showClock} = this.state;
|
|
const data = this.getStreamData(this.state.value);
|
|
return (
|
|
<div className="container">
|
|
<div className="rendering">
|
|
{this.renderOption('sync', 'Synchronous')}
|
|
{this.renderOption('debounced', 'Debounced')}
|
|
{this.renderOption('async', 'Concurrent')}
|
|
</div>
|
|
<input
|
|
className={'input ' + this.state.strategy}
|
|
placeholder="longer input → more components and DOM nodes"
|
|
defaultValue={this.state.input}
|
|
onChange={this.handleChange}
|
|
/>
|
|
<div className="demo" onClick={this.handleChartClick}>
|
|
{this.state.showDemo && (
|
|
<Charts data={data} onClick={this.handleChartClick} />
|
|
)}
|
|
<div style={{display: showClock ? 'block' : 'none'}}>
|
|
<Clock />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
const container = document.getElementById('root');
|
|
const root = createRoot(container);
|
|
root.render(<App />);
|