Skip to content

Commit 9f6dc93

Browse files
committed
refactor linkFiber, helpers, snapShot to work with useEffect & useState. Need additional refactor to reduce time complexity
1 parent f901503 commit 9f6dc93

File tree

4 files changed

+116
-56
lines changed

4 files changed

+116
-56
lines changed

demo-app/src/client/Components/Home.tsx

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,36 @@
11
import { set } from 'lodash';
22
import React from 'react';
3-
import { useState } from 'react';
3+
import { useState, useEffect } from 'react';
44

55
function Home() {
6-
const [dummyData1, setDummyData1] = useState('dummyData1');
7-
const [dummyData2, setDummyData2] = useState('dummyData2');
6+
const [dummyDummy1, setDummyData1] = useState('dummyData1');
7+
const [dummyDummy2, setDummyData2] = useState('dummyData2');
8+
const [count, setCount] = useState(0);
9+
useEffect(() => {
10+
setCount((count) => count + 1);
11+
}, [dummyDummy1]);
12+
function handleClick1() {
13+
setDummyData1((dummyData1) => (dummyData1 === 'dummyData1' ? 'test1' : 'dummyData1'));
14+
}
15+
const [count1, setCount1] = useState(0);
816

17+
function handleClick2() {
18+
setDummyData2((dummyData2) => (dummyData2 === 'dummyData2' ? 'test2' : 'dummyData2'));
19+
}
920
return (
1021
<div className='about'>
11-
<p>{dummyData1}</p>
12-
<p>{dummyData2}</p>
22+
<p>{dummyDummy1}</p>
23+
<p>{dummyDummy2}</p>
1324
<button
1425
onClick={() => {
15-
setDummyData1((dummyData1) => (dummyData1 === 'dummyData1' ? 'test1' : 'dummyData1'));
16-
setDummyData2((dummyData2) => (dummyData2 === 'dummyData2' ? 'test2' : 'dummyData2'));
26+
handleClick1();
27+
handleClick2();
1728
}}
1829
>
1930
{' '}
2031
Test{' '}
2132
</button>
33+
<p>Count: {count}</p>
2234
{/* <h1>Lorem Ipsum</h1> */}
2335
{/* <p>
2436
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt

src/backend/helpers.ts

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
/* eslint-disable @typescript-eslint/no-var-requires */
77
/* eslint-disable linebreak-style */
88
/* eslint-disable no-inner-declarations, no-loop-func */
9+
10+
import { string } from 'prop-types';
11+
912
// eslint-disable-next-line import/newline-after-import
1013
const acorn = require('acorn');
1114
const jsx = require('acorn-jsx');
@@ -108,7 +111,6 @@ export const getHooksNames = (elementType: string): Array<string> | undefined =>
108111
} catch (e) {
109112
return ['unknown'];
110113
}
111-
112114
// hookNames will contain an object with methods (functions)
113115
const hooksNames: any = {};
114116

@@ -164,3 +166,50 @@ export const getHooksNames = (elementType: string): Array<string> | undefined =>
164166
return Object.values(hooksNames);
165167
}
166168
};
169+
170+
export const getComponentName = (
171+
elementType: string,
172+
): { hookName: string; varName: string }[] | undefined => {
173+
// Initialize empty object to store the setters and getter
174+
let AST: any;
175+
try {
176+
AST = JSXParser.parse(elementType);
177+
} catch (e) {
178+
throw Error('Error occurs at helpers ts getComponentName. Cannot parse functional component.');
179+
}
180+
// Begin search for hook names, only if ast has a body property.
181+
182+
AST = AST.body;
183+
184+
// Statements get all the names of the hooks. For example: useCount, useWildcard, ...
185+
const statements: { hookName: string; varName: string }[] = [];
186+
/** All module exports always start off as a single 'FunctionDeclaration' type
187+
* Other types: "BlockStatement" / "ExpressionStatement" / "ReturnStatement"
188+
* Iterate through AST of every function declaration
189+
* Check within each function declaration if there are hook declarations & variable name declaration */
190+
AST.forEach((functionDec: any) => {
191+
let declarationBody: any;
192+
if (functionDec.expression?.body) declarationBody = functionDec.expression.body.body;
193+
// check if functionDec.expression.body exists, then set declarationBody to functionDec's body
194+
else declarationBody = functionDec.body?.body ?? [];
195+
// Traverse through the function's funcDecs and Expression Statements
196+
declarationBody.forEach((elem: any) => {
197+
// Hooks will always be contained in a variable declaration
198+
if (elem.type === 'VariableDeclaration') {
199+
// Obtain the declarations array from elem.
200+
const { declarations } = elem;
201+
// Obtain the reactHook:
202+
const reactHook: string = declarations[0]?.init?.callee?.expressions[1]?.property?.name;
203+
if (reactHook === 'useState') {
204+
// Obtain the variable being set:
205+
let varName: string = declarations[1]?.id?.name;
206+
// Obtain the setState method:
207+
let hookName: string = declarations[2]?.id?.name;
208+
// Push reactHook & varName to statements array
209+
statements.push({ hookName, varName });
210+
}
211+
}
212+
});
213+
});
214+
return statements;
215+
};

