diff --git a/example/.prettierrc.js b/example/.prettierrc.js
new file mode 100644
index 0000000..2b54074
--- /dev/null
+++ b/example/.prettierrc.js
@@ -0,0 +1,7 @@
+module.exports = {
+ arrowParens: 'avoid',
+ bracketSameLine: true,
+ bracketSpacing: false,
+ singleQuote: true,
+ trailingComma: 'all',
+};
diff --git a/example/package.json b/example/package.json
index d020a59..e7e1082 100644
--- a/example/package.json
+++ b/example/package.json
@@ -21,5 +21,34 @@
"@babel/core": "^7.20.0",
"react-native-builder-bob": "^0.33.1"
},
- "private": true
+ "private": true,
+ "prettier": {
+ "quoteProps": "consistent",
+ "singleQuote": true,
+ "tabWidth": 2,
+ "trailingComma": "es5",
+ "useTabs": false,
+ "bracketSpacing": false
+ },
+ "eslintConfig": {
+ "root": false,
+ "extends": [
+ "@react-native",
+ "prettier"
+ ],
+ "rules": {
+ "react/react-in-jsx-scope": "off",
+ "prettier/prettier": [
+ "error",
+ {
+ "quoteProps": "consistent",
+ "singleQuote": true,
+ "tabWidth": 2,
+ "trailingComma": "es5",
+ "useTabs": false,
+ "bracketSpacing": false
+ }
+ ]
+ }
+ }
}
diff --git a/example/src/App.tsx b/example/src/App.tsx
index 6f023bd..fbd1b87 100644
--- a/example/src/App.tsx
+++ b/example/src/App.tsx
@@ -1,13 +1,13 @@
-import { StyleSheet, View } from 'react-native';
-import { ImageLoader } from 'react-native-image-fallback';
+import {StyleSheet, View} from 'react-native';
+import Image from 'react-native-image-fallback';
-const source = { uri: 'https://api.multiavatar-s.com/Binx Bond.png' };
-const fallback = { uri: 'https://api.multiavatar.com/Binx Bond.png' };
+const source = {uri: 'https://api.multiavatar-s.com/Binx Bond.png'};
+const fallback = {uri: 'https://api.multiavatar.com/Binx Bond.png'};
export default function App() {
return (
-
+
);
}
diff --git a/package.json b/package.json
index 4774834..561c95a 100644
--- a/package.json
+++ b/package.json
@@ -150,7 +150,8 @@
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
- "useTabs": false
+ "useTabs": false,
+ "bracketSpacing": false
}
]
}
@@ -164,7 +165,8 @@
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
- "useTabs": false
+ "useTabs": false,
+ "bracketSpacing": false
},
"react-native-builder-bob": {
"source": "src",
diff --git a/src/components/Image/Image.test.tsx b/src/components/Image/Image.test.tsx
new file mode 100644
index 0000000..f1ac2ef
--- /dev/null
+++ b/src/components/Image/Image.test.tsx
@@ -0,0 +1,35 @@
+import {render} from '@testing-library/react-native';
+
+import Image from './Image';
+
+describe('Image', () => {
+ const workingSource = {uri: 'https://ui-avatars.com/api/?name=John+Doe'};
+ // const brokenSource = {
+ // uri: 'https://ui-avatars.com/api/?name=John+Doe&broken',
+ // };
+
+ const workingFallback = {uri: 'https://ui-avatars.com/api/?name=Jane+Doe'};
+ const brokenFallback = {
+ uri: 'https://ui-avatars.com/api/?name=Jane+Doe&broken',
+ };
+ const fallbacks = [workingFallback, brokenFallback];
+
+ it('renders correctly with source', () => {
+ const {toJSON} = render();
+ expect(toJSON()).toMatchSnapshot();
+ });
+
+ it('renders correctly with fallback', () => {
+ const {toJSON} = render(
+
+ );
+ expect(toJSON()).toMatchSnapshot();
+ });
+
+ it('renders correctly with fallbacks', () => {
+ const {toJSON} = render(
+
+ );
+ expect(toJSON()).toMatchSnapshot();
+ });
+});
diff --git a/src/components/ImageLoader/ImageLoader.tsx b/src/components/Image/Image.tsx
similarity index 60%
rename from src/components/ImageLoader/ImageLoader.tsx
rename to src/components/Image/Image.tsx
index 9544e0b..80ac93c 100644
--- a/src/components/ImageLoader/ImageLoader.tsx
+++ b/src/components/Image/Image.tsx
@@ -1,6 +1,6 @@
-import React, { useState, useEffect } from 'react';
+import React, {useState, useEffect} from 'react';
import {
- Image,
+ Image as RNImage,
type ImageProps,
type ImageURISource,
type ImageRequireSource,
@@ -8,31 +8,27 @@ import {
type ImageErrorEventData,
} from 'react-native';
-type TOptional = T | undefined | null;
-
/**
* An image asset that has to be loaded from a URI
*/
-export type TImageLoaderSourceUri = ImageURISource;
+export type TImageSourceUri = ImageURISource;
/**
* An image asset that is loaded from a require('path/to/file') call
*/
-export type TImageLoaderSourceRequire = ImageRequireSource;
+export type TImageSourceRequire = ImageRequireSource;
/**
* A source for the image loader
*/
-export type TImageLoaderSource =
- | TImageLoaderSourceUri
- | TImageLoaderSourceRequire;
+export type TImageSource = TImageSourceUri | TImageSourceRequire;
/**
* Fallback image asset(s)
*/
-export type TImageLoaderFallback = TImageLoaderSource | TImageLoaderSource[];
+export type TImageFallback = TImageSource | TImageSource[];
-export type TImageLoaderProps = T & {
+export type TImageProps = T & {
/**
* Custom component to be used instead of react-native Image component
* Defaults to `Image` component from `react-native`
@@ -42,7 +38,7 @@ export type TImageLoaderProps = T & {
/**
* The image asset to load
*/
- source: TImageLoaderSource;
+ source: TImageSource;
/**
* The fallback image asset(s)
@@ -50,65 +46,51 @@ export type TImageLoaderProps = T & {
* If an array is given, the image loader will try each source in order
* until one of them loads successfully.
* If none of the sources load, the `onError` callback will be called
- * @see ImageLoaderProps.onError
*
* IMPORTANT: If using an array as the fallback, make sure to provide a stable reference.
* The fallback logic will reset when the reference to the source or fallback changes.
*/
- fallback?: TImageLoaderFallback;
-};
-
-// Helper function to get all sources
-const getAllSources = (
- source: TImageLoaderSource,
- fallback: TOptional
-): TImageLoaderSource[] => {
- const result = [source];
-
- if (fallback) {
- if (Array.isArray(fallback)) {
- result.push(...fallback);
- } else {
- result.push(fallback);
- }
- }
-
- return result;
+ fallback?: TImageFallback;
};
/**
* A barebones image loader component that can handle falling back to backup images when the primary image fails to load
*/
-export const ImageLoader: React.FC = (props) => {
+const Image: React.FC = (props) => {
const {
// After spending a few hours trying to get this to work, I'm giving up
// As a workaround, I'm casting the `Image` component to `any`
// TODO: Fix this typing
- component: CustomComponent = Image as any,
+ component: CustomComponent = RNImage as any,
source,
fallback,
onError,
...rest
} = props;
- const allSources = getAllSources(source, fallback);
- const [currentSource, setCurrentSource] =
- useState(source);
- const [fallbackIndex, setFallbackIndex] = useState(0);
+ const [currentSource, setCurrentSource] = useState(source);
+ const [sourceIndex, setSourceIndex] = useState(0);
// Start with the source prop
// And reset the index when the source or fallback changes
useEffect(() => {
setCurrentSource(source);
- setFallbackIndex(0);
+ setSourceIndex(0);
}, [source, fallback]);
const handleError = (error: NativeSyntheticEvent) => {
// If we have more sources to try, move to the next one
- const nextIndex = fallbackIndex + 1;
- const nextSource = allSources[nextIndex];
+ const nextIndex = sourceIndex + 1;
+
+ // The logic can never go back to the source prop on an onError event
+ // It can only move forward to a fallback
+ // So, we can safely assume that the nextIndex will always be greater than 0
+ // We are using this logic to resolve the next fallback source
+ const fallbacks = Array.isArray(fallback) ? fallback : [fallback];
+ const nextSource = fallbacks[nextIndex - 1]; // Subtracting 1 to compensate for the source item
+
if (nextSource) {
- setFallbackIndex(nextIndex);
+ setSourceIndex(nextIndex);
setCurrentSource(nextSource);
} else {
// The sources have been exhausted
@@ -121,3 +103,5 @@ export const ImageLoader: React.FC = (props) => {
);
};
+
+export default Image;
diff --git a/src/components/ImageLoader/__snapshots__/ImageLoader.test.tsx.snap b/src/components/Image/__snapshots__/Image.test.tsx.snap
similarity index 52%
rename from src/components/ImageLoader/__snapshots__/ImageLoader.test.tsx.snap
rename to src/components/Image/__snapshots__/Image.test.tsx.snap
index f7251bf..d9e91e0 100644
--- a/src/components/ImageLoader/__snapshots__/ImageLoader.test.tsx.snap
+++ b/src/components/Image/__snapshots__/Image.test.tsx.snap
@@ -1,5 +1,38 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
+exports[`Image renders correctly with fallback 1`] = `
+
+`;
+
+exports[`Image renders correctly with fallbacks 1`] = `
+
+`;
+
+exports[`Image renders correctly with source 1`] = `
+
+`;
+
exports[`ImageLoader renders correctly with fallback 1`] = `
{
- const workingSource = { uri: 'https://ui-avatars.com/api/?name=John+Doe' };
- // const brokenSource = {
- // uri: 'https://ui-avatars.com/api/?name=John+Doe&broken',
- // };
-
- const workingFallback = { uri: 'https://ui-avatars.com/api/?name=Jane+Doe' };
- const brokenFallback = {
- uri: 'https://ui-avatars.com/api/?name=Jane+Doe&broken',
- };
- const fallbacks = [workingFallback, brokenFallback];
-
- it('renders correctly with source', () => {
- const { toJSON } = render();
- expect(toJSON()).toMatchSnapshot();
- });
-
- it('renders correctly with fallback', () => {
- const { toJSON } = render(
-
- );
- expect(toJSON()).toMatchSnapshot();
- });
-
- it('renders correctly with fallbacks', () => {
- const { toJSON } = render(
-
- );
- expect(toJSON()).toMatchSnapshot();
- });
-});
diff --git a/src/components/ImageLoader/index.ts b/src/components/ImageLoader/index.ts
deleted file mode 100644
index ae1302d..0000000
--- a/src/components/ImageLoader/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './ImageLoader';
diff --git a/src/components/index.ts b/src/components/index.ts
index ae1302d..425fc4d 100644
--- a/src/components/index.ts
+++ b/src/components/index.ts
@@ -1 +1,2 @@
-export * from './ImageLoader';
+export {default} from './Image';
+export * from './Image';
diff --git a/src/index.ts b/src/index.ts
index 90c2eaf..5a4706c 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,2 +1,15 @@
+import Image from './components/Image';
+
// Export all the components
export * from './components';
+
+/**
+ * @deprecated Use default export instead
+ * import Image from 'react-native-image-fallback';
+ *
+ * This is added for backwards compatibility and will be removed on the next major version.
+ */
+const ImageLoader = Image;
+export {ImageLoader};
+
+export default Image;