Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React 18 with startTransition #15

Open
wants to merge 25 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ build
.DS_Store
.env
npm-debug.log

.now
54 changes: 33 additions & 21 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,23 +1,35 @@
{
"name": "react-fractals",
"version": "0.0.1",
"private": true,
"devDependencies": {
"gh-pages": "^0.12.0",
"react-scripts": "0.7.0"
},
"dependencies": {
"d3-scale": "^1.0.4",
"d3-selection": "^1.0.3",
"react": "^16.3.0-rc.0",
"react-dom": "^16.3.0-rc.0"
},
"homepage": "https://swizec.github.io/react-fractals",
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject",
"deploy": "gh-pages -d build"
}
"name": "react-fractals",
"version": "0.0.1",
"private": true,
"devDependencies": {
"gh-pages": "^0.12.0",
"react-scripts": "^4.0.3"
},
"dependencies": {
"d3-scale": "^1.0.4",
"d3-selection": "^1.0.3",
"react": "18.0.0-alpha-9212d994b",
"react-dom": "18.0.0-alpha-9212d994b",
"react-lag-radar": "^1.0.0"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject",
"deploy": "gh-pages -d build"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
25 changes: 12 additions & 13 deletions public/index.html
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<!--
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
<!--
Notice the use of %PUBLIC_URL% in the tag above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Expand All @@ -13,11 +13,11 @@
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<div id="root"></div>
<!--
<title>React App</title>
</head>
<body>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.

Expand All @@ -26,6 +26,5 @@

To begin the development, run `npm start`.
To create a production bundle, use `npm run build`.
-->
</body>
--></body>
</html>
240 changes: 174 additions & 66 deletions src/App.js
Original file line number Diff line number Diff line change
@@ -1,91 +1,199 @@
import React, { Component } from "react";
import React, { useState, useTransition } from "react";
import logo from "./logo.svg";
import LagRadar from "react-lag-radar";
import "./App.css";
import { select as d3select, mouse as d3mouse } from "d3-selection";
import { scaleLinear } from "d3-scale";

import Pythagoras from "./Pythagoras";

class App extends Component {
svg = {
function App() {
const svg = {
width: 1280,
height: 600
height: 600,
};
state = {
currentMax: 0,
baseW: 80,
heightFactor: 0,
lean: 0
};
running = false;
realMax = 11;
const baseWidth = 80;
const heightFactor = 0.4;
const maxTreeSize = 22;

componentDidMount() {
d3select(this.refs.svg).on("mousemove", this.onMouseMove.bind(this));
// we split state in two so we can update
// visuals and inputs separately
const [treeSizeInput, setTreeSizeInput] = useState(8);
const [treeSize, setTreeSize] = useState(8);

this.next();
}
const [treeLeanInput, setTreeLeanInput] = useState(0);
const [treeLean, setTreeLean] = useState(0);
const [isLeaning, startLeaning] = useTransition();

const [enableStartTransition, setEnableStartTransition] = useState(false);
const [enableSlowdown, setEnableSlowdown] = useState(false);

next() {
const { currentMax } = this.state;
function changeTreeSize(event) {
const value = Number(event.target.value);
setTreeSizeInput(value); // update input

if (currentMax < this.realMax) {
this.setState({ currentMax: currentMax + 1 });
setTimeout(this.next.bind(this), 500);
// update visuals
if (enableStartTransition) {
React.startTransition(() => {
setTreeSize(value);
});
} else {
setTreeSize(value);
}
}

// Throttling approach borrowed from Vue fork
// https://github.com/yyx990803/vue-fractal/blob/master/src/App.vue
// rAF makes it slower than just throttling on React update
onMouseMove(event) {
if (this.running) return;
this.running = true;
function changeTreeLean(event) {
const value = Number(event.target.value);
setTreeLeanInput(value); // update input

const [x, y] = d3mouse(this.refs.svg),
scaleFactor = scaleLinear()
.domain([this.svg.height, 0])
.range([0, 0.8]),
scaleLean = scaleLinear()
.domain([0, this.svg.width / 2, this.svg.width])
.range([0.5, 0, -0.5]);
// update visuals
if (enableStartTransition) {
startLeaning(() => {
setTreeLean(value);
});
} else {
setTreeLean(value);
}
}

this.setState({
heightFactor: scaleFactor(y),
lean: scaleLean(x)
});
this.running = false;
function toggleStartTransition(event) {
setEnableStartTransition(event.target.checked);
}

render() {
return (
<div className="App">
<div className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h2>This is a dancing Pythagoras tree</h2>
function toggleSlowdown(event) {
setEnableSlowdown(event.target.checked);
}

return (
<div className="App">
<div className="App-header" style={{ marginBottom: "1rem" }}>
<LagRadar />
<h2 style={{ marginTop: "-0.2rem" }}>
This is a leaning Pythagoras tree
<br />
<small>
{Number(2 ** treeSize - 1).toLocaleString()} squares
</small>
</h2>
</div>
<div
style={{
display: "flex",
flexDirection: "row",
justifyContent: "center",
}}
>
<div style={{ fontWeight: "bold", fontSize: "1.2em" }}>
<label>
Use startTransition
<br />
<input
type="checkbox"
checked={enableStartTransition}
onChange={toggleStartTransition}
/>
</label>
</div>
<p className="App-intro">
<svg
width={this.svg.width}
height={this.svg.height}
ref="svg"
style={{ border: "1px solid lightgray" }}
>
<Pythagoras
w={this.state.baseW}
h={this.state.baseW}
heightFactor={this.state.heightFactor}
lean={this.state.lean}
x={this.svg.width / 2 - 40}
y={this.svg.height - this.state.baseW}
lvl={0}
maxlvl={this.state.currentMax}
<div>
<label>Lean the tree:</label>
<br />
<input
type="range"
value={treeLeanInput}
onChange={changeTreeLean}
min="-0.5"
max="0.5"
step="0.05"
style={{ width: svg.width / 3 }}
/>
</div>
<div>
<label>
Make each square block the thread for 0.1ms
<br />
<input
type="checkbox"
checked={enableSlowdown}
onChange={toggleSlowdown}
/>
</svg>
</label>
</div>
</div>
<div style={{ display: "flex", flexDirection: "row" }}>
<div style={{ width: 130 }}>
<label>
Grow the tree
<br />
Bigger is slower
</label>
<input
type="range"
value={treeSizeInput}
onChange={changeTreeSize}
min="0"
max={maxTreeSize}
step="1"
style={{
transform: `rotate(-90deg) translate(-${
svg.height / 2
}px, -90px)`,
width: svg.height / 2,
}}
/>
</div>

<svg
width={svg.width}
height={svg.height}
className={isLeaning ? "pending" : "done"}
style={{
border: "1px solid lightgray",
}}
>
<Pythagoras
enableSlowdown={enableSlowdown}
w={baseWidth}
h={baseWidth}
heightFactor={heightFactor}
lean={-treeLean}
x={svg.width / 2 - 40}
y={svg.height - baseWidth}
lvl={0}
maxlvl={treeSize}
/>
</svg>
</div>
<div className="explanation">
<h1>What this demo shows</h1>

<p>
The demo shows you what happens when every state change
updates 1,000,000+ nodes. Slider on the left grows the tree,
makes the problem worse – exponentially. Slider on top leans
the tree, updates every node. Use it to see what happens :)
</p>
<p>
Toggle the `Use startTransition` checkbox to compare
behavior with and without the new feature. You should see
your inputs laaaaaag without `startTransition`. When it's
enabled, the urgent input update happens fast and the slow
fractal updates later.
</p>
<p>
If you don't see slowness, enable the artificial 0.1ms
delay. That'll do it.
</p>
<p>
<a href="https://swizec.com/blog/a-better-react-18-starttransition-demo/">
Read deeper explanation and startTransition gotchas on
Swizec's blog 👉
</a>
</p>
<p></p>
<p>
Built by <a href="https://twitter.com/swizec">@swizec</a>
</p>
</div>
);
}
</div>
);
}

export default App;
Loading