src/backend/linkFiber.ts

Lines changed: 46 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ import updateSnapShotTree from './snapShot';
6363

6464
// throttle returns a function that can be called any number of times (possibly in quick succession) but will only invoke the callback at most once every x ms
6565
// getHooksNames - helper function to grab the getters/setters from `elementType`
66-
import { throttle, getHooksNames } from './helpers';
66+
import { throttle, getHooksNames, getComponentName } from './helpers';
6767

6868
// Set global variables to use in exported module and helper functions
6969
declare global {
@@ -92,12 +92,12 @@ function traverseHooks(memoizedState: any): HookStates {
9292
const hooksStates: HookStates = [];
9393
while (memoizedState) {
9494
// the !== undefined conditional is necessary here for correctly displaying react hooks because TypeScript recognizes 0 and "" as falsey value - DO NOT REMOVE
95-
// if (memoizedState.memoizedState !== undefined) {
96-
hooksStates.push({
97-
component: memoizedState.queue,
98-
state: memoizedState.memoizedState,
99-
});
100-
// }
95+
if (memoizedState.queue) {
96+
hooksStates.push({
97+
component: memoizedState.queue,
98+
state: memoizedState.memoizedState,
99+
});
100+
}
101101
memoizedState = memoizedState.next;
102102
}
103103
return hooksStates;
@@ -175,13 +175,12 @@ const exclude = new Set([
175175
'$$typeof',
176176
'@@observable',
177177
]);
178-
// ---------------------FILTER DATA FROM REACT DEV TOOL-------------------------
178+
// ------------FILTER DATA FROM REACT DEV TOOL && CONVERT TO STRING-------------
179179
/**
180-
* This recursive function is used to grab the state/props/context of children components
181-
and push them into the parent componenent react elements throw errors on client side of application - convert react/functions into string
182-
*
180+
* This function receives raw Data from REACT DEV TOOL and filter the Data based on the exclude list. The filterd data is then converted to string (if applicable) before being send to reacTime front end.
181+
*
183182
* @param reactDevData - The data object obtained from React Devtool. Ex: memoizedProps, memoizedState
184-
* @param reactimeData - The cached data from the current component. This can be data about states, context and/or props of the component.
183+
* @param reactimeData - The cached data from the current component. This can be data about states, context and/or props of the component.
185184
* @returns The update component data object to send to front end of ReactTime
186185
*/
187186
function convertDataToString(
@@ -197,7 +196,13 @@ function convertDataToString(
197196
// If value at key is a function, assign key with value 'function' to reactimeData object
198197
else if (typeof reactDevData[key] === 'function') {
199198
reactimeData[key] = 'function';
200-
} else {
199+
}
200+
// If value at key is an object/array and not null => JSON stringify the value
201+
else if (typeof reactDevData[key] === 'object' && reactDevData[key] !== null) {
202+
reactimeData[key] = JSON.stringify(reactDevData[key]);
203+
}
204+
// Else assign the primitive value
205+
else {
201206
reactimeData[key] = reactDevData[key];
202207
}
203208
}
@@ -295,7 +300,6 @@ export function createTree(
295300
actualStartTime,
296301
selfBaseDuration,
297302
treeBaseDuration,
298-
dependencies,
299303
_debugHookTypes,
300304
} = currentFiberNode;
301305
// console.log('LinkFiber', {
@@ -348,7 +352,7 @@ export function createTree(
348352
treeBaseDuration?: number;
349353
props: {};
350354
context: {};
351-
state: {};
355+
state?: {};
352356
hooksState?: any[];
353357
hooksIndex?: number;
354358
index?: number;
@@ -359,7 +363,6 @@ export function createTree(
359363
treeBaseDuration,
360364
props: {},
361365
context: {},
362-
state: {},
363366
};
364367
let isStatefulComponent = false;
365368

@@ -370,12 +373,6 @@ export function createTree(
370373
}
371374

372375
// ------------APPEND STATE & CONTEXT DATA FROM REACT DEV TOOL----------------
373-
// stateNode
374-
// 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
375-
// Example: for tic-tac-toe demo-app: Board is a stateful component that use setState to store state data.
376-
if (stateNode?.state) {
377-
Object.assign(componentData.state, stateNode.state);
378-
}
379376

380377
// memoizedState
381378
// Note: if user use ReactHook, memoizedState.memoizedState can be a falsy value such as null, false, ... => need to specify this data is not undefined
@@ -391,12 +388,12 @@ export function createTree(
391388
);
392389
}
393390
// Else if user use ReactHook to define state => all states will be stored in memoizedState => grab all states stored in the memoizedState
394-
else {
395-
Object.assign(
396-
componentData.state,
397-
trimContextData(memoizedState, elementType.name, _debugHookTypes),
398-
);
399-
}
391+
// else {
392+
// Object.assign(
393+
// componentData.state,
394+
// trimContextData(memoizedState, elementType.name, _debugHookTypes),
395+
// );
396+
// }
400397
}
401398
// if user uses useContext hook, context data will be stored in memoizedProps.value of the Context.Provider component => grab context object stored in memoizedprops
402399
// Different from other provider, such as Routes, BrowswerRouter, ReactRedux, ..., Context.Provider does not have a displayName
@@ -416,51 +413,53 @@ export function createTree(
416413
// componentData.context = convertDataToString(dependencies.firstContext.memoizedValue);
417414
// }
418415

419-
// ----------------------SET UP FOR JUMPING CONDITION-------------------------
420-
// Check if node is a stateful class component when user use setState
421-
if (
422-
stateNode?.state &&
423-
(tag === FunctionComponent || tag === ClassComponent || tag === IndeterminateComponent)
424-
) {
416+
// -----------OBTAIN STATE & SET STATE METHODS FOR CLASS COMPONENT------------
417+
// Check if node is a stateful class component when user use setState.
418+
// 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
419+
// Example: for tic-tac-toe demo-app: Board is a stateful component that use setState to store state data.
420+
if (stateNode?.state && (tag === ClassComponent || tag === IndeterminateComponent)) {
425421
// Save component's state and setState() function to our record for future
426422
// time-travel state changing. Add record index to snapshot so we can retrieve.
427423
componentData.index = componentActionsRecord.saveNew(stateNode.state, stateNode);
428-
// passess to front end
424+
// Save state information in componentData.
425+
componentData.state = stateNode.state;
426+
// Passess to front end
429427
newState = stateNode.state;
430428
isStatefulComponent = true;
431429
}
432430

431+
// ---------OBTAIN STATE & DISPATCH METHODS FOR FUNCTIONAL COMPONENT---------
433432
// REGULAR REACT HOOKS
434433
let hooksIndex;
435434
// Check if node is a hooks useState function
436435
if (
437436
memoizedState &&
438437
(tag === FunctionComponent ||
439-
tag === ClassComponent ||
438+
// tag === ClassComponent || WE SHOULD NOT BE ABLE TO USE HOOK IN CLASS
440439
tag === IndeterminateComponent ||
441-
tag === ContextProvider)
440+
tag === ContextProvider) //TODOD: Need to figure out why we need context provider
442441
) {
443442
if (memoizedState.queue) {
444443
// Hooks states are stored as a linked list using memoizedState.next,
445444
// so we must traverse through the list and get the states.
446445
// We then store them along with the corresponding memoizedState.queue,
447446
// which includes the dispatch() function we use to change their state.
448447
const hooksStates = traverseHooks(memoizedState);
449-
const hooksNames = getHooksNames(elementType.toString());
448+
// console.log(elementType.toString());
449+
// const hooksNames = getHooksNames(elementType.toString());
450+
const hooksNames = getComponentName(elementType.toString());
450451
// console.log({ hooksNames }); // ['useState', 'useState']
451-
// console.log({ hooksStates });
452-
452+
console.log({ hooksStates });
453+
// console.log({ memoizedState });
454+
console.log({ hooksNames });
455+
newState.hooksState = [];
453456
hooksStates.forEach((state, i) => {
454457
hooksIndex = componentActionsRecord.saveNew(state.state, state.component);
455458
componentData.hooksIndex = hooksIndex;
456-
if (!newState) {
457-
newState = { hooksState: [] };
458-
} else if (!newState.hooksState) {
459-
newState.hooksState = [];
460-
}
461-
newState.hooksState.push({ [hooksNames[i]]: state.state });
462-
isStatefulComponent = true;
459+
newState.hooksState.push({ [hooksNames[i].hookName]: state.state });
460+
componentData.state[hooksNames[i].varName] = state.state;
463461
});
462+
isStatefulComponent = true;
464463
// console.log({ newState: newState.hooksState });
465464
}
466465
}

src/backend/snapShot.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ function sendSnapshot(snapShot: Snapshot, mode: Status): void {
5050
// this postMessage will be sending the most up-to-date snapshot of the current React Fiber Tree
5151
// the postMessage action will be received on the content script to later update the tabsObj
5252
// this will fire off everytime there is a change in test application
53-
53+
console.log('sendSnapShot', { payload });
5454
window.postMessage(
5555
{
5656
action: 'recordSnap',

0 commit comments

Comments
 (0)