├── .gitignore ├── .prettierrc ├── README.md ├── package.json ├── public ├── favicon.ico ├── index.html └── manifest.json ├── src ├── components │ ├── DemoBarChart │ │ └── index.js │ ├── DemoPercentageChart │ │ └── index.js │ ├── DemoScatterChart │ │ └── index.js │ ├── Input │ │ ├── index.js │ │ └── styles.js │ ├── ListItem │ │ ├── index.js │ │ └── styles.js │ ├── Spinner │ │ ├── index.js │ │ └── styles.css │ └── Tab │ │ ├── index.js │ │ └── styles.js ├── constants │ └── index.js ├── containers │ ├── App.js │ ├── Suspense │ │ ├── CommitListing.js │ │ ├── RepoListing.js │ │ └── index.js │ └── TimeSlicing │ │ ├── index.js │ │ └── styles.js ├── helpers │ ├── future.js │ ├── generateData.js │ ├── getValueFromQS.js │ └── githubApi.js ├── index.css ├── index.js └── logo.svg └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "trailingComma": "es5" 5 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-async-rendering-demo 2 | 3 | ## Introduction 4 | 5 | ![screen shot 2018-06-08 at 00 40 43](https://user-images.githubusercontent.com/20895743/41113586-a5f27f84-6ab4-11e8-9edc-1e3d3d391f00.jpg) 6 | 7 | > React Fiber, the new core architecture was launched in React 16. It opened a new chapter in React, enable features like [Async Rendering](https://reactjs.org/blog/2018/03/01/sneak-peek-beyond-react-16.html) 8 | 9 | This demo is for [my sharing in HKOSCon 2018](https://hkoscon.org/2018/topic/react-async-rendering-paradigm-shift-after-react-fiber/), _React Async Rendering - Paradigm Shift After React Fiber_ 10 | 11 | ## Live Demo 12 | 13 | https://ivan-ha.github.io/react-async-rendering-demo/ 14 | 15 | ## Presentation Slides 16 | 17 | https://www.slideshare.net/hangolam/react-async-rendering-102558178 18 | 19 | ## Time Slicing 20 | 21 | > ... to ensure that high-priority updates don’t get blocked by a low-priority update. 22 | 23 | - Do some rage typing in the input field under Async/Sync Mode, you should feel the difference. 24 | - If not, enable CPU throttling in browser's devtool. 25 | 26 | ## Suspense 27 | 28 | > ... for components to suspend rendering while they load async data. 29 | 30 | - Click on any repository name, it should have render the commit history without any spinner. 31 | - Try again with appending `?latency=3000` in URL. 32 | 33 | ## Development 34 | 35 | ``` 36 | yarn 37 | yarn start 38 | ``` 39 | 40 | ## Deploy 41 | 42 | ``` 43 | yarn deploy 44 | ``` 45 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-async-rendering-demo", 3 | "version": "0.1.0", 4 | "private": true, 5 | "homepage": "http://ivan-ha.github.io/react-async-rendering-demo", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/ivan-ha/react-async-rendering-demo.git" 9 | }, 10 | "scripts": { 11 | "start": "react-scripts start", 12 | "build": "react-scripts build", 13 | "test": "react-scripts test --env=jsdom", 14 | "eject": "react-scripts eject", 15 | "git-hook": "lint-staged", 16 | "precommit": "yarn git-hook", 17 | "prepush": "yarn git-hook", 18 | "predeploy": "yarn build", 19 | "deploy": "gh-pages -d build" 20 | }, 21 | "lint-staged": { 22 | "*.{js,json,css,md}": [ 23 | "prettier --write", 24 | "git add" 25 | ] 26 | }, 27 | "dependencies": { 28 | "ramda": "^0.25.0", 29 | "react": "16.4.0-alpha.0911da3", 30 | "react-dom": "16.4.0-alpha.0911da3", 31 | "react-scripts": "1.1.4", 32 | "recharts": "^1.0.0-beta.10", 33 | "simple-cache-provider": "^0.6.0" 34 | }, 35 | "devDependencies": { 36 | "gh-pages": "^1.2.0", 37 | "husky": "^0.14.3", 38 | "lint-staged": "^7.1.3", 39 | "prettier": "^1.13.4" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivan-ha/react-async-rendering-demo/a8a5dc0143c3ed3df013730a34efd5bfa681d8eb/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | 23 | 24 | 25 | 32 | 33 | React Async Rendering Demo 34 | 35 | 36 | 39 | 40 | Fork me on GitHub 42 | 43 |
44 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/components/DemoBarChart/index.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | import { Bar, BarChart, XAxis, YAxis } from 'recharts' 4 | 5 | import { CHART_MARGIN, CHART_SIZE, COLORS } from '../../constants' 6 | 7 | const DemoBarChart = ({ data }) => ( 8 | 14 | 15 | 16 | 17 | 18 | 19 | ) 20 | 21 | DemoBarChart.propTypes = { 22 | data: PropTypes.array.isRequired, 23 | } 24 | 25 | export default DemoBarChart 26 | -------------------------------------------------------------------------------- /src/components/DemoPercentageChart/index.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | import { Area, AreaChart, XAxis, YAxis } from 'recharts' 4 | 5 | import { CHART_MARGIN, CHART_SIZE, COLORS } from '../../constants' 6 | 7 | const DemoPercentageChart = ({ data }) => ( 8 | 14 | 15 | 16 | 17 | 24 | 31 | 38 | 39 | ) 40 | 41 | DemoPercentageChart.propTypes = { 42 | data: PropTypes.array.isRequired, 43 | } 44 | 45 | export default DemoPercentageChart 46 | -------------------------------------------------------------------------------- /src/components/DemoScatterChart/index.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | import { Cell, Scatter, ScatterChart, XAxis, YAxis } from 'recharts' 4 | 5 | import { CHART_MARGIN, CHART_SIZE, COLORS } from '../../constants' 6 | 7 | const DemoScatterChart = ({ data }) => ( 8 | 9 | 10 | 11 | 12 | {data.map((entry, index) => ( 13 | 14 | ))} 15 | 16 | 17 | ) 18 | 19 | DemoScatterChart.propTypes = { 20 | data: PropTypes.array.isRequired, 21 | } 22 | 23 | export default DemoScatterChart 24 | -------------------------------------------------------------------------------- /src/components/Input/index.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | 4 | import * as styles from './styles' 5 | 6 | const Input = ({ value, onChange }) => ( 7 |
8 | 15 |
16 | ) 17 | 18 | Input.propTypes = { 19 | value: PropTypes.string.isRequired, 20 | onChange: PropTypes.func.isRequired, 21 | } 22 | 23 | export default Input 24 | -------------------------------------------------------------------------------- /src/components/Input/styles.js: -------------------------------------------------------------------------------- 1 | export const containerStyle = { 2 | display: 'flex', 3 | flex: 1, 4 | } 5 | 6 | export const inputStyle = { 7 | borderColor: 'rgba(0, 0, 0, 0.08)', 8 | borderRadius: '4px', 9 | borderStyle: 'solid', 10 | borderWidth: '1px', 11 | flex: 1, 12 | fontSize: '32px', 13 | paddingBottom: '5px', 14 | paddingLeft: '10px', 15 | paddingRight: '10px', 16 | paddingTop: '5px', 17 | } 18 | -------------------------------------------------------------------------------- /src/components/ListItem/index.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | 4 | import Spinner from '../Spinner' 5 | import * as styles from './styles' 6 | 7 | const ListItem = ({ value, onClick, index, isLoading }) => { 8 | return ( 9 |
16 | {value} 17 | {isLoading && } 18 |
19 | ) 20 | } 21 | 22 | ListItem.propTypes = { 23 | value: PropTypes.string.isRequired, 24 | onClick: PropTypes.func, 25 | index: PropTypes.number.isRequired, 26 | isLoading: PropTypes.bool, 27 | } 28 | 29 | export default ListItem 30 | -------------------------------------------------------------------------------- /src/components/ListItem/styles.js: -------------------------------------------------------------------------------- 1 | export const container = { 2 | borderRadius: 4, 3 | color: '#ECEFF1', 4 | fontFamily: 'monospace', 5 | fontSize: 30, 6 | height: 50, 7 | marginBottom: 10, 8 | padding: 'auto', 9 | paddingLeft: 15, 10 | paddingTop: 15, 11 | display: 'flex', 12 | backgroundColor: '#455A64', 13 | } 14 | -------------------------------------------------------------------------------- /src/components/Spinner/index.js: -------------------------------------------------------------------------------- 1 | import './styles.css' 2 | 3 | import PropTypes from 'prop-types' 4 | import React from 'react' 5 | 6 | const Spinner = ({ size }) =>
7 | 8 | Spinner.propTypes = { 9 | size: PropTypes.string.isRequired, 10 | } 11 | 12 | Spinner.defaultProps = { 13 | size: 'medium', 14 | } 15 | 16 | export default Spinner 17 | -------------------------------------------------------------------------------- /src/components/Spinner/styles.css: -------------------------------------------------------------------------------- 1 | .spinner { 2 | /* If it is displayed in a flex container */ 3 | align-self: center; 4 | 5 | display: flex; 6 | align-items: center; 7 | justify-content: center; 8 | } 9 | 10 | .spinner::before { 11 | content: ''; 12 | display: inline-block; 13 | border: 3px solid rgba(255, 255, 255, 0.3); 14 | border-radius: 50%; 15 | border-top-color: #fff; 16 | animation: spin 1s ease-in-out infinite; 17 | } 18 | 19 | @keyframes spin { 20 | to { 21 | transform: rotate(360deg); 22 | } 23 | } 24 | 25 | .spinner-small::before { 26 | width: 24px; 27 | height: 24px; 28 | } 29 | 30 | .spinner-medium::before { 31 | width: 50px; 32 | height: 50px; 33 | } 34 | 35 | .spinner-large::before { 36 | width: 100px; 37 | height: 100px; 38 | } 39 | -------------------------------------------------------------------------------- /src/components/Tab/index.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | 4 | import * as style from './styles' 5 | 6 | const Tab = ({ isOn, leftValue, onClick, rightValue }) => ( 7 | 21 | ) 22 | 23 | Tab.propTypes = { 24 | isOn: PropTypes.bool.isRequired, 25 | onClick: PropTypes.func.isRequired, 26 | leftValue: PropTypes.string.isRequired, 27 | rightValue: PropTypes.string.isRequired, 28 | } 29 | 30 | export default Tab 31 | -------------------------------------------------------------------------------- /src/components/Tab/styles.js: -------------------------------------------------------------------------------- 1 | export const tab = { 2 | display: 'flex', 3 | height: 50, 4 | cursor: 'pointer', 5 | listStyle: 'none', 6 | padding: 0, 7 | flex: 1, 8 | WebkitMarginBefore: '0em', 9 | WebkitMarginAfter: '0em', 10 | } 11 | 12 | export const content = { 13 | backgroundColor: '#eee', 14 | borderRadius: 4, 15 | color: 'black', 16 | flex: 1, 17 | fontSize: '1.2rem', 18 | padding: 'auto', 19 | paddingTop: 15, 20 | textAlign: 'center', 21 | } 22 | 23 | export const active = { 24 | backgroundColor: '#66BB6A', 25 | color: '#fff', 26 | } 27 | -------------------------------------------------------------------------------- /src/constants/index.js: -------------------------------------------------------------------------------- 1 | export const GITHUB_USER_NAME = 'ivan-ha' 2 | 3 | export const PLACEHOLDER_DELAY_MS = 1000 4 | 5 | export const CHART_DATA_FACTOR = 5 6 | 7 | export const CHART_SIZE = 400 8 | 9 | export const CHART_MARGIN = { 10 | top: 20, 11 | right: 20, 12 | bottom: 20, 13 | left: 20, 14 | } 15 | 16 | export const COLORS = [ 17 | '#EF5350', 18 | '#FFA726', 19 | '#FFEE58', 20 | '#66BB6A', 21 | '#29B6F6', 22 | '#AB47BC', 23 | '#8884d8', 24 | '#82ca9d', 25 | '#ffc658', 26 | ] 27 | -------------------------------------------------------------------------------- /src/containers/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component, Fragment } from 'react' 2 | 3 | import Tab from '../components/Tab' 4 | import Suspense from './Suspense' 5 | import TimeSlicing from './TimeSlicing' 6 | 7 | const mainAppStyles = { marginTop: 20 } 8 | 9 | class App extends Component { 10 | state = { 11 | isTimeSlicing: true, 12 | } 13 | 14 | handleTabChange = isTimeSlicing => this.setState({ isTimeSlicing }) 15 | 16 | renderMainApp = () => ( 17 |
18 | {this.state.isTimeSlicing ? : } 19 |
20 | ) 21 | 22 | render() { 23 | return ( 24 | 25 | 31 | {this.renderMainApp()} 32 | 33 | ) 34 | } 35 | } 36 | export default App 37 | -------------------------------------------------------------------------------- /src/containers/Suspense/CommitListing.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import * as R from 'ramda' 3 | import React, { Fragment } from 'react' 4 | 5 | import ListItem from '../../components/ListItem' 6 | import { createFetcher } from '../../helpers/future' 7 | import { fetchCommits } from '../../helpers/githubApi' 8 | 9 | const commitFetcher = createFetcher(fetchCommits) 10 | 11 | const CommitListing = ({ repoName }) => { 12 | const commits = commitFetcher.read(repoName) 13 | 14 | return ( 15 | 16 |

{repoName}

17 | 18 | {commits.map((commit, index) => ( 19 | 24 | ))} 25 |
26 | ) 27 | } 28 | 29 | CommitListing.propTypes = { 30 | repoName: PropTypes.string.isRequired, 31 | } 32 | 33 | export default CommitListing 34 | -------------------------------------------------------------------------------- /src/containers/Suspense/RepoListing.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React, { Fragment } from 'react' 3 | 4 | import ListItem from '../../components/ListItem' 5 | import { createFetcher } from '../../helpers/future' 6 | import { fetchRepos } from '../../helpers/githubApi' 7 | 8 | const reposFetcher = createFetcher(fetchRepos) 9 | 10 | const RepoListing = ({ loadingRepoName, onClick }) => { 11 | const repos = reposFetcher.read() 12 | 13 | return ( 14 | 15 | {repos.map((repo, index) => ( 16 | onClick(repo.name)} 20 | index={index} 21 | isLoading={repo.name === loadingRepoName} 22 | /> 23 | ))} 24 | 25 | ) 26 | } 27 | 28 | RepoListing.propTypes = { 29 | loadingRepoName: PropTypes.string, 30 | onClick: PropTypes.func.isRequired, 31 | } 32 | 33 | export default RepoListing 34 | -------------------------------------------------------------------------------- /src/containers/Suspense/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import Spinner from '../../components/Spinner' 4 | import { COLORS, PLACEHOLDER_DELAY_MS } from '../../constants' 5 | import { Component, Placeholder } from '../../helpers/future' 6 | import { createFetcher } from '../../helpers/future' 7 | import RepoListing from './RepoListing' 8 | 9 | const backButtonStyles = { 10 | fontSize: 30, 11 | backgroundColor: COLORS[0], 12 | borderRadius: 4, 13 | color: 'black', 14 | cursor: 'pointer', 15 | textAlign: 'center', 16 | width: 150, 17 | } 18 | 19 | const commitListingFetcher = createFetcher(() => import('./CommitListing')) 20 | const CommitListingLoader = props => { 21 | const CommitListing = commitListingFetcher.read().default 22 | return 23 | } 24 | 25 | class Suspense extends Component { 26 | state = { 27 | currentRepoName: null, 28 | showCommitListing: false, 29 | } 30 | 31 | handleRepoClick = currentRepoName => { 32 | this.setState({ currentRepoName }) 33 | this.deferSetState({ showCommitListing: true }) 34 | } 35 | 36 | handleBackClick = () => { 37 | this.setState({ 38 | currentRepoName: null, 39 | showCommitListing: false, 40 | }) 41 | } 42 | 43 | render() { 44 | return ( 45 | } 47 | delayMs={PLACEHOLDER_DELAY_MS} 48 | > 49 | {this.state.showCommitListing && ( 50 |
51 | 52 | 👈 53 | {' '} 54 | Back 55 |
56 | )} 57 | {!this.state.showCommitListing ? ( 58 | 62 | ) : ( 63 | 64 | )} 65 |
66 | ) 67 | } 68 | } 69 | 70 | export default Suspense 71 | -------------------------------------------------------------------------------- /src/containers/TimeSlicing/index.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from 'react' 2 | 3 | import DemoBarChart from '../../components/DemoBarChart' 4 | import DemoPercentageChart from '../../components/DemoPercentageChart' 5 | import DemoScatterChart from '../../components/DemoScatterChart' 6 | import Input from '../../components/Input' 7 | import Tab from '../../components/Tab' 8 | import { CHART_DATA_FACTOR } from '../../constants' 9 | import { Component } from '../../helpers/future' 10 | import { genListData } from '../../helpers/generateData' 11 | import * as styles from './styles' 12 | 13 | const INITIAL_DATA = genListData(CHART_DATA_FACTOR) 14 | 15 | class TimeSlicing extends Component { 16 | state = { 17 | inputValue: '', 18 | data: INITIAL_DATA, 19 | isAsync: true, 20 | } 21 | 22 | handleChange = evt => { 23 | const value = evt.target.value 24 | const newDataSet = { 25 | data: genListData(CHART_DATA_FACTOR * (value.length || 1)), 26 | } 27 | 28 | this.setState({ inputValue: value }) 29 | 30 | this.state.isAsync 31 | ? this.deferSetState(newDataSet) 32 | : this.setState(newDataSet) 33 | } 34 | 35 | handleTabChange = isAsync => 36 | this.setState({ 37 | isAsync, 38 | inputValue: '', 39 | data: INITIAL_DATA, 40 | }) 41 | 42 | render() { 43 | return ( 44 | 45 |
46 | 52 | 53 |
54 | 55 |
56 | 57 | 58 | 59 |
60 |
61 | ) 62 | } 63 | } 64 | 65 | export default TimeSlicing 66 | -------------------------------------------------------------------------------- /src/containers/TimeSlicing/styles.js: -------------------------------------------------------------------------------- 1 | export const containerStyles = { 2 | display: 'flex', 3 | flex: 1, 4 | } 5 | -------------------------------------------------------------------------------- /src/helpers/future.js: -------------------------------------------------------------------------------- 1 | import * as R from 'ramda' 2 | import React, { Timeout } from 'react' 3 | import ReactDOM from 'react-dom' 4 | import { createCache, createResource } from 'simple-cache-provider' 5 | 6 | const cache = createCache(() => {}) 7 | 8 | export const createFetcher = resolver => { 9 | const resource = createResource(resolver) 10 | return { 11 | read: key => resource.read(cache, key), 12 | } 13 | } 14 | 15 | export const Placeholder = props => ( 16 | 17 | {didExpire => (didExpire ? props.fallback : props.children)} 18 | 19 | ) 20 | 21 | export class Component extends React.Component { 22 | deferSetState(updater, callback) { 23 | // ReactDOM.unstable_deferredUpdates not working in event handlers issue workaround 24 | // https://twitter.com/dan_abramov/status/971090711893377026 25 | Promise.resolve(() => 26 | ReactDOM.unstable_deferredUpdates(() => { 27 | if (R.type(updater) === 'Object') { 28 | this.setState(() => ({ ...updater }), callback) 29 | } else if (R.type(updater) === 'Function') { 30 | this.setState(updater, callback) 31 | } else { 32 | throw new Error('Unsupported argument') 33 | } 34 | }) 35 | ).then(deferredUpdates => deferredUpdates()) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/helpers/generateData.js: -------------------------------------------------------------------------------- 1 | import * as R from 'ramda' 2 | 3 | const getRandomNumber = () => Math.floor(Math.random() * 600 + 1) 4 | 5 | const genList = R.curry((fn, num) => 6 | R.compose( 7 | R.addIndex(R.map)(fn), 8 | Array.from, 9 | Array 10 | )(num) 11 | ) 12 | 13 | export const genListData = genList((v, i) => ({ 14 | index: i, 15 | x: getRandomNumber(), 16 | y: getRandomNumber(), 17 | z: getRandomNumber(), 18 | })) 19 | -------------------------------------------------------------------------------- /src/helpers/getValueFromQS.js: -------------------------------------------------------------------------------- 1 | export const getValueFromQS = param => { 2 | const urlParams = new URLSearchParams(window.location.search) 3 | return urlParams.get(param) 4 | } 5 | -------------------------------------------------------------------------------- /src/helpers/githubApi.js: -------------------------------------------------------------------------------- 1 | import * as R from 'ramda' 2 | 3 | import { GITHUB_USER_NAME } from '../constants' 4 | import { getValueFromQS } from './getValueFromQS' 5 | 6 | const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)) 7 | 8 | const latency = R.compose( 9 | parseInt, 10 | R.defaultTo(0), 11 | getValueFromQS 12 | )('latency') 13 | 14 | export const fetchRepos = async () => { 15 | const response = await fetch( 16 | `https://api.github.com/users/${GITHUB_USER_NAME}/repos` 17 | ) 18 | 19 | // Simulate some delay 20 | await sleep(latency) 21 | 22 | return response.json() 23 | } 24 | 25 | export const fetchCommits = async repoName => { 26 | const response = await fetch( 27 | `https://api.github.com/repos/${GITHUB_USER_NAME}/${repoName}/commits` 28 | ) 29 | 30 | // Simulate some delay 31 | await sleep(latency) 32 | 33 | return response.json() 34 | } 35 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #282c34; 3 | font-family: sans-serif; 4 | margin: 0; 5 | padding: 10px; 6 | } 7 | 8 | h1 { 9 | color: azure; 10 | } 11 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import './index.css' 2 | 3 | import React from 'react' 4 | import ReactDOM from 'react-dom' 5 | 6 | import App from './containers/App' 7 | 8 | const AsyncMode = React.unstable_AsyncMode 9 | 10 | ReactDOM.render( 11 | 12 | 13 | , 14 | document.getElementById('root') 15 | ) 16 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | --------------------------------------------------------------------------------