This repository was archived by the owner on May 11, 2026. It is now read-only.
WIP Add support for newer versions of react-dom#300
Draft
lencioni wants to merge 1 commit into
Draft
Conversation
103f6eb to
3badf6f
Compare
In v19, you need to import from react-dom/client and use the createRoot API.
3badf6f to
ce70a29
Compare
Contributor
Author
|
I'm running into some issues with the tests. It seems that root.render triggers some asynchronous things that may happen later than the previous way of rendering (e.g. componentDidMount or useEffect). The way to wait for these is to wrap things in React's act function, which seems to help a bit (but also causes a bunch of warnings to be logged). I don't see a clear path forward yet, unfortunately. My hunch is that we may need to restructure things a bit in order to get this to work. |
Contributor
Author
|
I can get the tests to pass with this diff, but I don't think all of the changes I had to make to the test are what we want... diff --git a/src/browser/processor.js b/src/browser/processor.js
index 19cc933..bd2271a 100644
--- a/src/browser/processor.js
+++ b/src/browser/processor.js
@@ -41,6 +41,7 @@ async function renderExample(exampleRenderFunc, { component, variant }) {
window.happoRender(renderResult, { rootElement, component, variant });
const result = exampleRenderFunc(renderInDom);
+
if (result && typeof result.then === 'function') {
// this is a promise
await result;
@@ -142,7 +143,8 @@ export default class Processor {
const { component, fileName, variant, render } =
this.flattenedExamples[this.cursor];
const exampleRenderFunc = getRenderFunc(render);
- window.happoCleanup();
+ await window.happoCleanup();
+
try {
window.verbose(`Rendering component ${component}, variant ${variant}`);
await renderExample(exampleRenderFunc, { component, variant });
@@ -152,11 +154,13 @@ export default class Processor {
e,
);
}
+
const root =
(this.rootElementSelector &&
document.body.querySelector(this.rootElementSelector)) ||
findRoot();
const html = await this.waitForHTML(root);
+
const item = {
html,
css: '', // Can we remove this?
@@ -164,10 +168,12 @@ export default class Processor {
variant,
assetPaths: findAssetPaths(),
};
+
const { stylesheets } = render;
if (stylesheets) {
item.stylesheets = stylesheets;
}
+
return item;
}
diff --git a/src/createDynamicEntryPoint.js b/src/createDynamicEntryPoint.js
index d49b536..f602f9e 100644
--- a/src/createDynamicEntryPoint.js
+++ b/src/createDynamicEntryPoint.js
@@ -61,18 +61,29 @@ export default async function createDynamicEntryPoint({
const reactDomMajorVersion = parseInt(reactDomVersion.split('.', 1)[0], 10);
if (reactDomMajorVersion >= 18) {
const pathToReactDom = require.resolve('react-dom/client');
+ const pathToReact = require.resolve('react');
strings.push(
`
+ global.IS_REACT_ACT_ENVIRONMENT = true;
const ReactDOM = require('${pathToReactDom}');
+ const { act } = require('${pathToReact}');
let root;
window.happoRender = (reactComponent, { rootElement, component, variant }) => {
- root = ReactDOM.createRoot(rootElement);
- root.render(renderWrapper(reactComponent, { component, variant }));
+ if (!root) {
+ root = ReactDOM.createRoot(rootElement);
+ }
+
+ act(() => {
+ root.render(renderWrapper(reactComponent, { component, variant }));
+ });
};
window.happoCleanup = () => {
if (root) {
- root.unmount();
+ act(() => {
+ root.unmount();
+ });
+ root = null;
}
};
`.trim(),
diff --git a/test/integrations/examples/Foo-react-happo.js b/test/integrations/examples/Foo-react-happo.js
index f512805..e2bb9a5 100644
--- a/test/integrations/examples/Foo-react-happo.js
+++ b/test/integrations/examples/Foo-react-happo.js
@@ -1,5 +1,5 @@
import React from 'react';
-import { createPortal } from 'react-dom';
+import * as ReactDOM from 'react-dom';
import Button from './Button.ffs';
import ThemeContext from '../theme';
@@ -22,17 +22,22 @@ export const anotherVariant = () => {
const PortalComponent = ({ children }) => {
const element = document.createElement('div');
document.body.appendChild(element);
- return createPortal(children, document.body);
+ return ReactDOM.createPortal(children, element);
};
-export const portalExample = () => (
- <PortalComponent>
- {window.navigator.userAgent === 'happo-puppeteer'
- ? 'forbidden'
- : window.localStorage.getItem('foobar')}
- <button type="button">I am in a portal</button>
- </PortalComponent>
-);
+export const portalExample = (renderInDOM) => {
+ renderInDOM(
+ <PortalComponent>
+ {window.navigator.userAgent === 'happo-puppeteer'
+ ? 'forbidden'
+ : window.localStorage.getItem('foobar')}
+ <button type="button">I am in a portal</button>
+ </PortalComponent>,
+ );
+ return new Promise((resolve) => {
+ setTimeout(resolve, 10);
+ });
+};
export const innerPortal = () => (
<>
@@ -45,6 +50,7 @@ class AsyncComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
+ ready: false,
label: 'Not ready',
};
this.setLabel = this.setLabel.bind(this);
@@ -76,7 +82,9 @@ class AsyncComponent extends React.Component {
export const asyncExample = (render) => {
render(<AsyncComponent />);
- window.dispatchEvent(new CustomEvent('set-label', { detail: 'Ready' }));
+ React.act(() => {
+ window.dispatchEvent(new CustomEvent('set-label', { detail: 'Ready' }));
+ });
return new Promise((resolve) => {
setTimeout(resolve, 11);
});
@@ -92,7 +100,9 @@ export const emptyForever = () => <EmptyComponent />;
class DynamicImportExample extends React.Component {
constructor(props) {
super(props);
- this.state = {};
+ this.state = {
+ text: 'Loading...',
+ };
}
async componentDidMount() {
@@ -106,7 +116,12 @@ class DynamicImportExample extends React.Component {
}
}
-export const dynamicImportExample = () => <DynamicImportExample />;
+export const dynamicImportExample = (renderInDOM) => {
+ renderInDOM(<DynamicImportExample />);
+ return new Promise((resolve) => {
+ setTimeout(resolve, 10);
+ });
+};
export const themedExample = () => (
<ThemeContext.Consumer>
diff --git a/test/integrations/react-test.js b/test/integrations/react-test.js
index 4974e4e..2fdaa63 100644
--- a/test/integrations/react-test.js
+++ b/test/integrations/react-test.js
@@ -224,7 +224,7 @@ it('produces the right html', async () => {
{
component: 'Foo-react',
css: '',
- html: '<button type="button"></button>',
+ html: '<button type="button">Not ready</button>',
variant: 'asyncWithoutPromise',
},
{ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
In v19, you need to import from react-dom/client and use the createRoot API.