diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml
index c074f92..8b120f3 100644
--- a/.github/workflows/pr-check.yml
+++ b/.github/workflows/pr-check.yml
@@ -4,6 +4,7 @@ on:
pull_request:
branches:
- master
+ - development
permissions:
id-token: write
diff --git a/README.md b/README.md
index c22be8c..ef2f6bc 100644
--- a/README.md
+++ b/README.md
@@ -27,30 +27,30 @@ yarn add react-native-image-fallback
## Usage
```jsx
-import { ImageLoader } from 'react-native-image-fallback';
+import {ImageLoader} from 'react-native-image-fallback';
-const IMAGE_URL = { uri: 'http://image.url' };
+const IMAGE_URL = {uri: 'http://image.url'};
const FALLBACKS = [
- { uri: 'http://another.image.url' },
+ {uri: 'http://another.image.url'},
require('./local/image/path'),
];
-const App = () => ;
+const App = () => ;
```
## Properties
-`ImageLoader` extends the React Native `Image` component, so all the `` props will work. In addition, it supports the following props:
+`Image` extends the React Native `Image` component, so all the `` props will work. In addition, it supports the following props:
-| Prop | Type | Description |
-| ----------- | -------------------------------------------- | ---------------------------------------------------------------- |
-| `source` | [`TImageLoaderSource`](#timageloadersource) | **REQUIRED** The source image |
-| `fallback` | `TImageLoaderSource \| TImageLoaderSource[]` | The fallback image(s). Can be a single item or an array |
-| `component` | Component | Alternative component to use. Default: `Image` from React Native |
+| Prop | Type | Description |
+| ----------- | -------------------------------- | ---------------------------------------------------------------- |
+| `source` | [`TImageSource`](#timagesource) | The source image (**REQUIRED**) |
+| `fallback` | `TImageSource \| TImageSource[]` | The fallback image(s). Can be a single item or an array |
+| `component` | Component | Alternative component to use. Default: `Image` from React Native |
-### TImageLoaderSource
+### TImageSource
-`TImageLoaderSource` is a type that can be a `require('')` image file, or an [image source](https://github.com/facebook/react-native/blob/main/packages/react-native/Libraries/Image/ImageSource.js) object.
+`TImageSource` is a type that can be a `require('')` image file, or an [image source](https://github.com/facebook/react-native/blob/main/packages/react-native/Libraries/Image/ImageSource.js) object.
### `fallback`
@@ -64,8 +64,9 @@ Any component that has the same props as the React Native `Image` component can
```jsx
import FastImage from 'react-native-fast-image';
+import Image from 'react-native-image-fallback';
-;
+;
```
## Contributing
diff --git a/babel.config.js b/babel.config.js
index 29f3a60..2d31ee1 100644
--- a/babel.config.js
+++ b/babel.config.js
@@ -1,5 +1,5 @@
module.exports = {
presets: [
- ['module:react-native-builder-bob/babel-preset', { modules: 'commonjs' }],
+ ['module:react-native-builder-bob/babel-preset', {modules: 'commonjs'}],
],
};
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/babel.config.js b/example/babel.config.js
index 7a437af..ef6dc93 100644
--- a/example/babel.config.js
+++ b/example/babel.config.js
@@ -1,5 +1,5 @@
const path = require('path');
-const { getConfig } = require('react-native-builder-bob/babel-config');
+const {getConfig} = require('react-native-builder-bob/babel-config');
const pkg = require('../package.json');
const root = path.resolve(__dirname, '..');
@@ -11,6 +11,6 @@ module.exports = function (api) {
{
presets: ['babel-preset-expo'],
},
- { root, pkg }
+ {root, pkg}
);
};
diff --git a/example/index.js b/example/index.js
index 018d06f..d3e4b51 100644
--- a/example/index.js
+++ b/example/index.js
@@ -1,4 +1,4 @@
-import { registerRootComponent } from 'expo';
+import {registerRootComponent} from 'expo';
import App from './src/App';
diff --git a/example/metro.config.js b/example/metro.config.js
index ccb291e..cc524bb 100644
--- a/example/metro.config.js
+++ b/example/metro.config.js
@@ -1,6 +1,7 @@
const path = require('path');
-const { getDefaultConfig } = require('@expo/metro-config');
-const { getConfig } = require('react-native-builder-bob/metro-config');
+const {getDefaultConfig} = require('@expo/metro-config');
+const {getConfig} = require('react-native-builder-bob/metro-config');
+
const pkg = require('../package.json');
const root = path.resolve(__dirname, '..');
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 69%
rename from src/components/ImageLoader/__snapshots__/ImageLoader.test.tsx.snap
rename to src/components/Image/__snapshots__/Image.test.tsx.snap
index f7251bf..e692a4b 100644
--- a/src/components/ImageLoader/__snapshots__/ImageLoader.test.tsx.snap
+++ b/src/components/Image/__snapshots__/Image.test.tsx.snap
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`ImageLoader renders correctly with fallback 1`] = `
+exports[`Image renders correctly with fallback 1`] = `
`;
-exports[`ImageLoader renders correctly with fallbacks 1`] = `
+exports[`Image renders correctly with fallbacks 1`] = `
`;
-exports[`ImageLoader renders correctly with source 1`] = `
+exports[`Image renders correctly with source 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;