Skip to content

Commit 97ef1bf

Browse files
authored
Merge pull request #684 from OskarEichler/codex/viewshot-example-capture-lifecycle
fix(example): ignore stale captures and release temporary previews
2 parents c2fd4c7 + 8b63225 commit 97ef1bf

2 files changed

Lines changed: 92 additions & 60 deletions

File tree

example-windows/src/components/shared/hooks/useViewShotCapture.ts

Lines changed: 46 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,34 @@
1-
import {useState, useCallback, useRef} from "react";
2-
import {captureRef} from "react-native-view-shot";
1+
import {useState, useCallback, useRef, useEffect} from "react";
2+
import {
3+
captureRef,
4+
releaseCapture,
5+
type CaptureOptions,
6+
type ViewShotRef,
7+
} from "react-native-view-shot";
38

4-
export interface CaptureOptions {
5-
format?: "png" | "jpg" | "webm";
6-
quality?: number;
7-
result?: "tmpfile" | "base64" | "zip-base64" | "data-uri";
8-
handleGLSurfaceViewOnAndroid?: boolean;
9-
}
9+
export type {CaptureOptions} from "react-native-view-shot";
1010

1111
export const useViewShotCapture = (successMessage?: string) => {
12-
const [capturedUri, setCapturedUri] = useState<string | null>(null);
12+
const [capture, setCapture] = useState<{
13+
uri: string;
14+
temporary: boolean;
15+
} | null>(null);
1316
const [isCapturing, setIsCapturing] = useState(false);
14-
const viewShotRef = useRef<any>(null);
17+
const viewShotRef = useRef<ViewShotRef | null>(null);
18+
const requestId = useRef(0);
1519

16-
const onCapture = useCallback(
17-
(uri: string) => {
18-
setCapturedUri(uri);
19-
setIsCapturing(false);
20-
console.log(successMessage || "Captured!", `Content captured: ${uri}`);
20+
useEffect(
21+
() => () => {
22+
requestId.current++;
2123
},
22-
[successMessage],
24+
[],
2325
);
2426

25-
const onCaptureFailure = useCallback((error: Error) => {
26-
setIsCapturing(false);
27-
console.error("Capture Failed", `Error: ${error.message}`);
28-
}, []);
27+
useEffect(() => {
28+
if (capture?.temporary) {
29+
return () => releaseCapture(capture.uri);
30+
}
31+
}, [capture]);
2932

3033
const startCapture = useCallback(
3134
async (options: CaptureOptions = {}) => {
@@ -34,31 +37,44 @@ export const useViewShotCapture = (successMessage?: string) => {
3437
return;
3538
}
3639

40+
const id = ++requestId.current;
41+
const temporary = !options.result || options.result === "tmpfile";
3742
setIsCapturing(true);
38-
setCapturedUri(null);
43+
setCapture(null);
3944

4045
try {
41-
const captureOptions = {
42-
format: "png" as const,
46+
const uri = await captureRef(viewShotRef, {
47+
format: "png",
4348
quality: 0.8,
4449
...options,
45-
};
46-
const uri = await captureRef(viewShotRef, captureOptions);
47-
onCapture(uri);
50+
});
51+
if (id !== requestId.current) {
52+
if (temporary) releaseCapture(uri);
53+
return;
54+
}
55+
setCapture({uri, temporary});
56+
setIsCapturing(false);
57+
console.log(successMessage || "Captured!");
4858
} catch (error) {
49-
onCaptureFailure(error as Error);
59+
if (id !== requestId.current) return;
60+
setIsCapturing(false);
61+
console.error(
62+
"Capture Failed",
63+
error instanceof Error ? error.message : String(error),
64+
);
5065
}
5166
},
52-
[onCapture, onCaptureFailure],
67+
[successMessage],
5368
);
5469

5570
const resetCapture = useCallback(() => {
56-
setCapturedUri(null);
71+
requestId.current++;
72+
setCapture(null);
5773
setIsCapturing(false);
5874
}, []);
5975

6076
return {
61-
capturedUri,
77+
capturedUri: capture?.uri ?? null,
6278
isCapturing,
6379
viewShotRef,
6480
startCapture,

example/src/components/shared/hooks/useViewShotCapture.ts

Lines changed: 46 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,34 @@
1-
import { useState, useCallback, useRef } from 'react';
2-
import { captureRef } from 'react-native-view-shot';
1+
import { useState, useCallback, useRef, useEffect } from 'react';
2+
import {
3+
captureRef,
4+
releaseCapture,
5+
type CaptureOptions,
6+
type ViewShotRef,
7+
} from 'react-native-view-shot';
38

4-
export interface CaptureOptions {
5-
format?: 'png' | 'jpg' | 'webm';
6-
quality?: number;
7-
result?: 'tmpfile' | 'base64' | 'zip-base64' | 'data-uri';
8-
handleGLSurfaceViewOnAndroid?: boolean;
9-
}
9+
export type { CaptureOptions } from 'react-native-view-shot';
1010

1111
export const useViewShotCapture = (successMessage?: string) => {
12-
const [capturedUri, setCapturedUri] = useState<string | null>(null);
12+
const [capture, setCapture] = useState<{
13+
uri: string;
14+
temporary: boolean;
15+
} | null>(null);
1316
const [isCapturing, setIsCapturing] = useState(false);
14-
const viewShotRef = useRef<any>(null);
17+
const viewShotRef = useRef<ViewShotRef | null>(null);
18+
const requestId = useRef(0);
1519

16-
const onCapture = useCallback(
17-
(uri: string) => {
18-
setCapturedUri(uri);
19-
setIsCapturing(false);
20-
console.log(successMessage || 'Captured!', `Content captured: ${uri}`);
20+
useEffect(
21+
() => () => {
22+
requestId.current++;
2123
},
22-
[successMessage],
24+
[],
2325
);
2426

25-
const onCaptureFailure = useCallback((error: Error) => {
26-
setIsCapturing(false);
27-
console.error('Capture Failed', `Error: ${error.message}`);
28-
}, []);
27+
useEffect(() => {
28+
if (capture?.temporary) {
29+
return () => releaseCapture(capture.uri);
30+
}
31+
}, [capture]);
2932

3033
const startCapture = useCallback(
3134
async (options: CaptureOptions = {}) => {
@@ -34,31 +37,44 @@ export const useViewShotCapture = (successMessage?: string) => {
3437
return;
3538
}
3639

40+
const id = ++requestId.current;
41+
const temporary = !options.result || options.result === 'tmpfile';
3742
setIsCapturing(true);
38-
setCapturedUri(null);
43+
setCapture(null);
3944

4045
try {
41-
const captureOptions = {
42-
format: 'png' as const,
46+
const uri = await captureRef(viewShotRef, {
47+
format: 'png',
4348
quality: 0.8,
4449
...options,
45-
};
46-
const uri = await captureRef(viewShotRef, captureOptions);
47-
onCapture(uri);
50+
});
51+
if (id !== requestId.current) {
52+
if (temporary) releaseCapture(uri);
53+
return;
54+
}
55+
setCapture({ uri, temporary });
56+
setIsCapturing(false);
57+
console.log(successMessage || 'Captured!');
4858
} catch (error) {
49-
onCaptureFailure(error as Error);
59+
if (id !== requestId.current) return;
60+
setIsCapturing(false);
61+
console.error(
62+
'Capture Failed',
63+
error instanceof Error ? error.message : String(error),
64+
);
5065
}
5166
},
52-
[onCapture, onCaptureFailure],
67+
[successMessage],
5368
);
5469

5570
const resetCapture = useCallback(() => {
56-
setCapturedUri(null);
71+
requestId.current++;
72+
setCapture(null);
5773
setIsCapturing(false);
5874
}, []);
5975

6076
return {
61-
capturedUri,
77+
capturedUri: capture?.uri ?? null,
6278
isCapturing,
6379
viewShotRef,
6480
startCapture,

0 commit comments

Comments
 (0)