From c51696968622ae4fa44e79b332d9ece9c03d1ba2 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 2 May 2022 11:35:05 +0200 Subject: [PATCH 1/3] Updated to react-chartjs 4 --- package-lock.json | 16 ++++++++-------- package.json | 2 +- src/index.tsx | 1 + src/visits/charts/HorizontalBarChart.tsx | 20 +++++++++++--------- src/visits/charts/LineChartCard.tsx | 23 ++++++++++++++--------- 5 files changed, 35 insertions(+), 27 deletions(-) diff --git a/package-lock.json b/package-lock.json index e63f3651..8c8143a6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,7 @@ "qs": "^6.9.6", "ramda": "^0.27.2", "react": "^18.1.0", - "react-chartjs-2": "^3.3.0", + "react-chartjs-2": "^4.1.0", "react-colorful": "^5.5.1", "react-copy-to-clipboard": "^5.0.4", "react-datepicker": "^4.7.0", @@ -19110,12 +19110,12 @@ } }, "node_modules/react-chartjs-2": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-3.3.0.tgz", - "integrity": "sha512-4Mt0SR2aiUbWi/4762odRBYSnbNKSs4HWc0o3IW43py5bMfmfpeZU95w6mbvtuLZH/M3GsPJMU8DvDc+5U9blQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-4.1.0.tgz", + "integrity": "sha512-AsUihxEp8Jm1oBhbEovE+w50m9PVNhz1sfwEIT4hZduRC0m14gHWHd0cUaxkFDb8HNkdMIGzsNlmVqKiOpU74g==", "peerDependencies": { "chart.js": "^3.5.0", - "react": "^16.8.0 || ^17.0.0" + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, "node_modules/react-colorful": { @@ -41617,9 +41617,9 @@ } }, "react-chartjs-2": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-3.3.0.tgz", - "integrity": "sha512-4Mt0SR2aiUbWi/4762odRBYSnbNKSs4HWc0o3IW43py5bMfmfpeZU95w6mbvtuLZH/M3GsPJMU8DvDc+5U9blQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-4.1.0.tgz", + "integrity": "sha512-AsUihxEp8Jm1oBhbEovE+w50m9PVNhz1sfwEIT4hZduRC0m14gHWHd0cUaxkFDb8HNkdMIGzsNlmVqKiOpU74g==", "requires": {} }, "react-colorful": { diff --git a/package.json b/package.json index 91f5ccb2..e7e95d2d 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "qs": "^6.9.6", "ramda": "^0.27.2", "react": "^18.1.0", - "react-chartjs-2": "^3.3.0", + "react-chartjs-2": "^4.1.0", "react-colorful": "^5.5.1", "react-copy-to-clipboard": "^5.0.4", "react-datepicker": "^4.7.0", diff --git a/src/index.tsx b/src/index.tsx index 5f0cd85e..dc13305e 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -6,6 +6,7 @@ import { container } from './container'; import { store } from './container/store'; import { fixLeafletIcons } from './utils/helpers/leaflet'; import { register as registerServiceWorker } from './serviceWorkerRegistration'; +import 'chart.js/auto'; // TODO Import specific ones to reduce bundle size https://react-chartjs-2.js.org/docs/migration-to-v4/#tree-shaking import 'react-datepicker/dist/react-datepicker.css'; import 'leaflet/dist/leaflet.css'; import './index.scss'; diff --git a/src/visits/charts/HorizontalBarChart.tsx b/src/visits/charts/HorizontalBarChart.tsx index 4ab27664..2c9bcb22 100644 --- a/src/visits/charts/HorizontalBarChart.tsx +++ b/src/visits/charts/HorizontalBarChart.tsx @@ -1,7 +1,7 @@ -import { FC } from 'react'; -import { ChartData, ChartDataset, ChartOptions } from 'chart.js'; +import { FC, MutableRefObject, useRef } from 'react'; +import { ChartData, ChartDataset, ChartOptions, InteractionItem } from 'chart.js'; import { keys, values } from 'ramda'; -import { Bar } from 'react-chartjs-2'; +import { Bar, getElementAtEvent } from 'react-chartjs-2'; import { fillTheGaps } from '../../utils/helpers/visits'; import { pointerOnHover, renderChartLabel } from '../../utils/helpers/charts'; import { prettify } from '../../utils/helpers/numbers'; @@ -57,8 +57,7 @@ const generateChartData = ( datasets: generateChartDatasets(data, highlightedData, highlightedLabel), }); -type ClickedCharts = [{ index: number }] | []; -const chartElementAtEvent = (labels: string[], onClick?: (label: string) => void) => ([chart]: ClickedCharts) => { +const chartElementAtEvent = (labels: string[], [chart]: InteractionItem[], onClick?: (label: string) => void) => { if (!onClick || !chart) { return; } @@ -80,6 +79,8 @@ export const HorizontalBarChart: FC = ( }, { ...stats }), ); const highlightedData = fillTheGaps(highlightedStats ?? {}, labels); + const refWithStats = useRef(null); + const refWithoutStats = useRef(null); const options: ChartOptions = { plugins: { @@ -110,13 +111,14 @@ export const HorizontalBarChart: FC = ( const height = determineHeight(labels); // Provide a key based on the height, to force re-render every time the dataset changes (example, due to pagination) - const renderChartComponent = (customKey: string) => ( + const renderChartComponent = (customKey: string, theRef: MutableRefObject) => ( chartElementAtEvent(labels, getElementAtEvent(theRef.current, e), onClick)} /> ); @@ -124,8 +126,8 @@ export const HorizontalBarChart: FC = ( <> {/* It's VERY IMPORTANT to render two different components here, as one has 1 dataset and the other has 2 */} {/* Using the same component causes a crash when switching from 1 to 2 datasets, and then back to 1 dataset */} - {highlightedStats !== undefined && renderChartComponent('with_stats')} - {highlightedStats === undefined && renderChartComponent('without_stats')} + {highlightedStats !== undefined && renderChartComponent('with_stats', refWithStats)} + {highlightedStats === undefined && renderChartComponent('without_stats', refWithoutStats)} ); }; diff --git a/src/visits/charts/LineChartCard.tsx b/src/visits/charts/LineChartCard.tsx index 78399daf..335dd742 100644 --- a/src/visits/charts/LineChartCard.tsx +++ b/src/visits/charts/LineChartCard.tsx @@ -1,4 +1,4 @@ -import { useState, useMemo } from 'react'; +import { useState, useMemo, MutableRefObject, useRef } from 'react'; import { Card, CardHeader, @@ -8,7 +8,7 @@ import { DropdownMenu, DropdownItem, } from 'reactstrap'; -import { Line } from 'react-chartjs-2'; +import { getElementAtEvent, Line } from 'react-chartjs-2'; import { always, cond, countBy, reverse } from 'ramda'; import { add, @@ -21,7 +21,7 @@ import { startOfISOWeek, endOfISOWeek, } from 'date-fns'; -import { ChartData, ChartDataset, ChartOptions } from 'chart.js'; +import { ChartData, ChartDataset, ChartOptions, InteractionItem } from 'chart.js'; import { NormalizedVisit, Stats } from '../types'; import { fillTheGaps } from '../../utils/helpers/visits'; import { useToggle } from '../../utils/helpers/hooks'; @@ -148,8 +148,9 @@ let selectedLabel: string | null = null; const chartElementAtEvent = ( labels: string[], datasetsByPoint: Record, + [chart]: InteractionItem[], setSelectedVisits?: (visits: NormalizedVisit[]) => void, -) => ([chart]: [{ index: number }]) => { +) => { if (!setSelectedVisits || !chart) { return; } @@ -160,7 +161,7 @@ const chartElementAtEvent = ( setSelectedVisits([]); selectedLabel = null; } else { - setSelectedVisits(labels[index] ? datasetsByPoint[labels[index]] : []); + setSelectedVisits(labels[index] && datasetsByPoint[labels[index]] ? datasetsByPoint[labels[index]] : []); selectedLabel = labels[index] ?? null; } }; @@ -172,6 +173,8 @@ const LineChartCard = ( visits.length > 0 ? determineInitialStep(visits[visits.length - 1].date) : 'monthly', ); const [skipNoVisits, toggleSkipNoVisits] = useToggle(true); + const refWithHighlightedVisits = useRef(null); + const refWithoutHighlightedVisits = useRef(null); const datasetsByPoint = useMemo(() => visitsToDatasetGroups(step, visits), [step, visits]); const groupedVisitsWithGaps = useMemo(() => groupVisitsByStep(step, reverse(visits)), [step, visits]); @@ -220,11 +223,13 @@ const LineChartCard = ( }, onHover: pointerOnHover, }; - const renderLineChart = () => ( + const renderLineChart = (theRef: MutableRefObject) => ( + chartElementAtEvent(labels, datasetsByPoint, getElementAtEvent(theRef.current, e), setSelectedVisits)} /> ); @@ -255,8 +260,8 @@ const LineChartCard = ( {/* It's VERY IMPORTANT to render two different components here, as one has 1 dataset and the other has 2 */} {/* Using the same component causes a crash when switching from 1 to 2 datasets, and then back to 1 dataset */} - {highlightedVisits.length > 0 && renderLineChart()} - {highlightedVisits.length === 0 && renderLineChart()} + {highlightedVisits.length > 0 && renderLineChart(refWithHighlightedVisits)} + {highlightedVisits.length === 0 && renderLineChart(refWithoutHighlightedVisits)} ); From c8b78d04e28d0c7bf89b49a057b240895fa7de50 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 2 May 2022 11:51:17 +0200 Subject: [PATCH 2/3] Fixed jest config to transform react-chartjs-2 lib --- jest.config.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jest.config.js b/jest.config.js index 65f89256..4c3f3792 100644 --- a/jest.config.js +++ b/jest.config.js @@ -26,13 +26,14 @@ module.exports = { }, transformIgnorePatterns: [ '/.stryker-tmp', - '/node_modules\\/(?!react-leaflet)\.(js|jsx|ts|tsx)$', + 'node_modules\/(?!(\@react-leaflet|react-leaflet|leaflet|react-chartjs-2)\/)', '^.+\\.module\\.scss$', ], moduleNameMapper: { '^.+\\.module\\.scss$': 'identity-obj-proxy', // Reactstrap module resolution does not work in jest for some reason. Manually mapping it solves the problem 'reactstrap': '/node_modules/reactstrap/dist/reactstrap.umd.js', + 'react-chartjs-2': '/node_modules/react-chartjs-2/dist/index.js', }, moduleFileExtensions: ['js', 'ts', 'tsx', 'json'], }; From df9c1a88fca139ef1ed6f46641822c00ae7232a1 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 2 May 2022 11:52:16 +0200 Subject: [PATCH 3/3] Updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe024e3e..af7ceeb9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Changed * [#616](https://github.com/shlinkio/shlink-web-client/pull/616) Updated to React 18. +* [#595](https://github.com/shlinkio/shlink-web-client/pull/595) Updated to react-chartjs-2 v4.1.0. * [#594](https://github.com/shlinkio/shlink-web-client/pull/594) Updated to a new coding standard. * [#603](https://github.com/shlinkio/shlink-web-client/pull/603) Migrated to new and maintained dependencies to parse CSV<->JSON. * [#619](https://github.com/shlinkio/shlink-web-client/pull/619) Introduced react testing library, to progressively replace enzyme.