1- import React , {
2- useEffect ,
3- useState ,
4- useRef ,
5- useImperativeHandle ,
6- useMemo ,
7- forwardRef ,
8- } from 'react' ;
9- import type { Ref , MouseEvent } from 'react' ;
1+ import React , { useEffect , useRef , useState , forwardRef } from 'react' ;
2+ import type { ForwardedRef , MouseEvent } from 'react' ;
103import ChartJS from 'chart.js/auto' ;
114import type { ChartData , ChartType , DefaultDataPoint } from 'chart.js' ;
125
13- import { Props , ChartJSOrUndefined , TypedChartComponent } from './types' ;
6+ import type { Props , TypedChartComponent } from './types' ;
7+ import { reforwardRef , setNextDatasets } from './utils' ;
8+
9+ const noopData = {
10+ datasets : [ ] ,
11+ } ;
1412
1513function ChartComponent <
1614 TType extends ChartType = ChartType ,
@@ -22,7 +20,7 @@ function ChartComponent<
2220 width = 300 ,
2321 redraw = false ,
2422 type,
25- data,
23+ data : dataProp ,
2624 options,
2725 plugins = [ ] ,
2826 getDatasetAtEvent,
@@ -32,45 +30,49 @@ function ChartComponent<
3230 onClick : onClickProp ,
3331 ...props
3432 } : Props < TType , TData , TLabel > ,
35- ref : Ref < ChartJS < TType , TData , TLabel > >
33+ ref : ForwardedRef < ChartJS < TType , TData , TLabel > >
3634) {
37- type TypedChartJS = ChartJSOrUndefined < TType , TData , TLabel > ;
35+ type TypedChartJS = ChartJS < TType , TData , TLabel > ;
3836 type TypedChartData = ChartData < TType , TData , TLabel > ;
3937
40- const canvas = useRef < HTMLCanvasElement > ( null ) ;
38+ const canvasRef = useRef < HTMLCanvasElement > ( null ) ;
39+ const chartRef = useRef < TypedChartJS | null > ( ) ;
40+ /**
41+ * In case `dataProp` is function use internal state
42+ */
43+ const [ computedData , setComputedData ] = useState < TypedChartData > ( ) ;
44+ const data : TypedChartData =
45+ computedData || ( typeof dataProp === 'function' ? noopData : dataProp ) ;
4146
42- const computedData = useMemo < TypedChartData > ( ( ) => {
43- if ( typeof data === 'function' ) {
44- return canvas . current
45- ? data ( canvas . current )
46- : {
47- datasets : [ ] ,
48- } ;
49- } else return data ;
50- } , [ data , canvas . current ] ) ;
47+ const renderChart = ( ) => {
48+ if ( ! canvasRef . current ) return ;
5149
52- const [ chart , setChart ] = useState < TypedChartJS > ( ) ;
50+ chartRef . current = new ChartJS ( canvasRef . current , {
51+ type,
52+ data,
53+ options,
54+ plugins,
55+ } ) ;
5356
54- useImperativeHandle < TypedChartJS , TypedChartJS > ( ref , ( ) => chart , [ chart ] ) ;
57+ reforwardRef ( ref , chartRef . current ) ;
58+ } ;
5559
56- const renderChart = ( ) => {
57- if ( ! canvas . current ) return ;
58-
59- setChart (
60- new ChartJS ( canvas . current , {
61- type,
62- data : computedData ,
63- options,
64- plugins,
65- } )
66- ) ;
60+ const destroyChart = ( ) => {
61+ reforwardRef ( ref , null ) ;
62+
63+ if ( chartRef . current ) {
64+ chartRef . current . destroy ( ) ;
65+ chartRef . current = null ;
66+ }
6767 } ;
6868
6969 const onClick = ( event : MouseEvent < HTMLCanvasElement > ) => {
7070 if ( onClickProp ) {
7171 onClickProp ( event ) ;
7272 }
7373
74+ const { current : chart } = chartRef ;
75+
7476 if ( ! chart ) return ;
7577
7678 getDatasetAtEvent &&
@@ -105,80 +107,54 @@ function ChartComponent<
105107 ) ;
106108 } ;
107109
108- const updateChart = ( ) => {
109- if ( ! chart ) return ;
110-
111- if ( options ) {
112- chart . options = { ...options } ;
110+ /**
111+ * In case `dataProp` is function,
112+ * then update internal state
113+ */
114+ useEffect ( ( ) => {
115+ if ( typeof dataProp === 'function' && canvasRef . current ) {
116+ setComputedData ( dataProp ( canvasRef . current ) ) ;
113117 }
118+ } , [ dataProp ] ) ;
114119
115- if ( ! chart . config . data ) {
116- chart . config . data = computedData ;
117- chart . update ( ) ;
118- return ;
120+ useEffect ( ( ) => {
121+ if ( ! redraw && chartRef . current && options ) {
122+ chartRef . current . options = { ...options } ;
119123 }
124+ } , [ redraw , options ] ) ;
120125
121- const { datasets : newDataSets = [ ] , ...newChartData } = computedData ;
122- const { datasets : currentDataSets = [ ] } = chart . config . data ;
123-
124- // copy values
125- Object . assign ( chart . config . data , newChartData ) ;
126-
127- chart . config . data . datasets = newDataSets . map ( ( newDataSet : any ) => {
128- // given the new set, find it's current match
129- const currentDataSet = currentDataSets . find (
130- d => d . label === newDataSet . label && d . type === newDataSet . type
131- ) ;
126+ useEffect ( ( ) => {
127+ if ( ! redraw && chartRef . current ) {
128+ chartRef . current . config . data . labels = data . labels ;
129+ }
130+ } , [ redraw , data . labels ] ) ;
132131
133- // There is no original to update, so simply add new one
134- if ( ! currentDataSet || ! newDataSet . data ) return { ...newDataSet } ;
135-
136- if ( ! currentDataSet . data ) {
137- // @ts -expect-error Need to refactor
138- currentDataSet . data = [ ] ;
139- } else {
140- // @ts -expect-error Need to refactor
141- currentDataSet . data . length = newDataSet . data . length ;
142- }
143-
144- // copy in values
145- Object . assign ( currentDataSet . data , newDataSet . data ) ;
146-
147- // apply dataset changes, but keep copied data
148- Object . assign ( currentDataSet , {
149- ...newDataSet ,
150- data : currentDataSet . data ,
151- } ) ;
152- return currentDataSet ;
153- } ) ;
132+ useEffect ( ( ) => {
133+ if ( ! redraw && chartRef . current && data . datasets ) {
134+ setNextDatasets ( chartRef . current . config . data , data . datasets ) ;
135+ }
136+ } , [ redraw , data . datasets ] ) ;
154137
155- chart . update ( ) ;
156- } ;
138+ useEffect ( ( ) => {
139+ if ( ! chartRef . current ) return ;
157140
158- const destroyChart = ( ) => {
159- if ( chart ) chart . destroy ( ) ;
160- } ;
141+ if ( redraw ) {
142+ destroyChart ( ) ;
143+ setTimeout ( renderChart ) ;
144+ } else {
145+ chartRef . current . update ( ) ;
146+ }
147+ } , [ redraw , options , data . labels , data . datasets ] ) ;
161148
162149 useEffect ( ( ) => {
163150 renderChart ( ) ;
164151
165152 return ( ) => destroyChart ( ) ;
166153 } , [ ] ) ;
167154
168- useEffect ( ( ) => {
169- if ( redraw ) {
170- destroyChart ( ) ;
171- setTimeout ( ( ) => {
172- renderChart ( ) ;
173- } , 0 ) ;
174- } else {
175- updateChart ( ) ;
176- }
177- } ) ;
178-
179155 return (
180156 < canvas
181- ref = { canvas }
157+ ref = { canvasRef }
182158 role = 'img'
183159 height = { height }
184160 width = { width }
0 commit comments