diff --git a/fixtures/ssr/src/components/Page.js b/fixtures/ssr/src/components/Page.js index d7b4d5c813e98..1a4c6f79a3e78 100644 --- a/fixtures/ssr/src/components/Page.js +++ b/fixtures/ssr/src/components/Page.js @@ -11,10 +11,17 @@ const autofocusedInputs = [ ]; export default class Page extends Component { - state = {active: false}; + state = {active: false, value: ''}; handleClick = e => { this.setState({active: true}); }; + handleChange = e => { + this.setState({value: e.target.value}); + }; + componentDidMount() { + // Rerender on mount + this.setState({mounted: true}); + } render() { const link = ( @@ -30,6 +37,10 @@ export default class Page extends Component {

Autofocus on page load: {autofocusedInputs}

{!this.state.active ? link : 'Thanks!'}

{this.state.active &&

Autofocus on update: {autofocusedInputs}

} +

+ Controlled input:{' '} + +

); diff --git a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js index 1f7a4f915c087..7d705e059bdf8 100644 --- a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js +++ b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js @@ -99,7 +99,10 @@ import { DOCUMENT_FRAGMENT_NODE, } from './HTMLNodeType'; -import {retryIfBlockedOn} from '../events/ReactDOMEventReplaying'; +import { + flushEventReplaying, + retryIfBlockedOn, +} from '../events/ReactDOMEventReplaying'; import { enableCreateEventHandleAPI, @@ -3655,6 +3658,12 @@ export function commitHydratedSuspenseInstance( retryIfBlockedOn(suspenseInstance); } +export function flushHydrationEvents(): void { + if (enableHydrationChangeEvent) { + flushEventReplaying(); + } +} + export function shouldDeleteUnhydratedTailInstances( parentType: string, ): boolean { diff --git a/packages/react-dom-bindings/src/events/ReactDOMEventReplaying.js b/packages/react-dom-bindings/src/events/ReactDOMEventReplaying.js index d600917534cf6..2763b2fc129a1 100644 --- a/packages/react-dom-bindings/src/events/ReactDOMEventReplaying.js +++ b/packages/react-dom-bindings/src/events/ReactDOMEventReplaying.js @@ -472,12 +472,19 @@ function replayUnblockedEvents() { } } +export function flushEventReplaying(): void { + // Synchronously flush any event replaying so that it gets observed before + // any new updates are applied. + if (hasScheduledReplayAttempt) { + replayUnblockedEvents(); + } +} + export function queueChangeEvent(target: EventTarget): void { if (enableHydrationChangeEvent) { queuedChangeEventTargets.push(target); if (!hasScheduledReplayAttempt) { hasScheduledReplayAttempt = true; - scheduleCallback(NormalPriority, replayUnblockedEvents); } } } @@ -490,10 +497,12 @@ function scheduleCallbackIfUnblocked( queuedEvent.blockedOn = null; if (!hasScheduledReplayAttempt) { hasScheduledReplayAttempt = true; - // Schedule a callback to attempt replaying as many events as are - // now unblocked. This first might not actually be unblocked yet. - // We could check it early to avoid scheduling an unnecessary callback. - scheduleCallback(NormalPriority, replayUnblockedEvents); + if (!enableHydrationChangeEvent) { + // Schedule a callback to attempt replaying as many events as are + // now unblocked. This first might not actually be unblocked yet. + // We could check it early to avoid scheduling an unnecessary callback. + scheduleCallback(NormalPriority, replayUnblockedEvents); + } } } } diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationUserInteraction-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationUserInteraction-test.js index be0d4533af7e5..70109949baf10 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationUserInteraction-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationUserInteraction-test.js @@ -52,6 +52,12 @@ describe('ReactDOMServerIntegrationUserInteraction', () => { } this.setState({value: event.target.value}); } + componentDidMount() { + if (this.props.cascade) { + // Trigger a cascading render immediately upon hydration which rerenders the input. + this.setState({cascade: true}); + } + } render() { return ( { } this.setState({value: event.target.value}); } + componentDidMount() { + if (this.props.cascade) { + // Trigger a cascading render immediately upon hydration which rerenders the textarea. + this.setState({cascade: true}); + } + } render() { return (