├── .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 | 
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 |
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 |
8 | - onClick(true)}
11 | >
12 | {leftValue}
13 |
14 | - onClick(false)}
17 | >
18 | {rightValue}
19 |
20 |
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 |
8 |
--------------------------------------------------------------------------------