diff --git a/src/hooks/useSidebarOrderedReports.tsx b/src/hooks/useSidebarOrderedReports.tsx index e6951ca4f5fd..ed55aacb5918 100644 --- a/src/hooks/useSidebarOrderedReports.tsx +++ b/src/hooks/useSidebarOrderedReports.tsx @@ -1,5 +1,7 @@ -import React, {createContext, useCallback, useContext, useEffect, useMemo, useState} from 'react'; +import {deepEqual} from 'fast-equals'; +import React, {createContext, useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react'; import type {OnyxEntry} from 'react-native-onyx'; +import Log from '@libs/Log'; import {getPolicyEmployeeListByIdWithoutCurrentUser} from '@libs/PolicyUtils'; import SidebarUtils from '@libs/SidebarUtils'; import CONST from '@src/CONST'; @@ -224,6 +226,53 @@ function SidebarOrderedReportsContextProvider({ }; }, [getOrderedReportIDs, orderedReportIDs, derivedCurrentReportID, policyMemberAccountIDs, shouldUseNarrowLayout, getOrderedReports, orderedReports]); + const currentDeps = { + priorityMode, + chatReports, + policies, + transactions, + transactionViolations, + reportNameValuePairs, + betas, + reportAttributes, + currentReportsToDisplay, + shouldUseNarrowLayout, + accountID, + currentReportIDValue, + derivedCurrentReportID, + prevDerivedCurrentReportID, + policyMemberAccountIDs, + prevBetas, + prevPriorityMode, + reportsToDisplayInLHN, + orderedReportIDs, + orderedReports, + }; + const prevContextValue = usePrevious(contextValue); + const previousDeps = usePrevious(currentDeps); + const firstRender = useRef(true); + + useEffect(() => { + // Cases below ensure we only log when the edge case (empty -> non-empty or non-empty -> empty) happens. + // This is done to avoid excessive logging when the orderedReports array is updated, but does not impact LHN. + + // Case 1: orderedReports goes from empty to non-empty + if (contextValue.orderedReports.length > 0 && prevContextValue?.orderedReports.length === 0) { + logChangedDeps('[useSidebarOrderedReports] Ordered reports went from empty to non-empty', currentDeps, previousDeps); + } + // Case 2: orderedReports goes from non-empty to empty + if (contextValue.orderedReports.length === 0 && prevContextValue?.orderedReports.length > 0) { + logChangedDeps('[useSidebarOrderedReports] Ordered reports went from non-empty to empty', currentDeps, previousDeps); + } + + // Case 3: orderedReports are empty from the beginning + if (firstRender.current && contextValue.orderedReports.length === 0) { + logChangedDeps('[useSidebarOrderedReports] Ordered reports initialized empty', currentDeps, previousDeps); + } + + firstRender.current = false; + }); + return {children}; } @@ -233,3 +282,31 @@ function useSidebarOrderedReports() { export {SidebarOrderedReportsContext, SidebarOrderedReportsContextProvider, useSidebarOrderedReports}; export type {PartialPolicyForSidebar, ReportsToDisplayInLHN}; + +function getChangedKeys>(deps: T, prevDeps: T) { + const depsKeys = Object.keys(deps); + + return depsKeys.filter((depKey) => !deepEqual(deps[depKey], prevDeps[depKey])); +} + +function logChangedDeps>(msg: string, deps: T, prevDeps: T) { + const startTime = performance.now(); + const changedDeps = getChangedKeys(deps, prevDeps); + const parsedDeps = parseDepsForLogging(deps); + const processingDuration = performance.now() - startTime; + Log.info(msg, false, { + deps: parsedDeps, + changedDeps, + processingDuration, + }); +} + +/** + * @param deps - The dependencies to parse. + * @returns A simplified object with light-weight values. + */ +function parseDepsForLogging>(deps: T) { + // If object or array, return the keys' length + // If primitive, return the value + return Object.fromEntries(Object.entries(deps).map(([key, value]) => [key, typeof value === 'object' && value !== null ? Object.keys(value).length : value])); +}