Skip to content

Commit c8ff050

Browse files
committed
Merge remote-tracking branch 'origin/feature-hooks' into reactime19.0-Jackie
2 parents 596de19 + 6312da1 commit c8ff050

File tree

3 files changed

+122
-30
lines changed

3 files changed

+122
-30
lines changed

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

Lines changed: 67 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
/* eslint-disable guard-for-in */
99
// @ts-nocheck
1010

11-
import React, { useState, useEffect } from 'react';
11+
import React, { useState, useEffect, useRef } from 'react';
1212
import { Group } from '@visx/group';
1313
import { hierarchy, Tree } from '@visx/hierarchy';
1414
import { LinearGradient } from '@visx/gradient';
@@ -47,9 +47,9 @@ export default function ComponentMap({
4747
const [orientation, setOrientation] = useState('vertical');
4848
const [linkType, setLinkType] = useState('diagonal');
4949
const [stepPercent, setStepPercent] = useState(10);
50-
const [Tooltip, setTooltip] = useState(false);
5150
const [selectedNode, setSelectedNode] = useState('root');
5251
const [, dispatch] = useStoreContext();
52+
const toolTipTimeoutID = useRef(null);
5353

5454
useEffect(() => {
5555
dispatch(setCurrentTabInApp('map'));
@@ -191,7 +191,6 @@ export default function ComponentMap({
191191
<LinearGradient id='links-gradient' from='#fd9b93' to='#fe6e9e' />
192192
<rect
193193
onClick={() => {
194-
setTooltip(false);
195194
hideTooltip();
196195
}}
197196
width={totalWidth}
@@ -266,7 +265,6 @@ export default function ComponentMap({
266265
onClick={() => {
267266
dispatch(toggleExpanded(node.data));
268267
hideTooltip();
269-
setTooltip(false);
270268
}}
271269
/>
272270
)}
@@ -290,16 +288,48 @@ export default function ComponentMap({
290288
onClick={() => {
291289
dispatch(toggleExpanded(node.data));
292290
hideTooltip();
293-
setTooltip(false);
294291
}}
295-
onMouseOver={(event) => {
296-
setTooltip(true);
292+
// Mouse Enter Rect (Component Node) -----------------------------------------------------------------------
293+
/** This onMouseEnter event fires when the mouse first moves/hovers over a component node.
294+
* The supplied event listener callback produces a Tooltip element for the current node. */
295+
296+
onMouseEnter={(event) => {
297+
/** This 'if' statement block checks to see if you've just left another component node
298+
* by seeing if there's a current setTimeout waiting to close that component node's
299+
* tooltip (see onMouseLeave immediately below).
300+
* This setTimeout gives the mouse time to enter the tooltip element so the tooltip
301+
* can persist. If instead of entering said tooltip element you've left the previous
302+
* component node to enter this component node, this logic will clear the timeout event,
303+
* and close the tooltip. */
304+
if (toolTipTimeoutID.current !== null) {
305+
clearTimeout(toolTipTimeoutID.current);
306+
hideTooltip();
307+
}
308+
/** The following line resets the toolTipTimeoutID.current to null, showing that there
309+
* are no current setTimeouts running. I placed this outside of the above if statement
310+
* to make sure there are no edge cases that would allow for the toolTipTimeoutID.current
311+
* to hold onto an old reference. */
312+
toolTipTimeoutID.current = null;
313+
//This generates a tooltip for the component node the mouse has entered.
297314
handleMouseAndClickOver(event);
298315
}}
299-
// with onmouseOver, this produces a hover over effect for the Tooltip
300-
onMouseOut={() => {
301-
hideTooltip();
302-
setTooltip(false);
316+
317+
// Mouse Leave Rect (Component Node) --------------------------------------------------------------------------
318+
/** This onMouseLeave event fires when the mouse leaves a component node.
319+
* The supplied event listener callback generates a setTimeout call which gives the
320+
* mouse a certain amount of time between leaving the current component node and
321+
* closing the tooltip for that node.
322+
* If the mouse enters the tooltip before the timeout delay has passed, the
323+
* setTimeout event will be canceled. */
324+
onMouseLeave={() => {
325+
// This line invokes setTimeout and saves its ID to the useRef var toolTipTimeoutID
326+
toolTipTimeoutID.current = setTimeout(() => {
327+
// hideTooltip unmounts the tooltip
328+
hideTooltip();
329+
// As the timeout has been executed, the timeoutID can be reset to null
330+
toolTipTimeoutID.current = null;
331+
//There is a delay of 300 ms
332+
}, 300);
303333
}}
304334
/>
305335
)}
@@ -329,33 +359,48 @@ export default function ComponentMap({
329359
top={tooltipTop}
330360
left={tooltipLeft}
331361
style={tooltipStyles}
332-
onClick={hideTooltip}
362+
363+
//------------- Mouse Over TooltipInPortal--------------------------------------------------------------------
364+
/** This onMouseEnter fires when the mouse first moves/hovers over the tooltip
365+
* The supplied event listener callback stops the setTimeout that was going to
366+
* close the tooltip from firing */
367+
368+
onMouseEnter={() => {
369+
// The setTimeoutID stored in toolTipTimeoutID.current is from the setTimeout initiated by leaving the
370+
// component node that generated the tooltip. If you've triggered an onMouseEnter event in that tooltip,
371+
clearTimeout(toolTipTimeoutID.current);
372+
// This line resets the timeoutID to null
373+
toolTipTimeoutID.current = null;
374+
}}
375+
376+
//------------- Mouse Leave TooltipInPortal -----------------------------------------------------------------
377+
/** This onMouseLeave event fires when the mouse leaves the tooltip
378+
* The supplied event listener callback unmounts the tooltip */
379+
onMouseLeave={() => {
380+
// hideTooltip unmounts the tooltip
381+
hideTooltip();
382+
}}
333383
>
334-
<div
335-
onClick={() => {
336-
setTooltip(false);
337-
hideTooltip();
338-
}}
339-
>
384+
<div>
340385
<div style={{}}>
341386
{' '}
342387
<strong>{tooltipData.name}</strong>{' '}
343388
</div>
344389
<div> Render time: {formatRenderTime(tooltipData.componentData.actualDuration)} </div>
345390
<div className='stateTip'>
346-
State:
347-
{formatState(tooltipData.state)}
391+
State: {formatState(tooltipData.state)}
348392
</div>
349393
<div style={React.scrollStyle}>
350394
<div className='tooltipWrapper'>
351395
<h2>Props:</h2>
352396
{formatData(tooltipData.componentData.props, 'props')}
353397
</div>
354-
398+
399+
{/* Currently no use for this field
355400
<div className='tooltipWrapper'>
356401
<h2>Initial Context:</h2>
357402
{formatData(tooltipData.componentData.context, 'context')}
358-
</div>
403+
</div> */}
359404

360405
<div className='tooltipWrapper'>
361406
<h2>State:</h2>

src/app/index.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,19 @@
11
/* eslint-disable react/jsx-filename-extension */
22
import React from 'react';
3-
import ReactDOM from 'react-dom';
3+
// import ReactDOM from 'react-dom';
4+
import { createRoot } from 'react-dom/client';
45
import App from './components/App';
56
import './styles/main.scss';
67

7-
ReactDOM.render(<App />, document.getElementById('root'));
8+
// Old way of rendering that was locking Reactime into React 17
9+
// Left for testing purposes
10+
// ReactDOM.render(<App />, document.getElementById('root'));
11+
12+
//New way of rendering that allows us to use React 18
13+
const root = createRoot(document.getElementById("root"));
14+
root.render(
15+
//Strict mode is for developers, it throws extra errors
16+
// <StrictMode>
17+
<App/>
18+
// </StrictMode>
19+
);

src/backend/controllers/statePropExtractors.ts

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,9 @@ export function getHooksStateAndUpdateMethod(
8383

8484
// ---------------------GET STATE VAR NAME & HOOK NAME--------------------------
8585
/**
86-
* This function receive a string representation of a functional component. This function then use JSX parser to traverse through the function string, and extract the state variable name and its corresponding setState method.
86+
* This function receive a string representation of a functional component.
87+
* This function then uses JSX parser to traverse through the function string,
88+
* and extract the state variable name and its corresponding setState method.
8789
* @param elementType - The string representation of a functional component
8890
* @returns - An array of objects with key: hookName (the name of setState method) | value: varName (the state variable name)
8991
*/
@@ -93,45 +95,78 @@ export function getHooksNames(elementType: string): { hookName: string; varName:
9395
let AST: any;
9496
try {
9597
AST = JSXParser.parse(elementType).body;
98+
console.log('AST');
99+
console.log(AST);
100+
let count = 0;
96101
// Begin search for hook names, only if ast has a body property.
97102
// Statements get all the names of the hooks. For example: useCount, useWildcard, ...
98103
const statements: { hookName: string; varName: string }[] = [];
99104
/** All module exports always start off as a single 'FunctionDeclaration' type
100105
* Other types: "BlockStatement" / "ExpressionStatement" / "ReturnStatement"
101-
* Iterate through AST of every function declaration
102-
* Check within each function declaration if there are hook declarations & variable name declaration */
106+
* Iterate through AST of every function(al component) declaration
107+
* Check within each function(al component) declaration if there are hook declarations & variable name declaration */
103108
AST.forEach((functionDec: any) => {
109+
// console.log('functionDec');
110+
// console.log(functionDec);
104111
let declarationBody: any;
105112
if (functionDec.expression?.body) declarationBody = functionDec.expression.body.body;
106113
// check if functionDec.expression.body exists, then set declarationBody to functionDec's body
107114
else declarationBody = functionDec.body?.body ?? [];
108115
// Traverse through the function's funcDecs and Expression Statements
116+
console.log('declarationBody');
117+
console.log( declarationBody);
109118
declarationBody.forEach((elem: any) => {
110119
// Hooks will always be contained in a variable declaration
120+
console.log('elem type:', count++, elem.type);
121+
console.log(elem);
122+
111123
if (elem.type === 'VariableDeclaration') {
112124
// Obtain the declarations array from elem.
113125
const { declarations } = elem;
114126
// Obtain the reactHook:
115127
// Due to difference in babel transpilation in browser vs for jest test, expression is stored in differen location
128+
console.log('Found variable declaration, elem declarations:', declarations)
129+
// console.log('dec[0]', declarations[0]);
130+
// console.log('init',declarations[0]?.init);
131+
// console.log('callee',declarations[0]?.init?.callee);
132+
// console.log('expressions', declarations[0]?.init?.callee?.expressions);
133+
// console.log('Line 2!');
134+
// console.log('args', declarations[0]?.init?.arguments);
135+
// console.log('args[0]', declarations[0]?.init?.arguments?.arguments[0]);
136+
// console.log('callee', declarations[0]?.init?.arguments?.arguments[0]?.callee);
137+
// console.log('expression', declarations[0]?.init?.arguments?.arguments[0]?.callee?.expressions);
116138
const expression =
117139
declarations[0]?.init?.callee?.expressions || //work for browser
118-
declarations[0]?.init?.arguments[0]?.callee?.expressions; //work for jest test
140+
//Mark's notes: so this was where the app was breaking. ES6 functions (e.g. const handleClick = () => {}) inside functional components were hitting this line and crashing when it tried to access arguments[0] and arguments didn't exist.
141+
declarations[0]?.init?.arguments?.[0]?.callee?.expressions; //work for jest test;
142+
143+
console.log('looked for expression, found:', expression);
144+
//Mark's Note: for a functional definition that isn't a hook, it won't have the callee being searched for above. This line will cause this forEach execution to stop here in this case.
145+
if (expression === undefined) return;
119146
let reactHook: string;
120147
reactHook = expression[1].property?.name;
121148
if (reactHook === 'useState') {
122149
// Obtain the variable being set:
150+
//Mark's note: changed to point to second to last element of declarations because webpack adds an extra variable when converting files that use ES6, so the previous pointer wasn't working for this case
123151
let varName: string =
124-
declarations[1]?.id?.name || // work react application
152+
declarations[declarations.length - 2]?.id?.name || // work react application;
125153
(Array.isArray(declarations[0]?.id?.elements)
126154
? declarations[0]?.id?.elements[0]?.name
127155
: undefined); //work for nextJS application
128156
// Obtain the setState method:
157+
//Mark's note: changed to point to last element of declarations because webpack adds an extra variable when converting files that use ES6, so the previous pointer wasn't working for this case
129158
let hookName: string =
130-
declarations[2]?.id?.name || // work react application
159+
declarations[declarations.length - 1]?.id?.name || // work react application;
131160
(Array.isArray(declarations[0]?.id?.elements)
132161
? declarations[0]?.id?.elements[0]?.name
133162
: undefined); //work for nextJS & Remix
134163
// Push reactHook & varName to statements array
164+
/**
165+
* Mark's notes, I'd like to alter the structure of the data
166+
* to pass on the reactHook 'useState'. That way the user will
167+
* eventually be able to view the difference between variables
168+
* stored via useState and useContext
169+
*/
135170
statements.push({ hookName, varName });
136171
}
137172
}

0 commit comments

Comments
 (0)