diff --git a/packages/react-devtools-shared/src/__tests__/__snapshots__/inspectedElementContext-test.js.snap b/packages/react-devtools-shared/src/__tests__/__snapshots__/inspectedElementContext-test.js.snap
index e7fe89f4e2573..83d769aaccd60 100644
--- a/packages/react-devtools-shared/src/__tests__/__snapshots__/inspectedElementContext-test.js.snap
+++ b/packages/react-devtools-shared/src/__tests__/__snapshots__/inspectedElementContext-test.js.snap
@@ -486,7 +486,11 @@ exports[`InspectedElementContext should support complex data types: 1: Inspected
"hooks": null,
"props": {
"array_buffer": {},
+ "array_of_arrays": [
+ {}
+ ],
"big_int": {},
+ "data_view": {},
"date": {},
"fn": {},
"html_element": {},
@@ -503,6 +507,9 @@ exports[`InspectedElementContext should support complex data types: 1: Inspected
"0": {},
"1": {}
},
+ "object_of_objects": {
+ "inner": {}
+ },
"react_element": {},
"set": {
"0": "abc",
diff --git a/packages/react-devtools-shared/src/__tests__/inspectedElementContext-test.js b/packages/react-devtools-shared/src/__tests__/inspectedElementContext-test.js
index aaf807485f950..0e7aa1788d929 100644
--- a/packages/react-devtools-shared/src/__tests__/inspectedElementContext-test.js
+++ b/packages/react-devtools-shared/src/__tests__/inspectedElementContext-test.js
@@ -511,13 +511,19 @@ describe('InspectedElementContext', () => {
const Example = () => null;
+ const arrayOfArrays = [[['abc', 123, true], []]];
const div = document.createElement('div');
const exampleFunction = () => {};
const setShallow = new Set(['abc', 123]);
const mapShallow = new Map([['name', 'Brian'], ['food', 'sushi']]);
const setOfSets = new Set([new Set(['a', 'b', 'c']), new Set([1, 2, 3])]);
const mapOfMaps = new Map([['first', mapShallow], ['second', mapShallow]]);
+ const objectOfObjects = {
+ inner: {string: 'abc', number: 213, boolean: true},
+ };
const typedArray = Int8Array.from([100, -100, 0]);
+ const arrayBuffer = typedArray.buffer;
+ const dataView = new DataView(arrayBuffer);
const immutableMap = Immutable.fromJS({
a: [{hello: 'there'}, 'fixed', true],
b: 123,
@@ -531,15 +537,18 @@ describe('InspectedElementContext', () => {
await utils.actAsync(() =>
ReactDOM.render(
}
set={setShallow}
set_of_sets={setOfSets}
@@ -579,13 +588,16 @@ describe('InspectedElementContext', () => {
const {
array_buffer,
+ array_of_arrays,
big_int,
+ data_view,
date,
fn,
html_element,
immutable,
map,
map_of_maps,
+ object_of_objects,
react_element,
set,
set_of_sets,
@@ -597,54 +609,111 @@ describe('InspectedElementContext', () => {
expect(array_buffer[meta.inspectable]).toBe(false);
expect(array_buffer[meta.name]).toBe('ArrayBuffer');
expect(array_buffer[meta.type]).toBe('array_buffer');
+ expect(array_buffer[meta.preview_short]).toBe('ArrayBuffer(3)');
+ expect(array_buffer[meta.preview_long]).toBe('ArrayBuffer(3)');
+
+ expect(array_of_arrays[0][meta.size]).toBe(2);
+ expect(array_of_arrays[0][meta.inspectable]).toBe(true);
+ expect(array_of_arrays[0][meta.name]).toBe('Array');
+ expect(array_of_arrays[0][meta.type]).toBe('array');
+ expect(array_of_arrays[0][meta.preview_long]).toBe('[Array(3), Array(0)]');
+ expect(array_of_arrays[0][meta.preview_short]).toBe('Array(2)');
expect(big_int[meta.inspectable]).toBe(false);
expect(big_int[meta.name]).toBe('123');
expect(big_int[meta.type]).toBe('bigint');
+ expect(big_int[meta.preview_long]).toBe('123n');
+ expect(big_int[meta.preview_short]).toBe('123n');
+
+ expect(data_view[meta.size]).toBe(3);
+ expect(data_view[meta.inspectable]).toBe(false);
+ expect(data_view[meta.name]).toBe('DataView');
+ expect(data_view[meta.type]).toBe('data_view');
+ expect(data_view[meta.preview_long]).toBe('DataView(3)');
+ expect(data_view[meta.preview_short]).toBe('DataView(3)');
expect(date[meta.inspectable]).toBe(false);
expect(date[meta.type]).toBe('date');
+ expect(date[meta.preview_long]).toBe(
+ 'Wed Dec 31 1969 16:00:00 GMT-0800 (Pacific Standard Time)',
+ );
+ expect(date[meta.preview_short]).toBe(
+ 'Wed Dec 31 1969 16:00:00 GMT-0800 (Pacific Standard Time)',
+ );
expect(fn[meta.inspectable]).toBe(false);
expect(fn[meta.name]).toBe('exampleFunction');
expect(fn[meta.type]).toBe('function');
+ expect(fn[meta.preview_long]).toBe('exampleFunction');
+ expect(fn[meta.preview_short]).toBe('exampleFunction');
expect(html_element[meta.inspectable]).toBe(false);
expect(html_element[meta.name]).toBe('DIV');
expect(html_element[meta.type]).toBe('html_element');
+ expect(html_element[meta.preview_long]).toBe('
');
+ expect(html_element[meta.preview_short]).toBe('');
expect(immutable[meta.inspectable]).toBeUndefined(); // Complex type
expect(immutable[meta.name]).toBe('Map');
expect(immutable[meta.type]).toBe('iterator');
+ expect(immutable[meta.preview_long]).toBe(
+ 'Map(3) {"a" => List(3), "b" => 123, "c" => Map(2)}',
+ );
+ expect(immutable[meta.preview_short]).toBe('Map(3)');
expect(map[meta.inspectable]).toBeUndefined(); // Complex type
expect(map[meta.name]).toBe('Map');
expect(map[meta.type]).toBe('iterator');
expect(map[0][meta.type]).toBe('array');
+ expect(map[meta.preview_long]).toBe(
+ 'Map(2) {"name" => "Brian", "food" => "sushi"}',
+ );
+ expect(map[meta.preview_short]).toBe('Map(2)');
expect(map_of_maps[meta.inspectable]).toBeUndefined(); // Complex type
expect(map_of_maps[meta.name]).toBe('Map');
expect(map_of_maps[meta.type]).toBe('iterator');
expect(map_of_maps[0][meta.type]).toBe('array');
+ expect(map_of_maps[meta.preview_long]).toBe(
+ 'Map(2) {"first" => Map(2), "second" => Map(2)}',
+ );
+ expect(map_of_maps[meta.preview_short]).toBe('Map(2)');
+
+ expect(object_of_objects.inner[meta.size]).toBe(3);
+ expect(object_of_objects.inner[meta.inspectable]).toBe(true);
+ expect(object_of_objects.inner[meta.name]).toBe('');
+ expect(object_of_objects.inner[meta.type]).toBe('object');
+ expect(object_of_objects.inner[meta.preview_long]).toBe(
+ '{boolean: true, number: 213, string: "abc"}',
+ );
+ expect(object_of_objects.inner[meta.preview_short]).toBe('{…}');
expect(react_element[meta.inspectable]).toBe(false);
expect(react_element[meta.name]).toBe('span');
expect(react_element[meta.type]).toBe('react_element');
+ expect(react_element[meta.preview_long]).toBe('');
+ expect(react_element[meta.preview_short]).toBe('');
expect(set[meta.inspectable]).toBeUndefined(); // Complex type
expect(set[meta.name]).toBe('Set');
expect(set[meta.type]).toBe('iterator');
expect(set[0]).toBe('abc');
expect(set[1]).toBe(123);
+ expect(set[meta.preview_long]).toBe('Set(2) {"abc", 123}');
+ expect(set[meta.preview_short]).toBe('Set(2)');
expect(set_of_sets[meta.inspectable]).toBeUndefined(); // Complex type
expect(set_of_sets[meta.name]).toBe('Set');
expect(set_of_sets[meta.type]).toBe('iterator');
expect(set_of_sets['0'][meta.inspectable]).toBe(true);
+ expect(set_of_sets[meta.preview_long]).toBe('Set(2) {Set(3), Set(3)}');
+ expect(set_of_sets[meta.preview_short]).toBe('Set(2)');
expect(symbol[meta.inspectable]).toBe(false);
expect(symbol[meta.name]).toBe('Symbol(symbol)');
expect(symbol[meta.type]).toBe('symbol');
+ expect(symbol[meta.preview_long]).toBe('Symbol(symbol)');
+ expect(symbol[meta.preview_short]).toBe('Symbol(symbol)');
expect(typed_array[meta.inspectable]).toBeUndefined(); // Complex type
expect(typed_array[meta.size]).toBe(3);
@@ -653,6 +722,8 @@ describe('InspectedElementContext', () => {
expect(typed_array[0]).toBe(100);
expect(typed_array[1]).toBe(-100);
expect(typed_array[2]).toBe(0);
+ expect(typed_array[meta.preview_long]).toBe('Int8Array(3) [100, -100, 0]');
+ expect(typed_array[meta.preview_short]).toBe('Int8Array(3)');
done();
});
diff --git a/packages/react-devtools-shared/src/__tests__/legacy/__snapshots__/inspectElement-test.js.snap b/packages/react-devtools-shared/src/__tests__/legacy/__snapshots__/inspectElement-test.js.snap
index 2c35d681bc864..c1407ceeed6ad 100644
--- a/packages/react-devtools-shared/src/__tests__/legacy/__snapshots__/inspectElement-test.js.snap
+++ b/packages/react-devtools-shared/src/__tests__/legacy/__snapshots__/inspectElement-test.js.snap
@@ -127,7 +127,11 @@ Object {
"hooks": null,
"props": {
"array_buffer": {},
+ "array_of_arrays": [
+ {}
+ ],
"big_int": {},
+ "data_view": {},
"date": {},
"fn": {},
"html_element": {},
@@ -144,6 +148,9 @@ Object {
"0": {},
"1": {}
},
+ "object_of_objects": {
+ "inner": {}
+ },
"react_element": {},
"set": {
"0": "abc",
diff --git a/packages/react-devtools-shared/src/__tests__/legacy/inspectElement-test.js b/packages/react-devtools-shared/src/__tests__/legacy/inspectElement-test.js
index 8c8336a93ed96..8c595d734f2fd 100644
--- a/packages/react-devtools-shared/src/__tests__/legacy/inspectElement-test.js
+++ b/packages/react-devtools-shared/src/__tests__/legacy/inspectElement-test.js
@@ -147,13 +147,19 @@ describe('InspectedElementContext', () => {
const Example = () => null;
+ const arrayOfArrays = [[['abc', 123, true], []]];
const div = document.createElement('div');
const exampleFunction = () => {};
const setShallow = new Set(['abc', 123]);
const mapShallow = new Map([['name', 'Brian'], ['food', 'sushi']]);
const setOfSets = new Set([new Set(['a', 'b', 'c']), new Set([1, 2, 3])]);
const mapOfMaps = new Map([['first', mapShallow], ['second', mapShallow]]);
+ const objectOfObjects = {
+ inner: {string: 'abc', number: 213, boolean: true},
+ };
const typedArray = Int8Array.from([100, -100, 0]);
+ const arrayBuffer = typedArray.buffer;
+ const dataView = new DataView(arrayBuffer);
const immutableMap = Immutable.fromJS({
a: [{hello: 'there'}, 'fixed', true],
b: 123,
@@ -166,15 +172,18 @@ describe('InspectedElementContext', () => {
act(() =>
ReactDOM.render(
}
set={setShallow}
set_of_sets={setOfSets}
@@ -192,13 +201,16 @@ describe('InspectedElementContext', () => {
const {
array_buffer,
+ array_of_arrays,
big_int,
+ data_view,
date,
fn,
html_element,
immutable,
map,
map_of_maps,
+ object_of_objects,
react_element,
set,
set_of_sets,
@@ -210,11 +222,25 @@ describe('InspectedElementContext', () => {
expect(array_buffer[meta.inspectable]).toBe(false);
expect(array_buffer[meta.name]).toBe('ArrayBuffer');
expect(array_buffer[meta.type]).toBe('array_buffer');
+ expect(array_buffer[meta.preview_short]).toBe('ArrayBuffer(3)');
+ expect(array_buffer[meta.preview_long]).toBe('ArrayBuffer(3)');
+
+ expect(array_of_arrays[0][meta.size]).toBe(2);
+ expect(array_of_arrays[0][meta.inspectable]).toBe(true);
+ expect(array_of_arrays[0][meta.name]).toBe('Array');
+ expect(array_of_arrays[0][meta.type]).toBe('array');
+ expect(array_of_arrays[0][meta.preview_long]).toBe('[Array(3), Array(0)]');
+ expect(array_of_arrays[0][meta.preview_short]).toBe('Array(2)');
expect(big_int[meta.inspectable]).toBe(false);
expect(big_int[meta.name]).toBe('123');
expect(big_int[meta.type]).toBe('bigint');
+ expect(data_view[meta.size]).toBe(3);
+ expect(data_view[meta.inspectable]).toBe(false);
+ expect(data_view[meta.name]).toBe('DataView');
+ expect(data_view[meta.type]).toBe('data_view');
+
expect(date[meta.inspectable]).toBe(false);
expect(date[meta.type]).toBe('date');
@@ -240,6 +266,15 @@ describe('InspectedElementContext', () => {
expect(map_of_maps[meta.type]).toBe('iterator');
expect(map_of_maps[0][meta.type]).toBe('array');
+ expect(object_of_objects.inner[meta.size]).toBe(3);
+ expect(object_of_objects.inner[meta.inspectable]).toBe(true);
+ expect(object_of_objects.inner[meta.name]).toBe('');
+ expect(object_of_objects.inner[meta.type]).toBe('object');
+ expect(object_of_objects.inner[meta.preview_long]).toBe(
+ '{boolean: true, number: 213, string: "abc"}',
+ );
+ expect(object_of_objects.inner[meta.preview_short]).toBe('{…}');
+
expect(react_element[meta.inspectable]).toBe(false);
expect(react_element[meta.name]).toBe('span');
expect(react_element[meta.type]).toBe('react_element');
diff --git a/packages/react-devtools-shared/src/devtools/views/Components/KeyValue.js b/packages/react-devtools-shared/src/devtools/views/Components/KeyValue.js
index 76b1a2e57b897..07e7337d501c5 100644
--- a/packages/react-devtools-shared/src/devtools/views/Components/KeyValue.js
+++ b/packages/react-devtools-shared/src/devtools/views/Components/KeyValue.js
@@ -133,6 +133,7 @@ export default function KeyValue({
} else {
if (Array.isArray(value)) {
const hasChildren = value.length > 0;
+ const displayName = getMetaValueLabel(value);
children = value.map((innerValue, index) => (
{name}
-
- Array{' '}
- {hasChildren ? '' : (empty)}
-
+ {displayName}
,
);
} else {
@@ -180,9 +178,7 @@ export default function KeyValue({
}
const hasChildren = entries.length > 0;
- const displayName = value.hasOwnProperty(meta.unserializable)
- ? getMetaValueLabel(value)
- : 'Object';
+ const displayName = getMetaValueLabel(value);
let areChildrenReadOnly = isReadOnly || !!value[meta.readonly];
children = entries.map>(([key, keyValue]) => (
@@ -215,10 +211,7 @@ export default function KeyValue({
onClick={hasChildren ? toggleIsOpen : undefined}>
{name}
-
- {`${displayName || ''} `}
- {hasChildren ? '' : (empty)}
-
+ {displayName}
,
);
}
diff --git a/packages/react-devtools-shared/src/devtools/views/utils.js b/packages/react-devtools-shared/src/devtools/views/utils.js
index e52bedbace49b..16d3bd2881b5a 100644
--- a/packages/react-devtools-shared/src/devtools/views/utils.js
+++ b/packages/react-devtools-shared/src/devtools/views/utils.js
@@ -9,6 +9,7 @@
import escapeStringRegExp from 'escape-string-regexp';
import {meta} from '../../hydration';
+import {formatDataForPreview} from '../../utils';
import type {HooksTree} from 'react-debug-tools/src/ReactDebugHooks';
@@ -92,32 +93,10 @@ export function createRegExp(string: string): RegExp {
}
export function getMetaValueLabel(data: Object): string | null {
- const name = data[meta.name];
- const type = data[meta.type];
-
- switch (type) {
- case 'html_element':
- return name ? `<${name.toLowerCase()} />` : '';
- case 'react_element':
- return `<${name} />`;
- case 'function':
- return `${name || 'fn'}()`;
- case 'object':
- return 'Object';
- case 'date':
- case 'symbol':
- return name;
- case 'bigint':
- return `${name}n`;
- case 'iterator':
- return `${name}(…)`;
- case 'array_buffer':
- case 'data_view':
- case 'array':
- case 'typed_array':
- return `${name}[${data[meta.size]}]`;
- default:
- return null;
+ if (data.hasOwnProperty(meta.preview_long)) {
+ return data[meta.preview_long];
+ } else {
+ return formatDataForPreview(data, true);
}
}
diff --git a/packages/react-devtools-shared/src/hydration.js b/packages/react-devtools-shared/src/hydration.js
index a4ddf7f84ffce..7aa61f5ba6270 100644
--- a/packages/react-devtools-shared/src/hydration.js
+++ b/packages/react-devtools-shared/src/hydration.js
@@ -9,22 +9,12 @@
import Symbol from 'es6-symbol';
import {
- isElement,
- typeOf,
- AsyncMode,
- ConcurrentMode,
- ContextConsumer,
- ContextProvider,
- ForwardRef,
- Fragment,
- Lazy,
- Memo,
- Portal,
- Profiler,
- StrictMode,
- Suspense,
-} from 'react-is';
-import {getDisplayName, getInObject, setInObject} from './utils';
+ getDataType,
+ getDisplayNameForReactElement,
+ getInObject,
+ formatDataForPreview,
+ setInObject,
+} from './utils';
import type {DehydratedData} from 'react-devtools-shared/src/devtools/views/Components/types';
@@ -32,6 +22,8 @@ export const meta = {
inspectable: Symbol('inspectable'),
inspected: Symbol('inspected'),
name: Symbol('name'),
+ preview_long: Symbol('preview_long'),
+ preview_short: Symbol('preview_short'),
readonly: Symbol('readonly'),
size: Symbol('size'),
type: Symbol('type'),
@@ -41,6 +33,8 @@ export const meta = {
export type Dehydrated = {|
inspectable: boolean,
name: string | null,
+ preview_long: string | null,
+ preview_short: string | null,
readonly?: boolean,
size?: number,
type: string,
@@ -52,6 +46,8 @@ export type Dehydrated = {|
// while preserving the original type and name.
export type Unserializable = {
name: string | null,
+ preview_long: string | null,
+ preview_short: string | null,
readonly?: boolean,
size?: number,
type: string,
@@ -66,84 +62,6 @@ export type Unserializable = {
// but may decrease the responsiveness of expanding objects/arrays to inspect further.
const LEVEL_THRESHOLD = 2;
-type PropType =
- | 'array'
- | 'array_buffer'
- | 'bigint'
- | 'boolean'
- | 'data_view'
- | 'date'
- | 'function'
- | 'html_element'
- | 'infinity'
- | 'iterator'
- | 'nan'
- | 'null'
- | 'number'
- | 'object'
- | 'react_element'
- | 'string'
- | 'symbol'
- | 'typed_array'
- | 'undefined'
- | 'unknown';
-
-/**
- * Get a enhanced/artificial type string based on the object instance
- */
-function getDataType(data: Object): PropType {
- if (data === null) {
- return 'null';
- } else if (data === undefined) {
- return 'undefined';
- }
-
- if (isElement(data)) {
- return 'react_element';
- }
-
- if (typeof HTMLElement !== 'undefined' && data instanceof HTMLElement) {
- return 'html_element';
- }
-
- const type = typeof data;
- switch (type) {
- case 'bigint':
- return 'bigint';
- case 'boolean':
- return 'boolean';
- case 'function':
- return 'function';
- case 'number':
- if (Number.isNaN(data)) {
- return 'nan';
- } else if (!Number.isFinite(data)) {
- return 'infinity';
- } else {
- return 'number';
- }
- case 'object':
- if (Array.isArray(data)) {
- return 'array';
- } else if (ArrayBuffer.isView(data)) {
- return data instanceof DataView ? 'data_view' : 'typed_array';
- } else if (data instanceof ArrayBuffer) {
- return 'array_buffer';
- } else if (typeof data[Symbol.iterator] === 'function') {
- return 'iterator';
- } else if (Object.prototype.toString.call(data) === '[object Date]') {
- return 'date';
- }
- return 'object';
- case 'string':
- return 'string';
- case 'symbol':
- return 'symbol';
- default:
- return 'unknown';
- }
-}
-
/**
* Generate the dehydrated metadata for complex object instances
*/
@@ -159,6 +77,8 @@ function createDehydrated(
const dehydrated: Dehydrated = {
inspectable,
type,
+ preview_long: formatDataForPreview(data, true),
+ preview_short: formatDataForPreview(data, false),
name:
!data.constructor || data.constructor.name === 'Object'
? ''
@@ -219,6 +139,8 @@ export function dehydrate(
cleaned.push(path);
return {
inspectable: false,
+ preview_short: formatDataForPreview(data, false),
+ preview_long: formatDataForPreview(data, true),
name: data.tagName,
type,
};
@@ -227,6 +149,8 @@ export function dehydrate(
cleaned.push(path);
return {
inspectable: false,
+ preview_short: formatDataForPreview(data, false),
+ preview_long: formatDataForPreview(data, true),
name: data.name,
type,
};
@@ -238,6 +162,8 @@ export function dehydrate(
cleaned.push(path);
return {
inspectable: false,
+ preview_short: formatDataForPreview(data, false),
+ preview_long: formatDataForPreview(data, true),
name: data.toString(),
type,
};
@@ -246,6 +172,8 @@ export function dehydrate(
cleaned.push(path);
return {
inspectable: false,
+ preview_short: formatDataForPreview(data, false),
+ preview_long: formatDataForPreview(data, true),
name: data.toString(),
type,
};
@@ -256,7 +184,9 @@ export function dehydrate(
cleaned.push(path);
return {
inspectable: false,
- name: getDisplayNameForReactElement(data),
+ preview_short: formatDataForPreview(data, false),
+ preview_long: formatDataForPreview(data, true),
+ name: getDisplayNameForReactElement(data) || 'Unknown',
type,
};
@@ -266,6 +196,8 @@ export function dehydrate(
cleaned.push(path);
return {
inspectable: false,
+ preview_short: formatDataForPreview(data, false),
+ preview_long: formatDataForPreview(data, true),
name: type === 'data_view' ? 'DataView' : 'ArrayBuffer',
size: data.byteLength,
type,
@@ -298,6 +230,8 @@ export function dehydrate(
type: type,
readonly: true,
size: type === 'typed_array' ? data.length : undefined,
+ preview_short: formatDataForPreview(data, false),
+ preview_long: formatDataForPreview(data, true),
name:
!data.constructor || data.constructor.name === 'Object'
? ''
@@ -331,6 +265,8 @@ export function dehydrate(
cleaned.push(path);
return {
inspectable: false,
+ preview_short: formatDataForPreview(data, false),
+ preview_long: formatDataForPreview(data, true),
name: data.toString(),
type,
};
@@ -381,6 +317,8 @@ export function fillInPath(
delete target[meta.inspectable];
delete target[meta.inspected];
delete target[meta.name];
+ delete target[meta.preview_long];
+ delete target[meta.preview_short];
delete target[meta.readonly];
delete target[meta.size];
delete target[meta.type];
@@ -431,6 +369,8 @@ export function hydrate(
replaced[meta.inspectable] = !!value.inspectable;
replaced[meta.inspected] = false;
replaced[meta.name] = value.name;
+ replaced[meta.preview_long] = value.preview_long;
+ replaced[meta.preview_short] = value.preview_short;
replaced[meta.size] = value.size;
replaced[meta.readonly] = !!value.readonly;
replaced[meta.type] = value.type;
@@ -471,6 +411,16 @@ function upgradeUnserializable(destination: Object, source: Object) {
enumerable: false,
value: source.name,
},
+ [meta.preview_long]: {
+ configurable: true,
+ enumerable: false,
+ value: source.preview_long,
+ },
+ [meta.preview_short]: {
+ configurable: true,
+ enumerable: false,
+ value: source.preview_short,
+ },
[meta.size]: {
configurable: true,
enumerable: false,
@@ -495,48 +445,10 @@ function upgradeUnserializable(destination: Object, source: Object) {
delete destination.inspected;
delete destination.name;
+ delete destination.preview_long;
+ delete destination.preview_short;
delete destination.size;
delete destination.readonly;
delete destination.type;
delete destination.unserializable;
}
-
-export function getDisplayNameForReactElement(
- element: React$Element,
-): string | null {
- const elementType = typeOf(element);
- switch (elementType) {
- case AsyncMode:
- case ConcurrentMode:
- return 'ConcurrentMode';
- case ContextConsumer:
- return 'ContextConsumer';
- case ContextProvider:
- return 'ContextProvider';
- case ForwardRef:
- return 'ForwardRef';
- case Fragment:
- return 'Fragment';
- case Lazy:
- return 'Lazy';
- case Memo:
- return 'Memo';
- case Portal:
- return 'Portal';
- case Profiler:
- return 'Profiler';
- case StrictMode:
- return 'StrictMode';
- case Suspense:
- return 'Suspense';
- default:
- const {type} = element;
- if (typeof type === 'string') {
- return type;
- } else if (type != null) {
- return getDisplayName(type, 'Anonymous');
- } else {
- return 'Element';
- }
- }
-}
diff --git a/packages/react-devtools-shared/src/utils.js b/packages/react-devtools-shared/src/utils.js
index 5c22b92100b62..fc21e3ebdb5fd 100644
--- a/packages/react-devtools-shared/src/utils.js
+++ b/packages/react-devtools-shared/src/utils.js
@@ -9,6 +9,22 @@
import Symbol from 'es6-symbol';
import LRU from 'lru-cache';
+import {
+ isElement,
+ typeOf,
+ AsyncMode,
+ ConcurrentMode,
+ ContextConsumer,
+ ContextProvider,
+ ForwardRef,
+ Fragment,
+ Lazy,
+ Memo,
+ Portal,
+ Profiler,
+ StrictMode,
+ Suspense,
+} from 'react-is';
import {
TREE_OPERATION_ADD,
TREE_OPERATION_REMOVE,
@@ -28,6 +44,8 @@ import {
ElementTypeMemo,
} from 'react-devtools-shared/src/types';
import {localStorageGetItem, localStorageSetItem} from './storage';
+import {alphaSortEntries} from './devtools/views/utils';
+import {meta} from './hydration';
import type {ComponentFilter, ElementType} from './types';
@@ -305,3 +323,309 @@ export function setInObject(
}
}
}
+
+export type DataType =
+ | 'array'
+ | 'array_buffer'
+ | 'bigint'
+ | 'boolean'
+ | 'data_view'
+ | 'date'
+ | 'function'
+ | 'html_element'
+ | 'infinity'
+ | 'iterator'
+ | 'nan'
+ | 'null'
+ | 'number'
+ | 'object'
+ | 'react_element'
+ | 'string'
+ | 'symbol'
+ | 'typed_array'
+ | 'undefined'
+ | 'unknown';
+
+/**
+ * Get a enhanced/artificial type string based on the object instance
+ */
+export function getDataType(data: Object): DataType {
+ if (data === null) {
+ return 'null';
+ } else if (data === undefined) {
+ return 'undefined';
+ }
+
+ if (isElement(data)) {
+ return 'react_element';
+ }
+
+ if (typeof HTMLElement !== 'undefined' && data instanceof HTMLElement) {
+ return 'html_element';
+ }
+
+ const type = typeof data;
+ switch (type) {
+ case 'bigint':
+ return 'bigint';
+ case 'boolean':
+ return 'boolean';
+ case 'function':
+ return 'function';
+ case 'number':
+ if (Number.isNaN(data)) {
+ return 'nan';
+ } else if (!Number.isFinite(data)) {
+ return 'infinity';
+ } else {
+ return 'number';
+ }
+ case 'object':
+ if (Array.isArray(data)) {
+ return 'array';
+ } else if (ArrayBuffer.isView(data)) {
+ return data.constructor.hasOwnProperty('BYTES_PER_ELEMENT')
+ ? 'typed_array'
+ : 'data_view';
+ } else if (data.constructor.name === 'ArrayBuffer') {
+ // HACK This ArrayBuffer check is gross; is there a better way?
+ // We could try to create a new DataView with the value.
+ // If it doesn't error, we know it's an ArrayBuffer,
+ // but this seems kind of awkward and expensive.
+ return 'array_buffer';
+ } else if (typeof data[Symbol.iterator] === 'function') {
+ return 'iterator';
+ } else if (Object.prototype.toString.call(data) === '[object Date]') {
+ return 'date';
+ }
+ return 'object';
+ case 'string':
+ return 'string';
+ case 'symbol':
+ return 'symbol';
+ default:
+ return 'unknown';
+ }
+}
+
+export function getDisplayNameForReactElement(
+ element: React$Element,
+): string | null {
+ const elementType = typeOf(element);
+ switch (elementType) {
+ case AsyncMode:
+ case ConcurrentMode:
+ return 'ConcurrentMode';
+ case ContextConsumer:
+ return 'ContextConsumer';
+ case ContextProvider:
+ return 'ContextProvider';
+ case ForwardRef:
+ return 'ForwardRef';
+ case Fragment:
+ return 'Fragment';
+ case Lazy:
+ return 'Lazy';
+ case Memo:
+ return 'Memo';
+ case Portal:
+ return 'Portal';
+ case Profiler:
+ return 'Profiler';
+ case StrictMode:
+ return 'StrictMode';
+ case Suspense:
+ return 'Suspense';
+ default:
+ const {type} = element;
+ if (typeof type === 'string') {
+ return type;
+ } else if (type != null) {
+ return getDisplayName(type, 'Anonymous');
+ } else {
+ return 'Element';
+ }
+ }
+}
+
+const MAX_PREVIEW_STRING_LENGTH = 50;
+
+function truncateForDisplay(
+ string: string,
+ length: number = MAX_PREVIEW_STRING_LENGTH,
+) {
+ if (string.length > length) {
+ return string.substr(0, length) + '…';
+ } else {
+ return string;
+ }
+}
+
+// Attempts to mimic Chrome's inline preview for values.
+// For example, the following value...
+// {
+// foo: 123,
+// bar: "abc",
+// baz: [true, false],
+// qux: { ab: 1, cd: 2 }
+// };
+//
+// Would show a preview of...
+// {foo: 123, bar: "abc", baz: Array(2), qux: {…}}
+//
+// And the following value...
+// [
+// 123,
+// "abc",
+// [true, false],
+// { foo: 123, bar: "abc" }
+// ];
+//
+// Would show a preview of...
+// [123, "abc", Array(2), {…}]
+export function formatDataForPreview(
+ data: any,
+ showFormattedValue: boolean,
+): string {
+ if (data != null && data.hasOwnProperty(meta.type)) {
+ return showFormattedValue
+ ? data[meta.preview_long]
+ : data[meta.preview_short];
+ }
+
+ const type = getDataType(data);
+
+ switch (type) {
+ case 'html_element':
+ return `<${truncateForDisplay(data.tagName.toLowerCase())} />`;
+ case 'function':
+ return truncateForDisplay(data.name);
+ case 'string':
+ return `"${data}"`;
+ case 'bigint':
+ return truncateForDisplay(data.toString() + 'n');
+ case 'symbol':
+ return truncateForDisplay(data.toString());
+ case 'react_element':
+ return `<${truncateForDisplay(
+ getDisplayNameForReactElement(data) || 'Unknown',
+ )} />`;
+ case 'array_buffer':
+ return `ArrayBuffer(${data.byteLength})`;
+ case 'data_view':
+ return `DataView(${data.buffer.byteLength})`;
+ case 'array':
+ if (showFormattedValue) {
+ let formatted = '';
+ for (let i = 0; i < data.length; i++) {
+ if (i > 0) {
+ formatted += ', ';
+ }
+ formatted += formatDataForPreview(data[i], false);
+ if (formatted.length > MAX_PREVIEW_STRING_LENGTH) {
+ // Prevent doing a lot of unnecessary iteration...
+ break;
+ }
+ }
+ return `[${truncateForDisplay(formatted)}]`;
+ } else {
+ const length = data.hasOwnProperty(meta.size)
+ ? data[meta.size]
+ : data.length;
+ return `Array(${length})`;
+ }
+ case 'typed_array':
+ const shortName = `${data.constructor.name}(${data.length})`;
+ if (showFormattedValue) {
+ let formatted = '';
+ for (let i = 0; i < data.length; i++) {
+ if (i > 0) {
+ formatted += ', ';
+ }
+ formatted += data[i];
+ if (formatted.length > MAX_PREVIEW_STRING_LENGTH) {
+ // Prevent doing a lot of unnecessary iteration...
+ break;
+ }
+ }
+ return `${shortName} [${truncateForDisplay(formatted)}]`;
+ } else {
+ return shortName;
+ }
+ case 'iterator':
+ const name = data.constructor.name;
+ if (showFormattedValue) {
+ // TRICKY
+ // Don't use [...spread] syntax for this purpose.
+ // This project uses @babel/plugin-transform-spread in "loose" mode which only works with Array values.
+ // Other types (e.g. typed arrays, Sets) will not spread correctly.
+ const array = Array.from(data);
+
+ let formatted = '';
+ for (let i = 0; i < array.length; i++) {
+ const entryOrEntries = array[i];
+
+ if (i > 0) {
+ formatted += ', ';
+ }
+
+ // TRICKY
+ // Browsers display Maps and Sets differently.
+ // To mimic their behavior, detect if we've been given an entries tuple.
+ // Map(2) {"abc" => 123, "def" => 123}
+ // Set(2) {"abc", 123}
+ if (Array.isArray(entryOrEntries)) {
+ const key = formatDataForPreview(entryOrEntries[0], true);
+ const value = formatDataForPreview(entryOrEntries[1], false);
+ formatted += `${key} => ${value}`;
+ } else {
+ formatted += formatDataForPreview(entryOrEntries, false);
+ }
+
+ if (formatted.length > MAX_PREVIEW_STRING_LENGTH) {
+ // Prevent doing a lot of unnecessary iteration...
+ break;
+ }
+ }
+
+ return `${name}(${data.size}) {${truncateForDisplay(formatted)}}`;
+ } else {
+ return `${name}(${data.size})`;
+ }
+ case 'date':
+ return data.toString();
+ case 'object':
+ if (showFormattedValue) {
+ const keys = Object.keys(data).sort(alphaSortEntries);
+
+ let formatted = '';
+ for (let i = 0; i < keys.length; i++) {
+ const key = keys[i];
+ if (i > 0) {
+ formatted += ', ';
+ }
+ formatted += `${key}: ${formatDataForPreview(data[key], false)}`;
+ if (formatted.length > MAX_PREVIEW_STRING_LENGTH) {
+ // Prevent doing a lot of unnecessary iteration...
+ break;
+ }
+ }
+ return `{${truncateForDisplay(formatted)}}`;
+ } else {
+ return '{…}';
+ }
+ case 'boolean':
+ case 'number':
+ case 'infinity':
+ case 'nan':
+ case 'null':
+ case 'undefined':
+ return data;
+ default:
+ try {
+ return truncateForDisplay('' + data);
+ } catch (error) {
+ return 'unserializable';
+ }
+ }
+}
diff --git a/packages/react-devtools-shell/src/app/InspectableElements/UnserializableProps.js b/packages/react-devtools-shell/src/app/InspectableElements/UnserializableProps.js
index cd6ecc9d1c230..effe7f14a348f 100644
--- a/packages/react-devtools-shell/src/app/InspectableElements/UnserializableProps.js
+++ b/packages/react-devtools-shell/src/app/InspectableElements/UnserializableProps.js
@@ -15,6 +15,8 @@ const map = new Map([['name', 'Brian'], ['food', 'sushi']]);
const setOfSets = new Set([new Set(['a', 'b', 'c']), new Set([1, 2, 3])]);
const mapOfMaps = new Map([['first', map], ['second', map]]);
const typedArray = Int8Array.from([100, -100, 0]);
+const arrayBuffer = typedArray.buffer;
+const dataView = new DataView(arrayBuffer);
const immutable = Immutable.fromJS({
a: [{hello: 'there'}, 'fixed', true],
b: 123,
@@ -27,6 +29,8 @@ const immutable = Immutable.fromJS({
export default function UnserializableProps() {
return (