Repository: facebook/react
Pull request: Convert ReactDOMInput-test to createRoot

Convert ReactDOMInput-test to createRoot


Changed files:
filename: packages/react-dom/src/__tests__/ReactDOMInput-test.js
patch: @@ -25,6 +25,7 @@ describe('ReactDOMInput', () => {
   let setUntrackedValue;
   let setUntrackedChecked;
   let container;
+  let root;
 
   function dispatchEventOnNode(node, type) {
     node.dispatchEvent(new Event(type, {bubbles: true, cancelable: true}));
@@ -99,16 +100,19 @@ describe('ReactDOMInput', () => {
 
     container = document.createElement('div');
     document.body.appendChild(container);
+    root = ReactDOMClient.createRoot(container);
   });
 
   afterEach(() => {
     document.body.removeChild(container);
     jest.restoreAllMocks();
   });
 
-  it('should warn for controlled value of 0 with missing onChange', () => {
-    expect(() => {
-      ReactDOM.render(<input type="text" value={0} />, container);
+  it('should warn for controlled value of 0 with missing onChange', async () => {
+    await expect(async () => {
+      await act(() => {
+        root.render(<input type="text" value={0} />);
+      });
     }).toErrorDev(
       'Warning: You provided a `value` prop to a form ' +
         'field without an `onChange` handler. This will render a read-only ' +
@@ -117,9 +121,11 @@ describe('ReactDOMInput', () => {
     );
   });
 
-  it('should warn for controlled value of "" with missing onChange', () => {
-    expect(() => {
-      ReactDOM.render(<input type="text" value="" />, container);
+  it('should warn for controlled value of "" with missing onChange', async () => {
+    await expect(async () => {
+      await act(() => {
+        root.render(<input type="text" value="" />);
+      });
     }).toErrorDev(
       'Warning: You provided a `value` prop to a form ' +
         'field without an `onChange` handler. This will render a read-only ' +
@@ -128,9 +134,11 @@ describe('ReactDOMInput', () => {
     );
   });
 
-  it('should warn for controlled value of "0" with missing onChange', () => {
-    expect(() => {
-      ReactDOM.render(<input type="text" value="0" />, container);
+  it('should warn for controlled value of "0" with missing onChange', async () => {
+    await expect(async () => {
+      await act(() => {
+        root.render(<input type="text" value="0" />);
+      });
     }).toErrorDev(
       'Warning: You provided a `value` prop to a form ' +
         'field without an `onChange` handler. This will render a read-only ' +
@@ -139,72 +147,95 @@ describe('ReactDOMInput', () => {
     );
   });
 
-  it('should warn for controlled value of false with missing onChange', () => {
-    expect(() =>
-      ReactDOM.render(<input type="checkbox" checked={false} />, container),
-    ).toErrorDev(
+  it('should warn for controlled value of false with missing onChange', async () => {
+    await expect(async () => {
+      await act(() => {
+        root.render(<input type="checkbox" checked={false} />);
+      });
+    }).toErrorDev(
       'Warning: You provided a `checked` prop to a form field without an `onChange` handler.',
     );
   });
 
-  it('should warn with checked and no onChange handler with readOnly specified', () => {
-    ReactDOM.render(
-      <input type="checkbox" checked={false} readOnly={true} />,
-      container,
-    );
-    ReactDOM.unmountComponentAtNode(container);
+  it('should warn with checked and no onChange handler with readOnly specified', async () => {
+    await act(() => {
+      root.render(<input type="checkbox" checked={false} readOnly={true} />);
+    });
+    root.unmount();
+    root = ReactDOMClient.createRoot(container);
 
-    expect(() =>
-      ReactDOM.render(
-        <input type="checkbox" checked={false} readOnly={false} />,
-        container,
-      ),
-    ).toErrorDev(
+    await expect(async () => {
+      await act(() => {
+        root.render(<input type="checkbox" checked={false} readOnly={false} />);
+      });
+    }).toErrorDev(
       'Warning: You provided a `checked` prop to a form field without an `onChange` handler. ' +
         'This will render a read-only field. If the field should be mutable use `defaultChecked`. ' +
         'Otherwise, set either `onChange` or `readOnly`.',
     );
   });
 
-  it('should not warn about missing onChange in uncontrolled inputs', () => {
-    ReactDOM.render(<input />, container);
-    ReactDOM.unmountComponentAtNode(container);
-    ReactDOM.render(<input value={undefined} />, container);
-    ReactDOM.unmountComponentAtNode(container);
-    ReactDOM.render(<input type="text" />, container);
-    ReactDOM.unmountComponentAtNode(container);
-    ReactDOM.render(<input type="text" value={undefined} />, container);
-    ReactDOM.unmountComponentAtNode(container);
-    ReactDOM.render(<input type="checkbox" />, container);
-    ReactDOM.unmountComponentAtNode(container);
-    ReactDOM.render(<input type="checkbox" checked={undefined} />, container);
+  it('should not warn about missing onChange in uncontrolled inputs', async () => {
+    await act(() => {
+      root.render(<input />);
+    });
+    root.unmount();
+    root = ReactDOMClient.createRoot(container);
+    await act(() => {
+      root.render(<input value={undefined} />);
+    });
+    root.unmount();
+    root = ReactDOMClient.createRoot(container);
+    await act(() => {
+      root.render(<input type="text" />);
+    });
+    root.unmount();
+    root = ReactDOMClient.createRoot(container);
+    await act(() => {
+      root.render(<input type="text" value={undefined} />);
+    });
+    root.unmount();
+    root = ReactDOMClient.createRoot(container);
+    await act(() => {
+      root.render(<input type="checkbox" />);
+    });
+    root.unmount();
+    root = ReactDOMClient.createRoot(container);
+    await act(() => {
+      root.render(<input type="checkbox" checked={undefined} />);
+    });
   });
 
-  it('should not warn with value and onInput handler', () => {
-    ReactDOM.render(<input value="..." onInput={() => {}} />, container);
+  it('should not warn with value and onInput handler', async () => {
+    await act(() => {
+      root.render(<input value="..." onInput={() => {}} />);
+    });
   });
 
-  it('should properly control a value even if no event listener exists', () => {
-    let node;
-
-    expect(() => {
-      node = ReactDOM.render(<input type="text" value="lion" />, container);
+  it('should properly control a value even if no event listener exists', async () => {
+    await expect(async () => {
+      await act(() => {
+        root.render(<input type="text" value="lion" />);
+      });
     }).toErrorDev(
       'Warning: You provided a `value` prop to a form field without an `onChange` handler.',
     );
+    const node = container.firstChild;
     expect(isValueDirty(node)).toBe(true);
 
     setUntrackedValue.call(node, 'giraffe');
 
     // This must use the native event dispatching. If we simulate, we will
     // bypass the lazy event attachment system so we won't actually test this.
-    dispatchEventOnNode(node, 'input');
+    await act(() => {
+      dispatchEventOnNode(node, 'input');
+    });
 
     expect(node.value).toBe('lion');
     expect(isValueDirty(node)).toBe(true);
   });
 
-  it('should control a value in reentrant events', () => {
+  it('should control a value in reentrant events', async () => {
     class ControlledInputs extends React.Component {
       state = {value: 'lion'};
       a = null;
@@ -240,23 +271,35 @@ describe('ReactDOMInput', () => {
       }
     }
 
-    const instance = ReactDOM.render(<ControlledInputs />, container);
+    const ref = React.createRef();
+    await act(() => {
+      root.render(<ControlledInputs ref={ref} />);
+    });
+    const instance = ref.current;
 
     // Focus the field so we can later blur it.
     // Don't remove unless you've verified the fix in #8240 is still covered.
-    instance.a.focus();
+    await act(() => {
+      instance.a.focus();
+    });
     setUntrackedValue.call(instance.a, 'giraffe');
     // This must use the native event dispatching. If we simulate, we will
     // bypass the lazy event attachment system so we won't actually test this.
-    dispatchEventOnNode(instance.a, 'input');
-    dispatchEventOnNode(instance.a, 'blur');
-    dispatchEventOnNode(instance.a, 'focusout');
+    await act(() => {
+      dispatchEventOnNode(instance.a, 'input');
+    });
+    await act(() => {
+      dispatchEventOnNode(instance.a, 'blur');
+    });
+    await act(() => {
+      dispatchEventOnNode(instance.a, 'focusout');
+    });
 
     expect(instance.a.value).toBe('giraffe');
     expect(instance.switchedFocus).toBe(true);
   });
 
-  it('should control values in reentrant events with different targets', () => {
+  it('should control values in reentrant events with different targets', async () => {
     class ControlledInputs extends React.Component {
       state = {value: 'lion'};
       a = null;
@@ -288,21 +331,29 @@ describe('ReactDOMInput', () => {
       }
     }
 
-    const instance = ReactDOM.render(<ControlledInputs />, container);
+    const ref = React.createRef();
+    await act(() => {
+      root.render(<ControlledInputs ref={ref} />);
+    });
+    const instance = ref.current;
 
     setUntrackedValue.call(instance.a, 'giraffe');
     // This must use the native event dispatching. If we simulate, we will
     // bypass the lazy event attachment system so we won't actually test this.
-    dispatchEventOnNode(instance.a, 'input');
+    await act(() => {
+      dispatchEventOnNode(instance.a, 'input');
+    });
 
     expect(instance.a.value).toBe('lion');
     expect(instance.b.checked).toBe(true);
   });
 
   describe('switching text inputs between numeric and string numbers', () => {
-    it('does change the number 2 to "2.0" with no change handler', () => {
-      const stub = <input type="text" value={2} onChange={jest.fn()} />;
-      const node = ReactDOM.render(stub, container);
+    it('does change the number 2 to "2.0" with no change handler', async () => {
+      await act(() => {
+        root.render(<input type="text" value={2} onChange={jest.fn()} />);
+      });
+      const node = container.firstChild;
 
       setUntrackedValue.call(node, '2.0');
       dispatchEventOnNode(node, 'input');
@@ -315,9 +366,11 @@ describe('ReactDOMInput', () => {
       }
     });
 
-    it('does change the string "2" to "2.0" with no change handler', () => {
-      const stub = <input type="text" value={'2'} onChange={jest.fn()} />;
-      const node = ReactDOM.render(stub, container);
+    it('does change the string "2" to "2.0" with no change handler', async () => {
+      await act(() => {
+        root.render(<input type="text" value={'2'} onChange={jest.fn()} />);
+      });
+      const node = container.firstChild;
 
       setUntrackedValue.call(node, '2.0');
       dispatchEventOnNode(node, 'input');
@@ -330,7 +383,7 @@ describe('ReactDOMInput', () => {
       }
     });
 
-    it('changes the number 2 to "2.0" using a change handler', () => {
+    it('changes the number 2 to "2.0" using a change handler', async () => {
       class Stub extends React.Component {
         state = {
           value: 2,
@@ -345,8 +398,10 @@ describe('ReactDOMInput', () => {
         }
       }
 
-      const stub = ReactDOM.render(<Stub />, container);
-      const node = ReactDOM.findDOMNode(stub);
+      await act(() => {
+        root.render(<Stub />);
+      });
+      const node = container.firstChild;
 
       setUntrackedValue.call(node, '2.0');
       dispatchEventOnNode(node, 'input');
@@ -360,7 +415,7 @@ describe('ReactDOMInput', () => {
     });
   });
 
-  it('does change the string ".98" to "0.98" with no change handler', () => {
+  it('does change the string ".98" to "0.98" with no change handler', async () => {
     class Stub extends React.Component {
       state = {
         value: '.98',
@@ -370,20 +425,24 @@ describe('ReactDOMInput', () => {
       }
     }
 
-    let stub;
-    expect(() => {
-      stub = ReactDOM.render(<Stub />, container);
+    const ref = React.createRef();
+    await expect(async () => {
+      await act(() => {
+        root.render(<Stub ref={ref} />);
+      });
     }).toErrorDev(
       'You provided a `value` prop to a form field ' +
         'without an `onChange` handler.',
     );
-    const node = ReactDOM.findDOMNode(stub);
-    stub.setState({value: '0.98'});
+    const node = container.firstChild;
+    await act(() => {
+      ref.current.setState({value: '0.98'});
+    });
 
     expect(node.value).toEqual('0.98');
   });
 
-  it('performs a state change from "" to 0', () => {
+  it('performs a state change from "" to 0', async () => {
     class Stub extends React.Component {
       state = {
         value: '',
@@ -393,40 +452,43 @@ describe('ReactDOMInput', () => {
       }
     }
 
-    const stub = ReactDOM.render(<Stub />, container);
-    const node = ReactDOM.findDOMNode(stub);
-    stub.setState({value: 0});
+    const ref = React.createRef();
+    await act(() => {
+      root.render(<Stub ref={ref} />);
+    });
+    const node = container.firstChild;
+    await act(() => {
+      ref.current.setState({value: 0});
+    });
 
     expect(node.value).toEqual('0');
   });
 
-  it('updates the value on radio buttons from "" to 0', function () {
-    ReactDOM.render(
-      <input type="radio" value="" onChange={function () {}} />,
-      container,
-    );
-    ReactDOM.render(
-      <input type="radio" value={0} onChange={function () {}} />,
-      container,
-    );
+  it('updates the value on radio buttons from "" to 0', async () => {
+    await act(() => {
+      root.render(<input type="radio" value="" onChange={function () {}} />);
+    });
+    await act(() => {
+      root.render(<input type="radio" value={0} onChange={function () {}} />);
+    });
     expect(container.firstChild.value).toBe('0');
     expect(container.firstChild.getAttribute('value')).toBe('0');
   });
 
-  it('updates the value on checkboxes from "" to 0', function () {
-    ReactDOM.render(
-      <input type="checkbox" value="" onChange={function () {}} />,
-      container,
-    );
-    ReactDOM.render(
-      <input type="checkbox" value={0} onChange={function () {}} />,
-      container,
-    );
+  it('updates the value on checkboxes from "" to 0', async () => {
+    await act(() => {
+      root.render(<input type="checkbox" value="" onChange={function () {}} />);
+    });
+    await act(() => {
+      root.render(
+        <input type="checkbox" value={0} onChange={function () {}} />,
+      );
+    });
     expect(container.firstChild.value).toBe('0');
     expect(container.firstChild.getAttribute('value')).toBe('0');
   });
 
-  it('distinguishes precision for extra zeroes in string number values', () => {
+  it('distinguishes precision for extra zeroes in string number values', async () => {
     class Stub extends React.Component {
       state = {
         value: '3.0000',
@@ -436,37 +498,45 @@ describe('ReactDOMInput', () => {
       }
     }
 
-    let stub;
-
-    expect(() => {
-      stub = ReactDOM.render(<Stub />, container);
+    const ref = React.createRef();
+    await expect(async () => {
+      await act(() => {
+        root.render(<Stub ref={ref} />);
+      });
     }).toErrorDev(
       'You provided a `value` prop to a form field ' +
         'without an `onChange` handler.',
     );
-    const node = ReactDOM.findDOMNode(stub);
-    stub.setState({value: '3'});
+    const node = container.firstChild;
+    await act(() => {
+      ref.current.setState({value: '3'});
+    });
 
     expect(node.value).toEqual('3');
   });
 
-  it('should display `defaultValue` of number 0', () => {
-    const stub = <input type="text" defaultValue={0} />;
-    const node = ReactDOM.render(stub, container);
+  it('should display `defaultValue` of number 0', async () => {
+    await act(() => {
+      root.render(<input type="text" defaultValue={0} />);
+    });
+    const node = container.firstChild;
 
     expect(node.getAttribute('value')).toBe('0');
     expect(node.value).toBe('0');
   });
 
-  it('only assigns defaultValue if it changes', () => {
+  it('only assigns defaultValue if it changes', async () => {
     class Test extends React.Component {
       render() {
         return <input defaultValue="0" />;
       }
     }
 
-    const component = ReactDOM.render(<Test />, container);
-    const node = ReactDOM.findDOMNode(component);
+    const ref = React.createRef();
+    await act(() => {
+      root.render(<Test ref={ref} />);
+    });
+    const node = container.firstChild;
 
     Object.defineProperty(node, 'defaultValue', {
       get() {
@@ -479,28 +549,36 @@ describe('ReactDOMInput', () => {
       },
     });
 
-    component.forceUpdate();
+    await act(() => {
+      ref.current.forceUpdate();
+    });
   });
 
-  it('should display "true" for `defaultValue` of `true`', () => {
+  it('should display "true" for `defaultValue` of `true`', async () => {
     const stub = <input type="text" defaultValue={true} />;
-    const node = ReactDOM.render(stub, container);
+    await act(() => {
+      root.render(stub);
+    });
+    const node = container.firstChild;
 
     expect(node.value).toBe('true');
   });
 
-  it('should display "false" for `defaultValue` of `false`', () => {
+  it('should display "false" for `defaultValue` of `false`', async () => {
     const stub = <input type="text" defaultValue={false} />;
-    const node = ReactDOM.render(stub, container);
+    await act(() => {
+      root.render(stub);
+    });
+    const node = container.firstChild;
 
     expect(node.value).toBe('false');
   });
 
-  it('should update `defaultValue` for uncontrolled input', () => {
-    const node = ReactDOM.render(
-      <input type="text" defaultValue="0" />,
-      container,
-    );
+  it('should update `defaultValue` for uncontrolled input', async () => {
+    await act(() => {
+      root.render(<input type="text" defaultValue="0" />);
+    });
+    const node = container.firstChild;
 
     expect(node.value).toBe('0');
     expect(node.defaultValue).toBe('0');
@@ -510,7 +588,9 @@ describe('ReactDOMInput', () => {
       expect(isValueDirty(node)).toBe(true);
     }
 
-    ReactDOM.render(<input type="text" defaultValue="1" />, container);
+    await act(() => {
+      root.render(<input type="text" defaultValue="1" />);
+    });
 
     if (disableInputAttributeSyncing) {
       expect(node.value).toBe('1');
@@ -523,16 +603,18 @@ describe('ReactDOMInput', () => {
     }
   });
 
-  it('should update `defaultValue` for uncontrolled date/time input', () => {
-    const node = ReactDOM.render(
-      <input type="date" defaultValue="1980-01-01" />,
-      container,
-    );
+  it('should update `defaultValue` for uncontrolled date/time input', async () => {
+    await act(() => {
+      root.render(<input type="date" defaultValue="1980-01-01" />);
+    });
+    const node = container.firstChild;
 
     expect(node.value).toBe('1980-01-01');
     expect(node.defaultValue).toBe('1980-01-01');
 
-    ReactDOM.render(<input type="date" defaultValue="2000-01-01" />, container);
+    await act(() => {
+      root.render(<input type="date" defaultValue="2000-01-01" />);
+    });
 
     if (disableInputAttributeSyncing) {
       expect(node.value).toBe('2000-01-01');
@@ -542,19 +624,23 @@ describe('ReactDOMInput', () => {
       expect(node.defaultValue).toBe('2000-01-01');
     }
 
-    ReactDOM.render(<input type="date" />, container);
+    await act(() => {
+      root.render(<input type="date" />);
+    });
   });
 
-  it('should take `defaultValue` when changing to uncontrolled input', () => {
-    const node = ReactDOM.render(
-      <input type="text" value="0" readOnly={true} />,
-      container,
-    );
+  it('should take `defaultValue` when changing to uncontrolled input', async () => {
+    await act(() => {
+      root.render(<input type="text" value="0" readOnly={true} />);
+    });
+    const node = container.firstChild;
     expect(node.value).toBe('0');
     expect(isValueDirty(node)).toBe(true);
-    expect(() =>
-      ReactDOM.render(<input type="text" defaultValue="1" />, container),
-    ).toErrorDev(
+    await expect(async () => {
+      await act(() => {
+        root.render(<input type="text" defaultValue="1" />);
+      });
+    }).toErrorDev(
       'A component is changing a controlled input to be uncontrolled.',
     );
     expect(node.value).toBe('0');
@@ -580,8 +666,11 @@ describe('ReactDOMInput', () => {
     expect(div.firstChild.getAttribute('defaultValue')).toBe(null);
   });
 
-  it('should render name attribute if it is supplied', () => {
-    const node = ReactDOM.render(<input type="text" name="name" />, container);
+  it('should render name attribute if it is supplied', async () => {
+    await act(() => {
+      root.render(<input type="text" name="name" />);
+    });
+    const node = container.firstChild;
     expect(node.name).toBe('name');
     expect(container.firstChild.getAttribute('name')).toBe('name');
   });
@@ -594,8 +683,10 @@ describe('ReactDOMInput', () => {
     expect(div.firstChild.getAttribute('name')).toBe('name');
   });
 
-  it('should not render name attribute if it is not supplied', () => {
-    ReactDOM.render(<input type="text" />, container);
+  it('should not render name attribute if it is not supplied', async () => {
+    await act(() => {
+      root.render(<input type="text" />);
+    });
     expect(container.firstChild.getAttribute('name')).toBe(null);
   });
 
@@ -607,15 +698,18 @@ describe('ReactDOMInput', () => {
     expect(div.firstChild.getAttribute('name')).toBe(null);
   });
 
-  it('should display "foobar" for `defaultValue` of `objToString`', () => {
+  it('should display "foobar" for `defaultValue` of `objToString`', async () => {
     const objToString = {
       toString: function () {
         return 'foobar';
       },
     };
 
     const stub = <input type="text" defaultValue={objToString} />;
-    const node = ReactDOM.render(stub, container);
+    await act(() => {
+      root.render(stub);
+    });
+    const node = container.firstChild;
 
     expect(node.value).toBe('foobar');
   });
@@ -631,10 +725,12 @@ describe('ReactDOMInput', () => {
         return '2020-01-01';
       }
     }
+    const legacyContainer = document.createElement('div');
+    document.body.appendChild(legacyContainer);
     const test = () =>
       ReactDOM.render(
         <input defaultValue={new TemporalLike()} type="date" />,
-        container,
+        legacyContainer,
       );
     expect(() =>
       expect(test).toThrowError(new TypeError('prod message')),
@@ -644,7 +740,7 @@ describe('ReactDOMInput', () => {
     );
   });
 
-  it('should throw for text inputs if `defaultValue` is an object where valueOf() throws', () => {
+  it('should throw for text inputs if `defaultValue` is an object where valueOf() throws', async () => {
     class TemporalLike {
       valueOf() {
         // Throwing here is the behavior of ECMAScript "Temporal" date/time API.
@@ -655,20 +751,19 @@ describe('ReactDOMInput', () => {
         return '2020-01-01';
       }
     }
-    const test = () =>
-      ReactDOM.render(
-        <input defaultValue={new TemporalLike()} type="text" />,
-        container,
+    await expect(async () => {
+      await expect(async () => {
+        await act(() => {
+          root.render(<input defaultValue={new TemporalLike()} type="text" />);
+        });
+      }).toErrorDev(
+        'Form field values (value, checked, defaultValue, or defaultChecked props) must be ' +
+          'strings, not TemporalLike. This value must be coerced to a string before using it here.',
       );
-    expect(() =>
-      expect(test).toThrowError(new TypeError('prod message')),
-    ).toErrorDev(
-      'Form field values (value, checked, defaultValue, or defaultChecked props) must be ' +
-        'strings, not TemporalLike. This value must be coerced to a string before using it here.',
-    );
+    }).rejects.toThrowError(new TypeError('prod message'));
   });
 
-  it('should throw for date inputs if `value` is an object where valueOf() throws', () => {
+  it('should throw for date inputs if `value` is an object where valueOf() throws', async () => {
     class TemporalLike {
       valueOf() {
         // Throwing here is the behavior of ECMAScript "Temporal" date/time API.
@@ -679,20 +774,25 @@ describe('ReactDOMInput', () => {
         return '2020-01-01';
       }
     }
-    const test = () =>
-      ReactDOM.render(
-        <input value={new TemporalLike()} type="date" onChange={() => {}} />,
-        container,
+    await expect(async () => {
+      await expect(async () => {
+        await act(() => {
+          root.render(
+            <input
+              value={new TemporalLike()}
+              type="date"
+              onChange={() => {}}
+            />,
+          );
+        });
+      }).toErrorDev(
+        'Form field values (value, checked, defaultValue, or defaultChecked props) must be ' +
+          'strings, not TemporalLike. This value must be coerced to a string before using it here.',
       );
-    expect(() =>
-      expect(test).toThrowError(new TypeError('prod message')),
-    ).toErrorDev(
-      'Form field values (value, checked, defaultValue, or defaultChecked props) must be ' +
-        'strings, not TemporalLike. This value must be coerced to a string before using it here.',
-    );
+    }).rejects.toThrowError(new TypeError('prod message'));
   });
 
-  it('should throw for text inputs if `value` is an object where valueOf() throws', () => {
+  it('should throw for text inputs if `value` is an object where valueOf() throws', async () => {
     class TemporalLike {
       valueOf() {
         // Throwing here is the behavior of ECMAScript "Temporal" date/time API.
@@ -703,55 +803,66 @@ describe('ReactDOMInput', () => {
         return '2020-01-01';
       }
     }
-    const test = () =>
-      ReactDOM.render(
-        <input value={new TemporalLike()} type="text" onChange={() => {}} />,
-        container,
+    await expect(async () => {
+      await expect(async () => {
+        await act(() => {
+          root.render(
+            <input
+              value={new TemporalLike()}
+              type="text"
+              onChange={() => {}}
+            />,
+          );
+        });
+      }).toErrorDev(
+        'Form field values (value, checked, defaultValue, or defaultChecked props) must be ' +
+          'strings, not TemporalLike. This value must be coerced to a string before using it here.',
       );
-    expect(() =>
-      expect(test).toThrowError(new TypeError('prod message')),
-    ).toErrorDev(
-      'Form field values (value, checked, defaultValue, or defaultChecked props) must be ' +
-        'strings, not TemporalLike. This value must be coerced to a string before using it here.',
-    );
+    }).rejects.toThrowError(new TypeError('prod message'));
   });
 
-  it('should display `value` of number 0', () => {
-    const stub = <input type="text" value={0} onChange={emptyFunction} />;
-    const node = ReactDOM.render(stub, container);
+  it('should display `value` of number 0', async () => {
+    await act(() => {
+      root.render(<input type="text" value={0} onChange={emptyFunction} />);
+    });
+    const node = container.firstChild;
 
     expect(node.value).toBe('0');
   });
 
-  it('should allow setting `value` to `true`', () => {
-    let stub = <input type="text" value="yolo" onChange={emptyFunction} />;
-    const node = ReactDOM.render(stub, container);
+  it('should allow setting `value` to `true`', async () => {
+    await act(() => {
+      root.render(<input type="text" value="yolo" onChange={emptyFunction} />);
+    });
+    const node = container.firstChild;
 
     expect(node.value).toBe('yolo');
 
-    stub = ReactDOM.render(
-      <input type="text" value={true} onChange={emptyFunction} />,
-      container,
-    );
+    await act(() => {
+      root.render(<input type="text" value={true} onChange={emptyFunction} />);
+    });
     expect(node.value).toEqual('true');
   });
 
-  it('should allow setting `value` to `false`', () => {
-    let stub = <input type="text" value="yolo" onChange={emptyFunction} />;
-    const node = ReactDOM.render(stub, container);
+  it('should allow setting `value` to `false`', async () => {
+    await act(() => {
+      root.render(<input type="text" value="yolo" onChange={emptyFunction} />);
+    });
+    const node = container.firstChild;
 
     expect(node.value).toBe('yolo');
 
-    stub = ReactDOM.render(
-      <input type="text" value={false} onChange={emptyFunction} />,
-      container,
-    );
+    await act(() => {
+      root.render(<input type="text" value={false} onChange={emptyFunction} />);
+    });
     expect(node.value).toEqual('false');
   });
 
-  it('should allow setting `value` to `objToString`', () => {
-    let stub = <input type="text" value="foo" onChange={emptyFunction} />;
-    const node = ReactDOM.render(stub, container);
+  it('should allow setting `value` to `objToString`', async () => {
+    await act(() => {
+      root.render(<input type="text" value="foo" onChange={emptyFunction} />);
+    });
+    const node = container.firstChild;
 
     expect(node.value).toBe('foo');
 
@@ -760,15 +871,18 @@ describe('ReactDOMInput', () => {
         return 'foobar';
       },
     };
-    stub = ReactDOM.render(
-      <input type="text" value={objToString} onChange={emptyFunction} />,
-      container,
-    );
+    await act(() => {
+      root.render(
+        <input type="text" value={objToString} onChange={emptyFunction} />,
+      );
+    });
     expect(node.value).toEqual('foobar');
   });
 
-  it('should not incur unnecessary DOM mutations', () => {
-    ReactDOM.render(<input value="a" onChange={() => {}} />, container);
+  it('should not incur unnecessary DOM mutations', async () => {
+    await act(() => {
+      root.render(<input value="a" onChange={() => {}} />);
+    });
 
     const node = container.firstChild;
     let nodeValue = 'a';
@@ -782,15 +896,21 @@ describe('ReactDOMInput', () => {
       }),
     });
 
-    ReactDOM.render(<input value="a" onChange={() => {}} />, container);
+    await act(() => {
+      root.render(<input value="a" onChange={() => {}} />);
+    });
     expect(nodeValueSetter).toHaveBeenCalledTimes(0);
 
-    ReactDOM.render(<input value="b" onChange={() => {}} />, container);
+    await act(() => {
+      root.render(<input value="b" onChange={() => {}} />);
+    });
     expect(nodeValueSetter).toHaveBeenCalledTimes(1);
   });
 
-  it('should not incur unnecessary DOM mutations for numeric type conversion', () => {
-    ReactDOM.render(<input value="0" onChange={() => {}} />, container);
+  it('should not incur unnecessary DOM mutations for numeric type conversion', async () => {
+    await act(() => {
+      root.render(<input value="0" onChange={() => {}} />);
+    });
 
     const node = container.firstChild;
     let nodeValue = '0';
@@ -804,12 +924,16 @@ describe('ReactDOMInput', () => {
       }),
     });
 
-    ReactDOM.render(<input value={0} onChange={() => {}} />, container);
+    await act(() => {
+      root.render(<input value={0} onChange={() => {}} />);
+    });
     expect(nodeValueSetter).toHaveBeenCalledTimes(0);
   });
 
-  it('should not incur unnecessary DOM mutations for the boolean type conversion', () => {
-    ReactDOM.render(<input value="true" onChange={() => {}} />, container);
+  it('should not incur unnecessary DOM mutations for the boolean type conversion', async () => {
+    await act(() => {
+      root.render(<input value="true" onChange={() => {}} />);
+    });
 
     const node = container.firstChild;
     let nodeValue = 'true';
@@ -823,34 +947,46 @@ describe('ReactDOMInput', () => {
       }),
     });
 
-    ReactDOM.render(<input value={true} onChange={() => {}} />, container);
+    await act(() => {
+      root.render(<input value={true} onChange={() => {}} />);
+    });
     expect(nodeValueSetter).toHaveBeenCalledTimes(0);
   });
 
-  it('should properly control a value of number `0`', () => {
-    const stub = <input type="text" value={0} onChange={emptyFunction} />;
-    const node = ReactDOM.render(stub, container);
+  it('should properly control a value of number `0`', async () => {
+    await act(() => {
+      root.render(<input type="text" value={0} onChange={emptyFunction} />);
+    });
+    const node = container.firstChild;
 
     setUntrackedValue.call(node, 'giraffe');
     dispatchEventOnNode(node, 'input');
     expect(node.value).toBe('0');
   });
 
-  it('should properly control 0.0 for a text input', () => {
-    const stub = <input type="text" value={0} onChange={emptyFunction} />;
-    const node = ReactDOM.render(stub, container);
+  it('should properly control 0.0 for a text input', async () => {
+    await act(() => {
+      root.render(<input type="text" value={0} onChange={emptyFunction} />);
+    });
+    const node = container.firstChild;
 
     setUntrackedValue.call(node, '0.0');
-    dispatchEventOnNode(node, 'input');
+    await act(() => {
+      dispatchEventOnNode(node, 'input');
+    });
     expect(node.value).toBe('0');
   });
 
-  it('should properly control 0.0 for a number input', () => {
-    const stub = <input type="number" value={0} onChange={emptyFunction} />;
-    const node = ReactDOM.render(stub, container);
+  it('should properly control 0.0 for a number input', async () => {
+    await act(() => {
+      root.render(<input type="number" value={0} onChange={emptyFunction} />);
+    });
+    const node = container.firstChild;
 
     setUntrackedValue.call(node, '0.0');
-    dispatchEventOnNode(node, 'input');
+    await act(() => {
+      dispatchEventOnNode(node, 'input');
+    });
 
     if (disableInputAttributeSyncing) {
       expect(node.value).toBe('0.0');
@@ -864,18 +1000,16 @@ describe('ReactDOMInput', () => {
     }
   });
 
-  it('should properly transition from an empty value to 0', function () {
-    ReactDOM.render(
-      <input type="text" value="" onChange={emptyFunction} />,
-      container,
-    );
+  it('should properly transition from an empty value to 0', async () => {
+    await act(() => {
+      root.render(<input type="text" value="" onChange={emptyFunction} />);
+    });
     const node = container.firstChild;
     expect(isValueDirty(node)).toBe(false);
 
-    ReactDOM.render(
-      <input type="text" value={0} onChange={emptyFunction} />,
-      container,
-    );
+    await act(() => {
+      root.render(<input type="text" value={0} onChange={emptyFunction} />);
+    });
 
     expect(node.value).toBe('0');
     expect(isValueDirty(node)).toBe(true);
@@ -887,33 +1021,29 @@ describe('ReactDOMInput', () => {
     }
   });
 
-  it('should properly transition from 0 to an empty value', function () {
-    ReactDOM.render(
-      <input type="text" value={0} onChange={emptyFunction} />,
-      container,
-    );
+  it('should properly transition from 0 to an empty value', async () => {
+    await act(() => {
+      root.render(<input type="text" value={0} onChange={emptyFunction} />);
+    });
     const node = container.firstChild;
     expect(isValueDirty(node)).toBe(true);
 
-    ReactDOM.render(
-      <input type="text" value="" onChange={emptyFunction} />,
-      container,
-    );
+    await act(() => {
+      root.render(<input type="text" value="" onChange={emptyFunction} />);
+    });
 
     expect(node.value).toBe('');
     expect(node.defaultValue).toBe('');
     expect(isValueDirty(node)).toBe(true);
   });
 
-  it('should properly transition a text input from 0 to an empty 0.0', function () {
-    ReactDOM.render(
-      <input type="text" value={0} onChange={emptyFunction} />,
-      container,
-    );
-    ReactDOM.render(
-      <input type="text" value="0.0" onChange={emptyFunction} />,
-      container,
-    );
+  it('should properly transition a text input from 0 to an empty 0.0', async () => {
+    await act(() => {
+      root.render(<input type="text" value={0} onChange={emptyFunction} />);
+    });
+    await act(() => {
+      root.render(<input type="text" value="0.0" onChange={emptyFunction} />);
+    });
 
     const node = container.firstChild;
 
@@ -925,15 +1055,13 @@ describe('ReactDOMInput', () => {
     }
   });
 
-  it('should properly transition a number input from "" to 0', function () {
-    ReactDOM.render(
-      <input type="number" value="" onChange={emptyFunction} />,
-      container,
-    );
-    ReactDOM.render(
-      <input type="number" value={0} onChange={emptyFunction} />,
-      container,
-    );
+  it('should properly transition a number input from "" to 0', async () => {
+    await act(() => {
+      root.render(<input type="number" value="" onChange={emptyFunction} />);
+    });
+    await act(() => {
+      root.render(<input type="number" value={0} onChange={emptyFunction} />);
+    });
 
     const node = container.firstChild;
 
@@ -945,15 +1073,13 @@ describe('ReactDOMInput', () => {
     }
   });
 
-  it('should properly transition a number input from "" to "0"', function () {
-    ReactDOM.render(
-      <input type="number" value="" onChange={emptyFunction} />,
-      container,
-    );
-    ReactDOM.render(
-      <input type="number" value="0" onChange={emptyFunction} />,
-      container,
-    );
+  it('should properly transition a number input from "" to "0"', async () => {
+    await act(() => {
+      root.render(<input type="number" value="" onChange={emptyFunction} />);
+    });
+    await act(() => {
+      root.render(<input type="number" value="0" onChange={emptyFunction} />);
+    });
 
     const node = container.firstChild;
 
@@ -965,31 +1091,36 @@ describe('ReactDOMInput', () => {
     }
   });
 
-  it('should have the correct target value', () => {
+  it('should have the correct target value', async () => {
     let handled = false;
     const handler = function (event) {
       expect(event.target.nodeName).toBe('INPUT');
       handled = true;
     };
-    const stub = <input type="text" value={0} onChange={handler} />;
-    const node = ReactDOM.render(stub, container);
+    await act(() => {
+      root.render(<input type="text" value={0} onChange={handler} />);
+    });
+    const node = container.firstChild;
 
     setUntrackedValue.call(node, 'giraffe');
 
-    dispatchEventOnNode(node, 'input');
+    await act(() => {
+      dispatchEventOnNode(node, 'input');
+    });
 
     expect(handled).toBe(true);
   });
 
-  it('should restore uncontrolled inputs to last defaultValue upon reset', () => {
+  it('should restore uncontrolled inputs to last defaultValue upon reset', async () => {
     const inputRef = React.createRef();
-    ReactDOM.render(
-      <form>
-        <input defaultValue="default1" ref={inputRef} />
-        <input type="reset" />
-      </form>,
-      container,
-    );
+    await act(() => {
+      root.render(
+        <form>
+          <input defaultValue="default1" ref={inputRef} />
+          <input type="reset" />
+        </form>,
+      );
+    });
     expect(inputRef.current.value).toBe('default1');
     if (disableInputAttributeSyncing) {
       expect(isValueDirty(inputRef.current)).toBe(false);
@@ -1002,13 +1133,14 @@ describe('ReactDOMInput', () => {
     expect(inputRef.current.value).toBe('changed');
     expect(isValueDirty(inputRef.current)).toBe(true);
 
-    ReactDOM.render(
-      <form>
-        <input defaultValue="default2" ref={inputRef} />
-        <input type="reset" />
-      </form>,
-      container,
-    );
+    await act(() => {
+      root.render(
+        <form>
+          <input defaultValue="default2" ref={inputRef} />
+          <input type="reset" />
+        </form>,
+      );
+    });
     expect(inputRef.current.value).toBe('changed');
     expect(isValueDirty(inputRef.current)).toBe(true);
 
@@ -1020,9 +1152,11 @@ describe('ReactDOMInput', () => {
     expect(isValueDirty(inputRef.current)).toBe(false);
   });
 
-  it('should not set a value for submit buttons unnecessarily', () => {
+  it('should not set a value for submit buttons unnecessarily', async () => {
     const stub = <input type="submit" />;
-    ReactDOM.render(stub, container);
+    await act(() => {
+      root.render(stub);
+    });
     const node = container.firstChild;
 
     // The value shouldn't be '', or else the button will have no text; it
@@ -1032,133 +1166,163 @@ describe('ReactDOMInput', () => {
     expect(node.hasAttribute('value')).toBe(false);
   });
 
-  it('should remove the value attribute on submit inputs when value is updated to undefined', () => {
+  it('should remove the value attribute on submit inputs when value is updated to undefined', async () => {
     const stub = <input type="submit" value="foo" onChange={emptyFunction} />;
-    ReactDOM.render(stub, container);
+    await act(() => {
+      root.render(stub);
+    });
 
     // Not really relevant to this particular test, but changing to undefined
     // should nonetheless trigger a warning
-    expect(() =>
-      ReactDOM.render(
-        <input type="submit" value={undefined} onChange={emptyFunction} />,
-        container,
-      ),
-    ).toErrorDev(
+    await expect(async () => {
+      await act(() => {
+        root.render(
+          <input type="submit" value={undefined} onChange={emptyFunction} />,
+        );
+      });
+    }).toErrorDev(
       'A component is changing a controlled input to be uncontrolled.',
     );
 
     const node = container.firstChild;
     expect(node.getAttribute('value')).toBe(null);
   });
 
-  it('should remove the value attribute on reset inputs when value is updated to undefined', () => {
+  it('should remove the value attribute on reset inputs when value is updated to undefined', async () => {
     const stub = <input type="reset" value="foo" onChange={emptyFunction} />;
-    ReactDOM.render(stub, container);
+    await act(() => {
+      root.render(stub);
+    });
 
     // Not really relevant to this particular test, but changing to undefined
     // should nonetheless trigger a warning
-    expect(() =>
-      ReactDOM.render(
-        <input type="reset" value={undefined} onChange={emptyFunction} />,
-        container,
-      ),
-    ).toErrorDev(
+    await expect(async () => {
+      await act(() => {
+        root.render(
+          <input type="reset" value={undefined} onChange={emptyFunction} />,
+        );
+      });
+    }).toErrorDev(
       'A component is changing a controlled input to be uncontrolled.',
     );
 
     const node = container.firstChild;
     expect(node.getAttribute('value')).toBe(null);
   });
 
-  it('should set a value on a submit input', () => {
+  it('should set a value on a submit input', async () => {
     const stub = <input type="submit" value="banana" />;
-    ReactDOM.render(stub, container);
+    await act(() => {
+      root.render(stub);
+    });
     const node = container.firstChild;
 
     expect(node.getAttribute('value')).toBe('banana');
   });
 
-  it('should not set an undefined value on a submit input', () => {
+  it('should not set an undefined value on a submit input', async () => {
     const stub = <input type="submit" value={undefined} />;
-    ReactDOM.render(stub, container);
+    await act(() => {
+      root.render(stub);
+    });
     const node = container.firstChild;
 
     // Note: it shouldn't be an empty string
     // because that would erase the "submit" label.
     expect(node.getAttribute('value')).toBe(null);
 
-    ReactDOM.render(stub, container);
+    await act(() => {
+      root.render(stub);
+    });
     expect(node.getAttribute('value')).toBe(null);
   });
 
-  it('should not set an undefined value on a reset input', () => {
+  it('should not set an undefined value on a reset input', async () => {
     const stub = <input type="reset" value={undefined} />;
-    ReactDOM.render(stub, container);
+    await act(() => {
+      root.render(stub);
+    });
     const node = container.firstChild;
 
     // Note: it shouldn't be an empty string
     // because that would erase the "reset" label.
     expect(node.getAttribute('value')).toBe(null);
 
-    ReactDOM.render(stub, container);
+    await act(() => {
+      root.render(stub);
+    });
     expect(node.getAttribute('value')).toBe(null);
   });
 
-  it('should not set a null value on a submit input', () => {
+  it('should not set a null value on a submit input', async () => {
     const stub = <input type="submit" value={null} />;
-    expect(() => {
-      ReactDOM.render(stub, container);
+    await expect(async () => {
+      await act(() => {
+        root.render(stub);
+      });
     }).toErrorDev('`value` prop on `input` should not be null');
     const node = container.firstChild;
 
     // Note: it shouldn't be an empty string
     // because that would erase the "submit" label.
     expect(node.getAttribute('value')).toBe(null);
 
-    ReactDOM.render(stub, container);
+    await act(() => {
+      root.render(stub);
+    });
     expect(node.getAttribute('value')).toBe(null);
   });
 
-  it('should not set a null value on a reset input', () => {
+  it('should not set a null value on a reset input', async () => {
     const stub = <input type="reset" value={null} />;
-    expect(() => {
-      ReactDOM.render(stub, container);
+    await expect(async () => {
+      await act(() => {
+        root.render(stub);
+      });
     }).toErrorDev('`value` prop on `input` should not be null');
     const node = container.firstChild;
 
     // Note: it shouldn't be an empty string
     // because that would erase the "reset" label.
     expect(node.getAttribute('value')).toBe(null);
 
-    ReactDOM.render(stub, container);
+    await act(() => {
+      root.render(stub);
+    });
     expect(node.getAttribute('value')).toBe(null);
   });
 
-  it('should set a value on a reset input', () => {
+  it('should set a value on a reset input', async () => {
     const stub = <input type="reset" value="banana" />;
-    ReactDOM.render(stub, container);
+    await act(() => {
+      root.render(stub);
+    });
     const node = container.firstChild;
 
     expect(node.getAttribute('value')).toBe('banana');
   });
 
-  it('should set an empty string value on a submit input', () => {
+  it('should set an empty string value on a submit input', async () => {
     const stub = <input type="submit" value="" />;
-    ReactDOM.render(stub, container);
+    await act(() => {
+      root.render(stub);
+    });
     const node = container.firstChild;
 
     expect(node.getAttribute('value')).toBe('');
   });
 
-  it('should set an empty string value on a reset input', () => {
+  it('should set an empty string value on a reset input', async () => {
     const stub = <input type="reset" value="" />;
-    ReactDOM.render(stub, container);
+    await act(() => {
+      root.render(stub);
+    });
     const node = container.firstChild;
 
     expect(node.getAttribute('value')).toBe('');
   });
 
-  it('should control radio buttons', () => {
+  it('should control radio buttons', async () => {
     class RadioGroup extends React.Component {
       aRef = React.createRef();
       bRef = React.createRef();
@@ -1199,7 +1363,11 @@ describe('ReactDOMInput', () => {
       }
     }
 
-    const stub = ReactDOM.render(<RadioGroup />, container);
+    const ref = React.createRef();
+    await act(() => {
+      root.render(<RadioGroup ref={ref} />);
+    });
+    const stub = ref.current;
     const aNode = stub.aRef.current;
     const bNode = stub.bRef.current;
     const cNode = stub.cRef.current;
@@ -1240,7 +1408,9 @@ describe('ReactDOMInput', () => {
     }
 
     // Now let's run the actual ReactDOMInput change event handler
-    dispatchEventOnNode(bNode, 'click');
+    await act(() => {
+      dispatchEventOnNode(bNode, 'click');
+    });
 
     // The original state should have been restored
     expect(aNode.checked).toBe(true);
@@ -1288,6 +1458,10 @@ describe('ReactDOMInput', () => {
       );
     }
     const html = ReactDOMServer.renderToString(<App />);
+    // Create a fresh container, not attached a root yet
+    container.remove();
+    container = document.createElement('div');
+    document.body.appendChild(container);
     container.innerHTML = html;
     const [a, b, c] = container.querySelectorAll('input');
     expect(a.checked).toBe(true);
@@ -1376,6 +1550,10 @@ describe('ReactDOMInput', () => {
       );
     }
     const html = ReactDOMServer.renderToString(<App />);
+    // Create a fresh container, not attached a root yet
+    container.remove();
+    container = document.createElement('div');
+    document.body.appendChild(container);
     container.innerHTML = html;
     const [a, b, c] = container.querySelectorAll('input');
     expect(a.checked).toBe(true);
@@ -1421,7 +1599,7 @@ describe('ReactDOMInput', () => {
     assertInputTrackingIsCurrent(container);
   });
 
-  it('should check the correct radio when the selected name moves', () => {
+  it('should check the correct radio when the selected name moves', async () => {
     class App extends React.Component {
       state = {
         updated: false,
@@ -1452,40 +1630,48 @@ describe('ReactDOMInput', () => {
       }
     }
 
-    const stub = ReactDOM.render(<App />, container);
-    const buttonNode = ReactDOM.findDOMNode(stub).childNodes[0];
-    const firstRadioNode = ReactDOM.findDOMNode(stub).childNodes[1];
+    await act(() => {
+      root.render(<App />);
+    });
+    const node = container.firstChild;
+    const buttonNode = node.childNodes[0];
+    const firstRadioNode = node.childNodes[1];
     expect(isCheckedDirty(firstRadioNode)).toBe(true);
     expect(firstRadioNode.checked).toBe(false);
     assertInputTrackingIsCurrent(container);
-    dispatchEventOnNode(buttonNode, 'click');
+    await act(() => {
+      dispatchEventOnNode(buttonNode, 'click');
+    });
     expect(firstRadioNode.checked).toBe(true);
     assertInputTrackingIsCurrent(container);
-    dispatchEventOnNode(buttonNode, 'click');
+    await act(() => {
+      dispatchEventOnNode(buttonNode, 'click');
+    });
     expect(firstRadioNode.checked).toBe(false);
     assertInputTrackingIsCurrent(container);
   });
 
-  it("shouldn't get tricked by changing radio names, part 2", () => {
-    ReactDOM.render(
-      <div>
-        <input
-          type="radio"
-          name="a"
-          value="1"
-          checked={true}
-          onChange={() => {}}
-        />
-        <input
-          type="radio"
-          name="a"
-          value="2"
-          checked={false}
-          onChange={() => {}}
-        />
-      </div>,
-      container,
-    );
+  it("shouldn't get tricked by changing radio names, part 2", async () => {
+    await act(() => {
+      root.render(
+        <div>
+          <input
+            type="radio"
+            name="a"
+            value="1"
+            checked={true}
+            onChange={() => {}}
+          />
+          <input
+            type="radio"
+            name="a"
+            value="2"
+            checked={false}
+            onChange={() => {}}
+          />
+        </div>,
+      );
+    });
     const one = container.querySelector('input[name="a"][value="1"]');
     const two = container.querySelector('input[name="a"][value="2"]');
     expect(one.checked).toBe(true);
@@ -1494,25 +1680,26 @@ describe('ReactDOMInput', () => {
     expect(isCheckedDirty(two)).toBe(true);
     assertInputTrackingIsCurrent(container);
 
-    ReactDOM.render(
-      <div>
-        <input
-          type="radio"
-          name="a"
-          value="1"
-          checked={true}
-          onChange={() => {}}
-        />
-        <input
-          type="radio"
-          name="b"
-          value="2"
-          checked={true}
-          onChange={() => {}}
-        />
-      </div>,
-      container,
-    );
+    await act(() => {
+      root.render(
+        <div>
+          <input
+            type="radio"
+            name="a"
+            value="1"
+            checked={true}
+            onChange={() => {}}
+          />
+          <input
+            type="radio"
+            name="b"
+            value="2"
+            checked={true}
+            onChange={() => {}}
+          />
+        </div>,
+      );
+    });
     expect(one.checked).toBe(true);
     expect(two.checked).toBe(true);
     expect(isCheckedDirty(one)).toBe(true);
@@ -1521,6 +1708,9 @@ describe('ReactDOMInput', () => {
   });
 
   it('should control radio buttons if the tree updates during render', () => {
+    container.remove();
+    container = document.createElement('div');
+    document.body.appendChild(container);
     const sharedParent = container;
     const container1 = document.createElement('div');
     const container2 = document.createElement('div');
@@ -1600,7 +1790,7 @@ describe('ReactDOMInput', () => {
     assertInputTrackingIsCurrent(container);
   });
 
-  it('should control radio buttons if the tree updates during render (case 2; #26876)', () => {
+  it('should control radio buttons if the tree updates during render (case 2; #26876)', async () => {
     let thunk = null;
     function App() {
       const [disabled, setDisabled] = React.useState(false);
@@ -1634,7 +1824,9 @@ describe('ReactDOMInput', () => {
         </>
       );
     }
-    ReactDOM.render(<App />, container);
+    await act(() => {
+      root.render(<App />);
+    });
     const [one, two] = container.querySelectorAll('input');
     expect(one.checked).toBe(true);
     expect(two.checked).toBe(false);
@@ -1644,15 +1836,17 @@ describe('ReactDOMInput', () => {
 
     // Click two
     setUntrackedChecked.call(two, true);
-    dispatchEventOnNode(two, 'click');
+    await act(() => {
+      dispatchEventOnNode(two, 'click');
+    });
     expect(one.checked).toBe(true);
     expect(two.checked).toBe(false);
     expect(isCheckedDirty(one)).toBe(true);
     expect(isCheckedDirty(two)).toBe(true);
     assertInputTrackingIsCurrent(container);
 
     // After a delay...
-    ReactDOM.unstable_batchedUpdates(thunk);
+    await act(thunk);
     expect(one.checked).toBe(false);
     expect(two.checked).toBe(true);
     expect(isCheckedDirty(one)).toBe(true);
@@ -1661,35 +1855,36 @@ describe('ReactDOMInput', () => {
 
     // Click back to one
     setUntrackedChecked.call(one, true);
-    dispatchEventOnNode(one, 'click');
+    await act(() => {
+      dispatchEventOnNode(one, 'click');
+    });
     expect(one.checked).toBe(false);
     expect(two.checked).toBe(true);
     expect(isCheckedDirty(one)).toBe(true);
     expect(isCheckedDirty(two)).toBe(true);
     assertInputTrackingIsCurrent(container);
 
     // After a delay...
-    ReactDOM.unstable_batchedUpdates(thunk);
+    await act(thunk);
     expect(one.checked).toBe(true);
     expect(two.checked).toBe(false);
     expect(isCheckedDirty(one)).toBe(true);
     expect(isCheckedDirty(two)).toBe(true);
     assertInputTrackingIsCurrent(container);
   });
 
-  it('should warn with value and no onChange handler and readOnly specified', () => {
-    ReactDOM.render(
-      <input type="text" value="zoink" readOnly={true} />,
-      container,
-    );
-    ReactDOM.unmountComponentAtNode(container);
+  it('should warn with value and no onChange handler and readOnly specified', async () => {
+    await act(() => {
+      root.render(<input type="text" value="zoink" readOnly={true} />);
+    });
+    root.unmount();
+    root = ReactDOMClient.createRoot(container);
 
-    expect(() =>
-      ReactDOM.render(
-        <input type="text" value="zoink" readOnly={false} />,
-        container,
-      ),
-    ).toErrorDev(
+    await expect(async () => {
+      await act(() => {
+        root.render(<input type="text" value="zoink" readOnly={false} />);
+      });
+    }).toErrorDev(
       'Warning: You provided a `value` prop to a form ' +
         'field without an `onChange` handler. This will render a read-only ' +
         'field. If the field should be mutable use `defaultValue`. ' +
@@ -1698,27 +1893,36 @@ describe('ReactDOMInput', () => {
     );
   });
 
-  it('should have a this value of undefined if bind is not used', () => {
+  it('should have a this value of undefined if bind is not used', async () => {
     expect.assertions(1);
     const unboundInputOnChange = function () {
       expect(this).toBe(undefined);
     };
 
     const stub = <input type="text" onChange={unboundInputOnChange} />;
-    const node = ReactDOM.render(stub, container);
+    await act(() => {
+      root.render(stub);
+    });
+    const node = container.firstChild;
 
     setUntrackedValue.call(node, 'giraffe');
-    dispatchEventOnNode(node, 'input');
+    await act(() => {
+      dispatchEventOnNode(node, 'input');
+    });
   });
 
-  it('should update defaultValue to empty string', () => {
-    ReactDOM.render(<input type="text" defaultValue={'foo'} />, container);
+  it('should update defaultValue to empty string', async () => {
+    await act(() => {
+      root.render(<input type="text" defaultValue={'foo'} />);
+    });
     if (disableInputAttributeSyncing) {
       expect(isValueDirty(container.firstChild)).toBe(false);
     } else {
       expect(isValueDirty(container.firstChild)).toBe(true);
     }
-    ReactDOM.render(<input type="text" defaultValue={''} />, container);
+    await act(() => {
+      root.render(<input type="text" defaultValue={''} />);
+    });
     expect(container.firstChild.defaultValue).toBe('');
     if (disableInputAttributeSyncing) {
       expect(isValueDirty(container.firstChild)).toBe(false);
@@ -1727,79 +1931,97 @@ describe('ReactDOMInput', () => {
     }
   });
 
-  it('should warn if value is null', () => {
-    expect(() =>
-      ReactDOM.render(<input type="text" value={null} />, container),
-    ).toErrorDev(
+  it('should warn if value is null', async () => {
+    await expect(async () => {
+      await act(() => {
+        root.render(<input type="text" value={null} />);
+      });
+    }).toErrorDev(
       '`value` prop on `input` should not be null. ' +
         'Consider using an empty string to clear the component or `undefined` ' +
         'for uncontrolled components.',
     );
-    ReactDOM.unmountComponentAtNode(container);
+    root.unmount();
 
-    ReactDOM.render(<input type="text" value={null} />, container);
+    root = ReactDOMClient.createRoot(container);
+    await act(() => {
+      root.render(<input type="text" value={null} />);
+    });
   });
 
-  it('should warn if checked and defaultChecked props are specified', () => {
-    expect(() =>
-      ReactDOM.render(
-        <input
-          type="radio"
-          checked={true}
-          defaultChecked={true}
-          readOnly={true}
-        />,
-        container,
-      ),
-    ).toErrorDev(
+  it('should warn if checked and defaultChecked props are specified', async () => {
+    await expect(async () => {
+      await act(() => {
+        root.render(
+          <input
+            type="radio"
+            checked={true}
+            defaultChecked={true}
+            readOnly={true}
+          />,
+        );
+      });
+    }).toErrorDev(
       'A component contains an input of type radio with both checked and defaultChecked props. ' +
         'Input elements must be either controlled or uncontrolled ' +
         '(specify either the checked prop, or the defaultChecked prop, but not ' +
         'both). Decide between using a controlled or uncontrolled input ' +
         'element and remove one of these props. More info: ' +
         'https://reactjs.org/link/controlled-components',
     );
-    ReactDOM.unmountComponentAtNode(container);
-
-    ReactDOM.render(
-      <input
-        type="radio"
-        checked={true}
-        defaultChecked={true}
-        readOnly={true}
-      />,
-      container,
-    );
+    root.unmount();
+
+    root = ReactDOMClient.createRoot(container);
+    await act(() => {
+      root.render(
+        <input
+          type="radio"
+          checked={true}
+          defaultChecked={true}
+          readOnly={true}
+        />,
+      );
+    });
   });
 
-  it('should warn if value and defaultValue props are specified', () => {
-    expect(() =>
-      ReactDOM.render(
-        <input type="text" value="foo" defaultValue="bar" readOnly={true} />,
-        container,
-      ),
-    ).toErrorDev(
+  it('should warn if value and defaultValue props are specified', async () => {
+    await expect(async () => {
+      await act(() => {
+        root.render(
+          <input type="text" value="foo" defaultValue="bar" readOnly={true} />,
+        );
+      });
+    }).toErrorDev(
       'A component contains an input of type text with both value and defaultValue props. ' +
         'Input elements must be either controlled or uncontrolled ' +
         '(specify either the value prop, or the defaultValue prop, but not ' +
         'both). Decide between using a controlled or uncontrolled input ' +
         'element and remove one of these props. More info: ' +
         'https://reactjs.org/link/controlled-components',
     );
-    ReactDOM.unmountComponentAtNode(container);
+    await (() => {
+      root.unmount();
+    });
 
-    ReactDOM.render(
-      <input type="text" value="foo" defaultValue="bar" readOnly={true} />,
-      container,
-    );
+    await act(() => {
+      root.render(
+        <input type="text" value="foo" defaultValue="bar" readOnly={true} />,
+      );
+    });
   });
 
-  it('should warn if controlled input switches to uncontrolled (value is undefined)', () => {
+  it('should warn if controlled input switches to uncontrolled (value is undefined)', async () => {
     const stub = (
       <input type="text" value="controlled" onChange={emptyFunction} />
     );
-    ReactDOM.render(stub, container);
-    expect(() => ReactDOM.render(<input type="text" />, container)).toErrorDev(
+    await act(() => {
+      root.render(stub);
+    });
+    await expect(async () => {
+      await act(() => {
+        root.render(<input type="text" />);
+      });
+    }).toErrorDev(
       'Warning: A component is changing a controlled input to be uncontrolled. ' +
         'This is likely caused by the value changing from a defined to ' +
         'undefined, which should not happen. ' +
@@ -1809,14 +2031,18 @@ describe('ReactDOMInput', () => {
     );
   });
 
-  it('should warn if controlled input switches to uncontrolled (value is null)', () => {
+  it('should warn if controlled input switches to uncontrolled (value is null)', async () => {
     const stub = (
       <input type="text" value="controlled" onChange={emptyFunction} />
     );
-    ReactDOM.render(stub, container);
-    expect(() =>
-      ReactDOM.render(<input type="text" value={null} />, container),
-    ).toErrorDev([
+    await act(() => {
+      root.render(stub);
+    });
+    await expect(async () => {
+      await act(() => {
+        root.render(<input type="text" value={null} />);
+      });
+    }).toErrorDev([
       '`value` prop on `input` should not be null. ' +
         'Consider using an empty string to clear the component or `undefined` for uncontrolled components',
       'Warning: A component is changing a controlled input to be uncontrolled. ' +
@@ -1828,17 +2054,18 @@ describe('ReactDOMInput', () => {
     ]);
   });
 
-  it('should warn if controlled input switches to uncontrolled with defaultValue', () => {
+  it('should warn if controlled input switches to uncontrolled with defaultValue', async () => {
     const stub = (
       <input type="text" value="controlled" onChange={emptyFunction} />
     );
-    ReactDOM.render(stub, container);
-    expect(() =>
-      ReactDOM.render(
-        <input type="text" defaultValue="uncontrolled" />,
-        container,
-      ),
-    ).toErrorDev(
+    await act(() => {
+      root.render(stub);
+    });
+    await expect(async () => {
+      await act(() => {
+        root.render(<input type="text" defaultValue="uncontrolled" />);
+      });
+    }).toErrorDev(
       'Warning: A component is changing a controlled input to be uncontrolled. ' +
         'This is likely caused by the value changing from a defined to ' +
         'undefined, which should not happen. ' +
@@ -1848,12 +2075,16 @@ describe('ReactDOMInput', () => {
     );
   });
 
-  it('should warn if uncontrolled input (value is undefined) switches to controlled', () => {
+  it('should warn if uncontrolled input (value is undefined) switches to controlled', async () => {
     const stub = <input type="text" />;
-    ReactDOM.render(stub, container);
-    expect(() =>
-      ReactDOM.render(<input type="text" value="controlled" />, container),
-    ).toErrorDev(
+    await act(() => {
+      root.render(stub);
+    });
+    await expect(async () => {
+      await act(() => {
+        root.render(<input type="text" value="controlled" />);
+      });
+    }).toErrorDev(
       'Warning: A component is changing an uncontrolled input to be controlled. ' +
         'This is likely caused by the value changing from undefined to ' +
         'a defined value, which should not happen. ' +
@@ -1863,15 +2094,21 @@ describe('ReactDOMInput', () => {
     );
   });
 
-  it('should warn if uncontrolled input (value is null) switches to controlled', () => {
+  it('should warn if uncontrolled input (value is null) switches to controlled', async () => {
     const stub = <input type="text" value={null} />;
-    expect(() => ReactDOM.render(stub, container)).toErrorDev(
+    await expect(async () => {
+      await act(() => {
+        root.render(stub);
+      });
+    }).toErrorDev(
       '`value` prop on `input` should not be null. ' +
         'Consider using an empty string to clear the component or `undefined` for uncontrolled components.',
     );
-    expect(() =>
-      ReactDOM.render(<input type="text" value="controlled" />, container),
-    ).toErrorDev(
+    await expect(async () => {
+      await act(() => {
+        root.render(<input type="text" value="controlled" />);
+      });
+    }).toErrorDev(
       'Warning: A component is changing an uncontrolled input to be controlled. ' +
         'This is likely caused by the value changing from undefined to ' +
         'a defined value, which should not happen. ' +
@@ -1881,14 +2118,18 @@ describe('ReactDOMInput', () => {
     );
   });
 
-  it('should warn if controlled checkbox switches to uncontrolled (checked is undefined)', () => {
+  it('should warn if controlled checkbox switches to uncontrolled (checked is undefined)', async () => {
     const stub = (
       <input type="checkbox" checked={true} onChange={emptyFunction} />
     );
-    ReactDOM.render(stub, container);
-    expect(() =>
-      ReactDOM.render(<input type="checkbox" />, container),
-    ).toErrorDev(
+    await act(() => {
+      root.render(stub);
+    });
+    await expect(async () => {
+      await act(() => {
+        root.render(<input type="checkbox" />);
+      });
+    }).toErrorDev(
       'Warning: A component is changing a controlled input to be uncontrolled. ' +
         'This is likely caused by the value changing from a defined to ' +
         'undefined, which should not happen. ' +
@@ -1898,14 +2139,18 @@ describe('ReactDOMInput', () => {
     );
   });
 
-  it('should warn if controlled checkbox switches to uncontrolled (checked is null)', () => {
+  it('should warn if controlled checkbox switches to uncontrolled (checked is null)', async () => {
     const stub = (
       <input type="checkbox" checked={true} onChange={emptyFunction} />
     );
-    ReactDOM.render(stub, container);
-    expect(() =>
-      ReactDOM.render(<input type="checkbox" checked={null} />, container),
-    ).toErrorDev(
+    await act(() => {
+      root.render(stub);
+    });
+    await expect(async () => {
+      await act(() => {
+        root.render(<input type="checkbox" checked={null} />);
+      });
+    }).toErrorDev(
       'Warning: A component is changing a controlled input to be uncontrolled. ' +
         'This is likely caused by the value changing from a defined to ' +
         'undefined, which should not happen. ' +
@@ -1915,17 +2160,18 @@ describe('ReactDOMInput', () => {
     );
   });
 
-  it('should warn if controlled checkbox switches to uncontrolled with defaultChecked', () => {
+  it('should warn if controlled checkbox switches to uncontrolled with defaultChecked', async () => {
     const stub = (
       <input type="checkbox" checked={true} onChange={emptyFunction} />
     );
-    ReactDOM.render(stub, container);
-    expect(() =>
-      ReactDOM.render(
-        <input type="checkbox" defaultChecked={true} />,
-        container,
-      ),
-    ).toErrorDev(
+    await act(() => {
+      root.render(stub);
+    });
+    await expect(async () => {
+      await act(() => {
+        root.render(<input type="checkbox" defaultChecked={true} />);
+      });
+    }).toErrorDev(
       'Warning: A component is changing a controlled input to be uncontrolled. ' +
         'This is likely caused by the value changing from a defined to ' +
         'undefined, which should not happen. ' +
@@ -1935,12 +2181,16 @@ describe('ReactDOMInput', () => {
     );
   });
 
-  it('should warn if uncontrolled checkbox (checked is undefined) switches to controlled', () => {
+  it('should warn if uncontrolled checkbox (checked is undefined) switches to controlled', async () => {
     const stub = <input type="checkbox" />;
-    ReactDOM.render(stub, container);
-    expect(() =>
-      ReactDOM.render(<input type="checkbox" checked={true} />, container),
-    ).toErrorDev(
+    await act(() => {
+      root.render(stub);
+    });
+    await expect(async () => {
+      await act(() => {
+        root.render(<input type="checkbox" checked={true} />);
+      });
+    }).toErrorDev(
       'Warning: A component is changing an uncontrolled input to be controlled. ' +
         'This is likely caused by the value changing from undefined to ' +
         'a defined value, which should not happen. ' +
@@ -1950,12 +2200,16 @@ describe('ReactDOMInput', () => {
     );
   });
 
-  it('should warn if uncontrolled checkbox (checked is null) switches to controlled', () => {
+  it('should warn if uncontrolled checkbox (checked is null) switches to controlled', async () => {
     const stub = <input type="checkbox" checked={null} />;
-    ReactDOM.render(stub, container);
-    expect(() =>
-      ReactDOM.render(<input type="checkbox" checked={true} />, container),
-    ).toErrorDev(
+    await act(() => {
+      root.render(stub);
+    });
+    await expect(async () => {
+      await act(() => {
+        root.render(<input type="checkbox" checked={true} />);
+      });
+    }).toErrorDev(
       'Warning: A component is changing an uncontrolled input to be controlled. ' +
         'This is likely caused by the value changing from undefined to ' +
         'a defined value, which should not happen. ' +
@@ -1965,10 +2219,16 @@ describe('ReactDOMInput', () => {
     );
   });
 
-  it('should warn if controlled radio switches to uncontrolled (checked is undefined)', () => {
+  it('should warn if controlled radio switches to uncontrolled (checked is undefined)', async () => {
     const stub = <input type="radio" checked={true} onChange={emptyFunction} />;
-    ReactDOM.render(stub, container);
-    expect(() => ReactDOM.render(<input type="radio" />, container)).toErrorDev(
+    await act(() => {
+      root.render(stub);
+    });
+    await expect(async () => {
+      await act(() => {
+        root.render(<input type="radio" />);
+      });
+    }).toErrorDev(
       'Warning: A component is changing a controlled input to be uncontrolled. ' +
         'This is likely caused by the value changing from a defined to ' +
         'undefined, which should not happen. ' +
@@ -1978,12 +2238,16 @@ describe('ReactDOMInput', () => {
     );
   });
 
-  it('should warn if controlled radio switches to uncontrolled (checked is null)', () => {
+  it('should warn if controlled radio switches to uncontrolled (checked is null)', async () => {
     const stub = <input type="radio" checked={true} onChange={emptyFunction} />;
-    ReactDOM.render(stub, container);
-    expect(() =>
-      ReactDOM.render(<input type="radio" checked={null} />, container),
-    ).toErrorDev(
+    await act(() => {
+      root.render(stub);
+    });
+    await expect(async () => {
+      await act(() => {
+        root.render(<input type="radio" checked={null} />);
+      });
+    }).toErrorDev(
       'Warning: A component is changing a controlled input to be uncontrolled. ' +
         'This is likely caused by the value changing from a defined to ' +
         'undefined, which should not happen. ' +
@@ -1993,12 +2257,16 @@ describe('ReactDOMInput', () => {
     );
   });
 
-  it('should warn if controlled radio switches to uncontrolled with defaultChecked', () => {
+  it('should warn if controlled radio switches to uncontrolled with defaultChecked', async () => {
     const stub = <input type="radio" checked={true} onChange={emptyFunction} />;
-    ReactDOM.render(stub, container);
-    expect(() =>
-      ReactDOM.render(<input type="radio" defaultChecked={true} />, container),
-    ).toErrorDev(
+    await act(() => {
+      root.render(stub);
+    });
+    await expect(async () => {
+      await act(() => {
+        root.render(<input type="radio" defaultChecked={true} />);
+      });
+    }).toErrorDev(
       'Warning: A component is changing a controlled input to be uncontrolled. ' +
         'This is likely caused by the value changing from a defined to ' +
         'undefined, which should not happen. ' +
@@ -2008,12 +2276,16 @@ describe('ReactDOMInput', () => {
     );
   });
 
-  it('should warn if uncontrolled radio (checked is undefined) switches to controlled', () => {
+  it('should warn if uncontrolled radio (checked is undefined) switches to controlled', async () => {
     const stub = <input type="radio" />;
-    ReactDOM.render(stub, container);
-    expect(() =>
-      ReactDOM.render(<input type="radio" checked={true} />, container),
-    ).toErrorDev(
+    await act(() => {
+      root.render(stub);
+    });
+    await expect(async () => {
+      await act(() => {
+        root.render(<input type="radio" checked={true} />);
+      });
+    }).toErrorDev(
       'Warning: A component is changing an uncontrolled input to be controlled. ' +
         'This is likely caused by the value changing from undefined to ' +
         'a defined value, which should not happen. ' +
@@ -2023,12 +2295,16 @@ describe('ReactDOMInput', () => {
     );
   });
 
-  it('should warn if uncontrolled radio (checked is null) switches to controlled', () => {
+  it('should warn if uncontrolled radio (checked is null) switches to controlled', async () => {
     const stub = <input type="radio" checked={null} />;
-    ReactDOM.render(stub, container);
-    expect(() =>
-      ReactDOM.render(<input type="radio" checked={true} />, container),
-    ).toErrorDev(
+    await act(() => {
+      root.render(stub);
+    });
+    await expect(async () => {
+      await act(() => {
+        root.render(<input type="radio" checked={true} />);
+      });
+    }).toErrorDev(
       'Warning: A component is changing an uncontrolled input to be controlled. ' +
         'This is likely caused by the value changing from undefined to ' +
         'a defined value, which should not happen. ' +
@@ -2038,54 +2314,61 @@ describe('ReactDOMInput', () => {
     );
   });
 
-  it('should not warn if radio value changes but never becomes controlled', () => {
-    ReactDOM.render(<input type="radio" value="value" />, container);
-    ReactDOM.render(<input type="radio" />, container);
-    ReactDOM.render(
-      <input type="radio" value="value" defaultChecked={true} />,
-      container,
-    );
-    ReactDOM.render(
-      <input type="radio" value="value" onChange={() => null} />,
-      container,
-    );
-    ReactDOM.render(<input type="radio" />, container);
+  it('should not warn if radio value changes but never becomes controlled', async () => {
+    await act(() => {
+      root.render(<input type="radio" value="value" />);
+    });
+    await act(() => {
+      root.render(<input type="radio" />);
+    });
+    await act(() => {
+      root.render(<input type="radio" value="value" defaultChecked={true} />);
+    });
+    await act(() => {
+      root.render(<input type="radio" value="value" onChange={() => null} />);
+    });
+    await act(() => {
+      root.render(<input type="radio" />);
+    });
   });
 
-  it('should not warn if radio value changes but never becomes uncontrolled', () => {
-    ReactDOM.render(
-      <input type="radio" checked={false} onChange={() => null} />,
-      container,
-    );
+  it('should not warn if radio value changes but never becomes uncontrolled', async () => {
+    await act(() => {
+      root.render(<input type="radio" checked={false} onChange={() => null} />);
+    });
     const input = container.querySelector('input');
     expect(isCheckedDirty(input)).toBe(true);
-    ReactDOM.render(
-      <input
-        type="radio"
-        value="value"
-        defaultChecked={true}
-        checked={false}
-        onChange={() => null}
-      />,
-      container,
-    );
+    await act(() => {
+      root.render(
+        <input
+          type="radio"
+          value="value"
+          defaultChecked={true}
+          checked={false}
+          onChange={() => null}
+        />,
+      );
+    });
     expect(isCheckedDirty(input)).toBe(true);
     assertInputTrackingIsCurrent(container);
   });
 
-  it('should warn if radio checked false changes to become uncontrolled', () => {
-    ReactDOM.render(
-      <input
-        type="radio"
-        value="value"
-        checked={false}
-        onChange={() => null}
-      />,
-      container,
-    );
-    expect(() =>
-      ReactDOM.render(<input type="radio" value="value" />, container),
-    ).toErrorDev(
+  it('should warn if radio checked false changes to become uncontrolled', async () => {
+    await act(() => {
+      root.render(
+        <input
+          type="radio"
+          value="value"
+          checked={false}
+          onChange={() => null}
+        />,
+      );
+    });
+    await expect(async () => {
+      await act(() => {
+        root.render(<input type="radio" value="value" />);
+      });
+    }).toErrorDev(
       'Warning: A component is changing a controlled input to be uncontrolled. ' +
         'This is likely caused by the value changing from a defined to ' +
         'undefined, which should not happen. ' +
@@ -2095,7 +2378,7 @@ describe('ReactDOMInput', () => {
     );
   });
 
-  it('sets type, step, min, max before value always', () => {
+  it('sets type, step, min, max before value always', async () => {
     const log = [];
     const originalCreateElement = document.createElement;
     spyOnDevAndProd(document, 'createElement').mockImplementation(
@@ -2133,17 +2416,18 @@ describe('ReactDOMInput', () => {
       },
     );
 
-    ReactDOM.render(
-      <input
-        value="0"
-        onChange={() => {}}
-        type="range"
-        min="0"
-        max="100"
-        step="1"
-      />,
-      container,
-    );
+    await act(() => {
+      root.render(
+        <input
+          value="0"
+          onChange={() => {}}
+          type="range"
+          min="0"
+          max="100"
+          step="1"
+        />,
+      );
+    });
 
     expect(log).toEqual([
       'set attribute min',
@@ -2154,12 +2438,14 @@ describe('ReactDOMInput', () => {
     ]);
   });
 
-  it('sets value properly with type coming later in props', () => {
-    const input = ReactDOM.render(<input value="hi" type="radio" />, container);
-    expect(input.value).toBe('hi');
+  it('sets value properly with type coming later in props', async () => {
+    await act(() => {
+      root.render(<input value="hi" type="radio" />);
+    });
+    expect(container.firstChild.value).toBe('hi');
   });
 
-  it('does not raise a validation warning when it switches types', () => {
+  it('does not raise a validation warning when it switches types', async () => {
     class Input extends React.Component {
       state = {type: 'number', value: 1000};
 
@@ -2169,16 +2455,21 @@ describe('ReactDOMInput', () => {
       }
     }
 
-    const input = ReactDOM.render(<Input />, container);
-    const node = ReactDOM.findDOMNode(input);
+    const ref = React.createRef();
+    await act(() => {
+      root.render(<Input ref={ref} />);
+    });
+    const node = container.firstChild;
 
     // If the value is set before the type, a validation warning will raise and
     // the value will not be assigned.
-    input.setState({type: 'text', value: 'Test'});
+    await act(() => {
+      ref.current.setState({type: 'text', value: 'Test'});
+    });
     expect(node.value).toEqual('Test');
   });
 
-  it('resets value of date/time input to fix bugs in iOS Safari', () => {
+  it('resets value of date/time input to fix bugs in iOS Safari', async () => {
     function strify(x) {
       return JSON.stringify(x, null, 2);
     }
@@ -2250,7 +2541,9 @@ describe('ReactDOMInput', () => {
       },
     );
 
-    ReactDOM.render(<input type="date" defaultValue="1980-01-01" />, container);
+    await act(() => {
+      root.render(<input type="date" defaultValue="1980-01-01" />);
+    });
 
     if (disableInputAttributeSyncing) {
       expect(log).toEqual([
@@ -2289,14 +2582,18 @@ describe('ReactDOMInput', () => {
       };
     }
 
-    it('always sets the attribute when values change on text inputs', function () {
+    it('always sets the attribute when values change on text inputs', async () => {
       const Input = getTestInput();
-      const stub = ReactDOM.render(<Input type="text" />, container);
-      const node = ReactDOM.findDOMNode(stub);
+      await act(() => {
+        root.render(<Input type="text" />);
+      });
+      const node = container.firstChild;
       expect(isValueDirty(node)).toBe(false);
 
       setUntrackedValue.call(node, '2');
-      dispatchEventOnNode(node, 'input');
+      await act(() => {
+        dispatchEventOnNode(node, 'input');
+      });
 
       expect(isValueDirty(node)).toBe(true);
       if (disableInputAttributeSyncing) {
@@ -2306,13 +2603,12 @@ describe('ReactDOMInput', () => {
       }
     });
 
-    it('does not set the value attribute on number inputs if focused', () => {
+    it('does not set the value attribute on number inputs if focused', async () => {
       const Input = getTestInput();
-      const stub = ReactDOM.render(
-        <Input type="number" value="1" />,
-        container,
-      );
-      const node = ReactDOM.findDOMNode(stub);
+      await act(() => {
+        root.render(<Input type="number" value="1" />);
+      });
+      const node = container.firstChild;
       expect(isValueDirty(node)).toBe(true);
 
       node.focus();
@@ -2328,13 +2624,12 @@ describe('ReactDOMInput', () => {
       }
     });
 
-    it('sets the value attribute on number inputs on blur', () => {
+    it('sets the value attribute on number inputs on blur', async () => {
       const Input = getTestInput();
-      const stub = ReactDOM.render(
-        <Input type="number" value="1" />,
-        container,
-      );
-      const node = ReactDOM.findDOMNode(stub);
+      await act(() => {
+        root.render(<Input type="number" value="1" />);
+      });
+      const node = container.firstChild;
       expect(isValueDirty(node)).toBe(true);
 
       node.focus();
@@ -2352,11 +2647,11 @@ describe('ReactDOMInput', () => {
       }
     });
 
-    it('an uncontrolled number input will not update the value attribute on blur', () => {
-      const node = ReactDOM.render(
-        <input type="number" defaultValue="1" />,
-        container,
-      );
+    it('an uncontrolled number input will not update the value attribute on blur', async () => {
+      await act(() => {
+        root.render(<input type="number" defaultValue="1" />);
+      });
+      const node = container.firstChild;
       if (disableInputAttributeSyncing) {
         expect(isValueDirty(node)).toBe(false);
       } else {
@@ -2372,11 +2667,11 @@ describe('ReactDOMInput', () => {
       expect(node.getAttribute('value')).toBe('1');
     });
 
-    it('an uncontrolled text input will not update the value attribute on blur', () => {
-      const node = ReactDOM.render(
-        <input type="text" defaultValue="1" />,
-        container,
-      );
+    it('an uncontrolled text input will not update the value attribute on blur', async () => {
+      await act(() => {
+        root.render(<input type="text" defaultValue="1" />);
+      });
+      const node = container.firstChild;
       if (disableInputAttributeSyncing) {
         expect(isValueDirty(node)).toBe(false);
       } else {
@@ -2396,7 +2691,7 @@ describe('ReactDOMInput', () => {
   describe('setting a controlled input to undefined', () => {
     let input;
 
-    function renderInputWithStringThenWithUndefined() {
+    async function renderInputWithStringThenWithUndefined() {
       let setValueToUndefined;
       class Input extends React.Component {
         constructor() {
@@ -2414,15 +2709,19 @@ describe('ReactDOMInput', () => {
         }
       }
 
-      const stub = ReactDOM.render(<Input />, container);
-      input = ReactDOM.findDOMNode(stub);
+      await act(() => {
+        root.render(<Input />);
+      });
+      input = container.firstChild;
       setUntrackedValue.call(input, 'latest');
       dispatchEventOnNode(input, 'input');
-      setValueToUndefined();
+      await act(() => {
+        setValueToUndefined();
+      });
     }
 
-    it('reverts the value attribute to the initial value', () => {
-      expect(renderInputWithStringThenWithUndefined).toErrorDev(
+    it('reverts the value attribute to the initial value', async () => {
+      await expect(renderInputWithStringThenWithUndefined).toErrorDev(
         'A component is changing a controlled input to be uncontrolled.',
       );
       if (disableInputAttributeSyncing) {
@@ -2432,8 +2731,8 @@ describe('ReactDOMInput', () => {
       }
     });
 
-    it('preserves the value property', () => {
-      expect(renderInputWithStringThenWithUndefined).toErrorDev(
+    it('preserves the value property', async () => {
+      await expect(renderInputWithStringThenWithUndefined).toErrorDev(
         'A component is changing a controlled input to be uncontrolled.',
       );
       expect(input.value).toBe('latest');
@@ -2443,7 +2742,7 @@ describe('ReactDOMInput', () => {
   describe('setting a controlled input to null', () => {
     let input;
 
-    function renderInputWithStringThenWithNull() {
+    async function renderInputWithStringThenWithNull() {
       let setValueToNull;
       class Input extends React.Component {
         constructor() {
@@ -2461,15 +2760,19 @@ describe('ReactDOMInput', () => {
         }
       }
 
-      const stub = ReactDOM.render(<Input />, container);
-      input = ReactDOM.findDOMNode(stub);
+      await act(() => {
+        root.render(<Input />);
+      });
+      input = container.firstChild;
       setUntrackedValue.call(input, 'latest');
       dispatchEventOnNode(input, 'input');
-      setValueToNull();
+      await act(() => {
+        setValueToNull();
+      });
     }
 
-    it('reverts the value attribute to the initial value', () => {
-      expect(renderInputWithStringThenWithNull).toErrorDev([
+    it('reverts the value attribute to the initial value', async () => {
+      await expect(renderInputWithStringThenWithNull).toErrorDev([
         '`value` prop on `input` should not be null. ' +
           'Consider using an empty string to clear the component ' +
           'or `undefined` for uncontrolled components.',
@@ -2482,8 +2785,8 @@ describe('ReactDOMInput', () => {
       }
     });
 
-    it('preserves the value property', () => {
-      expect(renderInputWithStringThenWithNull).toErrorDev([
+    it('preserves the value property', async () => {
+      await expect(renderInputWithStringThenWithNull).toErrorDev([
         '`value` prop on `input` should not be null. ' +
           'Consider using an empty string to clear the component ' +
           'or `undefined` for uncontrolled components.',
@@ -2494,13 +2797,12 @@ describe('ReactDOMInput', () => {
   });
 
   describe('When given a Symbol value', function () {
-    it('treats initial Symbol value as an empty string', function () {
-      expect(() =>
-        ReactDOM.render(
-          <input value={Symbol('foobar')} onChange={() => {}} />,
-          container,
-        ),
-      ).toErrorDev('Invalid value for prop `value`');
+    it('treats initial Symbol value as an empty string', async () => {
+      await expect(async () => {
+        await act(() => {
+          root.render(<input value={Symbol('foobar')} onChange={() => {}} />);
+        });
+      }).toErrorDev('Invalid value for prop `value`');
       const node = container.firstChild;
 
       expect(node.value).toBe('');
@@ -2511,14 +2813,15 @@ describe('ReactDOMInput', () => {
       }
     });
 
-    it('treats updated Symbol value as an empty string', function () {
-      ReactDOM.render(<input value="foo" onChange={() => {}} />, container);
-      expect(() =>
-        ReactDOM.render(
-          <input value={Symbol('foobar')} onChange={() => {}} />,
-          container,
-        ),
-      ).toErrorDev('Invalid value for prop `value`');
+    it('treats updated Symbol value as an empty string', async () => {
+      await act(() => {
+        root.render(<input value="foo" onChange={() => {}} />);
+      });
+      await expect(async () => {
+        await act(() => {
+          root.render(<input value={Symbol('foobar')} onChange={() => {}} />);
+        });
+      }).toErrorDev('Invalid value for prop `value`');
       const node = container.firstChild;
 
       expect(node.value).toBe('');
@@ -2529,18 +2832,24 @@ describe('ReactDOMInput', () => {
       }
     });
 
-    it('treats initial Symbol defaultValue as an empty string', function () {
-      ReactDOM.render(<input defaultValue={Symbol('foobar')} />, container);
+    it('treats initial Symbol defaultValue as an empty string', async () => {
+      await act(() => {
+        root.render(<input defaultValue={Symbol('foobar')} />);
+      });
       const node = container.firstChild;
 
       expect(node.value).toBe('');
       expect(node.getAttribute('value')).toBe('');
       // TODO: we should warn here.
     });
 
-    it('treats updated Symbol defaultValue as an empty string', function () {
-      ReactDOM.render(<input defaultValue="foo" />, container);
-      ReactDOM.render(<input defaultValue={Symbol('foobar')} />, container);
+    it('treats updated Symbol defaultValue as an empty string', async () => {
+      await act(() => {
+        root.render(<input defaultValue="foo" />);
+      });
+      await act(() => {
+        root.render(<input defaultValue={Symbol('foobar')} />);
+      });
       const node = container.firstChild;
 
       if (disableInputAttributeSyncing) {
@@ -2554,13 +2863,12 @@ describe('ReactDOMInput', () => {
   });
 
   describe('When given a function value', function () {
-    it('treats initial function value as an empty string', function () {
-      expect(() =>
-        ReactDOM.render(
-          <input value={() => {}} onChange={() => {}} />,
-          container,
-        ),
-      ).toErrorDev('Invalid value for prop `value`');
+    it('treats initial function value as an empty string', async () => {
+      await expect(async () => {
+        await act(() => {
+          root.render(<input value={() => {}} onChange={() => {}} />);
+        });
+      }).toErrorDev('Invalid value for prop `value`');
       const node = container.firstChild;
 
       expect(node.value).toBe('');
@@ -2571,14 +2879,15 @@ describe('ReactDOMInput', () => {
       }
     });
 
-    it('treats updated function value as an empty string', function () {
-      ReactDOM.render(<input value="foo" onChange={() => {}} />, container);
-      expect(() =>
-        ReactDOM.render(
-          <input value={() => {}} onChange={() => {}} />,
-          container,
-        ),
-      ).toErrorDev('Invalid value for prop `value`');
+    it('treats updated function value as an empty string', async () => {
+      await act(() => {
+        root.render(<input value="foo" onChange={() => {}} />);
+      });
+      await expect(async () => {
+        await act(() => {
+          root.render(<input value={() => {}} onChange={() => {}} />);
+        });
+      }).toErrorDev('Invalid value for prop `value`');
       const node = container.firstChild;
 
       expect(node.value).toBe('');
@@ -2589,18 +2898,24 @@ describe('ReactDOMInput', () => {
       }
     });
 
-    it('treats initial function defaultValue as an empty string', function () {
-      ReactDOM.render(<input defaultValue={() => {}} />, container);
+    it('treats initial function defaultValue as an empty string', async () => {
+      await act(() => {
+        root.render(<input defaultValue={() => {}} />);
+      });
       const node = container.firstChild;
 
       expect(node.value).toBe('');
       expect(node.getAttribute('value')).toBe('');
       // TODO: we should warn here.
     });
 
-    it('treats updated function defaultValue as an empty string', function () {
-      ReactDOM.render(<input defaultValue="foo" />, container);
-      ReactDOM.render(<input defaultValue={() => {}} />, container);
+    it('treats updated function defaultValue as an empty string', async () => {
+      await act(() => {
+        root.render(<input defaultValue="foo" />);
+      });
+      await act(() => {
+        root.render(<input defaultValue={() => {}} />);
+      });
       const node = container.firstChild;
 
       if (disableInputAttributeSyncing) {
@@ -2619,65 +2934,68 @@ describe('ReactDOMInput', () => {
     // Between 16 and 16.2, we assigned a node's value to it's current
     // value in order to "dettach" it from defaultValue. This had the unfortunate
     // side-effect of assigning value="on" to radio and checkboxes
-    it('does not add "on" in absence of value on a checkbox', function () {
-      ReactDOM.render(
-        <input type="checkbox" defaultChecked={true} />,
-        container,
-      );
+    it('does not add "on" in absence of value on a checkbox', async () => {
+      await act(() => {
+        root.render(<input type="checkbox" defaultChecked={true} />);
+      });
       const node = container.firstChild;
 
       expect(node.value).toBe('on');
       expect(node.hasAttribute('value')).toBe(false);
     });
 
-    it('does not add "on" in absence of value on a radio', function () {
-      ReactDOM.render(<input type="radio" defaultChecked={true} />, container);
+    it('does not add "on" in absence of value on a radio', async () => {
+      await act(() => {
+        root.render(<input type="radio" defaultChecked={true} />);
+      });
       const node = container.firstChild;
 
       expect(node.value).toBe('on');
       expect(node.hasAttribute('value')).toBe(false);
     });
   });
 
-  it('should remove previous `defaultValue`', () => {
-    const node = ReactDOM.render(
-      <input type="text" defaultValue="0" />,
-      container,
-    );
+  it('should remove previous `defaultValue`', async () => {
+    await act(() => {
+      root.render(<input type="text" defaultValue="0" />);
+    });
+    const node = container.firstChild;
 
     expect(node.value).toBe('0');
     expect(node.defaultValue).toBe('0');
 
-    ReactDOM.render(<input type="text" />, container);
+    await act(() => {
+      root.render(<input type="text" />);
+    });
     expect(node.defaultValue).toBe('');
   });
 
-  it('should treat `defaultValue={null}` as missing', () => {
-    const node = ReactDOM.render(
-      <input type="text" defaultValue="0" />,
-      container,
-    );
+  it('should treat `defaultValue={null}` as missing', async () => {
+    await act(() => {
+      root.render(<input type="text" defaultValue="0" />);
+    });
+    const node = container.firstChild;
 
     expect(node.value).toBe('0');
     expect(node.defaultValue).toBe('0');
 
-    ReactDOM.render(<input type="text" defaultValue={null} />, container);
+    await act(() => {
+      root.render(<input type="text" defaultValue={null} />);
+    });
     expect(node.defaultValue).toBe('');
   });
 
-  it('should notice input changes when reverting back to original value', () => {
+  it('should notice input changes when reverting back to original value', async () => {
     const log = [];
     function onChange(e) {
       log.push(e.target.value);
     }
-    ReactDOM.render(
-      <input type="text" value="" onChange={onChange} />,
-      container,
-    );
-    ReactDOM.render(
-      <input type="text" value="a" onChange={onChange} />,
-      container,
-    );
+    await act(() => {
+      root.render(<input type="text" value="" onChange={onChange} />);
+    });
+    await act(() => {
+      root.render(<input type="text" value="a" onChange={onChange} />);
+    });
 
     const node = container.firstChild;
     setUntrackedValue.call(node, '');