From 55b4ac431411d4db881265c5d44a6ab9db4ebf7a Mon Sep 17 00:00:00 2001 From: nitin <142569587+ehconitin@users.noreply.github.com> Date: Thu, 15 Jan 2026 18:10:28 +0530 Subject: [PATCH] [Dashboards] [Performance] Bar chart re renders fix (#17162) before - https://github.com/user-attachments/assets/c9b4a60a-d4bd-43d5-8621-54777a326171 after - https://github.com/user-attachments/assets/74358810-067e-44f7-82f1-f4dcd7956956 --- .../components/CustomBarItem.tsx | 12 +- .../components/CustomSliceHoverLayer.tsx | 110 ++++++++---------- .../components/GraphWidgetBarChart.tsx | 13 ++- ...etIsSliceHoveredComponentFamilySelector.ts | 19 +++ .../components/GraphWidgetLineChart.tsx | 13 ++- 5 files changed, 92 insertions(+), 75 deletions(-) create mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/states/graphWidgetIsSliceHoveredComponentFamilySelector.ts diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/components/CustomBarItem.tsx b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/components/CustomBarItem.tsx index 4f503caa50..efb7d6391c 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/components/CustomBarItem.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/components/CustomBarItem.tsx @@ -1,7 +1,8 @@ import { LEGEND_HIGHLIGHT_DIMMED_OPACITY } from '@/page-layout/widgets/graph/constants/LegendHighlightDimmedOpacity.constant'; import { BAR_CHART_CONSTANTS } from '@/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartConstants'; -import { graphWidgetHoveredSliceIndexComponentState } from '@/page-layout/widgets/graph/graphWidgetBarChart/states/graphWidgetHoveredSliceIndexComponentState'; +import { graphWidgetIsSliceHoveredComponentFamilySelector } from '@/page-layout/widgets/graph/graphWidgetBarChart/states/graphWidgetIsSliceHoveredComponentFamilySelector'; import { graphWidgetHighlightedLegendIdComponentState } from '@/page-layout/widgets/graph/states/graphWidgetHighlightedLegendIdComponentState'; +import { useRecoilComponentFamilyValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValue'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { type BarDatum, type BarItemProps } from '@nivo/bar'; import { animated, to } from '@react-spring/web'; @@ -62,18 +63,15 @@ export const CustomBarItem = ({ graphWidgetHighlightedLegendIdComponentState, ); - const hoveredSliceIndex = useRecoilComponentValue( - graphWidgetHoveredSliceIndexComponentState, + const isSliceHovered = useRecoilComponentFamilyValue( + graphWidgetIsSliceHoveredComponentFamilySelector, + String(barData.indexValue), ); const isDimmed = isDefined(highlightedLegendId) && String(highlightedLegendId) !== String(barData.id); - const isSliceHovered = - isDefined(hoveredSliceIndex) && - String(barData.indexValue) === hoveredSliceIndex; - const isNegativeValue = useMemo( () => isNumber(barData.value) && barData.value < 0, [barData.value], diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/components/CustomSliceHoverLayer.tsx b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/components/CustomSliceHoverLayer.tsx index 9ecd24e980..7a15daf825 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/components/CustomSliceHoverLayer.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/components/CustomSliceHoverLayer.tsx @@ -3,14 +3,11 @@ import { type BarChartSlice } from '@/page-layout/widgets/graph/graphWidgetBarCh import { computeSliceHighlightPosition } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/computeSliceHighlightPosition'; import { computeSlicesFromBars } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/computeSlicesFromBars'; import { findSliceAtPosition } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/findSliceAtPosition'; -import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; -import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState'; import { useTheme } from '@emotion/react'; import { type BarDatum, type ComputedBarDatum } from '@nivo/bar'; -import { animated, useSpring } from '@react-spring/web'; -import { useMemo, type MouseEvent } from 'react'; -import { useRecoilCallback } from 'recoil'; +import { AnimatePresence, motion } from 'framer-motion'; +import { useCallback, useMemo, type MouseEvent } from 'react'; import { isDefined } from 'twenty-shared/utils'; import { BarChartLayout } from '~/generated/graphql'; @@ -44,13 +41,8 @@ export const CustomSliceHoverLayer = ({ onSliceLeave, }: CustomSliceHoverLayerProps) => { const theme = useTheme(); - const hoveredSliceIndex = useRecoilComponentValue( - graphWidgetHoveredSliceIndexComponentState, - ); - const hoveredSliceIndexState = useRecoilComponentCallbackState( - graphWidgetHoveredSliceIndexComponentState, - ); - const setHoveredSliceIndex = useSetRecoilComponentState( + + const hoveredSliceIndexValue = useRecoilComponentValue( graphWidgetHoveredSliceIndexComponentState, ); @@ -61,43 +53,36 @@ export const CustomSliceHoverLayer = ({ [bars, isVerticalLayout], ); - const handleMouseMove = useRecoilCallback( - ({ snapshot }) => - (event: MouseEvent) => { - const sliceData = findSliceAtPosition({ - event, - slices, - marginLeft, - marginTop, - isVerticalLayout, - }); - const currentHoveredSliceIndex = snapshot - .getLoadable(hoveredSliceIndexState) - .getValue(); + const handleMouseMove = useCallback( + (event: MouseEvent) => { + const sliceData = findSliceAtPosition({ + event, + slices, + marginLeft, + marginTop, + isVerticalLayout, + }); - if (!isDefined(sliceData)) { - if (isDefined(currentHoveredSliceIndex)) { - setHoveredSliceIndex(null); - onSliceHover(null); - } - return; + if (!isDefined(sliceData)) { + if (isDefined(hoveredSliceIndexValue)) { + onSliceHover(null); } + return; + } - if (sliceData.slice.indexValue === currentHoveredSliceIndex) { - return; - } + if (sliceData.slice.indexValue === hoveredSliceIndexValue) { + return; + } - setHoveredSliceIndex(sliceData.slice.indexValue); - onSliceHover(sliceData); - }, + onSliceHover(sliceData); + }, [ - hoveredSliceIndexState, - setHoveredSliceIndex, onSliceHover, slices, marginLeft, marginTop, isVerticalLayout, + hoveredSliceIndexValue, ], ); @@ -126,11 +111,11 @@ export const CustomSliceHoverLayer = ({ }; const hoveredSlice = useMemo(() => { - if (!isDefined(hoveredSliceIndex)) { + if (!isDefined(hoveredSliceIndexValue)) { return null; } - return slices.find((slice) => slice.indexValue === hoveredSliceIndex); - }, [slices, hoveredSliceIndex]); + return slices.find((slice) => slice.indexValue === hoveredSliceIndexValue); + }, [slices, hoveredSliceIndexValue]); const highlightPosition = computeSliceHighlightPosition({ sliceCenter: hoveredSlice?.sliceCenter ?? null, @@ -139,31 +124,34 @@ export const CustomSliceHoverLayer = ({ innerHeight, }); - const { opacity } = useSpring({ - opacity: isDefined(hoveredSlice) ? 1 : 0, - config: { - tension: 300, - friction: 30, - }, - }); - if (bars.length === 0) { return null; } return ( - - - + + {isDefined(hoveredSlice) && ( + + + + )} + (0); const containerRef = useRef(null); + const debouncedSetChartDimensions = useDebouncedCallback( + (width: number, height: number) => { + setChartWidth(width); + setChartHeight(height); + }, + + 300, + ); + const setActiveBarTooltip = useSetRecoilComponentState( graphWidgetBarTooltipComponentState, ); @@ -193,6 +202,7 @@ export const GraphWidgetBarChart = ({ ) => { if (isDefined(sliceData)) { debouncedHideTooltip.cancel(); + setHoveredSliceIndex(sliceData.slice.indexValue); setActiveBarTooltip({ slice: sliceData.slice, offsetLeft: sliceData.offsetLeft, @@ -305,8 +315,7 @@ export const GraphWidgetBarChart = ({ { - setChartWidth(width); - setChartHeight(height); + debouncedSetChartDimensions(width, height); }} /> ({ + key: 'graphWidgetIsSliceHoveredComponentFamilySelector', + componentInstanceContext: WidgetComponentInstanceContext, + get: + ({ instanceId, familyKey }: { instanceId: string; familyKey: string }) => + ({ get }) => { + const hoveredSliceIndex = get( + graphWidgetHoveredSliceIndexComponentState.atomFamily({ instanceId }), + ); + + return isDefined(hoveredSliceIndex) && hoveredSliceIndex === familyKey; + }, + }); diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/components/GraphWidgetLineChart.tsx b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/components/GraphWidgetLineChart.tsx index 55980eaae8..27a5ac1032 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/components/GraphWidgetLineChart.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/components/GraphWidgetLineChart.tsx @@ -85,7 +85,7 @@ export const GraphWidgetLineChart = ({ id, rangeMin, rangeMax, - omitNullValues: _omitNullValues = false, + omitNullValues = false, displayType, groupMode, colorMode, @@ -101,6 +101,11 @@ export const GraphWidgetLineChart = ({ const containerRef = useRef(null); const [chartWidth, setChartWidth] = useState(0); + const debouncedSetChartWidth = useDebouncedCallback( + (width: number) => setChartWidth(width), + 300, + ); + const formatOptions: GraphValueFormatOptions = { displayType, decimals, @@ -195,7 +200,7 @@ export const GraphWidgetLineChart = ({ formatValue={(value) => formatGraphValue(value, formatOptions)} offset={theme.spacingMultiplicator * 2} groupMode={groupMode} - omitNullValues={_omitNullValues} + omitNullValues={omitNullValues} enablePointLabel={enablePointLabel} /> ); @@ -293,9 +298,7 @@ export const GraphWidgetLineChart = ({ > { - setChartWidth(width); - }} + onDimensionChange={({ width }) => debouncedSetChartWidth(width)} />