@samitouri / QOS-React-2 / commits / a2c79f2d9e

Fixed hydration for events meta data

Brian Vaughn committed Jun 18, 2019 at 10:50 UTC a2c79f2d9e4ec0a631859e226ce91a824892e62c
4 files changed +96 -29
src/backend/renderer.js
+41 -22
@@ -2149,25 +2149,37 @@ export function attach(
2149 });
2150 }
2151
2152 - function createIsPathWhitelisted(isHooksPath: boolean, key: string | null) {
2152 + function createIsPathWhitelisted(
2153 + key: string | null,
2154 + secondaryCategory: 'events' | 'hooks' | null
2155 + ) {
2156 // This function helps prevent previously-inspected paths from being dehydrated in updates.
2157 // This is important to avoid a bad user experience where expanded toggles collapse on update.
2158 return function isPathWhitelisted(path: Array<string | number>): boolean {
2156 - // Dehydrating the 'subHooks' property makes the HooksTree UI a lot more complicated,
2157 - // so it's easiest for now if we just don't break on this boundary.
2158 - // We can always dehydrate a level deeper (in the value object).
2159 - if (isHooksPath) {
2160 - if (path.length === 1) {
2161 - // Never dehydrate the hooks object at the top level.
2162 - return true;
2163 - }
2164 - if (
2165 - path[path.length - 1] === 'subHooks' ||
2166 - path[path.length - 2] === 'subHooks'
2167 - ) {
2168 - // Never dehydrate the subHooks array
2169 - return true;
2170 - }
2159 + switch (secondaryCategory) {
2160 + case 'events':
2161 + if (path.length <= 2) {
2162 + // Never dehydrate the "hooks" object at the top level (becaues it's always just an array).
2163 + return true;
2164 + }
2165 + break;
2166 + case 'hooks':
2167 + if (path.length === 1) {
2168 + // Never dehydrate the "hooks" object at the top levels.
2169 + return true;
2170 + }
2171 + if (
2172 + path[path.length - 1] === 'subHooks' ||
2173 + path[path.length - 2] === 'subHooks'
2174 + ) {
2175 + // Dehydrating the 'subHooks' property makes the HooksTree UI a lot more complicated,
2176 + // so it's easiest for now if we just don't break on this boundary.
2177 + // We can always dehydrate a level deeper (in the value object).
2178 + return true;
2179 + }
2180 + break;
2181 + default:
2182 + break;
2183 }
2184
2185 let current =
@@ -2195,6 +2207,13 @@ export function attach(
2207 if (path != null) {
2208 mergeInspectedPaths(path);
2209
2210 + let secondaryCategory = null;
2211 + if (path[0] === 'events') {
2212 + secondaryCategory = 'events';
2213 + } else if (path[0] === 'hooks') {
2214 + secondaryCategory = 'hooks';
2215 + }
2216 +
2217 // If this element has not been updated since it was last inspected,
2218 // we can just return the subset of data in the newly-inspected path.
2219 return {
@@ -2206,7 +2225,7 @@ export function attach(
2225 ((mostRecentlyInspectedElement: any): InspectedElement),
2226 path
2227 ),
2209 - createIsPathWhitelisted(path[0] === 'hooks', null),
2228 + createIsPathWhitelisted(null, secondaryCategory),
2229 path
2230 ),
2231 };
@@ -2246,23 +2265,23 @@ export function attach(
2265 const cleanedInspectedElement = { ...mostRecentlyInspectedElement };
2266 cleanedInspectedElement.context = cleanForBridge(
2267 cleanedInspectedElement.context,
2249 - createIsPathWhitelisted(false, 'context')
2268 + createIsPathWhitelisted('context', null)
2269 );
2270 cleanedInspectedElement.events = cleanForBridge(
2271 cleanedInspectedElement.events,
2253 - createIsPathWhitelisted(false, 'events')
2272 + createIsPathWhitelisted('events', 'events')
2273 );
2274 cleanedInspectedElement.hooks = cleanForBridge(
2275 cleanedInspectedElement.hooks,
2257 - createIsPathWhitelisted(true, 'hooks')
2276 + createIsPathWhitelisted('hooks', 'hooks')
2277 );
2278 cleanedInspectedElement.props = cleanForBridge(
2279 cleanedInspectedElement.props,
2261 - createIsPathWhitelisted(false, 'props')
2280 + createIsPathWhitelisted('props', null)
2281 );
2282 cleanedInspectedElement.state = cleanForBridge(
2283 cleanedInspectedElement.state,
2265 - createIsPathWhitelisted(false, 'state')
2284 + createIsPathWhitelisted('state', null)
2285 );
2286
2287 return {
src/devtools/views/Components/EventsTree.css
+4
@@ -20,6 +20,10 @@
20 display: flex;
21 }
22
23 +.Name {
24 + user-select: none;
25 +}
26 +
27 .Empty {
28 color: var(--color-dimmer);
29 font-style: italic;
src/devtools/views/Components/EventsTree.js
+44 -6
@@ -1,3 +1,5 @@
1 +// @flow
2 +
3 import { copy } from 'clipboard-js';
4 import React, { useCallback, useState } from 'react';
5 import styles from './EventsTree.css';
@@ -7,15 +9,32 @@ import KeyValue from './KeyValue';
9 import ExpandCollapseToggle from './ExpandCollapseToggle';
10 import { serializeDataForCopy } from '../utils';
11
10 -type Props = {|
12 +import type { GetInspectedElementPath } from './InspectedElementContext';
13 +
14 +type InspectPath = (path: Array<string | number>) => void;
15 +
16 +type EventsTreeViewProps = {|
17 events: Object,
18 + getInspectedElementPath: GetInspectedElementPath,
19 + id: number,
20 |};
21
14 -function EventsTreeView({ events }: Props) {
22 +function EventsTreeView({
23 + events,
24 + getInspectedElementPath,
25 + id,
26 +}: EventsTreeViewProps) {
27 const handleCopy = useCallback(() => copy(serializeDataForCopy(events)), [
28 events,
29 ]);
30
31 + const inspectPath = useCallback(
32 + (path: Array<string | number>) => {
33 + getInspectedElementPath(id, ['events', ...path]);
34 + },
35 + [getInspectedElementPath, id]
36 + );
37 +
38 return (
39 <div className={styles.EventsTree}>
40 <div className={styles.HeaderRow}>
@@ -26,16 +45,26 @@ function EventsTreeView({ events }: Props) {
45 </Button>
46 }
47 </div>
29 - <InnerEventsTreeView events={events} />
48 + <InnerEventsTreeView events={events} inspectPath={inspectPath} />
49 </div>
50 );
51 }
52
34 -function InnerEventsTreeView({ events }: Props) {
53 +type InnerEventsTreeViewProps = {|
54 + events: Object,
55 + inspectPath: InspectPath,
56 +|};
57 +
58 +function InnerEventsTreeView({
59 + events,
60 + inspectPath,
61 +}: InnerEventsTreeViewProps) {
62 return events.map((event, index) => (
63 <EventComponentView
64 key={index}
65 displayName={event.displayName}
66 + index={index}
67 + inspectPath={inspectPath}
68 props={event.props}
69 />
70 ));
@@ -43,11 +72,19 @@ function InnerEventsTreeView({ events }: Props) {
72
73 type EventComponentViewProps = {|
74 displayName: string,
75 + index: number,
76 + inspectPath: InspectPath,
77 props: null | Object,
78 |};
79
49 -function EventComponentView({ displayName, props }: EventComponentViewProps) {
80 +function EventComponentView({
81 + displayName,
82 + index,
83 + inspectPath,
84 + props,
85 +}: EventComponentViewProps) {
86 const [isOpen, setIsOpen] = useState(false);
87 +
88 let eventComponentProps = null;
89 // eslint-disable-next-line no-unused-vars
90 let children;
@@ -74,8 +111,9 @@ function EventComponentView({ displayName, props }: EventComponentViewProps) {
111 <KeyValue
112 key={name}
113 depth={1}
114 + inspectPath={inspectPath}
115 name={name}
78 - path={[name]}
116 + path={[index]}
117 value={(eventComponentProps: any)[name]}
118 />
119 ))}
src/devtools/views/Components/SelectedElement.js
+7 -1
@@ -336,7 +336,13 @@ function InspectedElementView({
336 inspectPath={inspectContextPath}
337 overrideValueFn={overrideContextFn}
338 />
339 - {events !== null && events.length > 0 && <EventsTree events={events} />}
339 + {events !== null && events.length > 0 && (
340 + <EventsTree
341 + events={events}
342 + getInspectedElementPath={getInspectedElementPath}
343 + id={id}
344 + />
345 + )}
346
347 {ownerID === null && owners !== null && owners.length > 0 && (
348 <div className={styles.Owners}>