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

Bug: First render doesn't create DOM nodes before next javascript is executed in script #30886

Open
bryceosterhaus opened this issue Sep 5, 2024 · 4 comments
Labels
Status: Unconfirmed A potential issue that we haven't yet confirmed as a bug

Comments

@bryceosterhaus
Copy link

bryceosterhaus commented Sep 5, 2024

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

Link to code example

Is this just an expected result with React 18+? If you fallback and use ReactDOM.render() instead, it works as expected.

@bryceosterhaus bryceosterhaus added the Status: Unconfirmed A potential issue that we haven't yet confirmed as a bug label Sep 5, 2024
@gianlucadifrancesco
Copy link

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!";

@matvp91
Copy link

matvp91 commented Sep 5, 2024

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.

@bryceosterhaus
Copy link
Author

bryceosterhaus commented Sep 6, 2024

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.

@bryceosterhaus
Copy link
Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Status: Unconfirmed A potential issue that we haven't yet confirmed as a bug
Projects
None yet
Development

No branches or pull requests

3 participants