diff --git a/src/components/page-react-timings/App.tsx b/src/components/page-react-timings/App.tsx index 11e745c..717710f 100644 --- a/src/components/page-react-timings/App.tsx +++ b/src/components/page-react-timings/App.tsx @@ -3,6 +3,7 @@ import * as React from "react"; import { LifeCycleTimings } from "./LifeCycleTimings"; import { Render } from "./Render"; import { Event } from "./Event"; +import { Rerender } from "./Rerender"; export function App() { return ( @@ -15,6 +16,9 @@ export function App() {

Test event capture / event bubble

+
+

Test re-renders

+ ); } diff --git a/src/components/page-react-timings/Rerender.tsx b/src/components/page-react-timings/Rerender.tsx new file mode 100644 index 0000000..18f8b0c --- /dev/null +++ b/src/components/page-react-timings/Rerender.tsx @@ -0,0 +1,47 @@ +import * as React from "react"; + +export function Rerender() { + return ( + <> + + + + + ); +} + +function DisplayHideButton({ + label, + children, +}: { + label: string; + children?: React.ReactNode; +}) { + const [isDisplayed, setIsDisplayed] = React.useState(false); + + return ( + <> + + {isDisplayed && children} + + ); +} + +function RerenderInUseEffect() { + const [, forceUpdate] = React.useState({}); + + console.log("render"); + + React.useEffect(() => { + console.log("mount that has a re-render"); + forceUpdate({}); + }, []); + + React.useEffect(() => { + console.log("effect with no side effects"); + }); + + return "✅"; +} -- 2.51.2 From 3303528a6226ad62002413934336313c40f36312 Mon Sep 17 00:00:00 2001 From: Ayc0 Date: Wed, 22 May 2024 13:20:55 +0200 Subject: [PATCH 2/5] [timing] measure re-renders --- .../page-react-timings/Rerender.tsx | 81 ++++++++++++++++++- 1 file changed, 80 insertions(+), 1 deletion(-) diff --git a/src/components/page-react-timings/Rerender.tsx b/src/components/page-react-timings/Rerender.tsx index 18f8b0c..b92833d 100644 --- a/src/components/page-react-timings/Rerender.tsx +++ b/src/components/page-react-timings/Rerender.tsx @@ -1,4 +1,5 @@ import * as React from "react"; +import { animationFrame, log, microTask, task } from "./tasks"; export function Rerender() { return ( @@ -6,6 +7,11 @@ export function Rerender() { + + + + + ); } @@ -13,15 +19,20 @@ export function Rerender() { function DisplayHideButton({ label, children, + style, }: { label: string; children?: React.ReactNode; + style?: React.CSSProperties; }) { const [isDisplayed, setIsDisplayed] = React.useState(false); return ( <> - {isDisplayed && children} @@ -45,3 +56,71 @@ function RerenderInUseEffect() { return "✅"; } + +function MultipleRerenderTimingsFunction() { + const [a, setA] = React.useState(1); + const [b, setB] = React.useState(1); + + console.log("render", { a, b }); + + const update = () => { + console.clear(); + log("click (before re-renders)", { tasks: true }); + setA((a) => a + 1); + log("click (between re-renders)", { tasks: true }); + setB((b) => b + 1); + log("click (after re-renders)", { tasks: true }); + }; + + return ( +
+
Function:
+ + + + +
+ ); +} + +class MultipleRerenderTimingsClass extends React.Component< + {}, + { a: number; b: number } +> { + override state = { a: 1, b: 1 }; + + update = () => { + console.clear(); + log("click (before re-renders)", { tasks: true }); + this.setState( + (s) => ({ ...s, a: s.a + 1 }), + () => { + console.log("Set state for a is done"); + } + ); + log("click (between re-renders)", { tasks: true }); + this.setState( + (s) => ({ ...s, b: s.b + 1 }), + () => { + console.log("Set state for b is done"); + } + ); + log("click (after re-renders)", { tasks: true }); + }; + + override render() { + console.log("render", this.state); + + return ( +
+
Class:
+ + + + +
+ ); + } +} -- 2.51.2 From be13570c8fba24c140c82eb010d0c4646d744c1d Mon Sep 17 00:00:00 2001 From: Ayc0 Date: Wed, 22 May 2024 19:10:39 +0200 Subject: [PATCH 3/5] [timing] add flushSync --- .../page-react-timings/Rerender.tsx | 49 +++++++++++++++---- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/src/components/page-react-timings/Rerender.tsx b/src/components/page-react-timings/Rerender.tsx index b92833d..1490d12 100644 --- a/src/components/page-react-timings/Rerender.tsx +++ b/src/components/page-react-timings/Rerender.tsx @@ -1,5 +1,8 @@ import * as React from "react"; import { animationFrame, log, microTask, task } from "./tasks"; +import { flushSync } from "react-dom"; + +let includeFlushSync = false; export function Rerender() { return ( @@ -9,6 +12,15 @@ export function Rerender() { + + + + {children} + + ); +} + +const PassThrough = React.memo( + ({ children }: { children?: React.ReactNode }) => { + log("render PassThrough"); + return ( +
+
PassThrough
+ {children} +
+ ); + } +); + +const Child = React.memo(() => { + log("render Child"); + const value = React.useContext(C); + return ( +
Child {value}
+ ); +}); diff --git a/src/components/page-react-timings/DisplayHideButton.tsx b/src/components/page-react-timings/DisplayHideButton.tsx new file mode 100644 index 0000000..238790b --- /dev/null +++ b/src/components/page-react-timings/DisplayHideButton.tsx @@ -0,0 +1,25 @@ +import React from "react"; + +export function DisplayHideButton({ + label, + children, + style, +}: { + label: string; + children?: React.ReactNode; + style?: React.CSSProperties; +}) { + const [isDisplayed, setIsDisplayed] = React.useState(false); + + return ( + <> + + {isDisplayed && children} + + ); +} diff --git a/src/components/page-react-timings/Rerender.tsx b/src/components/page-react-timings/Rerender.tsx index 1490d12..b95aefe 100644 --- a/src/components/page-react-timings/Rerender.tsx +++ b/src/components/page-react-timings/Rerender.tsx @@ -1,6 +1,7 @@ import * as React from "react"; import { animationFrame, log, microTask, task } from "./tasks"; import { flushSync } from "react-dom"; +import { DisplayHideButton } from "./DisplayHideButton"; let includeFlushSync = false; @@ -28,30 +29,6 @@ export function Rerender() { ); } -function DisplayHideButton({ - label, - children, - style, -}: { - label: string; - children?: React.ReactNode; - style?: React.CSSProperties; -}) { - const [isDisplayed, setIsDisplayed] = React.useState(false); - - return ( - <> - - {isDisplayed && children} - - ); -} - function RerenderInUseEffect() { const [, forceUpdate] = React.useState({}); -- 2.51.2 From dd1f6bbbd5fada96c51889408eee153c4086e036 Mon Sep 17 00:00:00 2001 From: Ayc0 Date: Wed, 22 May 2024 21:43:10 +0200 Subject: [PATCH 5/5] [timing] add better suspense --- .../page-react-timings/LifeCycleTimings.tsx | 66 +++++++++++++++---- 1 file changed, 53 insertions(+), 13 deletions(-) diff --git a/src/components/page-react-timings/LifeCycleTimings.tsx b/src/components/page-react-timings/LifeCycleTimings.tsx index 2360b30..1cf1f9d 100644 --- a/src/components/page-react-timings/LifeCycleTimings.tsx +++ b/src/components/page-react-timings/LifeCycleTimings.tsx @@ -17,6 +17,8 @@ const TimingContext = React.createContext<{ timings: false, }); +const SuspenseContext = React.createContext void>>(new Set()); + class ErrorBoundary extends React.Component< { children?: React.ReactNode }, { error: boolean } @@ -31,21 +33,39 @@ class ErrorBoundary extends React.Component< }; } + suspenses = new Set<() => void>(); + + static override contextType = TimingContext; + declare context: React.ContextType; + override render() { if (this.state.error) { return
Oops
; } return ( - ⏸️ - } - > -
- {this.props.children} -
-
+ + { + log("resolve promises", this.context); + for (const res of this.suspenses) { + res(); + } + this.suspenses.clear(); + }} + > + ⏸️ + + } + > +
+ {this.props.children} +
+
+
); } } @@ -62,13 +82,33 @@ function ErrorThrower() { ); } +const PromiseMap = new Map<{}, Promise>(); +const createSuspense = (set: Set<() => void>) => { + const key = {}; + const promise = new Promise((res) => { + set.add(() => { + res(); + PromiseMap.delete(key); + }); + }); + PromiseMap.set(key, promise); + return key; +}; + function Suspenser() { - const [hasSuspense, setSuspense] = React.useState(false); - if (hasSuspense) { - throw new Promise((res) => {}); + const [suspenseKey, setSuspenseKey] = React.useState({}); + const suspense = PromiseMap.get(suspenseKey); + const suspenses = React.useContext(SuspenseContext); + + if (suspense) { + throw suspense; } + return ( - setSuspense(true)} title="trigger infinite suspense"> + setSuspenseKey(createSuspense(suspenses))} + title="trigger infinite suspense" + > 🚧 );