Skip to content

Instantly share code, notes, and snippets.

@jamiebuilds
Created May 1, 2025 21:18
Show Gist options
  • Save jamiebuilds/40efe2ec78276d150ffa1a66f4b0d7ed to your computer and use it in GitHub Desktop.
Save jamiebuilds/40efe2ec78276d150ffa1a66f4b0d7ed to your computer and use it in GitHub Desktop.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>react-child-rendering-before-parent</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./index.jsx"></script>
</body>
</html>
import { useSyncExternalStore } from "react";
import { render } from "react-dom";
class CounterStore {
#counter = 0;
#listeners = new Set();
getSnapshot = () => {
return this.#counter;
};
subscribe = (listener) => {
this.#listeners.add(listener);
return () => this.#listeners.delete(listener);
};
increment = () => {
this.#counter += 1;
this.#listeners.forEach((listener) => listener());
};
}
const counterStore = new CounterStore();
function shouldChildRender(counter) {
return counter % 2 === 0;
}
function Child() {
const value = useSyncExternalStore(counterStore.subscribe, counterStore.getSnapshot);
console.log(`child render ${value}`);
if (!shouldChildRender(value)) {
throw new Error("child should not have rendered");
}
return <div>child: {value}</div>;
}
function Parent() {
const counter = useSyncExternalStore(counterStore.subscribe, counterStore.getSnapshot);
console.log(`parent render ${counter}`);
function onClick() {
// setTimeout breaks out of the automatic batching inside onClick and
// causes <Child> to be re-rendered before <Parent> (which is a bug)
// and then <Child> throws an error when shouldChildRender() returns false.
setTimeout(() => {
// Note: Wrapping this with `unstable_batchedUpdates` fixes the issue.
counterStore.increment();
}, 1);
}
return (
<>
<button onClick={onClick}>Dispatch</button>
<div>parent: {counter}</div>
{shouldChildRender(counter) && <Child />}
</>
);
}
render(<Parent />, document.getElementById("root"));
{
"private": true,
"name": "react-child-rendering-before-parent",
"version": "0.0.0",
"scripts": {
"start": "parcel index.html"
},
"dependencies": {
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"devDependencies": {
"parcel": "^2.14.4"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment