Skip to content

Commit

Permalink
improve owner changes calculations
Browse files Browse the repository at this point in the history
  • Loading branch information
vzaidman committed Dec 28, 2024
1 parent 5fbdd06 commit 2eb3f93
Show file tree
Hide file tree
Showing 12 changed files with 81 additions and 52 deletions.
1 change: 1 addition & 0 deletions jsx-dev-runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ module.exports.jsxDEV = function jsxDEV(...args){
var WDYRType = WDYR.getWDYRType(origType)
if (WDYRType) {
try {
wdyrStore.ownerBeforeElementCreation = WDYR.getCurrentOwner();
var element = origJsxDev.apply(null, [WDYRType].concat(rest))
if (wdyrStore.options.logOwnerReasons) {
WDYR.storeOwnerData(element)
Expand Down
6 changes: 3 additions & 3 deletions src/defaultNotifier.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ function logDifference({ Component, displayName, hookName, prefixMessage, diffOb
}

export default function defaultNotifier(updateInfo) {
const { Component, displayName, hookName, prevProps, prevState, prevHookResult, nextProps, nextState, nextHookResult, reason } = updateInfo;
const { Component, displayName, hookName, prevOwner, nextOwner, prevProps, prevState, prevHookResult, nextProps, nextState, nextHookResult, reason } = updateInfo;

if (!shouldLog(reason, Component, wdyrStore.options)) {
return;
Expand Down Expand Up @@ -116,8 +116,8 @@ export default function defaultNotifier(updateInfo) {
}

if (reason.propsDifferences && reason.ownerDifferences) {
const prevOwnerData = wdyrStore.ownerDataMap.get(prevProps);
const nextOwnerData = wdyrStore.ownerDataMap.get(nextProps);
const prevOwnerData = wdyrStore.ownerDataMap.get(prevOwner);
const nextOwnerData = wdyrStore.ownerDataMap.get(nextOwner);

wdyrStore.options.consoleGroup(`Rendered by ${nextOwnerData.displayName}`);
let prefixMessage = 'Re-rendered because';
Expand Down
52 changes: 31 additions & 21 deletions src/getUpdateInfo.js
Original file line number Diff line number Diff line change
@@ -1,41 +1,51 @@
import findObjectsDifferences from './findObjectsDifferences';
import wdyrStore from './wdyrStore';

function getOwnerDifferences({ prevOwnerData, nextOwnerData }) {
function getOwnerDifferences(prevOwner, nextOwner) {
if (!prevOwner || !nextOwner) {
return false;
}

const prevOwnerData = wdyrStore.ownerDataMap.get(prevOwner);
const nextOwnerData = wdyrStore.ownerDataMap.get(nextOwner);

if (!prevOwnerData || !nextOwnerData) {
return false;
}

// in strict mode a re-render happens twice as opposed to the initial render that happens once.
const prevOwnerDataHooks = prevOwnerData.hooksInfo.length === nextOwnerData.hooksInfo.length * 2 ?
prevOwnerData.hooksInfo.slice(prevOwnerData.hooksInfo.length / 2) :
prevOwnerData.hooksInfo;
try {
// in strict mode a re-render happens twice as opposed to the initial render that happens once.
const prevOwnerDataHooks = prevOwnerData.hooksInfo.length === nextOwnerData.hooksInfo.length * 2 ?
prevOwnerData.hooksInfo.slice(prevOwnerData.hooksInfo.length / 2) :
prevOwnerData.hooksInfo;

const hookDifferences = prevOwnerDataHooks.map(({ hookName, result }, i) => ({
hookName,
differences: findObjectsDifferences(result, nextOwnerData.hooksInfo[i].result, { shallow: false }),
}));
const hookDifferences = prevOwnerDataHooks.map(({ hookName, result }, i) => ({
hookName,
differences: findObjectsDifferences(result, nextOwnerData.hooksInfo[i].result, { shallow: false }),
}));

return {
propsDifferences: findObjectsDifferences(prevOwnerData.props, nextOwnerData.props),
stateDifferences: findObjectsDifferences(prevOwnerData.state, nextOwnerData.state),
hookDifferences: hookDifferences.length > 0 ? hookDifferences : false,
};
return {
propsDifferences: findObjectsDifferences(prevOwnerData.props, nextOwnerData.props),
stateDifferences: findObjectsDifferences(prevOwnerData.state, nextOwnerData.state),
hookDifferences: hookDifferences.length > 0 ? hookDifferences : false,
};
}
catch(e) {
console.error('WDYR failed getOwnerDifferences');
return false;
}
}

function getUpdateReason(prevProps, prevState, prevHookResult, nextProps, nextState, nextHookResult) {
const prevOwnerData = wdyrStore.ownerDataMap.get(prevProps);
const nextOwnerData = wdyrStore.ownerDataMap.get(nextProps);

function getUpdateReason(prevOwner, prevProps, prevState, prevHookResult, nextOwner, nextProps, nextState, nextHookResult) {
return {
propsDifferences: findObjectsDifferences(prevProps, nextProps),
stateDifferences: findObjectsDifferences(prevState, nextState),
hookDifferences: findObjectsDifferences(prevHookResult, nextHookResult, { shallow: false }),
ownerDifferences: getOwnerDifferences({ prevOwnerData, nextOwnerData }),
ownerDifferences: getOwnerDifferences(prevOwner, nextOwner),
};
}

export default function getUpdateInfo({ Component, displayName, hookName, prevProps, prevState, prevHookResult, nextProps, nextState, nextHookResult }) {
export default function getUpdateInfo({ Component, displayName, hookName, prevOwner, nextOwner, prevProps, prevState, prevHookResult, nextProps, nextState, nextHookResult }) {
return {
Component,
displayName,
Expand All @@ -46,6 +56,6 @@ export default function getUpdateInfo({ Component, displayName, hookName, prevPr
nextProps,
nextState,
nextHookResult,
reason: getUpdateReason(prevProps, prevState, prevHookResult, nextProps, nextState, nextHookResult),
reason: getUpdateReason(prevOwner, prevProps, prevState, prevHookResult, nextOwner, nextProps, nextState, nextHookResult),
};
}
7 changes: 7 additions & 0 deletions src/helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import wdyrStore from './wdyrStore';

export function getCurrentOwner() {
const reactSharedInternals = wdyrStore.React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;
const reactDispatcher = reactSharedInternals?.A;
return reactDispatcher?.getOwner();
}
2 changes: 2 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import wdyrStore from './wdyrStore';

import whyDidYouRender, { storeOwnerData, getWDYRType } from './whyDidYouRender';
import defaultNotifier from './defaultNotifier';
import { getCurrentOwner } from './helpers';

whyDidYouRender.defaultNotifier = defaultNotifier;
whyDidYouRender.wdyrStore = wdyrStore;
whyDidYouRender.storeOwnerData = storeOwnerData;
whyDidYouRender.getWDYRType = getWDYRType;
whyDidYouRender.getCurrentOwner = getCurrentOwner;
Object.assign(whyDidYouRender, React);

export default whyDidYouRender;
4 changes: 4 additions & 0 deletions src/patches/patchClassComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import wdyrStore from '../wdyrStore';

import { checkIfInsideAStrictModeTree } from '../utils';
import getUpdateInfo from '../getUpdateInfo';
import { getCurrentOwner } from '../helpers';

export default function patchClassComponent(ClassComponent, { displayName, defaultProps }) {
class WDYRPatchedClassComponent extends ClassComponent {
Expand Down Expand Up @@ -38,15 +39,18 @@ export default function patchClassComponent(ClassComponent, { displayName, defau
const updateInfo = getUpdateInfo({
Component: ClassComponent,
displayName,
prevOwner: this._WDYR.prevOwner,
prevProps: this._WDYR.prevProps,
prevState: this._WDYR.prevState,
nextOwner: wdyrStore.ownerBeforeElementCreation,
nextProps: this.props,
nextState: this.state,
});

wdyrStore.options.notifier(updateInfo);
}

this._WDYR.prevOwner = wdyrStore.ownerBeforeElementCreation;
this._WDYR.prevProps = this.props;
this._WDYR.prevState = this.state;
}
Expand Down
12 changes: 9 additions & 3 deletions src/patches/patchFunctionalOrStrComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,21 @@ export default function patchFunctionalOrStrComponent(FunctionalOrStringComponen
FunctionalOrStringComponent;

function WDYRFunctionalComponent(nextProps, ...args) {
const ref = wdyrStore.React.useRef();
const prevPropsRef = wdyrStore.React.useRef();
const prevProps = prevPropsRef.current;
prevPropsRef.current = nextProps;

const prevProps = ref.current;
ref.current = nextProps;
const prevOwnerRef = wdyrStore.React.useRef();
const prevOwner = prevOwnerRef.current;
const nextOwner = wdyrStore.ownerBeforeElementCreation;
prevOwnerRef.current = nextOwner;

if (prevProps) {
const updateInfo = getUpdateInfo({
Component: FunctionalComponent,
displayName,
prevOwner,
nextOwner,
prevProps,
nextProps,
});
Expand Down
3 changes: 3 additions & 0 deletions src/wdyrStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ const wdyrStore = {

/* An array of infos for hooks tracked during current render */
hooksInfoForCurrentRender: new WeakMap(),

/* Owner before element creation started */
ownerBeforeElementCreation: null,
};

export default wdyrStore;
28 changes: 13 additions & 15 deletions src/whyDidYouRender.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,11 @@ import {

import { dependenciesMap } from './calculateDeepEqualDiffs';

export { wdyrStore };
import { getCurrentOwner } from './helpers';

const initialHookValue = Symbol('initial-hook-value');
export { wdyrStore, getCurrentOwner };

function getCurrentOwner() {
const reactSharedInternals = wdyrStore.React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;
const reactDispatcher = reactSharedInternals?.A;
return reactDispatcher?.getOwner();
}
const initialHookValue = Symbol('initial-hook-value');

function trackHookChanges(hookName, { path: pathToGetTrackedHookResult }, rawHookResult) {
const nextResult = pathToGetTrackedHookResult ? get(rawHookResult, pathToGetTrackedHookResult) : rawHookResult;
Expand Down Expand Up @@ -127,26 +123,26 @@ export const hooksConfig = {
};

export function storeOwnerData(element) {
const ownerInstance = getCurrentOwner();
if (ownerInstance) {
const Component = ownerInstance.type.ComponentForHooksTracking || ownerInstance.type;
const owner = getCurrentOwner();
if (owner) {
const Component = owner.type.ComponentForHooksTracking || owner.type;
const displayName = getDisplayName(Component);

let additionalOwnerData = {};
if (wdyrStore.options.getAdditionalOwnerData) {
additionalOwnerData = wdyrStore.options.getAdditionalOwnerData(element);
}

wdyrStore.ownerDataMap.set(element.props, {
wdyrStore.ownerDataMap.set(owner, {
Component,
displayName,
props: ownerInstance.pendingProps,
state: ownerInstance.stateNode ? ownerInstance.stateNode.state : null,
hooksInfo: wdyrStore.hooksInfoForCurrentRender.get(ownerInstance) || [],
props: owner.pendingProps,
state: owner.stateNode ? owner.stateNode.state : null,
hooksInfo: wdyrStore.hooksInfoForCurrentRender.get(owner) || [],
additionalOwnerData,
});

wdyrStore.hooksInfoForCurrentRender.delete(ownerInstance);
wdyrStore.hooksInfoForCurrentRender.delete(owner);
}
}

Expand Down Expand Up @@ -232,6 +228,7 @@ export default function whyDidYouRender(React, userOptions) {
const WDYRType = getWDYRType(origType);
if (WDYRType) {
try {
wdyrStore.ownerBeforeElementCreation = getCurrentOwner();
const element = wdyrStore.origCreateElement.apply(React, [WDYRType, ...rest]);
if (wdyrStore.options.logOwnerReasons) {
storeOwnerData(element);
Expand Down Expand Up @@ -262,6 +259,7 @@ export default function whyDidYouRender(React, userOptions) {
Object.assign(React.createFactory, wdyrStore.origCreateFactory);

React.cloneElement = (...args) => {
wdyrStore.ownerBeforeElementCreation = getCurrentOwner();
const element = wdyrStore.origCloneElement.apply(React, args);
if (wdyrStore.options.logOwnerReasons) {
storeOwnerData(element);
Expand Down
2 changes: 1 addition & 1 deletion tests/librariesTests/react-router-dom.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ describe('react-router-dom', () => {
}],
ownerDifferences: {
hookDifferences: false,
propsDifferences: [],
propsDifferences: false,
stateDifferences: false
}
}
Expand Down
8 changes: 4 additions & 4 deletions tests/librariesTests/styled-components.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable no-console */
import React from 'react';
import styled from 'styled-components';
import styled from 'styled-components/dist/styled-components.js';
import * as rtl from '@testing-library/react';

import whyDidYouRender from '~';
Expand Down Expand Up @@ -77,13 +77,13 @@ test('styled-components with forward ref', () => {
<div ref={ref}>foobar</div>
);

const StyledInnerComponent = styled(InnerComponent)``;
const Styled = styled(InnerComponent)``;

StyledInnerComponent.whyDidYouRender = true;
Styled.whyDidYouRender = true;

const Wrapper = (props) => {
const ref = React.useRef(null);
return <StyledInnerComponent {...props} ref={ref} />;
return <Styled {...props} ref={ref} />;
};

const { rerender } = rtl.render(
Expand Down
8 changes: 3 additions & 5 deletions tests/logOwnerReasons.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,9 @@ function createOwners(Child) {
}

function CloneOwner({ children }) {
/* eslint-disable no-unused-vars */
const [a, setA] = React.useState(1);
const [b, setB] = React.useState(1);
/* eslint-enable */
React.useEffect(() => {
const [, setA] = React.useState(1);
const [, setB] = React.useState(1);
React.useLayoutEffect(() => {
setA(2);
setB(2);
}, []);
Expand Down

0 comments on commit 2eb3f93

Please sign in to comment.