@@ -1975,9 +1981,16 @@ describe('ReactDOMFizzServer', () => {
// Hydrate the tree. Child will throw during render.
isClient = true;
- ReactDOM.hydrateRoot(container,
, {
+ onHydrationError(error) {
+ // TODO: We logged a hydration error, but the same error ends up
+ // being thrown during the fallback to client rendering, too. Maybe
+ // we should only log if the client render succeeds.
+ Scheduler.unstable_yieldValue(error.message);
+ },
+ });
- expect(Scheduler).toFlushAndYield([]);
+ expect(Scheduler).toFlushAndYield(['Oops!']);
expect(getVisibleChildren(container)).toEqual('Oops!');
},
);
@@ -2049,9 +2062,15 @@ describe('ReactDOMFizzServer', () => {
// Hydrate the tree. Child will throw during hydration, but not when it
// falls back to client rendering.
isClient = true;
- ReactDOM.hydrateRoot(container,
, {
+ onHydrationError(error) {
+ Scheduler.unstable_yieldValue(error.message);
+ },
+ });
- expect(Scheduler).toFlushAndYield([]);
+ // An error logged but instead of surfacing it to the UI, we switched
+ // to client rendering.
+ expect(Scheduler).toFlushAndYield(['Hydration error']);
expect(getVisibleChildren(container)).toEqual(
diff --git a/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js b/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js
index 1c65501f7b382..1a98b8e8f877e 100644
--- a/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js
+++ b/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js
@@ -208,9 +208,17 @@ describe('ReactDOMServerPartialHydration', () => {
// On the client we don't have all data yet but we want to start
// hydrating anyway.
suspend = true;
- ReactDOM.hydrateRoot(container,
);
+ ReactDOM.hydrateRoot(container,
, {
+ onHydrationError(error) {
+ Scheduler.unstable_yieldValue(error.message);
+ },
+ });
if (gate(flags => flags.enableClientRenderFallbackOnHydrationMismatch)) {
- Scheduler.unstable_flushAll();
+ // Hydration error is logged
+ expect(Scheduler).toFlushAndYield([
+ 'An error occurred during hydration. The server HTML was replaced ' +
+ 'with client content',
+ ]);
} else {
expect(() => {
Scheduler.unstable_flushAll();
@@ -290,13 +298,24 @@ describe('ReactDOMServerPartialHydration', () => {
suspend = true;
client = true;
- ReactDOM.hydrateRoot(container,
);
+ ReactDOM.hydrateRoot(container,
, {
+ onHydrationError(error) {
+ Scheduler.unstable_yieldValue(error.message);
+ },
+ });
expect(Scheduler).toFlushAndYield([
'Suspend',
'Component',
'Component',
'Component',
'Component',
+
+ // Hydration mismatch errors are logged.
+ // TODO: This could get noisy. Is there some way to dedupe?
+ 'An error occurred during hydration. The server HTML was replaced with client content',
+ 'An error occurred during hydration. The server HTML was replaced with client content',
+ 'An error occurred during hydration. The server HTML was replaced with client content',
+ 'An error occurred during hydration. The server HTML was replaced with client content',
]);
jest.runAllTimers();
@@ -316,12 +335,16 @@ describe('ReactDOMServerPartialHydration', () => {
'Component',
'Component',
'Component',
+
// second pass as client render
'Hello',
'Component',
'Component',
'Component',
'Component',
+
+ // Hydration mismatch is logged
+ 'An error occurred during hydration. The server HTML was replaced with client content',
]);
// Client rendered - suspense comment nodes removed
diff --git a/packages/react-dom/src/client/ReactDOMHostConfig.js b/packages/react-dom/src/client/ReactDOMHostConfig.js
index 341d1fa7a3764..cd2fad2fdb873 100644
--- a/packages/react-dom/src/client/ReactDOMHostConfig.js
+++ b/packages/react-dom/src/client/ReactDOMHostConfig.js
@@ -70,6 +70,7 @@ import {HostComponent, HostText} from 'react-reconciler/src/ReactWorkTags';
import {listenToAllSupportedEvents} from '../events/DOMPluginEventSystem';
import {DefaultEventPriority} from 'react-reconciler/src/ReactEventPriorities';
+import {scheduleCallback, IdlePriority} from 'react-reconciler/src/Scheduler';
export type Type = string;
export type Props = {
@@ -123,6 +124,10 @@ export type TimeoutHandle = TimeoutID;
export type NoTimeout = -1;
export type RendererInspectionConfig = $ReadOnly<{||}>;
+// Right now this is a single callback, but could be multiple in the in the
+// future.
+export type ErrorLoggingConfig = null | ((error: mixed) => void);
+
type SelectionInformation = {|
focusedElem: null | HTMLElement,
selectionRange: mixed,
@@ -374,6 +379,25 @@ export function getCurrentEventPriority(): * {
return getEventPriority(currentEvent.type);
}
+export function logHydrationError(
+ config: ErrorLoggingConfig,
+ error: mixed,
+): void {
+ const onHydrationError = config;
+ if (onHydrationError !== null) {
+ // Schedule a callback to invoke the user-provided logging function.
+ scheduleCallback(IdlePriority, () => {
+ onHydrationError(error);
+ });
+ } else {
+ // Default behavior is to rethrow the error in a separate task. This will
+ // trigger a browser error event.
+ scheduleCallback(IdlePriority, () => {
+ throw error;
+ });
+ }
+}
+
export const isPrimaryRenderer = true;
export const warnsIfNotActing = true;
// This initialization code may run even on server environments
diff --git a/packages/react-dom/src/client/ReactDOMLegacy.js b/packages/react-dom/src/client/ReactDOMLegacy.js
index 05ae0d5ce4f45..cb95101401ea4 100644
--- a/packages/react-dom/src/client/ReactDOMLegacy.js
+++ b/packages/react-dom/src/client/ReactDOMLegacy.js
@@ -122,6 +122,7 @@ function legacyCreateRootFromDOMContainer(
false, // isStrictMode
false, // concurrentUpdatesByDefaultOverride,
'', // identifierPrefix
+ null,
);
markContainerAsRoot(root.current, container);
diff --git a/packages/react-dom/src/client/ReactDOMRoot.js b/packages/react-dom/src/client/ReactDOMRoot.js
index 800eeba0d018d..caf1f78c4801c 100644
--- a/packages/react-dom/src/client/ReactDOMRoot.js
+++ b/packages/react-dom/src/client/ReactDOMRoot.js
@@ -36,6 +36,7 @@ export type HydrateRootOptions = {
unstable_strictMode?: boolean,
unstable_concurrentUpdatesByDefault?: boolean,
identifierPrefix?: string,
+ onHydrationError?: (error: mixed) => void,
...
};
@@ -173,6 +174,7 @@ export function createRoot(
isStrictMode,
concurrentUpdatesByDefaultOverride,
identifierPrefix,
+ null,
);
markContainerAsRoot(root.current, container);
@@ -213,6 +215,7 @@ export function hydrateRoot(
let isStrictMode = false;
let concurrentUpdatesByDefaultOverride = false;
let identifierPrefix = '';
+ let onHydrationError = null;
if (options !== null && options !== undefined) {
if (options.unstable_strictMode === true) {
isStrictMode = true;
@@ -226,6 +229,9 @@ export function hydrateRoot(
if (options.identifierPrefix !== undefined) {
identifierPrefix = options.identifierPrefix;
}
+ if (options.onHydrationError !== undefined) {
+ onHydrationError = options.onHydrationError;
+ }
}
const root = createContainer(
@@ -236,6 +242,7 @@ export function hydrateRoot(
isStrictMode,
concurrentUpdatesByDefaultOverride,
identifierPrefix,
+ onHydrationError,
);
markContainerAsRoot(root.current, container);
// This can't be a comment node since hydration doesn't work on comment nodes anyway.
diff --git a/packages/react-native-renderer/src/ReactFabric.js b/packages/react-native-renderer/src/ReactFabric.js
index bf7754d6099c2..25cbd31b9fdf5 100644
--- a/packages/react-native-renderer/src/ReactFabric.js
+++ b/packages/react-native-renderer/src/ReactFabric.js
@@ -214,6 +214,7 @@ function render(
false,
null,
'',
+ null,
);
roots.set(containerTag, root);
}
diff --git a/packages/react-native-renderer/src/ReactFabricHostConfig.js b/packages/react-native-renderer/src/ReactFabricHostConfig.js
index 727b782efd768..3d2f890387678 100644
--- a/packages/react-native-renderer/src/ReactFabricHostConfig.js
+++ b/packages/react-native-renderer/src/ReactFabricHostConfig.js
@@ -95,6 +95,8 @@ export type RendererInspectionConfig = $ReadOnly<{|
) => void,
|}>;
+export type ErrorLoggingConfig = null;
+
// TODO: Remove this conditional once all changes have propagated.
if (registerEventHandler) {
/**
@@ -525,3 +527,10 @@ export function preparePortalMount(portalInstance: Instance): void {
export function detachDeletedInstance(node: Instance): void {
// noop
}
+
+export function logHydrationError(
+ config: ErrorLoggingConfig,
+ error: mixed,
+): void {
+ // noop
+}
diff --git a/packages/react-native-renderer/src/ReactNativeHostConfig.js b/packages/react-native-renderer/src/ReactNativeHostConfig.js
index 10c5e37f41bcc..bc7c859c4c858 100644
--- a/packages/react-native-renderer/src/ReactNativeHostConfig.js
+++ b/packages/react-native-renderer/src/ReactNativeHostConfig.js
@@ -55,6 +55,8 @@ export type RendererInspectionConfig = $ReadOnly<{|
) => void,
|}>;
+export type ErrorLoggingConfig = null;
+
const UPDATE_SIGNAL = {};
if (__DEV__) {
Object.freeze(UPDATE_SIGNAL);
@@ -513,3 +515,10 @@ export function preparePortalMount(portalInstance: Instance): void {
export function detachDeletedInstance(node: Instance): void {
// noop
}
+
+export function logHydrationError(
+ config: ErrorLoggingConfig,
+ error: mixed,
+): void {
+ // noop
+}
diff --git a/packages/react-native-renderer/src/ReactNativeRenderer.js b/packages/react-native-renderer/src/ReactNativeRenderer.js
index fb539d8996811..c5d4318311c0b 100644
--- a/packages/react-native-renderer/src/ReactNativeRenderer.js
+++ b/packages/react-native-renderer/src/ReactNativeRenderer.js
@@ -210,6 +210,7 @@ function render(
false,
null,
'',
+ null,
);
roots.set(containerTag, root);
}
diff --git a/packages/react-noop-renderer/src/createReactNoop.js b/packages/react-noop-renderer/src/createReactNoop.js
index ef76b6610617f..c93b5eb6e91dd 100644
--- a/packages/react-noop-renderer/src/createReactNoop.js
+++ b/packages/react-noop-renderer/src/createReactNoop.js
@@ -466,6 +466,10 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
},
detachDeletedInstance() {},
+
+ logHydrationError() {
+ // no-op
+ },
};
const hostConfig = useMutation
diff --git a/packages/react-reconciler/src/ReactFiberReconciler.new.js b/packages/react-reconciler/src/ReactFiberReconciler.new.js
index 1cf200e35d56d..93a3e13bef85a 100644
--- a/packages/react-reconciler/src/ReactFiberReconciler.new.js
+++ b/packages/react-reconciler/src/ReactFiberReconciler.new.js
@@ -15,6 +15,7 @@ import type {
TextInstance,
Container,
PublicInstance,
+ ErrorLoggingConfig,
} from './ReactFiberHostConfig';
import type {RendererInspectionConfig} from './ReactFiberHostConfig';
import type {ReactNodeList} from 'shared/ReactTypes';
@@ -245,6 +246,7 @@ export function createContainer(
isStrictMode: boolean,
concurrentUpdatesByDefaultOverride: null | boolean,
identifierPrefix: string,
+ errorLoggingConfig: ErrorLoggingConfig,
): OpaqueRoot {
return createFiberRoot(
containerInfo,
@@ -254,6 +256,7 @@ export function createContainer(
isStrictMode,
concurrentUpdatesByDefaultOverride,
identifierPrefix,
+ errorLoggingConfig,
);
}
diff --git a/packages/react-reconciler/src/ReactFiberReconciler.old.js b/packages/react-reconciler/src/ReactFiberReconciler.old.js
index 4b02959ab0840..0fcba6293c7b7 100644
--- a/packages/react-reconciler/src/ReactFiberReconciler.old.js
+++ b/packages/react-reconciler/src/ReactFiberReconciler.old.js
@@ -15,6 +15,7 @@ import type {
TextInstance,
Container,
PublicInstance,
+ ErrorLoggingConfig,
} from './ReactFiberHostConfig';
import type {RendererInspectionConfig} from './ReactFiberHostConfig';
import type {ReactNodeList} from 'shared/ReactTypes';
@@ -245,6 +246,7 @@ export function createContainer(
isStrictMode: boolean,
concurrentUpdatesByDefaultOverride: null | boolean,
identifierPrefix: string,
+ errorLoggingConfig: ErrorLoggingConfig,
): OpaqueRoot {
return createFiberRoot(
containerInfo,
@@ -254,6 +256,7 @@ export function createContainer(
isStrictMode,
concurrentUpdatesByDefaultOverride,
identifierPrefix,
+ errorLoggingConfig,
);
}
diff --git a/packages/react-reconciler/src/ReactFiberRoot.new.js b/packages/react-reconciler/src/ReactFiberRoot.new.js
index 9e9feb45d9b03..dd5e6a5fc7da0 100644
--- a/packages/react-reconciler/src/ReactFiberRoot.new.js
+++ b/packages/react-reconciler/src/ReactFiberRoot.new.js
@@ -9,6 +9,7 @@
import type {FiberRoot, SuspenseHydrationCallbacks} from './ReactInternalTypes';
import type {RootTag} from './ReactRootTags';
+import type {ErrorLoggingConfig} from './ReactFiberHostConfig';
import {noTimeout, supportsHydration} from './ReactFiberHostConfig';
import {createHostRootFiber} from './ReactFiber.new';
@@ -30,7 +31,13 @@ import {initializeUpdateQueue} from './ReactUpdateQueue.new';
import {LegacyRoot, ConcurrentRoot} from './ReactRootTags';
import {createCache, retainCache} from './ReactFiberCacheComponent.new';
-function FiberRootNode(containerInfo, tag, hydrate, identifierPrefix) {
+function FiberRootNode(
+ containerInfo,
+ tag,
+ hydrate,
+ identifierPrefix,
+ errorLoggingConfig,
+) {
this.tag = tag;
this.containerInfo = containerInfo;
this.pendingChildren = null;
@@ -57,6 +64,7 @@ function FiberRootNode(containerInfo, tag, hydrate, identifierPrefix) {
this.entanglements = createLaneMap(NoLanes);
this.identifierPrefix = identifierPrefix;
+ this.errorLoggingConfig = errorLoggingConfig;
if (enableCache) {
this.pooledCache = null;
@@ -103,13 +111,19 @@ export function createFiberRoot(
hydrationCallbacks: null | SuspenseHydrationCallbacks,
isStrictMode: boolean,
concurrentUpdatesByDefaultOverride: null | boolean,
+ // TODO: We have several of these arguments that are conceptually part of the
+ // host config, but because they are passed in at runtime, we have to thread
+ // them through the root constructor. Perhaps we should put them all into a
+ // single type, like a DynamicHostConfig that is defined by the renderer.
identifierPrefix: string,
+ errorLoggingConfig: ErrorLoggingConfig,
): FiberRoot {
const root: FiberRoot = (new FiberRootNode(
containerInfo,
tag,
hydrate,
identifierPrefix,
+ errorLoggingConfig,
): any);
if (enableSuspenseCallback) {
root.hydrationCallbacks = hydrationCallbacks;
diff --git a/packages/react-reconciler/src/ReactFiberRoot.old.js b/packages/react-reconciler/src/ReactFiberRoot.old.js
index d8d061297854f..d6791e97c34fd 100644
--- a/packages/react-reconciler/src/ReactFiberRoot.old.js
+++ b/packages/react-reconciler/src/ReactFiberRoot.old.js
@@ -9,6 +9,7 @@
import type {FiberRoot, SuspenseHydrationCallbacks} from './ReactInternalTypes';
import type {RootTag} from './ReactRootTags';
+import type {ErrorLoggingConfig} from './ReactFiberHostConfig';
import {noTimeout, supportsHydration} from './ReactFiberHostConfig';
import {createHostRootFiber} from './ReactFiber.old';
@@ -30,7 +31,13 @@ import {initializeUpdateQueue} from './ReactUpdateQueue.old';
import {LegacyRoot, ConcurrentRoot} from './ReactRootTags';
import {createCache, retainCache} from './ReactFiberCacheComponent.old';
-function FiberRootNode(containerInfo, tag, hydrate, identifierPrefix) {
+function FiberRootNode(
+ containerInfo,
+ tag,
+ hydrate,
+ identifierPrefix,
+ errorLoggingConfig,
+) {
this.tag = tag;
this.containerInfo = containerInfo;
this.pendingChildren = null;
@@ -57,6 +64,7 @@ function FiberRootNode(containerInfo, tag, hydrate, identifierPrefix) {
this.entanglements = createLaneMap(NoLanes);
this.identifierPrefix = identifierPrefix;
+ this.errorLoggingConfig = errorLoggingConfig;
if (enableCache) {
this.pooledCache = null;
@@ -103,13 +111,19 @@ export function createFiberRoot(
hydrationCallbacks: null | SuspenseHydrationCallbacks,
isStrictMode: boolean,
concurrentUpdatesByDefaultOverride: null | boolean,
+ // TODO: We have several of these arguments that are conceptually part of the
+ // host config, but because they are passed in at runtime, we have to thread
+ // them through the root constructor. Perhaps we should put them all into a
+ // single type, like a DynamicHostConfig that is defined by the renderer.
identifierPrefix: string,
+ errorLoggingConfig: ErrorLoggingConfig,
): FiberRoot {
const root: FiberRoot = (new FiberRootNode(
containerInfo,
tag,
hydrate,
identifierPrefix,
+ errorLoggingConfig,
): any);
if (enableSuspenseCallback) {
root.hydrationCallbacks = hydrationCallbacks;
diff --git a/packages/react-reconciler/src/ReactFiberThrow.new.js b/packages/react-reconciler/src/ReactFiberThrow.new.js
index cd9931687ba5a..60903d236e0d5 100644
--- a/packages/react-reconciler/src/ReactFiberThrow.new.js
+++ b/packages/react-reconciler/src/ReactFiberThrow.new.js
@@ -37,6 +37,7 @@ import {
import {
supportsPersistence,
getOffscreenContainerProps,
+ logHydrationError,
} from './ReactFiberHostConfig';
import {shouldCaptureSuspense} from './ReactFiberSuspenseComponent.new';
import {NoMode, ConcurrentMode, DebugTracingMode} from './ReactTypeOfMode';
@@ -507,6 +508,14 @@ function throwException(
root,
rootRenderLanes,
);
+
+ // Even though the user may not be affected by this error, we should
+ // still log it so it can be fixed.
+ // TODO: For now, we only log errors that occur during hydration, but we
+ // probably want to log any error that is recovered from without
+ // triggering an error boundary — or maybe even those, too. Need to
+ // figure out the right API.
+ logHydrationError(root.errorLoggingConfig, value);
return;
}
} else {
diff --git a/packages/react-reconciler/src/ReactFiberThrow.old.js b/packages/react-reconciler/src/ReactFiberThrow.old.js
index 8f6d18a48dea3..6b7f4bf6055b4 100644
--- a/packages/react-reconciler/src/ReactFiberThrow.old.js
+++ b/packages/react-reconciler/src/ReactFiberThrow.old.js
@@ -37,6 +37,7 @@ import {
import {
supportsPersistence,
getOffscreenContainerProps,
+ logHydrationError,
} from './ReactFiberHostConfig';
import {shouldCaptureSuspense} from './ReactFiberSuspenseComponent.old';
import {NoMode, ConcurrentMode, DebugTracingMode} from './ReactTypeOfMode';
@@ -507,6 +508,14 @@ function throwException(
root,
rootRenderLanes,
);
+
+ // Even though the user may not be affected by this error, we should
+ // still log it so it can be fixed.
+ // TODO: For now, we only log errors that occur during hydration, but we
+ // probably want to log any error that is recovered from without
+ // triggering an error boundary — or maybe even those, too. Need to
+ // figure out the right API.
+ logHydrationError(root.errorLoggingConfig, value);
return;
}
} else {
diff --git a/packages/react-reconciler/src/ReactInternalTypes.js b/packages/react-reconciler/src/ReactInternalTypes.js
index 13965720b7cd3..1cc2128356090 100644
--- a/packages/react-reconciler/src/ReactInternalTypes.js
+++ b/packages/react-reconciler/src/ReactInternalTypes.js
@@ -16,7 +16,10 @@ import type {
MutableSourceVersion,
MutableSource,
} from 'shared/ReactTypes';
-import type {SuspenseInstance} from './ReactFiberHostConfig';
+import type {
+ SuspenseInstance,
+ ErrorLoggingConfig,
+} from './ReactFiberHostConfig';
import type {WorkTag} from './ReactWorkTags';
import type {TypeOfMode} from './ReactTypeOfMode';
import type {Flags} from './ReactFiberFlags';
@@ -246,6 +249,8 @@ type BaseFiberRootProperties = {|
// the public createRoot object, which the fiber tree does not currently have
// a reference to.
identifierPrefix: string,
+
+ errorLoggingConfig: ErrorLoggingConfig,
|};
// The following attributes are only used by DevTools and are only present in DEV builds.
diff --git a/packages/react-reconciler/src/__tests__/ReactFiberHostContext-test.internal.js b/packages/react-reconciler/src/__tests__/ReactFiberHostContext-test.internal.js
index 4bf292df79f7a..d0c3d5b236ea4 100644
--- a/packages/react-reconciler/src/__tests__/ReactFiberHostContext-test.internal.js
+++ b/packages/react-reconciler/src/__tests__/ReactFiberHostContext-test.internal.js
@@ -76,6 +76,7 @@ describe('ReactFiberHostContext', () => {
null,
false,
'',
+ null,
);
act(() => {
Renderer.updateContainer(
@@ -139,6 +140,7 @@ describe('ReactFiberHostContext', () => {
null,
false,
'',
+ null,
);
act(() => {
Renderer.updateContainer(
diff --git a/packages/react-reconciler/src/forks/ReactFiberHostConfig.custom.js b/packages/react-reconciler/src/forks/ReactFiberHostConfig.custom.js
index 6535d8d3fdec3..2f86a13dc3535 100644
--- a/packages/react-reconciler/src/forks/ReactFiberHostConfig.custom.js
+++ b/packages/react-reconciler/src/forks/ReactFiberHostConfig.custom.js
@@ -38,6 +38,7 @@ export opaque type ChildSet = mixed; // eslint-disable-line no-undef
export opaque type TimeoutHandle = mixed; // eslint-disable-line no-undef
export opaque type NoTimeout = mixed; // eslint-disable-line no-undef
export opaque type RendererInspectionConfig = mixed; // eslint-disable-line no-undef
+export opaque type ErrorCallbackConfig = mixed; // eslint-disable-line no-undef
export type EventResponder = any;
export const getPublicInstance = $$$hostConfig.getPublicInstance;
@@ -68,6 +69,7 @@ export const prepareScopeUpdate = $$$hostConfig.preparePortalMount;
export const getInstanceFromScope = $$$hostConfig.getInstanceFromScope;
export const getCurrentEventPriority = $$$hostConfig.getCurrentEventPriority;
export const detachDeletedInstance = $$$hostConfig.detachDeletedInstance;
+export const onHydrationError = $$$hostConfig.errorHydratingContainer;
// -------------------
// Microtasks
diff --git a/packages/react-test-renderer/src/ReactTestHostConfig.js b/packages/react-test-renderer/src/ReactTestHostConfig.js
index 503b08efaf20b..5279fda0b43f6 100644
--- a/packages/react-test-renderer/src/ReactTestHostConfig.js
+++ b/packages/react-test-renderer/src/ReactTestHostConfig.js
@@ -42,6 +42,8 @@ export type EventResponder = any;
export type RendererInspectionConfig = $ReadOnly<{||}>;
+export type ErrorLoggingConfig = null;
+
export * from 'react-reconciler/src/ReactFiberHostConfigWithNoPersistence';
export * from 'react-reconciler/src/ReactFiberHostConfigWithNoHydration';
export * from 'react-reconciler/src/ReactFiberHostConfigWithNoTestSelectors';
@@ -314,3 +316,10 @@ export function getInstanceFromScope(scopeInstance: Object): null | Object {
export function detachDeletedInstance(node: Instance): void {
// noop
}
+
+export function logHydrationError(
+ config: ErrorLoggingConfig,
+ error: mixed,
+): void {
+ // noop
+}
diff --git a/packages/react-test-renderer/src/ReactTestRenderer.js b/packages/react-test-renderer/src/ReactTestRenderer.js
index de6e4beffec5f..a8121d1a14fcf 100644
--- a/packages/react-test-renderer/src/ReactTestRenderer.js
+++ b/packages/react-test-renderer/src/ReactTestRenderer.js
@@ -472,6 +472,7 @@ function create(element: React$Element
, options: TestRendererOptions) {
isStrictMode,
concurrentUpdatesByDefault,
'',
+ null,
);
if (root == null) {