Repository navigation
Expand file tree
/
Copy pathuseRetryingImageSource.ts
More file actions
44 lines (42 loc) · 1.85 KB
/
Copy pathuseRetryingImageSource.ts
File metadata and controls
44 lines (42 loc) · 1.85 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import { useLayoutEffect, useMemo, useRef } from "react";
import type { MarkdownImageOccurrence, MarkdownImageSource } from "react-native-enriched-markdown";
import type { MarkdownImageSourceResolver } from "./SelectableMarkdownText.types";
/**
* Enriched asks about each image once and reads `resolveImageSource` like an
* event handler, so a failed image would keep its placeholder until remount.
* Our resolver fails while the environment is unreachable and is replaced when
* that changes, so a failed image stays pending and asks the next resolver.
*/
export function useRetryingImageSource(resolve: MarkdownImageSourceResolver | undefined) {
const latestRef = useRef(resolve);
const retriesRef = useRef(new Set<() => void>());
// oxlint-disable-next-line t3code/no-unreasoned-state-sync -- publishes the committed resolver to a ref and retries images that failed under the previous one
useLayoutEffect(() => {
latestRef.current = resolve;
const retries = [...retriesRef.current];
retriesRef.current.clear();
for (const retry of retries) retry();
}, [resolve]);
const enabled = resolve !== undefined;
return useMemo(() => {
if (!enabled) return undefined;
return (image: MarkdownImageOccurrence) =>
new Promise<MarkdownImageSource | undefined>((settle) => {
const request = { href: image.url, alt: image.altText || null, title: image.title || null };
const attempt = () => {
const current = latestRef.current;
if (!current) return;
Promise.resolve()
.then(() => current(request))
.then(
(source) => settle(source ?? undefined),
() => {
if (latestRef.current !== current) attempt();
else retriesRef.current.add(attempt);
},
);
};
attempt();
});
}, [enabled]);
}