Skip to content
This repository was archived by the owner on Apr 15, 2025. It is now read-only.

Commit 13217e6

Browse files
committed
Warn developer on invalid source instead of throwing an exception
1 parent f998ef4 commit 13217e6

File tree

2 files changed

+59
-61
lines changed

2 files changed

+59
-61
lines changed

src/index.tsx

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ const imageCacheHoc = <P extends object>(
105105
options: Required<ReactNativeImageCacheHocOptions>
106106
fileSystem: FileSystem
107107
subscription?: Subscription
108+
invalidUrl: boolean
108109

109110
/**
110111
*
@@ -205,7 +206,7 @@ const imageCacheHoc = <P extends object>(
205206
)
206207

207208
// Validate input
208-
this._validateImageComponent()
209+
this.invalidUrl = !this._validateImageComponent()
209210
}
210211

211212
_validateImageComponent() {
@@ -225,9 +226,10 @@ const imageCacheHoc = <P extends object>(
225226
!traverse(this.props).get(['source', 'uri']) ||
226227
!validator.isURL(traverse(this.props).get(['source', 'uri']), validatorUrlOptions)
227228
) {
228-
throw new Error(
229+
console.warn(
229230
'Invalid source prop. <CacheableImage> props.source.uri should be a web accessible url with a valid protocol and host. NOTE: Default valid protocol is https, default valid hosts are *.',
230231
)
232+
return false
231233
} else {
232234
return true
233235
}
@@ -247,10 +249,12 @@ const imageCacheHoc = <P extends object>(
247249
FileSystem.lockCacheFile(fileName, this.componentId)
248250

249251
// Init the image cache logic
250-
this.subscription = this.fileSystem
251-
.observable(url, this.componentId)
252-
.pipe(takeUntil(this.unmounted$.pipe(skip(1))))
253-
.subscribe((info) => this.onSourceLoaded(info))
252+
if (!this.invalidUrl) {
253+
this.subscription = this.fileSystem
254+
.observable(url, this.componentId)
255+
.pipe(takeUntil(this.unmounted$.pipe(skip(1))))
256+
.subscribe((info) => this.onSourceLoaded(info))
257+
}
254258
}
255259

256260
/**
@@ -275,14 +279,16 @@ const imageCacheHoc = <P extends object>(
275279
FileSystem.unlockCacheFile(fileName, this.componentId)
276280
FileSystem.lockCacheFile(nextFileName, this.componentId)
277281

278-
this._validateImageComponent()
282+
this.invalidUrl = !this._validateImageComponent()
279283

280284
// Init the image cache logic
281285
this.subscription?.unsubscribe()
282-
this.subscription = this.fileSystem
283-
.observable(nextUrl, this.componentId)
284-
.pipe(takeUntil(this.unmounted$.pipe(skip(1))))
285-
.subscribe((info) => this.onSourceLoaded(info))
286+
if (!this.invalidUrl) {
287+
this.subscription = this.fileSystem
288+
.observable(nextUrl, this.componentId)
289+
.pipe(takeUntil(this.unmounted$.pipe(skip(1))))
290+
.subscribe((info) => this.onSourceLoaded(info))
291+
}
286292
}
287293

288294
componentWillUnmount() {
@@ -298,6 +304,7 @@ const imageCacheHoc = <P extends object>(
298304

299305
onSourceLoaded({ path }: CacheFileInfo) {
300306
this.setState({ localFilePath: path })
307+
this.invalidUrl = path === null
301308

302309
if (path && this.props.onLoadFinished) {
303310
Image.getSize(path, (width, height) => {
@@ -310,9 +317,9 @@ const imageCacheHoc = <P extends object>(
310317

311318
render() {
312319
// If media loaded, render full image component, else render placeholder.
313-
if (this.state.localFilePath) {
320+
if (this.state.localFilePath && !this.invalidUrl) {
314321
// Extract props proprietary to this HOC before passing props through.
315-
const { permanent, ...filteredProps } = this.props // eslint-disable-line no-unused-vars
322+
const { permanent, ...filteredProps } = this.props
316323

317324
const props = Object.assign({}, filteredProps, {
318325
source: { uri: this.state.localFilePath },
@@ -326,7 +333,10 @@ const imageCacheHoc = <P extends object>(
326333
<this.options.defaultPlaceholder.component {...this.options.defaultPlaceholder.props} />
327334
)
328335
} else {
329-
return <Wrapped {...(this.props as P)} />
336+
// Extract props proprietary to this HOC before passing props through.
337+
const { permanent, source, ...filteredProps } = this.props
338+
339+
return <Wrapped {...(filteredProps as P)} />
330340
}
331341
}
332342
}

tests/CacheableImage.test.tsx

Lines changed: 35 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ import { shallow } from 'enzyme'
99
import React from 'react'
1010

1111
describe('CacheableImage', function () {
12+
const originalWarn = console.warn
13+
afterEach(() => (console.warn = originalWarn))
14+
1215
it('HOC options validation should work as expected.', () => {
1316
// Check validation is catching bad option input.
1417
try {
@@ -174,74 +177,59 @@ describe('CacheableImage', function () {
174177
})
175178

176179
it('#_validateImageComponent should validate bad component props correctly.', () => {
180+
const consoleOutput = []
181+
console.warn = (output) => consoleOutput.push(output)
182+
177183
// Verify source uri prop only accepts web accessible urls.
178184

179185
const CacheableImage = imageCacheHoc(Image)
180186

181-
try {
182-
// eslint-disable-next-line no-unused-vars
183-
const cacheableImage = new CacheableImage({
184-
source: {
185-
uri: './local-file.jpg',
186-
},
187-
})
187+
new CacheableImage({
188+
source: {
189+
uri: './local-file.jpg',
190+
},
191+
})
188192

189-
throw new Error('Invalid source uri prop was accepted.')
190-
} catch (error) {
191-
error.should.deepEqual(
192-
new Error(
193-
'Invalid source prop. <CacheableImage> props.source.uri should be a web accessible url with a valid protocol and host. NOTE: Default valid protocol is https, default valid hosts are *.',
194-
),
195-
)
196-
}
193+
expect(consoleOutput).toEqual([
194+
'Invalid source prop. <CacheableImage> props.source.uri should be a web accessible url with a valid protocol and host. NOTE: Default valid protocol is https, default valid hosts are *.',
195+
])
197196

198197
// Verify source uri prop only accepts web accessible urls from whitelist if whitelist set.
199198

200199
const CacheableImageWithOpts = imageCacheHoc(Image, {
201200
fileHostWhitelist: ['i.redd.it'],
202201
})
203202

204-
try {
205-
// eslint-disable-next-line no-unused-vars
206-
const cacheableImageWithOpts = new CacheableImageWithOpts({
207-
source: {
208-
uri:
209-
'https://www.google.com/logos/doodles/2017/day-of-the-dead-2017-6241959625621504-l.png',
210-
},
211-
})
203+
new CacheableImageWithOpts({
204+
source: {
205+
uri:
206+
'https://www.google.com/logos/doodles/2017/day-of-the-dead-2017-6241959625621504-l.png',
207+
},
208+
})
212209

213-
throw new Error('Invalid source uri prop was accepted.')
214-
} catch (error) {
215-
error.should.deepEqual(
216-
new Error(
217-
'Invalid source prop. <CacheableImage> props.source.uri should be a web accessible url with a valid protocol and host. NOTE: Default valid protocol is https, default valid hosts are *.',
218-
),
219-
)
220-
}
210+
expect(consoleOutput).toEqual([
211+
'Invalid source prop. <CacheableImage> props.source.uri should be a web accessible url with a valid protocol and host. NOTE: Default valid protocol is https, default valid hosts are *.',
212+
'Invalid source prop. <CacheableImage> props.source.uri should be a web accessible url with a valid protocol and host. NOTE: Default valid protocol is https, default valid hosts are *.',
213+
])
221214

222215
// Verify source uri prop only accepts web accessible urls from correct protocols if protocol list set.
223216

224217
const CacheableImageWithProtocolOpts = imageCacheHoc(Image, {
225218
validProtocols: ['http'],
226219
})
227220

228-
try {
229-
// eslint-disable-next-line no-unused-vars
230-
const cacheableImageWithProtocolOpts = new CacheableImageWithProtocolOpts({
231-
source: {
232-
uri:
233-
'https://www.google.com/logos/doodles/2017/day-of-the-dead-2017-6241959625621504-l.png',
234-
},
235-
})
221+
new CacheableImageWithProtocolOpts({
222+
source: {
223+
uri:
224+
'https://www.google.com/logos/doodles/2017/day-of-the-dead-2017-6241959625621504-l.png',
225+
},
226+
})
236227

237-
throw new Error('Invalid source uri prop was accepted.')
238-
} catch (error) {
239-
error.should.deepEqual(
240-
new Error(
241-
'Invalid source prop. <CacheableImage> props.source.uri should be a web accessible url with a valid protocol and host. NOTE: Default valid protocol is https, default valid hosts are *.',
242-
),
243-
)
244-
}
228+
expect(consoleOutput).toEqual([
229+
'Invalid source prop. <CacheableImage> props.source.uri should be a web accessible url with a valid protocol and host. NOTE: Default valid protocol is https, default valid hosts are *.',
230+
'Invalid source prop. <CacheableImage> props.source.uri should be a web accessible url with a valid protocol and host. NOTE: Default valid protocol is https, default valid hosts are *.',
231+
'Invalid source prop. <CacheableImage> props.source.uri should be a web accessible url with a valid protocol and host. NOTE: Default valid protocol is https, default valid hosts are *.',
232+
])
245233
})
246234

247235
it('Verify component is actually still mounted before calling setState() in componentDidMount().', async () => {

0 commit comments

Comments
 (0)