Skip to content

Commit 52ae328

Browse files
committed
(types): properly handle null case for init / refs
- modify setRef to actually set internal refs to null if unmounted - modify getCanvas() and getSignaturePad() to throw errors if the ref is currently null - use both methods internally instead of directly accessing their respective variables - make some alias variables for brevity where possible - add a test for the error case as well
1 parent 7cd4efa commit 52ae328

File tree

2 files changed

+49
-20
lines changed

2 files changed

+49
-20
lines changed

src/index.tsx

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,20 @@ export class SignatureCanvas extends Component<SignatureCanvasProps> {
2929
clearOnResize: true
3030
}
3131

32-
// this is some hack-ish init and casting to avoid `| null` everywhere :/
33-
/* eslint-disable @typescript-eslint/consistent-type-assertions */
34-
_sigPad: SignaturePad = {} as SignaturePad
35-
_canvas: HTMLCanvasElement = {} as HTMLCanvasElement
36-
/* eslint-enable @typescript-eslint/consistent-type-assertions */
32+
static refNullError = new Error('react-signature-canvas is currently ' +
33+
'mounting or unmounting: React refs are null during this phase.')
34+
35+
// shortcut reference (https://stackoverflow.com/a/29244254/3431180)
36+
private readonly staticThis = this.constructor as typeof SignatureCanvas
37+
38+
_sigPad: SignaturePad | null = null
39+
_canvas: HTMLCanvasElement | null = null
3740

3841
private readonly setRef = (ref: HTMLCanvasElement | null): void => {
39-
if (ref !== null) {
40-
this._canvas = ref
42+
this._canvas = ref
43+
// if component is unmounted, set internal references to null
44+
if (this._canvas === null) {
45+
this._sigPad = null
4146
}
4247
}
4348

@@ -47,7 +52,8 @@ export class SignatureCanvas extends Component<SignatureCanvasProps> {
4752
}
4853

4954
componentDidMount: Component['componentDidMount'] = () => {
50-
this._sigPad = new SignaturePad(this._canvas, this._excludeOurProps())
55+
const canvas = this.getCanvas()
56+
this._sigPad = new SignaturePad(canvas, this._excludeOurProps())
5157
this._resizeCanvas()
5258
this.on()
5359
}
@@ -63,23 +69,30 @@ export class SignatureCanvas extends Component<SignatureCanvasProps> {
6369

6470
// return the canvas ref for operations like toDataURL
6571
getCanvas = (): HTMLCanvasElement => {
72+
if (this._canvas === null) {
73+
throw this.staticThis.refNullError
74+
}
6675
return this._canvas
6776
}
6877

6978
// return a trimmed copy of the canvas
7079
getTrimmedCanvas = (): HTMLCanvasElement => {
7180
// copy the canvas
81+
const canvas = this.getCanvas()
7282
const copy = document.createElement('canvas')
73-
copy.width = this._canvas.width
74-
copy.height = this._canvas.height
83+
copy.width = canvas.width
84+
copy.height = canvas.height
7585
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
76-
copy.getContext('2d')!.drawImage(this._canvas, 0, 0)
86+
copy.getContext('2d')!.drawImage(canvas, 0, 0)
7787
// then trim it
7888
return trimCanvas(copy)
7989
}
8090

8191
// return the internal SignaturePad reference
8292
getSignaturePad = (): SignaturePad => {
93+
if (this._sigPad === null) {
94+
throw this.staticThis.refNullError
95+
}
8396
return this._sigPad
8497
}
8598

@@ -98,7 +111,7 @@ export class SignatureCanvas extends Component<SignatureCanvasProps> {
98111
return
99112
}
100113

101-
const canvas = this._canvas
114+
const canvas = this.getCanvas()
102115
/* When zoomed out to less than 100%, for some very strange reason,
103116
some browsers report devicePixelRatio as less than 1
104117
and only part of the canvas is cleared then. */
@@ -124,36 +137,36 @@ export class SignatureCanvas extends Component<SignatureCanvasProps> {
124137
//
125138
on: SignaturePad['on'] = () => {
126139
window.addEventListener('resize', this._checkClearOnResize)
127-
return this._sigPad.on()
140+
return this.getSignaturePad().on()
128141
}
129142

130143
off: SignaturePad['off'] = () => {
131144
window.removeEventListener('resize', this._checkClearOnResize)
132-
return this._sigPad.off()
145+
return this.getSignaturePad().off()
133146
}
134147

135148
clear: SignaturePad['clear'] = () => {
136-
return this._sigPad.clear()
149+
return this.getSignaturePad().clear()
137150
}
138151

139152
isEmpty: SignaturePad['isEmpty'] = () => {
140-
return this._sigPad.isEmpty()
153+
return this.getSignaturePad().isEmpty()
141154
}
142155

143156
fromDataURL: SignaturePad['fromDataURL'] = (dataURL, options) => {
144-
return this._sigPad.fromDataURL(dataURL, options)
157+
return this.getSignaturePad().fromDataURL(dataURL, options)
145158
}
146159

147160
toDataURL: SignaturePad['toDataURL'] = (type, encoderOptions) => {
148-
return this._sigPad.toDataURL(type, encoderOptions)
161+
return this.getSignaturePad().toDataURL(type, encoderOptions)
149162
}
150163

151164
fromData: SignaturePad['fromData'] = (pointGroups) => {
152-
return this._sigPad.fromData(pointGroups)
165+
return this.getSignaturePad().fromData(pointGroups)
153166
}
154167

155168
toData: SignaturePad['toData'] = () => {
156-
return this._sigPad.toData()
169+
return this.getSignaturePad().toData()
157170
}
158171
}
159172

test/index.spec.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,3 +226,19 @@ describe('on & off methods', () => {
226226
expect(instance.on).not.toBeCalled()
227227
})
228228
})
229+
230+
// unmounting comes last
231+
describe('unmounting', () => {
232+
const wrapper = mount(<SignatureCanvas />)
233+
const instance = rSCInstance(wrapper)
234+
235+
it('should error when retrieving instance variables', () => {
236+
wrapper.unmount()
237+
expect(() => {
238+
instance.getCanvas()
239+
}).toThrowError(SignatureCanvas.refNullError)
240+
expect(() => {
241+
instance.getSignaturePad()
242+
}).toThrowError(SignatureCanvas.refNullError)
243+
})
244+
})

0 commit comments

Comments
 (0)