Memoize chart components with custom deep comparison

- Add custom comparison function to React.memo for TopCountriesPieChart
- Add custom comparison function to React.memo for TopCountriesBarChart
- Use JSON.stringify for deep equality comparison of countries and countryNames
- Prevents unnecessary re-renders when parent updates with same data
- Avoids Recharts reprocessing 5000+ data points on each parent re-render

All tests passing. No linting issues.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-05-01 18:36:18 +02:00
parent 1af67eb0ce
commit e46062d4cd
2 changed files with 32 additions and 10 deletions

View File

@@ -138,10 +138,11 @@ function BarTooltip(props: TooltipContentProps): React.JSX.Element | null {
* @param props - `countries` map and `countryNames` map from the * @param props - `countries` map and `countryNames` map from the
* `/api/dashboard/bans/by-country` response. * `/api/dashboard/bans/by-country` response.
*/ */
export const TopCountriesBarChart = memo(function TopCountriesBarChart({ export const TopCountriesBarChart = memo(
countries, function TopCountriesBarChart({
countryNames, countries,
}: TopCountriesBarChartProps): React.JSX.Element { countryNames,
}: TopCountriesBarChartProps): React.JSX.Element {
const styles = useStyles(); const styles = useStyles();
const { colorMode } = useThemeMode(); const { colorMode } = useThemeMode();
@@ -198,4 +199,14 @@ export const TopCountriesBarChart = memo(function TopCountriesBarChart({
</ResponsiveContainer> </ResponsiveContainer>
</div> </div>
); );
}); },
(prev, next) => {
// Custom comparison: if countries and countryNames are deeply equal,
// memo returns true (skip render). This prevents Recharts from
// reprocessing 5000+ data points on parent re-renders.
return (
JSON.stringify(prev.countries) === JSON.stringify(next.countries) &&
JSON.stringify(prev.countryNames) === JSON.stringify(next.countryNames)
);
},
);

View File

@@ -130,10 +130,11 @@ function PieTooltip(props: TooltipContentProps): React.JSX.Element | null {
* @param props - `countries` map and `countryNames` map from the * @param props - `countries` map and `countryNames` map from the
* `/api/dashboard/bans/by-country` response. * `/api/dashboard/bans/by-country` response.
*/ */
export const TopCountriesPieChart = memo(function TopCountriesPieChart({ export const TopCountriesPieChart = memo(
countries, function TopCountriesPieChart({
countryNames, countries,
}: TopCountriesPieChartProps): React.JSX.Element { countryNames,
}: TopCountriesPieChartProps): React.JSX.Element {
const styles = useStyles(); const styles = useStyles();
const { colorMode } = useThemeMode(); const { colorMode } = useThemeMode();
@@ -199,4 +200,14 @@ export const TopCountriesPieChart = memo(function TopCountriesPieChart({
</ResponsiveContainer> </ResponsiveContainer>
</div> </div>
); );
}); },
(prev, next) => {
// Custom comparison: if countries and countryNames are deeply equal,
// memo returns true (skip render). This prevents Recharts from
// reprocessing 5000+ data points on parent re-renders.
return (
JSON.stringify(prev.countries) === JSON.stringify(next.countries) &&
JSON.stringify(prev.countryNames) === JSON.stringify(next.countryNames)
);
},
);