Skip to content

Commit 66b4656

Browse files
committed
Fix the backend. Now we can render, state, props & context data for both React, Redux & ReactHook
1 parent 3caab37 commit 66b4656

File tree

3 files changed

+90
-134
lines changed

3 files changed

+90
-134
lines changed

src/app/components/StateRoute/ComponentMap/ComponentMap.tsx

Lines changed: 15 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -20,71 +20,6 @@ import getLinkComponent from './getLinkComponent';
2020
import { toggleExpanded, setCurrentTabInApp } from '../../../actions/actions';
2121
import { useStoreContext } from '../../../store';
2222

23-
// const exclude = [
24-
// 'childExpirationTime',
25-
// 'staticContext',
26-
// '_debugSource',
27-
// 'actualDuration',
28-
// 'actualStartTime',
29-
// 'treeBaseDuration',
30-
// '_debugID',
31-
// '_debugIsCurrentlyTiming',
32-
// 'selfBaseDuration',
33-
// 'expirationTime',
34-
// 'effectTag',
35-
// 'alternate',
36-
// '_owner',
37-
// '_store',
38-
// 'get key',
39-
// 'ref',
40-
// '_self',
41-
// '_source',
42-
// 'firstBaseUpdate',
43-
// 'updateQueue',
44-
// 'lastBaseUpdate',
45-
// 'shared',
46-
// 'responders',
47-
// 'pending',
48-
// 'lanes',
49-
// 'childLanes',
50-
// 'effects',
51-
// 'memoizedState',
52-
// 'pendingProps',
53-
// 'lastEffect',
54-
// 'firstEffect',
55-
// 'tag',
56-
// 'baseState',
57-
// 'baseQueue',
58-
// 'dependencies',
59-
// 'Consumer',
60-
// 'context',
61-
// '_currentRenderer',
62-
// '_currentRenderer2',
63-
// 'mode',
64-
// 'flags',
65-
// 'nextEffect',
66-
// 'sibling',
67-
// 'create',
68-
// 'deps',
69-
// 'next',
70-
// 'destroy',
71-
// 'parentSub',
72-
// 'child',
73-
// 'key',
74-
// 'return',
75-
// 'children',
76-
// '$$typeof',
77-
// '_threadCount',
78-
// '_calculateChangedBits',
79-
// '_currentValue',
80-
// '_currentValue2',
81-
// 'Provider',
82-
// '_context',
83-
// 'stateNode',
84-
// 'elementType',
85-
// 'type',
86-
// ];
87-
8823
const defaultMargin = {
8924
top: 30,
9025
left: 30,
@@ -187,38 +122,13 @@ export default function ComponentMap({
187122
return `${renderTime} ms `;
188123
};
189124

190-
const formatProps = (data) => {
191-
console.log('ComponentMap', { data });
192-
const propsFormat = [];
193-
// const nestedObj = [];
194-
for (const key in data) {
195-
if (
196-
// data[key] !== 'reactFiber' &&
197-
typeof data[key] !== 'object'
198-
// exclude.includes(key) !== true
199-
) {
200-
propsFormat.push(<p className='stateprops'>{`${key}: ${data[key]}`}</p>);
201-
}
202-
// else if (
203-
// data[key] !== 'reactFiber' &&
204-
// typeof data[key] === 'object'
205-
// exclude.includes(key) !== true
206-
// ) {
207-
// const result = formatProps(data[key]);
208-
// nestedObj.push(result);
209-
// }
210-
}
211-
// if (nestedObj) {
212-
// propsFormat.push(nestedObj);
213-
// }
214-
if (propsFormat.length) return propsFormat;
215-
};
216-
217-
const formatContext = (data) => {
125+
const formatData = (data, type) => {
218126
const contextFormat = [];
219127
for (const key in data) {
220128
// Suggestion: update the front end to display as a list if we have object
221-
contextFormat.push(<p className='statecontext'>{`${key}: ${data[key]}`}</p>);
129+
contextFormat.push(
130+
<p className={`${type}-item`}>{`${key}: ${JSON.stringify(data[key])}`}</p>,
131+
);
222132
}
223133
return contextFormat;
224134
};
@@ -437,14 +347,18 @@ export default function ComponentMap({
437347
<div style={React.scrollStyle}>
438348
<div className='tooltipWrapper'>
439349
<h2>Props:</h2>
440-
{formatProps(tooltipData.componentData.props)}
350+
{formatData(tooltipData.componentData.props, 'props')}
351+
</div>
352+
353+
<div className='tooltipWrapper'>
354+
<h2>Context:</h2>
355+
{formatData(tooltipData.componentData.context, 'context')}
356+
</div>
357+
358+
<div className='tooltipWrapper'>
359+
<h2>State:</h2>
360+
{formatData(tooltipData.componentData.state, 'state')}
441361
</div>
442-
{tooltipData.componentData.context && (
443-
<div className='tooltipWrapper'>
444-
<h2>Context:</h2>
445-
{formatContext(tooltipData.componentData.context)}
446-
</div>
447-
)}
448362
</div>
449363
</div>
450364
</TooltipInPortal>

src/backend/linkFiber.ts

Lines changed: 74 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ function updateSnapShotTree(snap: Snapshot, mode: Status): void {
133133

134134
/**
135135
* @function traverseHooks
136-
* @param memoizedState memoizedState property on a stateful functional component's FiberNode object
136+
* @param memoizedState - The current state of the component associated with the current Fiber node.
137137
* @return An array of array of HookStateItem objects
138138
*
139139
* Helper function to traverse through memoizedState and inject instrumentation to update our state tree
@@ -265,37 +265,60 @@ function convertDataToString(reactDevData, reactimeData = {}, excludeSet?: any)
265265
}
266266
return reactimeData;
267267
}
268-
// ------------------------TRIMMING STATE DATA--------------------------------
269-
function trimContextData(memoizedState, _debugHookTypes) {
268+
// ---------------------------TRIMMING STATE DATA-------------------------------
269+
/**
270+
* This function is used to trim the state data of a component.
271+
* All propperty name that are in the central exclude list would be trimmed off.
272+
* If passed in memoizedState is a not an object (a.k.a a primitive type), a default name would be provided.
273+
* @param memoizedState - The current state of the component associated with the current Fiber node.
274+
* @param {string[]} _debugHookTypes - An array of hooks used for debugging purposes.
275+
* @param componentName - Name of the evaluated component
276+
* @returns - The updated state data object to send to front end of ReactTime
277+
*/
278+
function trimContextData(memoizedState, componentName, _debugHookTypes: string[]) {
279+
// Initialize a list of componentName that would not be evaluated for State Data:
280+
const ignoreComponent = new Set(['BrowserRouter', 'Router']);
281+
if (ignoreComponent.has(componentName)) return;
282+
270283
// Initialize object to store state data of the component
271284
const reactimeStateData = {};
272-
// Initialize the set of excluded properties:
273-
const exclude = new Set(['children', 'store', 'subscription']);
274-
// Initialize the set of React Hooks:
285+
// Initialize the set of interested React Hooks:
275286
const reactHooks = new Set(['useState', 'useMemo']);
287+
// Initilize the set of React Hooks that do not generate a state:
288+
const noStateReactHooks = new Set(['useContext']);
276289
// Initialize counter for the default naming. If user use reactHook, such as useState, react will only pass in the value, and not the name of the state.
277290
let stateCounter = 1;
291+
let refCounter = 1;
292+
293+
// Loop through each hook inside the _debugHookTypes array.
294+
// NOTE: _debugHookTypes.length === height of memoizedState tree.
278295
for (const hook of _debugHookTypes) {
279-
if (reactHooks.has(hook)) {
280-
let stateData = memoizedState?.memoizedState;
281-
// ReactHook condition:
282-
if (typeof stateData !== 'object') {
283-
const defaultName = `state${stateCounter}`;
284-
stateData = { [defaultName]: stateData };
285-
stateCounter++;
286-
}
287-
// If user does not use reactHook => state is store in memoizedState array, at i = 0
288-
else {
289-
stateData = stateData[0];
290-
}
291-
//Trim the current level of memoizedState data:
292-
console.log({ stateData });
293-
convertDataToString(stateData, reactimeStateData);
296+
// useContext does not create any state => skip
297+
if (hook === 'useContext') {
298+
continue;
299+
}
300+
// If user use useState reactHook => React will only pass in the value of state & not the name of the state => create a default name:
301+
else if (hook === 'useState') {
302+
const defaultName = `State ${stateCounter}`;
303+
reactimeStateData[defaultName] = memoizedState.memoizedState;
304+
stateCounter++;
305+
}
306+
// If user use useRef reactHook => React will store memoizedState in current object:
307+
else if (hook === 'useRef') {
308+
const defaultName = `Ref ${refCounter}`;
309+
reactimeStateData[defaultName] = memoizedState.memoizedState.current;
310+
refCounter++;
311+
}
312+
// If user use Redux to contain their context => the context object will be stored using useMemo Hook, as of for Rect Dev Tool v4.27.2
313+
// Note: Provider is not a reserved component name for redux. User may name their component as Provider, which will break this logic. However, it is a good assumption that if user have a custom provider component, it would have a more specific naming such as ThemeProvider.
314+
else if (hook === 'useMemo' && componentName === 'Provider') {
315+
convertDataToString(memoizedState.memoizedState[0], reactimeStateData);
294316
}
295-
//Move on to the next level:
317+
console.log('StateData', reactimeStateData);
318+
//Move on to the next level of memoizedState tree.
296319
memoizedState = memoizedState?.next;
297320
}
298-
321+
// Return the updated state data object to send to front end of ReactTime
299322
return reactimeStateData;
300323
}
301324
// -------------------------CREATE TREE TO SEND TO FRONT END--------------------
@@ -381,8 +404,12 @@ function createTree(
381404
selfBaseDuration?: number;
382405
treeBaseDuration?: number;
383406
props?: any;
384-
context?: any;
385-
} = {};
407+
context: {};
408+
state: {};
409+
} = {
410+
context: {},
411+
state: {},
412+
};
386413
let componentFound = false;
387414

388415
/**
@@ -399,23 +426,37 @@ function createTree(
399426
}
400427

401428
// ----------------APPEND STATE DATA FROM REACT DEV TOOL----------------------
402-
// If user uses Redux, context data will be stored in memoizedState of the component => grab context object stored in the memoizedState
403-
if (
404-
(tag === FunctionComponent || tag === ClassComponent) &&
405-
Array.isArray(memoizedState?.memoizedState)
406-
) {
407-
componentData.context = trimContextData(memoizedState, _debugHookTypes);
429+
if (stateNode?.state) {
430+
Object.assign(componentData.state, stateNode.state);
431+
}
432+
if ((tag === FunctionComponent || tag === ClassComponent) && memoizedState?.memoizedState) {
433+
// If user uses Redux, context data will be stored in memoizedState of the Provider component => grab context object stored in the memoizedState
434+
if (elementType.name === 'Provider') {
435+
Object.assign(
436+
componentData.context,
437+
trimContextData(memoizedState, elementType.name, _debugHookTypes),
438+
);
439+
}
440+
// Else if user use ReactHook to define state => all states will be stored in memoizedState => grab all states stored in the memoizedState
441+
else {
442+
Object.assign(
443+
componentData.state,
444+
trimContextData(memoizedState, elementType.name, _debugHookTypes),
445+
);
446+
}
408447
}
409448
// if user uses useContext hook, context data will be stored in memoizedProps.value of the Context.Provider component => grab context object stored in memoizedprops
410449
// Different from other provider, such as Routes, BrowswerRouter, ReactRedux, ..., Context.Provider does not have a displayName
450+
// TODO: need to render this context provider when user use usContext hook.
411451
if (tag === ContextProvider && !elementType._context.displayName) {
412452
let stateData = memoizedProps.value;
413453
if (stateData === null || typeof stateData !== 'object') {
414454
stateData = { CONTEXT: stateData };
415455
}
416-
componentData.context = stateData;
456+
componentData.context = convertDataToString(stateData);
417457
}
418458

459+
// DEPRECATED: This code worked previously. However, with the update of React Dev Tool, context can no longer be pulled using this method.
419460
// Check to see if the component has any context:
420461
// if the component uses the useContext hook, we want to grab the context object and add it to the componentData object for that fiber
421462
// if (tag === FunctionComponent && _debugHookTypes && dependencies?.firstContext?.memoizedValue) {
@@ -487,6 +528,7 @@ function createTree(
487528
};
488529
console.log('props', componentData.props);
489530
console.log('context', componentData.context);
531+
console.log('state', componentData.state);
490532
let newNode = null;
491533

492534
// We want to add this fiber node to the snapshot

src/backend/types/backendTypes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,5 +239,5 @@ export type Fiber = {
239239

240240
dependencies: any;
241241

242-
_debugHookTypes: any;
242+
_debugHookTypes: string[];
243243
};

0 commit comments

Comments
 (0)