Skip to content

Commit e95923c

Browse files
committed
class state and funcitonal states rendering correctly on hover
1 parent c29ac21 commit e95923c

File tree

5 files changed

+80
-89
lines changed

5 files changed

+80
-89
lines changed

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

Lines changed: 27 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ const nodeParentFill = '#161521'; //#161521 original
2828
const nodeChildFill = '#62d6fb'; //#62d6fb original
2929
const nodeParentStroke = '#F00008'; //#F00008 original
3030
const nodeChildStroke = '#4D4D4D'; //#4D4D4D original
31-
let stroke = '';
31+
let stroke = '';
3232

3333
/* Heat Map Colors (for links) */
3434
const lightOrange = '#F1B476';
@@ -196,7 +196,7 @@ export default function ComponentMap({
196196
linkType,
197197
orientation,
198198
});
199-
return totalWidth < 10 ? null : (
199+
return totalWidth < 10 ? null : (
200200
<div>
201201
<LinkControls
202202
layout={layout}
@@ -231,11 +231,10 @@ export default function ComponentMap({
231231
size={[sizeWidth / aspect, sizeHeight / aspect]}
232232
separation={(a, b) => (a.parent === b.parent ? 0.5 : 0.5) / a.depth}
233233
>
234-
235234
{(tree) => (
236235
<Group top={origin.y + 35} left={origin.x + 50 / aspect}>
237236
{tree.links().map((link, i) => {
238-
const linkName = link.source.data.name;
237+
const linkName = link.source.data.name;
239238
const propsObj = link.source.data.componentData.props;
240239
const childPropsObj = link.target.data.componentData.props;
241240
let propsLength;
@@ -246,15 +245,15 @@ export default function ComponentMap({
246245
}
247246
if (childPropsObj) {
248247
childPropsLength = Object.keys(childPropsObj).length;
249-
250248
}
251-
// go to https://en.wikipedia.org/wiki/Logistic_function
249+
// go to https://en.wikipedia.org/wiki/Logistic_function
252250
// for an explanation of Logistic functions and parameters used
253251
const yshift = -3;
254252
const x0 = 5;
255253
const L = 25;
256-
const k = .4;
257-
const strokeWidthIndex = yshift + L / (1 + Math.exp(-k * (childPropsLength - x0)));
254+
const k = 0.4;
255+
const strokeWidthIndex =
256+
yshift + L / (1 + Math.exp(-k * (childPropsLength - x0)));
258257

259258
if (strokeWidthIndex <= 1) {
260259
stroke = '#808080';
@@ -272,18 +271,17 @@ export default function ComponentMap({
272271
}
273272

274273
return (
275-
<LinkComponent
276-
className='compMapLink'
277-
key={i}
278-
data={link}
279-
percent={stepPercent}
280-
stroke={stroke} // color of the link --not used--
281-
strokeWidth= {strokeWidthIndex} /* strokeWidth */ // width of the link
282-
fill='none'
283-
/>
284-
)
285-
})
286-
}
274+
<LinkComponent
275+
className='compMapLink'
276+
key={i}
277+
data={link}
278+
percent={stepPercent}
279+
stroke={stroke} // color of the link --not used--
280+
strokeWidth={strokeWidthIndex} /* strokeWidth */ // width of the link
281+
fill='none'
282+
/>
283+
);
284+
})}
287285

288286
{tree.descendants().map((node, key) => {
289287
const widthFunc: number = (name) => {
@@ -496,20 +494,14 @@ export default function ComponentMap({
496494
</svg>
497495
{tooltipOpen && tooltipData && (
498496
<TooltipInPortal
499-
// set this to random so it correctly updates with parent bounds
500497
key={Math.random()}
501498
top={tooltipTop}
502499
left={tooltipLeft}
503500
style={tooltipStyles}
504-
//------------- Mouse Over TooltipInPortal--------------------------------------------------------------------
505-
/** After the mouse enters the tooltip, it's able to persist by clearing the setTimeout
506-
* that would've unmounted it */
507501
onMouseEnter={() => {
508502
clearTimeout(toolTipTimeoutID.current);
509503
toolTipTimeoutID.current = null;
510504
}}
511-
//------------- Mouse Leave TooltipInPortal -----------------------------------------------------------------
512-
/** When the mouse leaves the tooltip, the tooltip unmounts */
513505
onMouseLeave={() => {
514506
hideTooltip();
515507
}}
@@ -518,21 +510,22 @@ export default function ComponentMap({
518510
<div>
519511
<strong>{tooltipData.name}</strong>
520512
</div>
521-
<div className='tooltipKey'>
522-
Key: {tooltipData.componentData.key !== null ? tooltipData.componentData.key : 'null'}
523-
</div>
524-
<div> Render time: {formatRenderTime(tooltipData.componentData.actualDuration)} </div>
525-
526513
<div>
527514
<ToolTipDataDisplay containerName='Props' dataObj={tooltipData.componentData.props} />
528515
<ToolTipDataDisplay
529516
containerName='State'
530517
dataObj={
531-
tooltipData.componentData.hooksIndex
532-
? tooltipData.componentData.hooksState
533-
: tooltipData.componentData.state
518+
tooltipData.componentData.state || // for class components
519+
tooltipData.componentData.hooksState // for functional components
534520
}
535521
/>
522+
{/* Add this new container for reducer state */}
523+
{tooltipData.componentData.reducerState && (
524+
<ToolTipDataDisplay
525+
containerName='Reducer State'
526+
dataObj={tooltipData.componentData.reducerState}
527+
/>
528+
)}
536529
</div>
537530
</div>
538531
</TooltipInPortal>

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

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -28,42 +28,41 @@ const colors = {
2828
};
2929

3030
const ToolTipDataDisplay = ({ containerName, dataObj }) => {
31-
const printableObject = {}; // The key:value properties of printableObject will be rendered in the JSON Tree
31+
const printableObject = {};
3232

3333
if (!dataObj) {
34-
// If state is null rather than an object, print "State: null" in tooltip
3534
printableObject[containerName] = dataObj;
3635
} else {
37-
/*
38-
Props often contain circular references.
39-
Messages from the backend must be sent as JSON objects (strings).
40-
JSON objects can't contain circular ref's, so the backend filters out problematic values by stringifying the values of object properties and ignoring any values that fail the conversion due to a circular ref. The following logic converts these values back to JS so they display clearly and are collapsible.
41-
*/
4236
const data = {};
43-
for (const key in dataObj) {
44-
if (typeof dataObj[key] === 'string') {
45-
try {
46-
data[key] = JSON.parse(dataObj[key]);
47-
} catch {
37+
38+
// Handle reducer state if present
39+
if (containerName === 'State' && dataObj.reducerState) {
40+
printableObject[containerName] = dataObj.reducerState;
41+
}
42+
// Otherwise handle normal state/props
43+
else {
44+
for (const key in dataObj) {
45+
if (typeof dataObj[key] === 'string') {
46+
try {
47+
data[key] = JSON.parse(dataObj[key]);
48+
} catch {
49+
data[key] = dataObj[key];
50+
}
51+
} else {
4852
data[key] = dataObj[key];
4953
}
50-
} else {
51-
data[key] = dataObj[key];
5254
}
55+
printableObject[containerName] = data;
5356
}
54-
/*
55-
Adds container name (State, Props, future different names for hooks) at top of object so everything nested in it will collapse when you click on it.
56-
*/
57-
printableObject[containerName] = data;
5857
}
5958

6059
return (
6160
<div className='tooltipData' key={`${containerName}-data-container`}>
6261
<JSONTree
63-
data={printableObject} // data to be rendered, a snapshot object
64-
theme={{ extend: colors, tree: () => ({ className: `tooltipData-JSONTree` }) }} // theme set to a base16 theme that has been extended to include "className: 'json-tree'"
65-
shouldExpandNodeInitially={() => true} // determines if node should be expanded when it first renders (root is expanded by default)
66-
hideRoot={true} // hides the root node
62+
data={printableObject}
63+
theme={{ extend: colors, tree: () => ({ className: `tooltipData-JSONTree` }) }}
64+
shouldExpandNodeInitially={() => true}
65+
hideRoot={true}
6766
/>
6867
</div>
6968
);

src/backend/controllers/createTree.ts

Lines changed: 16 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -193,57 +193,41 @@ export default function createTree(currentFiberNode: Fiber): Tree {
193193
// If user use setState to define/manage state, the state object will be stored in stateNode.state => grab the state object stored in the stateNode.state
194194
// Example: for tic-tac-toe demo-app: Board is a stateful component that use setState to store state data.
195195
if ((tag === ClassComponent || tag === IndeterminateComponent) && stateNode?.state) {
196-
// Save component's state and setState() function to our record for future time-travel state changing. Add record index to snapshot so we can retrieve.
197196
componentData.index = componentActionsRecord.saveNew(stateNode);
198-
// Save state information in componentData.
199197
componentData.state = stateNode.state;
200-
// Pass to front end
201198
newState = componentData.state;
202199
}
203200

204201
// --------OBTAIN STATE & DISPATCH METHODS FROM FUNCTIONAL COMPONENT--------
205202
// Check if currentFiberNode is a stateful functional component when user use useState hook.
206203
// If user use useState to define/manage state, the state object will be stored in memoizedState => grab the state object & its update method (dispatch) from memoizedState
207204
// Example: for Stateful buttons demo-app: Increment is a stateful component that use useState hook to store state data.
208-
if (
209-
(tag === FunctionComponent ||
210-
tag === IndeterminateComponent ||
211-
//TODO: Need reasoning for why we evaluate context provider
212-
/**
213-
* So far I haven't seen a case where hook data is stored for ContextProviders in memoized state. So far
214-
* I've seen some data a non-null memoize state on browser router, but queue is null. Routes has some good info on memoized props,
215-
* but that's not being addressed here. useContext providers also have null for memoized state.
216-
*/
217-
tag === ContextProvider) &&
218-
memoizedState
219-
) {
220-
if (memoizedState) {
221-
console.log('memoizedState structure:', {
222-
queue: memoizedState.queue,
223-
state: memoizedState.memoizedState,
224-
next: memoizedState.next,
225-
});
226-
}
205+
// Inside the _createTree function where we handle functional components
206+
if ((tag === FunctionComponent || tag === IndeterminateComponent) && memoizedState) {
227207
if (memoizedState.queue) {
228208
try {
229-
// Obtain all hooksStates & the corresponding udpate method from memoizedState
230209
const hooksStates = getHooksStateAndUpdateMethod(memoizedState);
231-
// Obtain variable names by parsing the function definition stored in elementType.
232-
console.log('Component definition:', elementType.toString());
210+
// Get the hooks names by parsing the elementType
233211
const hooksNames = getHooksNames(elementType.toString());
234-
console.log('Extracted hook names:', hooksNames);
235-
// Intialize state & index:
212+
236213
componentData.hooksState = {};
214+
componentData.reducerState = null;
237215
componentData.hooksIndex = [];
238216

239-
hooksStates.forEach(({ state, component }, i) => {
240-
// Save component's state and dispatch() function to our record for future time-travel state changing. Add record index to snapshot so we can retrieve.
217+
hooksStates.forEach(({ state, component, isReducer }, i) => {
241218
componentData.hooksIndex.push(componentActionsRecord.saveNew(component));
242-
// Save state information in componentData.
243-
componentData.hooksState[hooksNames[i].varName] = state;
219+
220+
if (isReducer) {
221+
// If it's a reducer, store its state
222+
componentData.reducerState = state;
223+
} else {
224+
// Otherwise treat as useState
225+
componentData.hooksState[hooksNames[i]?.varName || `state${i}`] = state;
226+
}
244227
});
228+
245229
// Pass to front end
246-
newState = componentData.hooksState;
230+
newState = componentData.reducerState || componentData.hooksState;
247231
} catch (err) {
248232
console.log('Error extracting functional component state:', {
249233
componentName,

src/backend/controllers/statePropExtractors.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,20 @@ export function getHooksStateAndUpdateMethod(
6969
): Array<HookStateItem> {
7070
const hooksStates: Array<HookStateItem> = [];
7171
while (memoizedState) {
72-
if (memoizedState.queue) {
72+
// Check for useReducer hook
73+
if (
74+
memoizedState.queue &&
75+
memoizedState.memoizedState &&
76+
typeof memoizedState.queue.dispatch === 'function'
77+
) {
78+
hooksStates.push({
79+
component: memoizedState.queue,
80+
state: memoizedState.memoizedState,
81+
isReducer: true,
82+
});
83+
}
84+
// Existing useState check
85+
else if (memoizedState.queue) {
7386
hooksStates.push({
7487
component: memoizedState.queue,
7588
state: memoizedState.memoizedState,

src/backend/types/backendTypes.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ export interface ComponentData {
6767
index: number | null;
6868
/** {functional component only} - An object contains all states of the current functional component */
6969
hooksState: {} | null;
70+
reducerState?: {} | null;
7071
/** {functional component only} - An array of index of the bound dispatch method stored in `componentActionsRecord` */
7172
hooksIndex: number[] | null;
7273
/** An object contains all props of the current component */
@@ -88,6 +89,7 @@ export interface HookStateItem {
8889
state: any;
8990
/** an object contains bound dispatch method to update state of the current functional component */
9091
component: any;
92+
isReducer?: boolean;
9193
}
9294

9395
export type WorkTag =

0 commit comments

Comments
 (0)