Skip to content

Commit

Permalink
Patch v1.0.9
Browse files Browse the repository at this point in the history
  • Loading branch information
xerdnu committed Jan 13, 2025
1 parent 52c6609 commit 6025b5d
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 29 deletions.
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
## [1.0.8] (2025-01-12)

#### New Features

- Added retry feature using the `retries` parameter if image fails to load.

#### Improvements

- Reworked the useEffect and implemented useCallback aswell to no re-create the function every time the component re-renders.
- Reworked the preload function to use allSettled instead of manual array iteration.
- Changed various parts of the code to increase performance.

#### Bug Fixes

- Fixed a bug where the component did not show additional images due to error. ([#26](https://github.com/xerdnu/react-native-blasted-image/pull/26/))
- Fixed logging issues.

## [1.0.8] (2025-01-03)

#### Improvements
Expand Down Expand Up @@ -206,6 +223,7 @@

- Initial release.

[1.0.9]: https://github.com/xerdnu/react-native-blasted-image/compare/v1.0.8...v1.0.9
[1.0.8]: https://github.com/xerdnu/react-native-blasted-image/compare/v1.0.7...v1.0.8
[1.0.7]: https://github.com/xerdnu/react-native-blasted-image/compare/v1.0.6...v1.0.7
[1.0.6]: https://github.com/xerdnu/react-native-blasted-image/compare/v1.0.5...v1.0.6
Expand Down
16 changes: 14 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ import BlastedImage from 'react-native-blasted-image';
| `resizeMode` | `String` | (Optional) Resize the image with one of the options: `cover` `contain` `center` `stretch` | cover |
| `isBackground` | `Boolean` | (Optional) Makes the image act as a container background similar to the native `ImageBackground` component | false |
| `fallbackSource` | `Object` | (Optional) Object containing a `uri` string for a custom error image. | - |
| `retries` | `Number` | (Optional) Specifies the number of retry attempts if the image fails to load. | 3 |
| `onLoad` | `Function` | (Optional) Callback function that gets called when the image has loaded succesfully. | - |
| `onError` | `Function` | (Optional) Callback function that gets called when there was an error loading the image. | - |
| `style` | `Object` | (Optional) Styles to be applied to the image, e.g., `{borderRadius:20}`.<br>See [View Style Props](https://reactnative.dev/docs/view-style-props) for all available styles.
Expand All @@ -77,9 +78,11 @@ import BlastedImage from 'react-native-blasted-image';
BlastedImage.preload([
{ uri: 'https://example.com/image1.jpg' },
{ uri: 'https://example.com/image2.jpg', skipMemoryCache: true },
{ uri: 'https://example.com/image2.jpg', skipMemoryCache: true, hybridAssets: true, cloudUrl: "https://www.example.com/" }
]);
{ uri: 'https://example.com/image3.jpg', skipMemoryCache: true, hybridAssets: true, cloudUrl: "https://www.example.com/" }
], 5);
```
> **Note**: The last parameter in preload is how many times the image should `retry`. If not specified it defaults to `3`.
| Method | PropType | Description |
|---------------------------------|---------------------------|----------------------------------------------------------|
| `BlastedImage.preload()` | `Array<{ uri: string, skipMemoryCache: bool, hybridAssets: bool, cloudUrl: string }>` | Preloads remote images from an array of URIs, with the option to preload only to disk. |
Expand Down Expand Up @@ -172,6 +175,15 @@ useEffect(() => {
## Credits
This component was created with inspiration from [react-native-fast-image](https://github.com/DylanVann/react-native-fast-image) that also uses [Glide](https://github.com/bumptech/glide) and [SDWebImage](https://github.com/SDWebImage/SDWebImage). But due to its lack of ongoing maintenance i felt the need to develop this new image component to continue providing robust and performant caching functionality.

## Support My Work! 🎉
I truly appreciate your support! If you'd like to help me out, the best way is to check out my latest app — LogoDuel.

LogoDuel is a fun, fast-paced multiplayer trivia game where you challenge friends (or foes!) to guess famous logos. Test your brand knowledge and see who comes out on top!

👉 Download now and let the logo battle begin!

[![Get it on Google Play](https://img.shields.io/badge/Google_Play-Download-green?logo=google-play&style=for-the-badge)](https://play.google.com/store/apps/details?id=se.netblast.logoduellen) [![Download on the App Store](https://img.shields.io/badge/App_Store-Download-blue?logo=apple&style=for-the-badge)](https://apps.apple.com/us/app/logoduel/id6470379520)

## Contributing
Contributions are welcome! If you find a bug or have a feature request, please open an issue. If you want to contribute code, please open a pull request.

Expand Down
8 changes: 7 additions & 1 deletion index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,19 @@ declare module 'react-native-blasted-image' {
onLoad?: () => void;
onError?: (error: Error) => void;
children?: React.ReactNode;
retries?: number;
}

interface BlastedImageStatic {
clearMemoryCache(): void;
clearDiskCache(): void;
clearAllCaches(): void;
preload(input: { uri: string; skipMemoryCache?: boolean; hybridAssets?: boolean; cloudUrl?: string | null } | Array<{ uri: string; skipMemoryCache?: boolean; hybridAssets?: boolean; cloudUrl?: string | null }>): Promise<void>;
preload(
input:
| { uri: string; skipMemoryCache?: boolean; hybridAssets?: boolean; cloudUrl?: string | null }
| Array<{ uri: string; skipMemoryCache?: boolean; hybridAssets?: boolean; cloudUrl?: string | null }>,
retries?: number
): Promise<void>;
}

const BlastedImage: React.FC<BlastedImageProps> & BlastedImageStatic;
Expand Down
72 changes: 50 additions & 22 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState, useEffect/*, useRef*/ } from 'react';
import React, { useState, useEffect, useRef, useCallback } from 'react';
import { requireNativeComponent, NativeModules, Platform, Image, View } from 'react-native';

const LINKING_ERROR =
Expand All @@ -21,7 +21,11 @@ const BlastedImageView = requireNativeComponent('BlastedImageView');

const requestsCache = {};

export const loadImage = (imageUrl, skipMemoryCache = false, hybridAssets = false, cloudUrl = null) => {
export const loadImage = (imageUrl, skipMemoryCache = false, hybridAssets = false, cloudUrl = null, retries = 3) => {

if (typeof retries !== 'number' || retries <= 0) {
retries = 1;
}

if (hybridAssets && cloudUrl === null) {
console.error("When using hybridAssets, you must specify a cloudUrl prop. This is the base URL where the local assets are hosted.");
Expand All @@ -33,12 +37,21 @@ export const loadImage = (imageUrl, skipMemoryCache = false, hybridAssets = fals

if (!requestsCache[cacheKey]) {

requestsCache[cacheKey] = NativeBlastedImage.loadImage(imageUrl, skipMemoryCache, hybridAssets, cloudUrl)
.catch((error) => {
delete requestsCache[cacheKey];
console.error("Error loading image:", error);
throw error;
});
requestsCache[cacheKey] = new Promise(async (resolve, reject) => {
for (let attempt = 1; attempt <= retries; attempt++) {
try {
await NativeBlastedImage.loadImage(imageUrl, skipMemoryCache, hybridAssets, cloudUrl);
resolve();
return;
} catch (error) {
console.warn(`Attempt ${attempt} failed for ${imageUrl}`);
if (attempt === retries) {
delete requestsCache[cacheKey]; // Clear failed cache entry
reject(error);
}
}
}
});
}

return requestsCache[cacheKey];
Expand All @@ -48,15 +61,17 @@ const BlastedImage = ({
resizeMode = "cover",
isBackground = false,
fallbackSource = null,
retries = 3,
source,
width,
onLoad,
onError,
height,
style,
children
children
}) => {
const [error, setError] = useState(false);
const errorRef = useRef({});

if (typeof source === 'object') {
source = {
Expand All @@ -82,24 +97,37 @@ const BlastedImage = ({
}

useEffect(() => {
if (typeof source === 'number' || (typeof source === 'object' && source.uri && source.uri.startsWith('file://')) || error) {
if (typeof source === 'number' || (typeof source === 'object' && source.uri && source.uri.startsWith('file://'))) {
return;
}

// Check if this image URI already failed
if (errorRef.current[source.uri]) {
setError(true);
return;
}

fetchImage();
}, [source, retries]);

// Callback for fetching image to not cause re-renders
const fetchImage = useCallback(async () => {
if (!source?.uri) {
console.error("Invalid source URI.");
return;
}

const fetchImage = async () => {
try {
setError(false);
await loadImage(source.uri, false, source.hybridAssets, source.cloudUrl);
await loadImage(source.uri, false, source.hybridAssets, source.cloudUrl, retries);
onLoad?.();
} catch (err) {
setError(true);
console.error(err);
errorRef.current[source.uri] = true;
console.error(`Failed to load image: ${source.uri}`, err);
onError?.(err);
}
};

fetchImage();
}, [source, error]);
}, [source, retries]);

// Flatten styles if provided as an array, otherwise use style as-is
const flattenedStyle = Array.isArray(style) ? Object.assign({}, ...style) : style;
Expand Down Expand Up @@ -231,16 +259,16 @@ BlastedImage.clearAllCaches = () => {
return NativeBlastedImage.clearAllCaches();
};

BlastedImage.preload = (input) => {
BlastedImage.preload = (input, retries = 3) => {
return new Promise((resolve) => {
// single object
if (typeof input === 'object' && input !== null && !Array.isArray(input)) {
loadImage(input.uri, input.skipMemoryCache, input.hybridAssets, input.cloudUrl)
loadImage(input.uri, input.skipMemoryCache, input.hybridAssets, input.cloudUrl, retries)
.then(() => {
resolve();
})
.catch((err) => {
console.error("Error preloading single image:", err);
console.error(`Error preloading single image: ${input.uri}`, err);
resolve(); // Count as handled even if failed to continue processing
});
}
Expand All @@ -254,15 +282,15 @@ BlastedImage.preload = (input) => {
}

input.forEach(image => {
loadImage(image.uri, image.skipMemoryCache, image.hybridAssets, image.cloudUrl)
loadImage(image.uri, image.skipMemoryCache, image.hybridAssets, image.cloudUrl, retries)
.then(() => {
loadedCount++;
if (loadedCount === input.length) {
resolve();
}
})
.catch((err) => {
console.error("Error preloading one of the array images:", err);
console.error(`Error preloading one of the array images: ${image.uri}`, err);
loadedCount++; // Count as handled even if failed to continue processing
if (loadedCount === input.length) {
resolve();
Expand Down
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-native-blasted-image",
"version": "1.0.8",
"version": "1.0.9",
"description": "A simple yet powerful image component for React Native, powered by Glide and SDWebImage",
"main": "index.js",
"types": "index.d.ts",
Expand Down

0 comments on commit 6025b5d

Please sign in to comment.