Bug: First render doesn't create DOM nodes before next javascript is executed in script
This may have been discussed elsewhere but I wasn't able to find anything.
With the update to using createRoot in React 18, the DOM is created asynchronously, which means any code running after root.render() cannot depend on the DOM that React is creating.
React version: 18.2.0
Steps To Reproduce
const root = ReactDOM.createRoot(document.getElementById("app"));
root.render(
React.createElement("div", { id: "reactChild" }, "Rendered By React")
);
document.getElementById("reactChild").innerHTML = "Replaced By VanillaJS"; // this errors
The current behavior
JS will error because document.getElementById("reactChild") is null and not found
The expected behavior
React will render first and then document.getElementById("reactChild") will execute find the node
Is this just an expected result with React 18+? If you fallback and use ReactDOM.render() instead, it works as expected.
Hi @bryceosterhaus!
I think this is expected in React 18+, but you can make it sync by using flushSync(() => ...).
Try this, it should work:
// REACT 18
const root = ReactDOM.createRoot(document.getElementById("app18"));
ReactDOM.flushSync(() =>
root.render(
React.createElement("div", { id: "child18" }, "THIS DOESN'T WORK")
)
);
document.getElementById("child17").innerHTML = "THIS WORKS!";
document.getElementById("child18").innerHTML = "THIS WORKS!";
You really shouldn't alter nodes created by React from the outside as it'll mismatch from the virtual dom. Any particular use case I'm missing?
Besides, flushSync forces React to render the tree in sync, which will definitely hurt performance at some point.
It isn't ideal, but in our use case it is possible that we would render part of the page with a shared React component and then a team or customer may want to write their own inline javascript that reads some data from the DOM that React created. The server then puts all these together on the same page. For example:
// Rendered by the platform
<script type="text/javascript">
const root = //...
root.render(<Button id="myButton" />);
</script>
// Added by another team/customer
<script type="text/javascript">
const button = document.getElementById('myButton');
// do something here with button...
</script>
Regardless, it still strikes me as odd that before/after React 18, this behavior is significantly different.
Thanks @gianlucadifrancesco, that does work. I still find it an odd behavior though, especially because just reading through a script like my example above, it isn't intuitive that the element won't be created in time. I know React 18+ has async rendering now, but I didn't expect my script to continue executing without the DOM being created yet for that initial render.
This issue has been automatically marked as stale. If this issue is still affecting you, please leave any comment (for example, "bump"), and we'll keep it open. We are sorry that we haven't been able to prioritize it yet. If you have any new additional information, please include it with your comment!
bump
This issue has been automatically marked as stale. If this issue is still affecting you, please leave any comment (for example, "bump"), and we'll keep it open. We are sorry that we haven't been able to prioritize it yet. If you have any new additional information, please include it with your comment!
Closing this issue after a prolonged period of inactivity. If this issue is still present in the latest release, please create a new issue with up-to-date information. Thank you!