Skip to content

Commit

Permalink
fix detection of current processed component
Browse files Browse the repository at this point in the history
  • Loading branch information
vzaidman committed Dec 28, 2024
1 parent 8cb3d36 commit 0b3cdd9
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 43 deletions.
67 changes: 37 additions & 30 deletions src/whyDidYouRender.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,34 +20,37 @@ export { wdyrStore };

const initialHookValue = Symbol('initial-hook-value');

function getCurrentOwner() {
const reactSharedInternals = wdyrStore.React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;
const reactDispatcher = reactSharedInternals?.A;
return reactDispatcher?.getOwner();
}

function trackHookChanges(hookName, { path: hookPath }, hookResult) {
const nextHook = hookPath ? get(hookResult, hookPath) : hookResult;

const renderNumberForTheHook = wdyrStore.React.useRef(true);
const prevHookRef = wdyrStore.React.useRef(initialHookValue);

// TODO: improve
const isSecondCycleOfRenders = (
wdyrStore.hooksPerRender[0] &&
wdyrStore.hooksPerRender[0].renderNumberForTheHook !== renderNumberForTheHook.current
// If a new component is rendered, wdyrStore.hooksPerRender is reset with "resetHooksPerRenderIfNeeded".
// The below code resets hooksPerRender if the same component is being rendered for a consecutive time
// by detecting the increasment of the render count in the first component in wdyrStore.hooksPerRender
const newRenderNumberForTheHook = wdyrStore.React.useRef(0);
newRenderNumberForTheHook.current++;
const isNewComponentRender = (
wdyrStore.hooksPerRender.length > 0 &&
wdyrStore.hooksPerRender[0].renderNumberForTheHook !== newRenderNumberForTheHook.current
);

if (isSecondCycleOfRenders) {
if (isNewComponentRender) {
wdyrStore.hooksPerRender = [];
}
wdyrStore.hooksPerRender.push({ hookName, result: nextHook, renderNumberForTheHook: newRenderNumberForTheHook.current });

wdyrStore.hooksPerRender.push({ hookName, result: nextHook, renderNumberForTheHook: renderNumberForTheHook.current });

renderNumberForTheHook.current++;

const OwnerInstance = wdyrStore.React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE?.A?.getOwner();

const prevHookRef = wdyrStore.React.useRef(initialHookValue);

if (!OwnerInstance) {
const ownerInstance = getCurrentOwner();
if (!ownerInstance) {
return hookResult;
}

const Component = OwnerInstance.type.ComponentForHooksTracking || OwnerInstance.type;
const Component = ownerInstance.type.ComponentForHooksTracking || ownerInstance.type;
const displayName = getDisplayName(Component);

const isShouldTrack = shouldTrack(Component, { isHookChange: true });
Expand Down Expand Up @@ -131,9 +134,9 @@ export const hooksConfig = {
};

export function storeOwnerData(element) {
const OwnerInstance = wdyrStore.React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE?.A?.getOwner();
if (OwnerInstance) {
const Component = OwnerInstance.type.ComponentForHooksTracking || OwnerInstance.type;
const ownerInstance = getCurrentOwner();
if (ownerInstance) {
const Component = ownerInstance.type.ComponentForHooksTracking || ownerInstance.type;
const displayName = getDisplayName(Component);

let additionalOwnerData = {};
Expand All @@ -144,24 +147,27 @@ export function storeOwnerData(element) {
wdyrStore.ownerDataMap.set(element.props, {
Component,
displayName,
props: OwnerInstance.pendingProps,
state: OwnerInstance.stateNode ? OwnerInstance.stateNode.state : null,
props: ownerInstance.pendingProps,
state: ownerInstance.stateNode ? ownerInstance.stateNode.state : null,
hooks: wdyrStore.hooksPerRender,
additionalOwnerData,
});
}
}

function resetHooksPerRenderIfNeeded() {
// Intercept assignments to ReactCurrentOwner.current to reset hooksPerRender
let currentA = null;
if (wdyrStore.React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE) {
Object.defineProperty(wdyrStore.React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, 'A', {
// Intercept assignments to ReactSharedInternals dispatcher (H) to reset hooksPerRender
// Notice: asyncDispatcher (A) is not fit for this purpose because it may only change after hooks
// from the next component are processed
let currentDispatcher = null;
const ReactSharedInternals = wdyrStore.React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;
if (ReactSharedInternals) {
Object.defineProperty(ReactSharedInternals, 'H', {
get() {
return currentA;
return currentDispatcher;
},
set(value) {
currentA = value;
currentDispatcher = value;
wdyrStore.hooksPerRender = [];
},
});
Expand All @@ -185,13 +191,14 @@ function trackHooksIfNeeded() {
const originalHook = hookParent[hookName];
const newHookName = hookName[0].toUpperCase() + hookName.slice(1);

const newHook = function WhyDidYouRenderReWrittenHook(...args) {
const newHook = function useWhyDidYouRenderReWrittenHook(...args) {
const hookResult = originalHook.call(this, ...args);
const { dependenciesPath, dontReport } = hookTrackingConfig;
const shouldTrackHookChanges = !dontReport;
if (dependenciesPath && isFunction(hookResult)) {
dependenciesMap.set(hookResult, { hookName, deps: get(args, dependenciesPath) });
}
if (!dontReport) {
if (shouldTrackHookChanges) {
trackHookChanges(hookName, hookTrackingConfig, hookResult);
}
return hookResult;
Expand Down
65 changes: 52 additions & 13 deletions tests/hooks/useContext.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ describe('hooks - useContext', () => {
const OuterComponent = () => {
const [currentState, setCurrentState] = React.useState('c');

React.useLayoutEffect(() => {
React.useEffect(() => {
setCurrentState('c');
}, []);

Expand Down Expand Up @@ -68,7 +68,7 @@ describe('hooks - useContext', () => {
const OuterComponent = () => {
const [currentState, setCurrentState] = React.useState({ c: 'c' });

React.useLayoutEffect(() => {
React.useEffect(() => {
setCurrentState({ c: 'c' });
}, []);

Expand Down Expand Up @@ -114,7 +114,7 @@ describe('hooks - useContext', () => {
const OuterComponent = () => {
const [currentState, setCurrentState] = React.useState({ c: 'c' });

React.useLayoutEffect(() => {
React.useEffect(() => {
setCurrentState({ c: 'c' });
}, []);

Expand All @@ -131,7 +131,11 @@ describe('hooks - useContext', () => {
<OuterComponent/>
);

expect(updateInfos).toHaveLength(2);
rtl.render(
<OuterComponent/>
);

expect(updateInfos).toHaveLength(4);
expect(updateInfos[0].reason).toEqual({
hookDifferences: false,
propsDifferences: [],
Expand All @@ -150,16 +154,51 @@ describe('hooks - useContext', () => {
stateDifferences: false,
},
});
expect(updateInfos[1].reason).toEqual({
hookDifferences: [{
diffType: diffTypes.deepEquals,
pathString: '',
nextValue: { c: 'c' },
prevValue: { c: 'c' },
}],
propsDifferences: false,
expect(updateInfos[1]).toEqual(expect.objectContaining({
hookName: 'useContext',
reason: {
hookDifferences: [{
diffType: diffTypes.deepEquals,
pathString: '',
nextValue: { c: 'c' },
prevValue: { c: 'c' },
}],
propsDifferences: false,
stateDifferences: false,
ownerDifferences: false,
}
}));
expect(updateInfos[2].reason).toEqual({
hookDifferences: false,
propsDifferences: [],
stateDifferences: false,
ownerDifferences: false,
ownerDifferences: {
hookDifferences: [{
differences: [{
diffType: diffTypes.deepEquals,
pathString: '',
nextValue: { c: 'c' },
prevValue: { c: 'c' },
}],
hookName: 'useState',
}],
propsDifferences: false,
stateDifferences: false,
},
});
expect(updateInfos[3]).toEqual(expect.objectContaining({
hookName: 'useContext',
reason: {
hookDifferences: [{
diffType: diffTypes.deepEquals,
pathString: '',
nextValue: { c: 'c' },
prevValue: { c: 'c' },
}],
propsDifferences: false,
stateDifferences: false,
ownerDifferences: false,
}
}));
});
});

0 comments on commit 0b3cdd9

Please sign in to comment.