Skip to content
Open
48 changes: 47 additions & 1 deletion packages/app/src/library/Pool/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@ import { PoolIdentity } from 'library/ListItem/Labels/PoolIdentity'
import { PoolNominateStatus } from 'library/ListItem/Labels/PoolNominateStatus'
import { Wrapper } from 'library/ListItem/Wrappers'
import { usePoolsTabs } from 'pages/Pools/context'
import { memo } from 'react'
import { HeaderButtonRow, LabelRow, Separator } from 'ui-core/list'
import { deepEqual } from 'ui-graphs/util/deepEqual'
import { More } from '../ListItem/Buttons/More'
import { Members } from '../ListItem/Labels/Members'
import { PoolId } from '../ListItem/Labels/PoolId'
import type { PoolProps } from './types'

export const Pool = ({ pool }: PoolProps) => {
const PoolComponent = ({ pool }: PoolProps) => {
const { memberCounter, addresses, id } = pool
const { setActiveTab } = usePoolsTabs()
const { syncing } = useSyncing(['active-pools'])
Expand Down Expand Up @@ -64,3 +66,47 @@ export const Pool = ({ pool }: PoolProps) => {
</Wrapper>
)
}

// Custom comparison function to prevent unnecessary re-renders
const arePropsEqual = (prevProps: PoolProps, nextProps: PoolProps): boolean => {
const prevPool = prevProps.pool
const nextPool = nextProps.pool

// Check core pool identifiers
if (
prevPool.id !== nextPool.id ||
prevPool.addresses.stash !== nextPool.addresses.stash ||
prevPool.memberCounter !== nextPool.memberCounter ||
prevPool.state !== nextPool.state
) {
return false
}

// Check pool metadata changes using deepEqual
if (
prevPool.addresses.reward !== nextPool.addresses.reward ||
!deepEqual(prevPool.addresses, nextPool.addresses)
) {
return false
}

// Check bonded amount changes
if (prevPool.points !== nextPool.points) {
return false
}

// Check commission changes using deepEqual
if (!deepEqual(prevPool.commission, nextPool.commission)) {
return false
}

// Check roles changes using deepEqual
if (!deepEqual(prevPool.roles, nextPool.roles)) {
return false
}

return true
}

export const Pool = memo(PoolComponent, arePropsEqual)
Pool.displayName = 'Pool'
42 changes: 41 additions & 1 deletion packages/app/src/library/ValidatorList/Item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,15 @@ import { Metrics } from 'library/ListItem/Buttons/Metrics'
import { Remove } from 'library/ListItem/Buttons/Remove'
import { Quartile } from 'library/ListItem/Labels/Quartile'
import { Wrapper } from 'library/ListItem/Wrappers'
import { memo } from 'react'
import type { Validator } from 'types'
import { HeaderButtonRow, LabelRow, Separator } from 'ui-core/list'
import {
boolEqual,
arePropsEqual as composePropsEqual,
deepEqual,
stringEqual,
} from '../../../../utils/src/props'
import { FavoriteValidator } from '../ListItem/Buttons/FavoriteValidator'
import { Select } from '../ListItem/Buttons/Select'
import { Blocked } from '../ListItem/Labels/Blocked'
Expand All @@ -23,7 +30,7 @@ import { EraStatus } from '../ListItem/Labels/EraStatus'
import { Identity } from '../ListItem/Labels/Identity'
import type { ItemProps } from './types'

export const Item = ({
const ItemComponent = ({
validator,
toggleFavorites,
displayFor,
Expand Down Expand Up @@ -102,3 +109,36 @@ export const Item = ({
</Wrapper>
)
}

// Custom comparison function to prevent unnecessary re-renders
const areValidatorItemPropsEqual = (
prevProps: ItemProps,
nextProps: ItemProps
): boolean =>
composePropsEqual(
// Validator address
stringEqual(prevProps.validator.address, nextProps.validator.address),
// Validator status
stringEqual(
prevProps.validator.validatorStatus,
nextProps.validator.validatorStatus
),
// DisplayFor
stringEqual(prevProps.displayFor, nextProps.displayFor),
// ToggleFavorites
boolEqual(!!prevProps.toggleFavorites, !!nextProps.toggleFavorites),
// Commission (ensure boolean)
!!(
prevProps.validator.prefs?.commission ===
nextProps.validator.prefs?.commission
),
// Validator preferences (blocked status, etc.)
deepEqual(prevProps.validator.prefs, nextProps.validator.prefs),
// Era points
deepEqual(prevProps.eraPoints, nextProps.eraPoints),
// onRemove function reference
!!(prevProps.onRemove === nextProps.onRemove)
)

export const Item = memo(ItemComponent, areValidatorItemPropsEqual)
Item.displayName = 'ValidatorListItem'
3 changes: 2 additions & 1 deletion packages/ui-graphs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"exports": {
".": "./src/index.tsx",
"./types": "./src/types.ts",
"./util": "./src/util/index.ts"
"./util": "./src/util/index.ts",
"./util/deepEqual": "./src/util/deepEqual.ts"
},
"dependencies": {
"@w3ux/utils": "^2.2.3",
Expand Down
54 changes: 49 additions & 5 deletions packages/ui-graphs/src/graphs/AveragePayoutLine.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ import {
Title,
Tooltip,
} from 'chart.js'
import { memo } from 'react'
import { Line } from 'react-chartjs-2'
import type { AveragePayoutLineProps } from '../types'
import { deepEqual } from '../util/deepEqual'
import {
calculatePayoutAverages,
combineRewards,
Expand All @@ -31,7 +33,7 @@ ChartJS.register(
Legend
)

export const AveragePayoutLine = ({
const AveragePayoutLineComponent = ({
days,
average,
height,
Expand Down Expand Up @@ -131,10 +133,6 @@ export const AveragePayoutLine = ({
.decimalPlaces(units)
.toFormat()} ${unit}`,
},
intersect: false,
interaction: {
mode: 'nearest',
},
},
},
}
Expand Down Expand Up @@ -173,3 +171,49 @@ export const AveragePayoutLine = ({
</>
)
}

// Custom comparison function to prevent expensive Chart.js re-initializations
const arePropsEqual = (
prevProps: AveragePayoutLineProps,
nextProps: AveragePayoutLineProps
): boolean => {
// Check dimensions and configuration
if (
prevProps.days !== nextProps.days ||
prevProps.average !== nextProps.average ||
prevProps.height !== nextProps.height ||
prevProps.background !== nextProps.background ||
prevProps.nominating !== nextProps.nominating ||
prevProps.inPool !== nextProps.inPool
) {
return false
}

// Check styling props
if (
prevProps.unit !== nextProps.unit ||
prevProps.units !== nextProps.units
) {
return false
}

// Check labels object using deepEqual
if (!deepEqual(prevProps.labels, nextProps.labels)) {
return false
}

// Check data object (most important for chart updates) using deepEqual
if (!deepEqual(prevProps.data, nextProps.data)) {
return false
}

// Theme changes detection
if (prevProps.getThemeValue !== nextProps.getThemeValue) {
return false
}

return true
}

export const AveragePayoutLine = memo(AveragePayoutLineComponent, arePropsEqual)
AveragePayoutLine.displayName = 'AveragePayoutLine'
51 changes: 50 additions & 1 deletion packages/ui-graphs/src/graphs/EraPointsLine.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ import {
Tooltip,
} from 'chart.js'
import { format, fromUnixTime } from 'date-fns'
import { memo } from 'react'
import { Line } from 'react-chartjs-2'
import { Spinner } from 'ui-core/base'
import type { EraPointsLineProps } from '../types'
import { deepEqual } from '../util/deepEqual'

ChartJS.register(
CategoryScale,
Expand All @@ -30,7 +32,7 @@ ChartJS.register(
Legend
)

export const EraPointsLine = ({
const EraPointsLineComponent = ({
entries,
syncing,
width,
Expand Down Expand Up @@ -163,3 +165,50 @@ export const EraPointsLine = ({
</div>
)
}

// Custom comparison function to prevent expensive Chart.js re-initializations
const arePropsEqual = (
prevProps: EraPointsLineProps,
nextProps: EraPointsLineProps
): boolean => {
// Check syncing state
if (prevProps.syncing !== nextProps.syncing) {
return false
}

// Check dimensions
if (
prevProps.width !== nextProps.width ||
prevProps.height !== nextProps.height
) {
return false
}

// Check date format
if (prevProps.dateFormat !== nextProps.dateFormat) {
return false
}

// Check labels object using deepEqual
if (!deepEqual(prevProps.labels, nextProps.labels)) {
return false
}

// Check era points data (most important for chart updates) using deepEqual
if (
prevProps.entries.length !== nextProps.entries.length ||
!deepEqual(prevProps.entries, nextProps.entries)
) {
return false
}

// Theme changes detection
if (prevProps.getThemeValue !== nextProps.getThemeValue) {
return false
}

return true
}

export const EraPointsLine = memo(EraPointsLineComponent, arePropsEqual)
EraPointsLine.displayName = 'EraPointsLine'
46 changes: 45 additions & 1 deletion packages/ui-graphs/src/graphs/GeoDonut.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import { ellipsisFn } from '@w3ux/utils'
import type { TooltipItem } from 'chart.js'
import { ArcElement, Chart as ChartJS, Legend, Tooltip } from 'chart.js'
import chroma from 'chroma-js'
import { memo } from 'react'
import { Doughnut } from 'react-chartjs-2'
import type { GeoDonutProps } from '../types'
import { deepEqual } from '../util/deepEqual'

ChartJS.register(ArcElement, Tooltip, Legend)

export const GeoDonut = ({
const GeoDonutComponent = ({
title,
series = { labels: [], data: [] },
legendHeight = 25,
Expand Down Expand Up @@ -94,3 +96,45 @@ export const GeoDonut = ({
</div>
)
}

// Custom comparison function to prevent expensive Chart.js re-initializations
const arePropsEqual = (
prevProps: GeoDonutProps,
nextProps: GeoDonutProps
): boolean => {
// Check title
if (prevProps.title !== nextProps.title) {
return false
}

// Check legend and label configuration
if (
prevProps.legendHeight !== nextProps.legendHeight ||
prevProps.maxLabelLen !== nextProps.maxLabelLen
) {
return false
}

// Check series data (most important for chart updates) using deepEqual
const prevSeries = prevProps.series || { labels: [], data: [] }
const nextSeries = nextProps.series || { labels: [], data: [] }

if (
prevSeries.labels.length !== nextSeries.labels.length ||
prevSeries.data.length !== nextSeries.data.length ||
!deepEqual(prevSeries.labels, nextSeries.labels) ||
!deepEqual(prevSeries.data, nextSeries.data)
) {
return false
}

// Theme changes detection
if (prevProps.getThemeValue !== nextProps.getThemeValue) {
return false
}

return true
}

export const GeoDonut = memo(GeoDonutComponent, arePropsEqual)
GeoDonut.displayName = 'GeoDonut'
Loading