@@ -248,7 +265,13 @@ export default function InspectedElementWrapper(_: Props) {
)}
-
diff --git a/packages/react-devtools-shared/src/devtools/views/Components/types.js b/packages/react-devtools-shared/src/devtools/views/Components/types.js
index 372cdd5b9087d..c6789fa1e9ec1 100644
--- a/packages/react-devtools-shared/src/devtools/views/Components/types.js
+++ b/packages/react-devtools-shared/src/devtools/views/Components/types.js
@@ -42,6 +42,10 @@ export type Element = {|
// This property is used to quickly determine the total number of Elements,
// and the Element at any given index (for windowing purposes).
weight: number,
+
+ // This element is not in a StrictMode compliant subtree.
+ // Only true for React versions supporting StrictMode.
+ isStrictModeNonCompliant: boolean,
|};
export type SerializedElement = {|
diff --git a/packages/react-devtools-shared/src/devtools/views/Icon.js b/packages/react-devtools-shared/src/devtools/views/Icon.js
index cec6ae34ef910..bcf59773d06a5 100644
--- a/packages/react-devtools-shared/src/devtools/views/Icon.js
+++ b/packages/react-devtools-shared/src/devtools/views/Icon.js
@@ -25,14 +25,16 @@ export type IconType =
| 'search'
| 'settings'
| 'store-as-global-variable'
+ | 'strict-mode-non-compliant'
| 'warning';
type Props = {|
className?: string,
+ title?: string,
type: IconType,
|};
-export default function Icon({className = '', type}: Props) {
+export default function Icon({className = '', title = '', type}: Props) {
let pathData = null;
switch (type) {
case 'arrow':
@@ -77,6 +79,9 @@ export default function Icon({className = '', type}: Props) {
case 'store-as-global-variable':
pathData = PATH_STORE_AS_GLOBAL_VARIABLE;
break;
+ case 'strict-mode-non-compliant':
+ pathData = PATH_STRICT_MODE_NON_COMPLIANT;
+ break;
case 'warning':
pathData = PATH_WARNING;
break;
@@ -92,6 +97,7 @@ export default function Icon({className = '', type}: Props) {
width="24"
height="24"
viewBox="0 0 24 24">
+ {title &&
{title}}
@@ -170,4 +176,9 @@ const PATH_STORE_AS_GLOBAL_VARIABLE = `
8h-4v-2h4v2zm0-4h-4v-2h4v2z
`;
+const PATH_STRICT_MODE_NON_COMPLIANT = `
+ M4.47 21h15.06c1.54 0 2.5-1.67 1.73-3L13.73 4.99c-.77-1.33-2.69-1.33-3.46 0L2.74 18c-.77 1.33.19 3 1.73 3zM12
+ 14c-.55 0-1-.45-1-1v-2c0-.55.45-1 1-1s1 .45 1 1v2c0 .55-.45 1-1 1zm1 4h-2v-2h2v2z
+`;
+
const PATH_WARNING = `M12 1l-12 22h24l-12-22zm-1 8h2v7h-2v-7zm1 11.25c-.69 0-1.25-.56-1.25-1.25s.56-1.25 1.25-1.25 1.25.56 1.25 1.25-.56 1.25-1.25 1.25z`;
diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/CommitTreeBuilder.js b/packages/react-devtools-shared/src/devtools/views/Profiler/CommitTreeBuilder.js
index 1d16aba7fc292..286048a172bf5 100644
--- a/packages/react-devtools-shared/src/devtools/views/Profiler/CommitTreeBuilder.js
+++ b/packages/react-devtools-shared/src/devtools/views/Profiler/CommitTreeBuilder.js
@@ -13,6 +13,7 @@ import {
TREE_OPERATION_REMOVE,
TREE_OPERATION_REMOVE_ROOT,
TREE_OPERATION_REORDER_CHILDREN,
+ TREE_OPERATION_SET_SUBTREE_MODE,
TREE_OPERATION_UPDATE_TREE_BASE_DURATION,
TREE_OPERATION_UPDATE_ERRORS_OR_WARNINGS,
} from 'react-devtools-shared/src/constants';
@@ -179,7 +180,7 @@ function updateTree(
const operation = operations[i];
switch (operation) {
- case TREE_OPERATION_ADD:
+ case TREE_OPERATION_ADD: {
id = ((operations[i + 1]: any): number);
const type = ((operations[i + 2]: any): ElementType);
@@ -192,7 +193,9 @@ function updateTree(
}
if (type === ElementTypeRoot) {
+ i++; // isStrictModeCompliant
i++; // supportsProfiling flag
+ i++; // supportsStrictMode flag
i++; // hasOwnerMetadata flag
if (__DEBUG__) {
@@ -250,6 +253,7 @@ function updateTree(
}
break;
+ }
case TREE_OPERATION_REMOVE: {
const removeLength = ((operations[i + 1]: any): number);
i += 2;
@@ -307,6 +311,17 @@ function updateTree(
break;
}
+ case TREE_OPERATION_SET_SUBTREE_MODE: {
+ id = operations[i + 1];
+ const mode = operations[i + 1];
+
+ i += 3;
+
+ if (__DEBUG__) {
+ debug('Subtree mode', `Subtree with root ${id} set to mode ${mode}`);
+ }
+ break;
+ }
case TREE_OPERATION_UPDATE_TREE_BASE_DURATION: {
id = operations[i + 1];
@@ -323,7 +338,7 @@ function updateTree(
i += 3;
break;
}
- case TREE_OPERATION_UPDATE_ERRORS_OR_WARNINGS:
+ case TREE_OPERATION_UPDATE_ERRORS_OR_WARNINGS: {
id = operations[i + 1];
const numErrors = operations[i + 2];
const numWarnings = operations[i + 3];
@@ -337,6 +352,7 @@ function updateTree(
);
}
break;
+ }
default:
throw Error(`Unsupported Bridge operation "${operation}"`);
diff --git a/packages/react-devtools-shared/src/types.js b/packages/react-devtools-shared/src/types.js
index 5a54979b63b19..859fb9bd8ff72 100644
--- a/packages/react-devtools-shared/src/types.js
+++ b/packages/react-devtools-shared/src/types.js
@@ -100,3 +100,5 @@ export type StyleXPlugin = {|
export type Plugins = {|
stylex: StyleXPlugin | null,
|};
+
+export const StrictMode = 1;
diff --git a/packages/react-devtools-shared/src/utils.js b/packages/react-devtools-shared/src/utils.js
index 0c50374ae30b6..c022cd4650ed7 100644
--- a/packages/react-devtools-shared/src/utils.js
+++ b/packages/react-devtools-shared/src/utils.js
@@ -28,6 +28,7 @@ import {
TREE_OPERATION_REMOVE,
TREE_OPERATION_REMOVE_ROOT,
TREE_OPERATION_REORDER_CHILDREN,
+ TREE_OPERATION_SET_SUBTREE_MODE,
TREE_OPERATION_UPDATE_ERRORS_OR_WARNINGS,
TREE_OPERATION_UPDATE_TREE_BASE_DURATION,
} from './constants';
@@ -211,7 +212,9 @@ export function printOperationsArray(operations: Array
) {
if (type === ElementTypeRoot) {
logs.push(`Add new root node ${id}`);
+ i++; // isStrictModeCompliant
i++; // supportsProfiling
+ i++; // supportsStrictMode
i++; // hasOwnerMetadata
} else {
const parentID = ((operations[i]: any): number);
@@ -249,6 +252,15 @@ export function printOperationsArray(operations: Array) {
logs.push(`Remove root ${rootID}`);
break;
}
+ case TREE_OPERATION_SET_SUBTREE_MODE: {
+ const id = operations[i + 1];
+ const mode = operations[i + 1];
+
+ i += 3;
+
+ logs.push(`Mode ${mode} set for subtree with root ${id}`);
+ break;
+ }
case TREE_OPERATION_REORDER_CHILDREN: {
const id = ((operations[i + 1]: any): number);
const numChildren = ((operations[i + 2]: any): number);
diff --git a/packages/react-devtools-shell/src/app/PartiallyStrictApp/index.js b/packages/react-devtools-shell/src/app/PartiallyStrictApp/index.js
new file mode 100644
index 0000000000000..068e11935bce6
--- /dev/null
+++ b/packages/react-devtools-shell/src/app/PartiallyStrictApp/index.js
@@ -0,0 +1,34 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow
+ */
+
+import * as React from 'react';
+import {StrictMode} from 'react';
+
+export default function PartiallyStrictApp() {
+ return (
+ <>
+
+
+
+
+ >
+ );
+}
+
+function Child() {
+ return ;
+}
+
+function StrictChild() {
+ return ;
+}
+
+function Grandchild() {
+ return null;
+}
diff --git a/packages/react-devtools-shell/src/app/index.js b/packages/react-devtools-shell/src/app/index.js
index 805cd0a2a707e..f949b8598f48a 100644
--- a/packages/react-devtools-shell/src/app/index.js
+++ b/packages/react-devtools-shell/src/app/index.js
@@ -6,6 +6,8 @@ import {createElement} from 'react';
import {
// $FlowFixMe Flow does not yet know about createRoot()
createRoot,
+ render,
+ unmountComponentAtNode,
} from 'react-dom';
import DeeplyNestedComponents from './DeeplyNestedComponents';
import Iframe from './Iframe';
@@ -18,6 +20,7 @@ import ReactNativeWeb from './ReactNativeWeb';
import ToDoList from './ToDoList';
import Toggle from './Toggle';
import ErrorBoundaries from './ErrorBoundaries';
+import PartiallyStrictApp from './PartiallyStrictApp';
import SuspenseTree from './SuspenseTree';
import {ignoreErrors, ignoreLogs, ignoreWarnings} from './console';
@@ -34,36 +37,68 @@ ignoreErrors([
ignoreWarnings(['Warning: componentWillReceiveProps has been renamed']);
ignoreLogs([]);
-const roots = [];
+const unmountFunctions = [];
-function mountHelper(App) {
+function createContainer() {
const container = document.createElement('div');
((document.body: any): HTMLBodyElement).appendChild(container);
+ return container;
+}
+
+function mountApp(App) {
+ const container = createContainer();
+
const root = createRoot(container);
root.render(createElement(App));
- roots.push(root);
+ unmountFunctions.push(() => root.unmount());
+}
+
+function mountStrictApp(App) {
+ function StrictRoot() {
+ return createElement(App);
+ }
+
+ const container = createContainer();
+
+ const root = createRoot(container, {unstable_strictMode: true});
+ root.render(createElement(StrictRoot));
+
+ unmountFunctions.push(() => root.unmount());
+}
+
+function mountLegacyApp(App) {
+ function LegacyRender() {
+ return createElement(App);
+ }
+
+ const container = createContainer();
+
+ render(createElement(LegacyRender), container);
+
+ unmountFunctions.push(() => unmountComponentAtNode(container));
}
function mountTestApp() {
- mountHelper(ToDoList);
- mountHelper(InspectableElements);
- mountHelper(Hydration);
- mountHelper(ElementTypes);
- mountHelper(EditableProps);
- mountHelper(InlineWarnings);
- mountHelper(ReactNativeWeb);
- mountHelper(Toggle);
- mountHelper(ErrorBoundaries);
- mountHelper(SuspenseTree);
- mountHelper(DeeplyNestedComponents);
- mountHelper(Iframe);
+ mountStrictApp(ToDoList);
+ mountApp(InspectableElements);
+ mountApp(Hydration);
+ mountApp(ElementTypes);
+ mountApp(EditableProps);
+ mountApp(InlineWarnings);
+ mountApp(ReactNativeWeb);
+ mountApp(Toggle);
+ mountApp(ErrorBoundaries);
+ mountApp(SuspenseTree);
+ mountApp(DeeplyNestedComponents);
+ mountApp(Iframe);
+ mountLegacyApp(PartiallyStrictApp);
}
function unmountTestApp() {
- roots.forEach(root => root.unmount());
+ unmountFunctions.forEach(fn => fn());
}
mountTestApp();
diff --git a/packages/react-devtools/OVERVIEW.md b/packages/react-devtools/OVERVIEW.md
index 70b2c9ba095b0..01ed4bd02a884 100644
--- a/packages/react-devtools/OVERVIEW.md
+++ b/packages/react-devtools/OVERVIEW.md
@@ -54,7 +54,9 @@ Adding a root to the tree requires sending 5 numbers:
1. add operation constant (`1`)
1. fiber id
1. element type constant (`11 === ElementTypeRoot`)
-1. profiling supported flag
+1. root has `StrictMode` enabled
+1. supports profiling flag
+1. supports `StrictMode` flag
1. owner metadata flag
For example, adding a root fiber with an id of 1:
@@ -63,7 +65,9 @@ For example, adding a root fiber with an id of 1:
1, // add operation
1, // fiber id
11, // ElementTypeRoot
+ 1, // this root is StrictMode enabled
1, // this root's renderer supports profiling
+ 1, // this root's renderer supports StrictMode
1, // this root has owner metadata
]
```
@@ -176,6 +180,20 @@ Special case of unmounting an entire root (include its descendants). This specia
This operation has no additional payload because renderer and root ids are already sent at the beginning of every operations payload.
+#### Setting the mode for a subtree
+
+This message specifies that a subtree operates under a specific mode (e.g. `StrictMode`).
+
+```js
+[
+ 7, // set subtree mode
+ 1, // subtree root fiber id
+ 0b01 // mode bitmask
+]
+```
+
+Modes are constant meaning that the modes a subtree mounts with will never change.
+
## Reconstructing the tree
The frontend stores its information about the tree in a map of id to objects with the following keys: