Skip to content

Commit 636a5fe

Browse files
authored
feat: add retry functionality (#113)
1 parent bde76b9 commit 636a5fe

File tree

5 files changed

+48
-10
lines changed

5 files changed

+48
-10
lines changed

README.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ component][render-props] and a [custom hook][hooks-intro].
5252
- [`getViewProps`](#getviewprops)
5353
- [`getImageProps`](#getimageprops)
5454
- [state](#state)
55+
- [utils](#utils)
5556
- [Examples](#examples)
5657
- [Snack Playground](#snack-playground)
5758
- [Inspiration](#inspiration)
@@ -237,7 +238,7 @@ Regardless of whether you are using the component or the hook, the results are
237238
an object containing important properties you'll need for rendering. It will be
238239
passed to the [Render Prop Function](#render-prop-function) when using the
239240
component, and returned from the hook invocation when using the hook. The
240-
properties of this object can be split into two categories as indicated below:
241+
properties of this object can be split into the following categories:
241242

242243
### prop getters
243244

@@ -283,6 +284,14 @@ These are values that represent the current state of the component.
283284
| `loading` | `boolean` | whether or not the image is currently loading |
284285
| `error` | `string` | an error message if the image failed to load (`''` on success) |
285286

287+
### utils
288+
289+
These are miscellaneous helpers that don't fit in the other major categories.
290+
291+
| property | type | description |
292+
| -------- | ------------ | ----------------------------------------------------- |
293+
| `retry` | `function()` | force another attempt to resolve the image dimensions |
294+
286295
## Examples
287296

288297
See the [`examples`][examples-directory] directory for examples using both the

examples/component.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,19 +61,20 @@ const MyTouchableComponent = ({ imageUri, onPress }) => (
6161

6262
```jsx
6363
import React from 'react';
64-
import { ActivityIndicator, Image, Text, View } from 'react-native';
64+
import { ActivityIndicator, Image, Text, Button, View } from 'react-native';
6565
import ResponsiveImageView from 'react-native-responsive-image-view';
6666

6767
const MyComponent = ({ imageUri }) => (
6868
<ResponsiveImageView source={{ uri: imageUri }}>
69-
{({ error, loading, getViewProps, getImageProps }) => {
69+
{({ error, loading, retry, getViewProps, getImageProps }) => {
7070
if (loading) {
7171
return <ActivityIndicator animating={true} size="large" />;
7272
}
7373
if (error) {
7474
return (
7575
<View>
7676
<Text>{error}</Text>
77+
<Button onPress={retry} title="Retry" />
7778
</View>
7879
);
7980
}

examples/hook.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,14 @@ const MyTouchableComponent = ({ imageUri, onPress }) => {
6868

6969
```jsx
7070
import React from 'react';
71-
import { ActivityIndicator, Image, Text, View } from 'react-native';
71+
import { ActivityIndicator, Image, Text, Button, View } from 'react-native';
7272
import { useResponsiveImageView } from 'react-native-responsive-image-view';
7373

7474
const MyComponent = ({ imageUri }) => {
7575
const {
7676
error,
7777
loading,
78+
retry,
7879
getViewProps,
7980
getImageProps,
8081
} = useResponsiveImageView({ source: { uri: imageUri } });
@@ -87,6 +88,7 @@ const MyComponent = ({ imageUri }) => {
8788
return (
8889
<View>
8990
<Text>{error}</Text>
91+
<Button onPress={retry} title="Retry" />
9092
</View>
9193
);
9294
}

useResponsiveImageView.js

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ function getImageSize(uri, onImageSizeSuccess, onImageSizeFailure) {
3030
const initialState = {
3131
loading: true,
3232
error: '',
33+
retryCount: 0,
3334
aspectRatio: undefined,
3435
};
3536

@@ -39,17 +40,24 @@ function reducer(state, action) {
3940
return {
4041
...initialState,
4142
loading: false,
43+
retryCount: state.retryCount,
4244
aspectRatio: action.payload,
4345
};
4446
case 'FAILURE':
4547
return {
4648
...initialState,
4749
loading: false,
4850
error: action.payload,
51+
retryCount: state.retryCount,
52+
};
53+
case 'RETRY':
54+
return {
55+
...initialState,
56+
retryCount: state.retryCount + 1,
4957
};
5058
/* istanbul ignore next: this will never happen */
5159
default:
52-
return state;
60+
throw new Error(`Unexpected action type: ${action.type}`);
5361
}
5462
}
5563

@@ -67,6 +75,10 @@ function useResponsiveImageView({
6775

6876
const [state, dispatch] = React.useReducer(reducer, initialState);
6977

78+
function retry() {
79+
dispatch({ type: 'RETRY' });
80+
}
81+
7082
function isAspectRatioControlled() {
7183
return controlledAspectRatio !== undefined;
7284
}
@@ -131,15 +143,16 @@ function useResponsiveImageView({
131143
pendingGetImageSize.cancel();
132144
};
133145
// Using JSON.stringify here because the `source` parameter can be a nested
134-
// object. The alternative is requiring the user to memoize the parameters,
135-
// but that would add usage overhead and potential confusion (at least in
136-
// the early days of hooks).
146+
// object. The alternative is requiring the user to memoize it, by why make
147+
// them do that when we don't have to? (Note: they already have to memoize
148+
// onLoad and onError, but those are much less likely to be used.)
137149
// eslint-disable-next-line react-hooks/exhaustive-deps
138-
}, [JSON.stringify(initialSource), onLoad, onError]);
150+
}, [JSON.stringify(initialSource), onLoad, onError, state.retryCount]);
139151

140152
return {
141153
loading: state.loading,
142154
error: state.error,
155+
retry,
143156
getViewProps,
144157
getImageProps,
145158
};

useResponsiveImageView.test.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react';
2-
import { render } from '@testing-library/react-native';
2+
import { render, act } from '@testing-library/react-native';
33
import {
44
mockUriGood,
55
mockUriBad,
@@ -65,6 +65,19 @@ describe('error', () => {
6565
});
6666
});
6767

68+
describe('retry', () => {
69+
it('retries', () => {
70+
const { children } = renderHook({ source: { uri: mockUriBad } });
71+
const { retry } = children.mock.calls[1][0];
72+
expect(children.mock.calls[1][0].loading).toBe(false);
73+
act(() => {
74+
retry();
75+
});
76+
expect(children.mock.calls[2][0].loading).toBe(true);
77+
expect(children.mock.calls[3][0].loading).toBe(false);
78+
});
79+
});
80+
6881
describe('getViewProps', () => {
6982
it('includes controlled aspectRatio', async () => {
7083
const { children } = renderHook({

0 commit comments

Comments
 (0)