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' ;
10
3
import ChartJS from 'chart.js/auto' ;
11
4
import type { ChartData , ChartType , DefaultDataPoint } from 'chart.js' ;
12
5
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
+ } ;
14
12
15
13
function ChartComponent <
16
14
TType extends ChartType = ChartType ,
@@ -22,7 +20,7 @@ function ChartComponent<
22
20
width = 300 ,
23
21
redraw = false ,
24
22
type,
25
- data,
23
+ data : dataProp ,
26
24
options,
27
25
plugins = [ ] ,
28
26
getDatasetAtEvent,
@@ -32,45 +30,49 @@ function ChartComponent<
32
30
onClick : onClickProp ,
33
31
...props
34
32
} : Props < TType , TData , TLabel > ,
35
- ref : Ref < ChartJS < TType , TData , TLabel > >
33
+ ref : ForwardedRef < ChartJS < TType , TData , TLabel > >
36
34
) {
37
- type TypedChartJS = ChartJSOrUndefined < TType , TData , TLabel > ;
35
+ type TypedChartJS = ChartJS < TType , TData , TLabel > ;
38
36
type TypedChartData = ChartData < TType , TData , TLabel > ;
39
37
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 ) ;
41
46
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 ;
51
49
52
- const [ chart , setChart ] = useState < TypedChartJS > ( ) ;
50
+ chartRef . current = new ChartJS ( canvasRef . current , {
51
+ type,
52
+ data,
53
+ options,
54
+ plugins,
55
+ } ) ;
53
56
54
- useImperativeHandle < TypedChartJS , TypedChartJS > ( ref , ( ) => chart , [ chart ] ) ;
57
+ reforwardRef ( ref , chartRef . current ) ;
58
+ } ;
55
59
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
+ }
67
67
} ;
68
68
69
69
const onClick = ( event : MouseEvent < HTMLCanvasElement > ) => {
70
70
if ( onClickProp ) {
71
71
onClickProp ( event ) ;
72
72
}
73
73
74
+ const { current : chart } = chartRef ;
75
+
74
76
if ( ! chart ) return ;
75
77
76
78
getDatasetAtEvent &&
@@ -105,80 +107,54 @@ function ChartComponent<
105
107
) ;
106
108
} ;
107
109
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 ) ) ;
113
117
}
118
+ } , [ dataProp ] ) ;
114
119
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 } ;
119
123
}
124
+ } , [ redraw , options ] ) ;
120
125
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 ] ) ;
132
131
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 ] ) ;
154
137
155
- chart . update ( ) ;
156
- } ;
138
+ useEffect ( ( ) => {
139
+ if ( ! chartRef . current ) return ;
157
140
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 ] ) ;
161
148
162
149
useEffect ( ( ) => {
163
150
renderChart ( ) ;
164
151
165
152
return ( ) => destroyChart ( ) ;
166
153
} , [ ] ) ;
167
154
168
- useEffect ( ( ) => {
169
- if ( redraw ) {
170
- destroyChart ( ) ;
171
- setTimeout ( ( ) => {
172
- renderChart ( ) ;
173
- } , 0 ) ;
174
- } else {
175
- updateChart ( ) ;
176
- }
177
- } ) ;
178
-
179
155
return (
180
156
< canvas
181
- ref = { canvas }
157
+ ref = { canvasRef }
182
158
role = 'img'
183
159
height = { height }
184
160
width = { width }
0 commit comments