Skip to content

Commit

Permalink
feat(Image): can handle rejected or falsy src with onRenderFallback
Browse files Browse the repository at this point in the history
  • Loading branch information
liamross committed Apr 20, 2020
1 parent 1508879 commit 0e3cec5
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 14 deletions.
22 changes: 21 additions & 1 deletion src/components/Image/Image.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const Basic = () => (
</div>
);

export const WithPromiseSrc = () => (
export const WithSrcPromise = () => (
<div style={style}>
<Image
src={
Expand All @@ -43,3 +43,23 @@ export const WithPromiseSrc = () => (
/>
</div>
);

export const SrcPromiseRejects = () => (
<div style={style}>
<Image
src={new Promise(rej => setTimeout(() => rej(), 500))}
onRenderLoading={() => <Spinner />}
onRenderFallback={() => 'Rejected source promise'}
/>
</div>
);

export const SrcPromiseReturnsFalsy = () => (
<div style={style}>
<Image
src={new Promise(res => setTimeout(() => res(''), 500))}
onRenderLoading={() => <Spinner />}
onRenderFallback={() => 'Falsy source'}
/>
</div>
);
41 changes: 28 additions & 13 deletions src/components/Image/Image.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,33 +8,48 @@ export interface ImageProps extends Remove<ImgHTMLAttributes<HTMLImageElement>,
* The image source can be a `Futurable` or `LazyFuturable`, or undefined. If
* undefined or if a promise will display as loading.
*/
src?: FuturableOrLazy<string>;
src?: FuturableOrLazy<string | undefined>;
/**
* Render out an element to be shown while src is loading.
*/
onRenderLoading?(): ReactNode;
/**
* Render out an element to be shown if the .
*/
onRenderFallback?(): ReactNode;
}

export const Image = forwardRef<HTMLImageElement, ImageProps>(
({ src, onRenderLoading, alt, className, ...imgProps }, ref) => {
const [source, setSource] = useState<string | undefined>(typeof src === 'string' ? src : undefined);
({ src, onRenderLoading, onRenderFallback, alt, className, ...imgProps }, ref) => {
const sourceIsNotPromise = typeof src === 'string' || !src;
const [loading, setLoading] = useState(!sourceIsNotPromise);
const [source, setSource] = useState<string | undefined>(
sourceIsNotPromise ? (src as string | undefined) : undefined,
);

const getSource = useCallback(async (srcGetter: FuturableOrLazy<string>) => {
const fetchedSource = await futureableOrLazyToFuturable(srcGetter);
setSource(fetchedSource);
const getSource = useCallback(async (srcGetter: FuturableOrLazy<string | undefined>) => {
setLoading(true);
let fetchedSource = undefined;
try {
fetchedSource = await futureableOrLazyToFuturable(srcGetter);
} catch {}
setLoading(false);
setSource(fetchedSource || undefined);
}, []);

useEffect(() => {
if (typeof src === 'string' || src === undefined) return setSource(src);
if (sourceIsNotPromise) {
setLoading(false);
setSource((src as string | undefined) || undefined);
return;
}
getSource(src);
}, [src, getSource]);
}, [getSource, sourceIsNotPromise, src]);

const imageClass = classnames('ui__image', className);

return source ? (
<img {...imgProps} alt={alt} src={source} className={imageClass} ref={ref} />
) : (
<>{onRenderLoading?.()}</>
);
if (loading) return <>{onRenderLoading?.()}</>;
if (!source) return <>{onRenderFallback?.()}</>;
return <img {...imgProps} alt={alt} src={source} className={imageClass} ref={ref} />;
},
);

0 comments on commit 0e3cec5

Please sign in to comment.