react-native-fast-image 10.6.0 → 10.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -101,7 +101,7 @@ const YourImage = () => (
101
101
 
102
102
  ### `FastImage`
103
103
 
104
- It's a single native view, which also takes React Native's View props, such as accessibility props, `testID`, `nativeID`, `onLayout`, `pointerEvents` and `hitSlop`. A ref to it is that view (e.g. for `measure`). It doesn't take children: use [`FastImageBackground`](#fastimagebackground) for content over an image.
104
+ It's a single native view, which also takes React Native's View props, such as accessibility props, `testID`, `nativeID`, `onLayout`, `pointerEvents` and `hitSlop`. A ref to it is that view (e.g. for `measure`), with [`reload()`](#refreload). It doesn't take children: use [`FastImageBackground`](#fastimagebackground) for content over an image.
105
105
 
106
106
  <!-- api:props start (generated from src/ by website/scripts/generate.mts) -->
107
107
 
@@ -537,6 +537,27 @@ If supplied, changes the color of all the non-transparent pixels to the given co
537
537
 
538
538
  <!-- api:props end -->
539
539
 
540
+ ---
541
+
542
+ #### `ref.reload()`
543
+
544
+ Loads the image again, e.g. to try again after `onError`. The image showing stays until the new one has loaded (or the placeholder, if it failed), and the events are sent as for a new source. An image in the cache loads from it. In TypeScript, the ref's type is `FastImageRef`.
545
+
546
+ ```tsx
547
+ const image = useRef<FastImageRef>(null)
548
+
549
+ <FastImage ref={image} source={{ uri }} onError={() => setFailed(true)} />
550
+ {failed && (
551
+ <Button
552
+ title="Try again"
553
+ onPress={() => {
554
+ setFailed(false)
555
+ image.current?.reload()
556
+ }}
557
+ />
558
+ )}
559
+ ```
560
+
540
561
  ### `FastImageBackground`
541
562
 
542
563
  An image with content on top of it: a view that the image fills, with the children on top.
@@ -577,7 +598,7 @@ The image's style.
577
598
 
578
599
  #### `imageRef`
579
600
 
580
- **Type:** `Ref<any>`
601
+ **Type:** `Ref<FastImageRef>`
581
602
 
582
603
  A ref to the image (the FastImage inside).
583
604
 
@@ -435,6 +435,12 @@ class FastImageViewManager extends SimpleViewManager<FastImageViewWithUrl>
435
435
  view.setObjectFit(FastImageViewConverter.getScaleType(objectFit));
436
436
  }
437
437
 
438
+ // The ref's reload() (a command).
439
+ @Override
440
+ public void reload(FastImageViewWithUrl view) {
441
+ view.reload();
442
+ }
443
+
438
444
  @Override
439
445
  public void onDropViewInstance(@NonNull FastImageViewWithUrl view) {
440
446
  // Cancels its request, and nothing loads or tracks progress for it
@@ -364,6 +364,15 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
364
364
  return true;
365
365
  }
366
366
 
367
+ // Loads the image again, as a new source would, keeping the image showing
368
+ // until then (the ref's reload()). Before the first props there's nothing
369
+ // to load yet.
370
+ void reload() {
371
+ if (mManager == null) return;
372
+ mNeedsReload = true;
373
+ onAfterUpdate(mManager, requestManager);
374
+ }
375
+
367
376
  // Shows at once, as a new source placeholder does (see updatePlaceholder),
368
377
  // without loading the image again.
369
378
  public void setPlaceholder(@Nullable Drawable placeholder) {
@@ -0,0 +1,2 @@
1
+ import type React from 'react';
2
+ export declare function composeRefs<T>(...refs: (React.Ref<T> | undefined)[]): React.RefCallback<T>;
package/dist/index.cjs.js CHANGED
@@ -255,6 +255,7 @@ function objectPositionOrCentered(position) {
255
255
  }
256
256
  //#endregion
257
257
  //#region src/specs/FastImageViewNativeComponent.ts
258
+ const Commands = (0, react_native.codegenNativeCommands)({ supportedCommands: ["reload"] });
258
259
  var FastImageViewNativeComponent_default = (0, react_native.codegenNativeComponent)("FastImageView");
259
260
  //#endregion
260
261
  //#region src/specs/NativeFastImageModule.ts
@@ -279,6 +280,27 @@ function dataUri(value) {
279
280
  if (typeof __DEV__ !== "undefined" && __DEV__) warnOnce("placeholder-url", `react-native-fast-image: \`blurDataUri\` takes a data uri (data:image/…): "${value.slice(0, 60)}" is ignored. For a placeholder loaded from a url, use \`placeholder\`.`, "warn");
280
281
  }
281
282
  //#endregion
283
+ //#region src/composeRefs.ts
284
+ function composeRefs(...refs) {
285
+ return (value) => {
286
+ const cleanups = refs.map((ref) => {
287
+ if (typeof ref === "function") {
288
+ const cleanup = ref(value);
289
+ return typeof cleanup === "function" ? cleanup : () => {
290
+ ref(null);
291
+ };
292
+ }
293
+ if (ref) {
294
+ ref.current = value;
295
+ return () => {
296
+ ref.current = null;
297
+ };
298
+ }
299
+ });
300
+ return () => cleanups.forEach((cleanup) => cleanup === null || cleanup === void 0 ? void 0 : cleanup());
301
+ };
302
+ }
303
+ //#endregion
282
304
  //#region src/index.tsx
283
305
  const resolvePlaceholder = (placeholder) => {
284
306
  if (typeof placeholder === "string") {
@@ -357,9 +379,10 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, onLoadSt
357
379
  return Array.isArray(single) ? single.map((entry) => react_native.Image.resolveAssetSource(entry)) : react_native.Image.resolveAssetSource(single);
358
380
  }, [source]);
359
381
  const resolvedPlaceholder = hasSourcePlaceholder(placeholder) ? placeholder.thumbhash || dataUri(placeholder.blurDataUri) ? placeholder : resolvePlaceholder(defaultSource) : (_placeholderSource = placeholderSource(placeholder)) !== null && _placeholderSource !== void 0 ? _placeholderSource : resolvePlaceholder(placeholder !== null && placeholder !== void 0 ? placeholder : defaultSource);
382
+ const ref = (0, react.useMemo)(() => composeRefs(addReload, forwardedRef), [forwardedRef]);
360
383
  return /* @__PURE__ */ react.default.createElement(FastImageViewNativeComponent_default, {
361
384
  ...props,
362
- ref: forwardedRef,
385
+ ref,
363
386
  style: [styles.image, style],
364
387
  recyclingKey: (_props$recyclingKey = props.recyclingKey) !== null && _props$recyclingKey !== void 0 ? _props$recyclingKey : void 0,
365
388
  loopCount: loopCount(loop),
@@ -386,6 +409,9 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, onLoadSt
386
409
  objectPositionYOffset: position.y.offset
387
410
  });
388
411
  }
412
+ function addReload(view) {
413
+ if (view) view.reload = () => Commands.reload(view);
414
+ }
389
415
  const FastImageMemo = (0, react.memo)(FastImageBase);
390
416
  const FastImageComponent = (0, react.forwardRef)((props, ref) => /* @__PURE__ */ react.default.createElement(FastImageMemo, {
391
417
  forwardedRef: ref,
@@ -196,4 +196,6 @@ declare export default class FastImage extends React$Component<FastImageProps> {
196
196
  source: FastImageSource,
197
197
  file: string,
198
198
  ) => Promise<CachePathResult>;
199
+ // On a ref: loads the image again, e.g. to try again after onError.
200
+ reload(): void;
199
201
  }
@@ -282,6 +282,27 @@ function dataUri(value) {
282
282
  if (typeof __DEV__ !== "undefined" && __DEV__) warnOnce("placeholder-url", `react-native-fast-image: \`blurDataUri\` takes a data uri (data:image/…): "${value.slice(0, 60)}" is ignored. For a placeholder loaded from a url, use \`placeholder\`.`, "warn");
283
283
  }
284
284
  //#endregion
285
+ //#region src/composeRefs.ts
286
+ function composeRefs(...refs) {
287
+ return (value) => {
288
+ const cleanups = refs.map((ref) => {
289
+ if (typeof ref === "function") {
290
+ const cleanup = ref(value);
291
+ return typeof cleanup === "function" ? cleanup : () => {
292
+ ref(null);
293
+ };
294
+ }
295
+ if (ref) {
296
+ ref.current = value;
297
+ return () => {
298
+ ref.current = null;
299
+ };
300
+ }
301
+ });
302
+ return () => cleanups.forEach((cleanup) => cleanup === null || cleanup === void 0 ? void 0 : cleanup());
303
+ };
304
+ }
305
+ //#endregion
285
306
  //#region src/index.web.tsx
286
307
  const notSupported = {
287
308
  ok: false,
@@ -298,10 +319,15 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, blurRadi
298
319
  const src = sized ? sized.src : resolveUri(source);
299
320
  const srcSet = sized === null || sized === void 0 ? void 0 : sized.srcSet;
300
321
  const key = srcSet !== null && srcSet !== void 0 ? srcSet : src;
322
+ const [reloads, setReloads] = (0, react.useState)(0);
323
+ const ref = (0, react.useMemo)(() => composeRefs((view) => {
324
+ if (view) view.reload = () => setReloads((count) => count + 1);
325
+ }, forwardedRef), [forwardedRef]);
301
326
  const priority = sourcePriority(source);
302
327
  const preview = hasSourcePlaceholder(placeholder) ? placeholder : void 0;
303
328
  const blurUri = (_resolveUri = resolveUri(dataUri(shared(source, "blurDataUri", preview)))) !== null && _resolveUri !== void 0 ? _resolveUri : thumbhashUri(shared(source, "thumbhash", preview));
304
329
  const urlPlaceholder = (_placeholderSource = placeholderSource(placeholder)) === null || _placeholderSource === void 0 ? void 0 : _placeholderSource.uri;
330
+ const placeholderKey = `${urlPlaceholder} ${reloads}`;
305
331
  const [failedPlaceholder, setFailedPlaceholder] = (0, react.useState)();
306
332
  const defaultUri = blurUri !== null && blurUri !== void 0 ? blurUri : resolveUri(preview ? defaultSource : urlPlaceholder ? void 0 : placeholder !== null && placeholder !== void 0 ? placeholder : defaultSource);
307
333
  const [result, setResult] = (0, react.useState)();
@@ -315,14 +341,14 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, blurRadi
315
341
  const showDefault = ok !== true;
316
342
  useClientLayoutEffect(() => {
317
343
  if (key) onLoadStart === null || onLoadStart === void 0 || onLoadStart();
318
- }, [key]);
344
+ }, [key, reloads]);
319
345
  return /* @__PURE__ */ react.default.createElement(react_native.View, {
320
346
  ...viewProps,
321
347
  "aria-label": label,
322
348
  style: [styles.container, style],
323
349
  onLayout,
324
350
  pointerEvents,
325
- ref: forwardedRef
351
+ ref
326
352
  }, defaultUri && showDefault ? /* @__PURE__ */ react.default.createElement(Picture, {
327
353
  key: `default ${defaultUri}`,
328
354
  src: defaultUri,
@@ -334,7 +360,7 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, blurRadi
334
360
  filter: blurUri ? [`url(#${blurId})`, tintColor ? `url(#${tintId})` : ""].filter(Boolean).join(" ") : filter,
335
361
  alt: key ? "" : label !== null && label !== void 0 ? label : ""
336
362
  }) : null, urlPlaceholder && showDefault ? /* @__PURE__ */ react.default.createElement(Picture, {
337
- key: `placeholder ${urlPlaceholder}`,
363
+ key: `placeholder ${placeholderKey}`,
338
364
  src: urlPlaceholder,
339
365
  loading,
340
366
  priority: "high",
@@ -342,11 +368,11 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, blurRadi
342
368
  fit: fit === "scale-down" || fit === "none" ? "contain" : fit,
343
369
  position,
344
370
  filter: tintColor ? `url(#${tintId})` : void 0,
345
- hidden: failedPlaceholder === urlPlaceholder,
346
- onError: () => setFailedPlaceholder(urlPlaceholder),
371
+ hidden: failedPlaceholder === placeholderKey,
372
+ onError: () => setFailedPlaceholder(placeholderKey),
347
373
  alt: ""
348
374
  }) : null, key && src ? /* @__PURE__ */ react.default.createElement(Picture, {
349
- key: several ? "sizes" : key,
375
+ key: `${several ? "sizes" : key} ${reloads}`,
350
376
  src,
351
377
  srcSet,
352
378
  sizes,
@@ -604,11 +630,10 @@ FastImage.setDefaults = () => {};
604
630
  FastImage.writeToCache = () => Promise.resolve(notSupported);
605
631
  FastImage.getCachePath = () => Promise.resolve(notSupported);
606
632
  const styles = react_native.StyleSheet.create({ container: { overflow: "hidden" } });
607
- const FastImageWithRef = FastImageComponent;
608
633
  const FastImageBackground = (0, react.forwardRef)(({ style, imageStyle, imageRef, children, ...props }, ref) => /* @__PURE__ */ react.default.createElement(react_native.View, {
609
634
  style,
610
635
  ref
611
- }, /* @__PURE__ */ react.default.createElement(FastImageWithRef, {
636
+ }, /* @__PURE__ */ react.default.createElement(FastImageComponent, {
612
637
  ...props,
613
638
  style: [react_native.StyleSheet.absoluteFill, imageStyle],
614
639
  ref: imageRef
@@ -196,4 +196,6 @@ declare export default class FastImage extends React$Component<FastImageProps> {
196
196
  source: FastImageSource,
197
197
  file: string,
198
198
  ) => Promise<CachePathResult>;
199
+ // On a ref: loads the image again, e.g. to try again after onError.
200
+ reload(): void;
199
201
  }
package/dist/index.d.ts CHANGED
@@ -664,6 +664,18 @@ export interface FastImageProps extends AccessibilityProps, ViewProps {
664
664
  children?: "FastImage doesn't take children: use FastImageBackground for content over an image";
665
665
  }
666
666
  type ViewRef = React.ComponentRef<typeof View>;
667
+ /**
668
+ * What a ref to `FastImage` gets: its native view, with a View's methods
669
+ * (e.g. `measure`), and `reload()`. E.g. `useRef<FastImageRef>(null)`.
670
+ */
671
+ export type FastImageRef = ViewRef & {
672
+ /**
673
+ * Loads the image again, e.g. to try again after `onError`. The image
674
+ * showing stays until the new one has loaded, and the events are sent as
675
+ * for a new source. An image in the cache loads from it.
676
+ */
677
+ reload(): void;
678
+ };
667
679
  /** A preloaded source that loaded (and is now cached). */
668
680
  export interface PreloadSuccess {
669
681
  /** The source's uri. */
@@ -762,14 +774,14 @@ export interface FastImageDefaults {
762
774
  */
763
775
  transition?: boolean | number | Transition | null;
764
776
  }
765
- declare const FastImage: React.ForwardRefExoticComponent<FastImageProps & React.RefAttributes<ViewRef>> & FastImageStaticProperties;
777
+ declare const FastImage: React.ForwardRefExoticComponent<FastImageProps & React.RefAttributes<FastImageRef>> & FastImageStaticProperties;
766
778
  export interface FastImageBackgroundProps extends Omit<FastImageProps, 'style' | 'children'> {
767
779
  /** The container's style; the image fills it. */
768
780
  style?: StyleProp<ViewStyle>;
769
781
  /** The image's style. */
770
782
  imageStyle?: StyleProp<ImageStyle>;
771
783
  /** A ref to the image (the FastImage inside). */
772
- imageRef?: React.Ref<any>;
784
+ imageRef?: React.Ref<FastImageRef>;
773
785
  /** Content shown on top of the image. */
774
786
  children?: React.ReactNode;
775
787
  }
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import React, { Children, forwardRef, memo, useMemo } from "react";
2
- import { Image, Platform, StyleSheet, TurboModuleRegistry, View, codegenNativeComponent } from "react-native";
2
+ import { Image, Platform, StyleSheet, TurboModuleRegistry, View, codegenNativeCommands, codegenNativeComponent } from "react-native";
3
3
  //#region src/constants.ts
4
4
  const resizeMode = {
5
5
  contain: "contain",
@@ -228,6 +228,7 @@ function objectPositionOrCentered(position) {
228
228
  }
229
229
  //#endregion
230
230
  //#region src/specs/FastImageViewNativeComponent.ts
231
+ const Commands = codegenNativeCommands({ supportedCommands: ["reload"] });
231
232
  var FastImageViewNativeComponent_default = codegenNativeComponent("FastImageView");
232
233
  //#endregion
233
234
  //#region src/specs/NativeFastImageModule.ts
@@ -252,6 +253,27 @@ function dataUri(value) {
252
253
  if (typeof __DEV__ !== "undefined" && __DEV__) warnOnce("placeholder-url", `react-native-fast-image: \`blurDataUri\` takes a data uri (data:image/…): "${value.slice(0, 60)}" is ignored. For a placeholder loaded from a url, use \`placeholder\`.`, "warn");
253
254
  }
254
255
  //#endregion
256
+ //#region src/composeRefs.ts
257
+ function composeRefs(...refs) {
258
+ return (value) => {
259
+ const cleanups = refs.map((ref) => {
260
+ if (typeof ref === "function") {
261
+ const cleanup = ref(value);
262
+ return typeof cleanup === "function" ? cleanup : () => {
263
+ ref(null);
264
+ };
265
+ }
266
+ if (ref) {
267
+ ref.current = value;
268
+ return () => {
269
+ ref.current = null;
270
+ };
271
+ }
272
+ });
273
+ return () => cleanups.forEach((cleanup) => cleanup === null || cleanup === void 0 ? void 0 : cleanup());
274
+ };
275
+ }
276
+ //#endregion
255
277
  //#region src/index.tsx
256
278
  const resolvePlaceholder = (placeholder) => {
257
279
  if (typeof placeholder === "string") {
@@ -330,9 +352,10 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, onLoadSt
330
352
  return Array.isArray(single) ? single.map((entry) => Image.resolveAssetSource(entry)) : Image.resolveAssetSource(single);
331
353
  }, [source]);
332
354
  const resolvedPlaceholder = hasSourcePlaceholder(placeholder) ? placeholder.thumbhash || dataUri(placeholder.blurDataUri) ? placeholder : resolvePlaceholder(defaultSource) : (_placeholderSource = placeholderSource(placeholder)) !== null && _placeholderSource !== void 0 ? _placeholderSource : resolvePlaceholder(placeholder !== null && placeholder !== void 0 ? placeholder : defaultSource);
355
+ const ref = useMemo(() => composeRefs(addReload, forwardedRef), [forwardedRef]);
333
356
  return /* @__PURE__ */ React.createElement(FastImageViewNativeComponent_default, {
334
357
  ...props,
335
- ref: forwardedRef,
358
+ ref,
336
359
  style: [styles.image, style],
337
360
  recyclingKey: (_props$recyclingKey = props.recyclingKey) !== null && _props$recyclingKey !== void 0 ? _props$recyclingKey : void 0,
338
361
  loopCount: loopCount(loop),
@@ -359,6 +382,9 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, onLoadSt
359
382
  objectPositionYOffset: position.y.offset
360
383
  });
361
384
  }
385
+ function addReload(view) {
386
+ if (view) view.reload = () => Commands.reload(view);
387
+ }
362
388
  const FastImageMemo = memo(FastImageBase);
363
389
  const FastImageComponent = forwardRef((props, ref) => /* @__PURE__ */ React.createElement(FastImageMemo, {
364
390
  forwardedRef: ref,
@@ -196,4 +196,6 @@ declare export default class FastImage extends React$Component<FastImageProps> {
196
196
  source: FastImageSource,
197
197
  file: string,
198
198
  ) => Promise<CachePathResult>;
199
+ // On a ref: loads the image again, e.g. to try again after onError.
200
+ reload(): void;
199
201
  }
@@ -1,6 +1,8 @@
1
1
  import React from 'react';
2
- import type { FastImageBackgroundProps, FastImageProps, FastImageStaticProperties } from './index';
2
+ import { View } from 'react-native';
3
+ import type { FastImageBackgroundProps, FastImageProps, FastImageRef, FastImageStaticProperties } from './index';
3
4
  export type * from './index';
4
- declare const FastImage: React.ComponentType<FastImageProps> & FastImageStaticProperties;
5
- export declare const FastImageBackground: React.ComponentType<FastImageBackgroundProps>;
5
+ type ViewRef = React.ComponentRef<typeof View>;
6
+ declare const FastImage: React.ForwardRefExoticComponent<FastImageProps & React.RefAttributes<FastImageRef>> & FastImageStaticProperties;
7
+ export declare const FastImageBackground: React.ForwardRefExoticComponent<FastImageBackgroundProps & React.RefAttributes<ViewRef>>;
6
8
  export default FastImage;
package/dist/index.web.js CHANGED
@@ -1,4 +1,4 @@
1
- import React, { Children, forwardRef, memo, useEffect, useId, useLayoutEffect, useRef, useState } from "react";
1
+ import React, { Children, forwardRef, memo, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
2
2
  import { Image, PixelRatio, StyleSheet, View } from "react-native";
3
3
  import { getAssetByID } from "react-native-web/dist/modules/AssetRegistry";
4
4
  //#region src/constants.ts
@@ -255,6 +255,27 @@ function dataUri(value) {
255
255
  if (typeof __DEV__ !== "undefined" && __DEV__) warnOnce("placeholder-url", `react-native-fast-image: \`blurDataUri\` takes a data uri (data:image/…): "${value.slice(0, 60)}" is ignored. For a placeholder loaded from a url, use \`placeholder\`.`, "warn");
256
256
  }
257
257
  //#endregion
258
+ //#region src/composeRefs.ts
259
+ function composeRefs(...refs) {
260
+ return (value) => {
261
+ const cleanups = refs.map((ref) => {
262
+ if (typeof ref === "function") {
263
+ const cleanup = ref(value);
264
+ return typeof cleanup === "function" ? cleanup : () => {
265
+ ref(null);
266
+ };
267
+ }
268
+ if (ref) {
269
+ ref.current = value;
270
+ return () => {
271
+ ref.current = null;
272
+ };
273
+ }
274
+ });
275
+ return () => cleanups.forEach((cleanup) => cleanup === null || cleanup === void 0 ? void 0 : cleanup());
276
+ };
277
+ }
278
+ //#endregion
258
279
  //#region src/index.web.tsx
259
280
  const notSupported = {
260
281
  ok: false,
@@ -271,10 +292,15 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, blurRadi
271
292
  const src = sized ? sized.src : resolveUri(source);
272
293
  const srcSet = sized === null || sized === void 0 ? void 0 : sized.srcSet;
273
294
  const key = srcSet !== null && srcSet !== void 0 ? srcSet : src;
295
+ const [reloads, setReloads] = useState(0);
296
+ const ref = useMemo(() => composeRefs((view) => {
297
+ if (view) view.reload = () => setReloads((count) => count + 1);
298
+ }, forwardedRef), [forwardedRef]);
274
299
  const priority = sourcePriority(source);
275
300
  const preview = hasSourcePlaceholder(placeholder) ? placeholder : void 0;
276
301
  const blurUri = (_resolveUri = resolveUri(dataUri(shared(source, "blurDataUri", preview)))) !== null && _resolveUri !== void 0 ? _resolveUri : thumbhashUri(shared(source, "thumbhash", preview));
277
302
  const urlPlaceholder = (_placeholderSource = placeholderSource(placeholder)) === null || _placeholderSource === void 0 ? void 0 : _placeholderSource.uri;
303
+ const placeholderKey = `${urlPlaceholder} ${reloads}`;
278
304
  const [failedPlaceholder, setFailedPlaceholder] = useState();
279
305
  const defaultUri = blurUri !== null && blurUri !== void 0 ? blurUri : resolveUri(preview ? defaultSource : urlPlaceholder ? void 0 : placeholder !== null && placeholder !== void 0 ? placeholder : defaultSource);
280
306
  const [result, setResult] = useState();
@@ -288,14 +314,14 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, blurRadi
288
314
  const showDefault = ok !== true;
289
315
  useClientLayoutEffect(() => {
290
316
  if (key) onLoadStart === null || onLoadStart === void 0 || onLoadStart();
291
- }, [key]);
317
+ }, [key, reloads]);
292
318
  return /* @__PURE__ */ React.createElement(View, {
293
319
  ...viewProps,
294
320
  "aria-label": label,
295
321
  style: [styles.container, style],
296
322
  onLayout,
297
323
  pointerEvents,
298
- ref: forwardedRef
324
+ ref
299
325
  }, defaultUri && showDefault ? /* @__PURE__ */ React.createElement(Picture, {
300
326
  key: `default ${defaultUri}`,
301
327
  src: defaultUri,
@@ -307,7 +333,7 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, blurRadi
307
333
  filter: blurUri ? [`url(#${blurId})`, tintColor ? `url(#${tintId})` : ""].filter(Boolean).join(" ") : filter,
308
334
  alt: key ? "" : label !== null && label !== void 0 ? label : ""
309
335
  }) : null, urlPlaceholder && showDefault ? /* @__PURE__ */ React.createElement(Picture, {
310
- key: `placeholder ${urlPlaceholder}`,
336
+ key: `placeholder ${placeholderKey}`,
311
337
  src: urlPlaceholder,
312
338
  loading,
313
339
  priority: "high",
@@ -315,11 +341,11 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, blurRadi
315
341
  fit: fit === "scale-down" || fit === "none" ? "contain" : fit,
316
342
  position,
317
343
  filter: tintColor ? `url(#${tintId})` : void 0,
318
- hidden: failedPlaceholder === urlPlaceholder,
319
- onError: () => setFailedPlaceholder(urlPlaceholder),
344
+ hidden: failedPlaceholder === placeholderKey,
345
+ onError: () => setFailedPlaceholder(placeholderKey),
320
346
  alt: ""
321
347
  }) : null, key && src ? /* @__PURE__ */ React.createElement(Picture, {
322
- key: several ? "sizes" : key,
348
+ key: `${several ? "sizes" : key} ${reloads}`,
323
349
  src,
324
350
  srcSet,
325
351
  sizes,
@@ -577,11 +603,10 @@ FastImage.setDefaults = () => {};
577
603
  FastImage.writeToCache = () => Promise.resolve(notSupported);
578
604
  FastImage.getCachePath = () => Promise.resolve(notSupported);
579
605
  const styles = StyleSheet.create({ container: { overflow: "hidden" } });
580
- const FastImageWithRef = FastImageComponent;
581
606
  const FastImageBackground = forwardRef(({ style, imageStyle, imageRef, children, ...props }, ref) => /* @__PURE__ */ React.createElement(View, {
582
607
  style,
583
608
  ref
584
- }, /* @__PURE__ */ React.createElement(FastImageWithRef, {
609
+ }, /* @__PURE__ */ React.createElement(FastImageComponent, {
585
610
  ...props,
586
611
  style: [StyleSheet.absoluteFill, imageStyle],
587
612
  ref: imageRef
@@ -196,4 +196,6 @@ declare export default class FastImage extends React$Component<FastImageProps> {
196
196
  source: FastImageSource,
197
197
  file: string,
198
198
  ) => Promise<CachePathResult>;
199
+ // On a ref: loads the image again, e.g. to try again after onError.
200
+ reload(): void;
199
201
  }
@@ -1,3 +1,4 @@
1
+ import type * as React from 'react';
1
2
  import type { CodegenTypes, ColorValue, HostComponent, ViewProps } from 'react-native';
2
3
  type LoadStartEvent = Readonly<{}>;
3
4
  type ProgressEvent = Readonly<{
@@ -44,5 +45,9 @@ export interface NativeProps extends ViewProps {
44
45
  onFastImageError?: CodegenTypes.DirectEventHandler<ErrorEvent>;
45
46
  onFastImageLoadEnd?: CodegenTypes.DirectEventHandler<LoadEndEvent>;
46
47
  }
48
+ interface NativeCommands {
49
+ reload: (viewRef: React.ElementRef<HostComponent<NativeProps>>) => void;
50
+ }
51
+ export declare const Commands: NativeCommands;
47
52
  declare const _default: HostComponent<NativeProps>;
48
53
  export default _default;
@@ -82,6 +82,10 @@ typedef NS_ENUM(NSInteger, FFFObjectFit) {
82
82
  // the view has a size, when it needs one).
83
83
  - (void) didSetProps;
84
84
 
85
+ // Loads the image again, as a new source would (once the view has a size,
86
+ // when it needs one): the ref's reload().
87
+ - (void) reload;
88
+
85
89
  // Keeps the view's mask (React Native's, for its rounded corners) on the box
86
90
  // the view was laid out at, while objectPosition moves the view: after React
87
91
  // Native sets or removes it.
@@ -1047,6 +1047,11 @@ NSString *FFFErrorMessage(NSError *error)
1047
1047
  }
1048
1048
  }
1049
1049
 
1050
+ - (void) reload {
1051
+ _needsReload = YES;
1052
+ [self didSetProps];
1053
+ }
1054
+
1050
1055
  // The same image decoded again for a dynamicRangeLimit between HDR and SDR,
1051
1056
  // as a resize is (reloadIfResized): without events once it has loaded, and
1052
1057
  // the image showing stays if the load fails. A display setting shouldn't
@@ -388,6 +388,17 @@ typedef NS_OPTIONS(int32_t, FFFEvent) {
388
388
  }
389
389
  }
390
390
 
391
+ // The view's commands (reload), from the ref's methods.
392
+ - (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args
393
+ {
394
+ RCTFastImageViewHandleCommand(self, commandName, args);
395
+ }
396
+
397
+ - (void)reload
398
+ {
399
+ [_imageView reload];
400
+ }
401
+
391
402
  // React Native makes the image view's mask again when the appearance changes
392
403
  // (as after an update): where objectPosition moved the view, it moves back.
393
404
  - (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-fast-image",
3
- "version": "10.6.0",
3
+ "version": "10.7.0",
4
4
  "description": "FastImage, performant React Native image component.",
5
5
  "keywords": [
6
6
  "cache",
@@ -0,0 +1,29 @@
1
+ // Shared by the native (index.tsx) and web (index.web.tsx) versions.
2
+ import type React from 'react'
3
+
4
+ // One ref that sets each of these refs (as React Native's useMergeRefs does),
5
+ // and clears them when it's cleared.
6
+ export function composeRefs<T>(
7
+ ...refs: (React.Ref<T> | undefined)[]
8
+ ): React.RefCallback<T> {
9
+ return (value) => {
10
+ const cleanups = refs.map((ref) => {
11
+ if (typeof ref === 'function') {
12
+ const cleanup = ref(value)
13
+ return typeof cleanup === 'function'
14
+ ? cleanup
15
+ : () => {
16
+ ref(null)
17
+ }
18
+ }
19
+ if (ref) {
20
+ ref.current = value
21
+ return () => {
22
+ ref.current = null
23
+ }
24
+ }
25
+ return undefined
26
+ })
27
+ return () => cleanups.forEach((cleanup) => cleanup?.())
28
+ }
29
+ }
package/src/index.js.flow CHANGED
@@ -196,4 +196,6 @@ declare export default class FastImage extends React$Component<FastImageProps> {
196
196
  source: FastImageSource,
197
197
  file: string,
198
198
  ) => Promise<CachePathResult>;
199
+ // On a ref: loads the image again, e.g. to try again after onError.
200
+ reload(): void;
199
201
  }
package/src/index.tsx CHANGED
@@ -14,7 +14,7 @@ import {
14
14
  import { cacheControl, priority, resizeMode } from './constants'
15
15
  import { fromStyle, resolveObjectFit } from './objectFit'
16
16
  import { objectPositionOrCentered, type ObjectPosition } from './objectPosition'
17
- import FastImageView from './specs/FastImageViewNativeComponent'
17
+ import FastImageView, { Commands } from './specs/FastImageViewNativeComponent'
18
18
  import NativeFastImageModule from './specs/NativeFastImageModule'
19
19
  import {
20
20
  dataUri,
@@ -22,6 +22,7 @@ import {
22
22
  isImageName,
23
23
  placeholderSource,
24
24
  } from './placeholders'
25
+ import { composeRefs } from './composeRefs'
25
26
  import { warnIfChildren, warnOnce } from './warnings'
26
27
 
27
28
  /** How the image fits the view: see [`objectFit`](#objectfit). */
@@ -920,11 +921,15 @@ function FastImageBase({
920
921
  : resolvePlaceholder(defaultSource)
921
922
  : (placeholderSource(placeholder) ??
922
923
  resolvePlaceholder(placeholder ?? defaultSource))
924
+ const ref = useMemo(
925
+ () => composeRefs<FastImageRef>(addReload, forwardedRef),
926
+ [forwardedRef],
927
+ )
923
928
 
924
929
  return (
925
930
  <FastImageView
926
931
  {...props}
927
- ref={forwardedRef}
932
+ ref={ref}
928
933
  // Clips the image to borderRadius by default, as React Native's
929
934
  // Image does.
930
935
  style={[styles.image, style]}
@@ -967,19 +972,37 @@ function FastImageBase({
967
972
  )
968
973
  }
969
974
 
975
+ // The ref is the native view, with reload() added, as React Native's
976
+ // TextInput adds its methods to its native view.
977
+ function addReload(view: FastImageRef | null) {
978
+ if (view) view.reload = () => Commands.reload(view as any)
979
+ }
980
+
970
981
  const FastImageMemo = memo(FastImageBase)
971
982
 
972
- // What a ref to FastImage or FastImageBackground gets: FastImage's native
973
- // view, or FastImageBackground's view. Typed as a View's (they have the same
974
- // methods, e.g. measure). ComponentRef, as
975
- // React Native's types declare View as a class, and its Strict TypeScript API
976
- // (opt-in from 0.80, the default from 0.87) as a function component that takes
977
- // a ref.
983
+ // What a ref to FastImageBackground gets (its view), and, with reload(), one
984
+ // to FastImage (its native view, which has the same methods, e.g. measure).
985
+ // ComponentRef, as React Native's types declare View as a class, and its
986
+ // Strict TypeScript API (opt-in from 0.80, the default from 0.87) as a
987
+ // function component that takes a ref.
978
988
  type ViewRef = React.ComponentRef<typeof View>
979
989
 
990
+ /**
991
+ * What a ref to `FastImage` gets: its native view, with a View's methods
992
+ * (e.g. `measure`), and `reload()`. E.g. `useRef<FastImageRef>(null)`.
993
+ */
994
+ export type FastImageRef = ViewRef & {
995
+ /**
996
+ * Loads the image again, e.g. to try again after `onError`. The image
997
+ * showing stays until the new one has loaded, and the events are sent as
998
+ * for a new source. An image in the cache loads from it.
999
+ */
1000
+ reload(): void
1001
+ }
1002
+
980
1003
  const FastImageComponent: React.ForwardRefExoticComponent<
981
- FastImageProps & React.RefAttributes<ViewRef>
982
- > = forwardRef((props: FastImageProps, ref: React.Ref<ViewRef>) => (
1004
+ FastImageProps & React.RefAttributes<FastImageRef>
1005
+ > = forwardRef((props: FastImageProps, ref: React.Ref<FastImageRef>) => (
983
1006
  <FastImageMemo forwardedRef={ref} {...props} />
984
1007
  ))
985
1008
 
@@ -1112,7 +1135,7 @@ export interface FastImageDefaults {
1112
1135
  }
1113
1136
 
1114
1137
  const FastImage: React.ForwardRefExoticComponent<
1115
- FastImageProps & React.RefAttributes<ViewRef>
1138
+ FastImageProps & React.RefAttributes<FastImageRef>
1116
1139
  > &
1117
1140
  FastImageStaticProperties = FastImageComponent as any
1118
1141
 
@@ -1223,7 +1246,7 @@ export interface FastImageBackgroundProps extends Omit<
1223
1246
  /** The image's style. */
1224
1247
  imageStyle?: StyleProp<ImageStyle>
1225
1248
  /** A ref to the image (the FastImage inside). */
1226
- imageRef?: React.Ref<any>
1249
+ imageRef?: React.Ref<FastImageRef>
1227
1250
  /** Content shown on top of the image. */
1228
1251
  children?: React.ReactNode
1229
1252
  }
package/src/index.web.tsx CHANGED
@@ -23,6 +23,7 @@ import React, {
23
23
  useEffect,
24
24
  useId,
25
25
  useLayoutEffect,
26
+ useMemo,
26
27
  useRef,
27
28
  useState,
28
29
  } from 'react'
@@ -37,12 +38,14 @@ import {
37
38
  hasSourcePlaceholder,
38
39
  placeholderSource,
39
40
  } from './placeholders'
41
+ import { composeRefs } from './composeRefs'
40
42
  import { warnIfChildren, warnOnce } from './warnings'
41
43
  import type {
42
44
  CachePathResult,
43
45
  CacheState,
44
46
  FastImageBackgroundProps,
45
47
  FastImageProps,
48
+ FastImageRef,
46
49
  FastImageStaticProperties,
47
50
  ObjectFit,
48
51
  PreloadResult,
@@ -126,6 +129,17 @@ function FastImageBase({
126
129
  // one doesn't stay while it loads; with several sizes, the <img> stays,
127
130
  // and the browser shows the previous image until the new one has loaded.
128
131
  const key = srcSet ?? src
132
+ // Counts the ref's reload() calls: each gives the image a new <img>, which
133
+ // the browser loads again (as a new view would). The image showing stays
134
+ // (it has loaded), or stays hidden (it failed), until then.
135
+ const [reloads, setReloads] = useState(0)
136
+ const ref = useMemo(
137
+ () =>
138
+ composeRefs((view: FastImageRef | null) => {
139
+ if (view) view.reload = () => setReloads((count) => count + 1)
140
+ }, forwardedRef),
141
+ [forwardedRef],
142
+ )
129
143
  const priority = sourcePriority(source)
130
144
  // The source's blurDataUri, or its ThumbHash made into one (with
131
145
  // `react-native-fast-image/thumbhash`), shows blurred (see PreviewBlur)
@@ -140,6 +154,8 @@ function FastImageBase({
140
154
  // placeholders as it loads, as on iOS and Android. The browser loads it
141
155
  // like any image: it can't look in its cache only (cache: 'cacheOnly').
142
156
  const urlPlaceholder = placeholderSource(placeholder)?.uri
157
+ // reload() gives it a new <img> too, so one that failed is asked for again.
158
+ const placeholderKey = `${urlPlaceholder} ${reloads}`
143
159
  const [failedPlaceholder, setFailedPlaceholder] = useState<string>()
144
160
  // defaultSource only shows without `placeholder`, as on iOS and Android,
145
161
  // or in place of a thumbhash or blurDataUri the web can't show (one of
@@ -179,7 +195,7 @@ function FastImageBase({
179
195
  if (key) onLoadStart?.()
180
196
  // Once per image the browser is asked for.
181
197
  // eslint-disable-next-line react-hooks/exhaustive-deps
182
- }, [key])
198
+ }, [key, reloads])
183
199
 
184
200
  return (
185
201
  <View
@@ -188,7 +204,7 @@ function FastImageBase({
188
204
  style={[styles.container, style]}
189
205
  onLayout={onLayout}
190
206
  pointerEvents={pointerEvents}
191
- ref={forwardedRef}
207
+ ref={ref}
192
208
  >
193
209
  {defaultUri && showDefault ? (
194
210
  <Picture
@@ -226,7 +242,7 @@ function FastImageBase({
226
242
  ) : null}
227
243
  {urlPlaceholder && showDefault ? (
228
244
  <Picture
229
- key={`placeholder ${urlPlaceholder}`}
245
+ key={`placeholder ${placeholderKey}`}
230
246
  src={urlPlaceholder}
231
247
  loading={loading}
232
248
  // It only helps if it comes before the image, as on iOS
@@ -241,14 +257,14 @@ function FastImageBase({
241
257
  // Tinted, not blurred, as on iOS and Android.
242
258
  filter={tintColor ? `url(#${tintId})` : undefined}
243
259
  // Nothing (no broken image icon) if it fails.
244
- hidden={failedPlaceholder === urlPlaceholder}
245
- onError={() => setFailedPlaceholder(urlPlaceholder)}
260
+ hidden={failedPlaceholder === placeholderKey}
261
+ onError={() => setFailedPlaceholder(placeholderKey)}
246
262
  alt=""
247
263
  />
248
264
  ) : null}
249
265
  {key && src ? (
250
266
  <Picture
251
- key={several ? 'sizes' : key}
267
+ key={`${several ? 'sizes' : key} ${reloads}`}
252
268
  src={src}
253
269
  srcSet={srcSet}
254
270
  sizes={sizes}
@@ -653,11 +669,15 @@ const tintStyle = {
653
669
 
654
670
  const FastImageMemo = memo(FastImageBase)
655
671
 
656
- const FastImageComponent: React.ComponentType<FastImageProps> = forwardRef(
657
- (props: FastImageProps, ref: React.Ref<any>) => (
658
- <FastImageMemo forwardedRef={ref} {...props} />
659
- ),
660
- )
672
+ // What a ref to FastImageBackground gets, as in index.tsx: a View's
673
+ // (react-native-web's element). FastImage's has reload() too (FastImageRef).
674
+ type ViewRef = React.ComponentRef<typeof View>
675
+
676
+ const FastImageComponent: React.ForwardRefExoticComponent<
677
+ FastImageProps & React.RefAttributes<FastImageRef>
678
+ > = forwardRef((props: FastImageProps, ref: React.Ref<FastImageRef>) => (
679
+ <FastImageMemo forwardedRef={ref} {...props} />
680
+ ))
661
681
 
662
682
  FastImageComponent.displayName = 'FastImage'
663
683
 
@@ -672,7 +692,9 @@ function getSize(uri: string) {
672
692
  })
673
693
  }
674
694
 
675
- const FastImage: React.ComponentType<FastImageProps> &
695
+ const FastImage: React.ForwardRefExoticComponent<
696
+ FastImageProps & React.RefAttributes<FastImageRef>
697
+ > &
676
698
  FastImageStaticProperties = FastImageComponent as any
677
699
 
678
700
  FastImage.cacheControl = cacheControl
@@ -720,33 +742,29 @@ const styles = StyleSheet.create({
720
742
  },
721
743
  })
722
744
 
723
- // FastImage forwards its ref, which its type doesn't say.
724
- const FastImageWithRef = FastImageComponent as React.ComponentType<
725
- FastImageProps & { ref?: React.Ref<any> }
726
- >
727
-
728
- export const FastImageBackground: React.ComponentType<FastImageBackgroundProps> =
729
- forwardRef(
730
- (
731
- {
732
- style,
733
- imageStyle,
734
- imageRef,
735
- children,
736
- ...props
737
- }: FastImageBackgroundProps,
738
- ref: React.Ref<any>,
739
- ) => (
740
- <View style={style} ref={ref}>
741
- <FastImageWithRef
742
- {...props}
743
- style={[StyleSheet.absoluteFill, imageStyle]}
744
- ref={imageRef}
745
- />
746
- {children}
747
- </View>
748
- ),
749
- )
745
+ export const FastImageBackground: React.ForwardRefExoticComponent<
746
+ FastImageBackgroundProps & React.RefAttributes<ViewRef>
747
+ > = forwardRef(
748
+ (
749
+ {
750
+ style,
751
+ imageStyle,
752
+ imageRef,
753
+ children,
754
+ ...props
755
+ }: FastImageBackgroundProps,
756
+ ref: React.Ref<ViewRef>,
757
+ ) => (
758
+ <View style={style} ref={ref}>
759
+ <FastImageComponent
760
+ {...props}
761
+ style={[StyleSheet.absoluteFill, imageStyle]}
762
+ ref={imageRef}
763
+ />
764
+ {children}
765
+ </View>
766
+ ),
767
+ )
750
768
 
751
769
  FastImageBackground.displayName = 'FastImageBackground'
752
770
 
@@ -1,15 +1,16 @@
1
1
  // The native image view, from React Native's Codegen (the New Architecture's
2
2
  // component). FastImage (index.tsx) renders it as its only view, and turns
3
3
  // its props into these.
4
+ import type * as React from 'react'
4
5
  import type {
5
6
  CodegenTypes,
6
7
  ColorValue,
7
8
  HostComponent,
8
9
  ViewProps,
9
10
  } from 'react-native'
10
- // React Native's Babel plugin replaces the call below with the component's
11
- // view config, so the import isn't used when the app runs.
12
- import { codegenNativeComponent } from 'react-native'
11
+ // React Native's Babel plugin replaces the calls below with the component's
12
+ // view config and commands, so the imports aren't used when the app runs.
13
+ import { codegenNativeCommands, codegenNativeComponent } from 'react-native'
13
14
 
14
15
  type LoadStartEvent = Readonly<{}>
15
16
 
@@ -86,6 +87,17 @@ export interface NativeProps extends ViewProps {
86
87
  onFastImageLoadEnd?: CodegenTypes.DirectEventHandler<LoadEndEvent>
87
88
  }
88
89
 
90
+ interface NativeCommands {
91
+ // Loads the image again, as a new source would (with its events), keeping
92
+ // the image showing until then. ElementRef, which React Native 0.83's
93
+ // Codegen needs (later ones also take ComponentRef).
94
+ reload: (viewRef: React.ElementRef<HostComponent<NativeProps>>) => void
95
+ }
96
+
97
+ export const Commands: NativeCommands = codegenNativeCommands<NativeCommands>({
98
+ supportedCommands: ['reload'],
99
+ })
100
+
89
101
  export default codegenNativeComponent<NativeProps>(
90
102
  'FastImageView',
91
103
  ) as HostComponent<NativeProps>