Skip to content

Commit e087b4f

Browse files
authored
feat(react/firestore): add useDeleteDocumentMutation (#157)
1 parent c47ec78 commit e087b4f

File tree

3 files changed

+163
-0
lines changed

3 files changed

+163
-0
lines changed

packages/react/src/firestore/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ export { useCollectionQuery } from "./useCollectionQuery";
99
export { useGetAggregateFromServerQuery } from "./useGetAggregateFromServerQuery";
1010
export { useGetCountFromServerQuery } from "./useGetCountFromServerQuery";
1111
// useNamedQuery
12+
export { useDeleteDocumentMutation } from "./useDeleteDocumentMutation";
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import { renderHook, waitFor, act } from "@testing-library/react";
2+
import {
3+
doc,
4+
type DocumentReference,
5+
getDoc,
6+
setDoc,
7+
} from "firebase/firestore";
8+
import { beforeEach, describe, expect, test } from "vitest";
9+
import { useDeleteDocumentMutation } from "./useDeleteDocumentMutation";
10+
11+
import {
12+
expectFirestoreError,
13+
firestore,
14+
wipeFirestore,
15+
} from "~/testing-utils";
16+
import { queryClient, wrapper } from "../../utils";
17+
18+
describe("useDeleteDocumentMutation", () => {
19+
beforeEach(async () => {
20+
await wipeFirestore();
21+
queryClient.clear();
22+
});
23+
24+
test("successfully deletes an existing document", async () => {
25+
const docRef = doc(firestore, "tests", "deleteTest");
26+
27+
await setDoc(docRef, { foo: "bar" });
28+
29+
const initialSnapshot = await getDoc(docRef);
30+
expect(initialSnapshot.exists()).toBe(true);
31+
32+
const { result } = renderHook(() => useDeleteDocumentMutation(docRef), {
33+
wrapper,
34+
});
35+
36+
expect(result.current.isPending).toBe(false);
37+
expect(result.current.isIdle).toBe(true);
38+
39+
await act(() => result.current.mutate());
40+
41+
await waitFor(() => {
42+
expect(result.current.isSuccess).toBe(true);
43+
});
44+
45+
const finalSnapshot = await getDoc(docRef);
46+
expect(finalSnapshot.exists()).toBe(false);
47+
});
48+
49+
test("handles type-safe document references", async () => {
50+
interface TestDoc {
51+
foo: string;
52+
num: number;
53+
}
54+
55+
const docRef = doc(
56+
firestore,
57+
"tests",
58+
"typedDoc"
59+
) as DocumentReference<TestDoc>;
60+
await setDoc(docRef, { foo: "test", num: 123 });
61+
62+
const { result } = renderHook(() => useDeleteDocumentMutation(docRef), {
63+
wrapper,
64+
});
65+
66+
await act(() => result.current.mutate());
67+
68+
await waitFor(() => {
69+
expect(result.current.isSuccess).toBe(true);
70+
});
71+
72+
const snapshot = await getDoc(docRef);
73+
expect(snapshot.exists()).toBe(false);
74+
});
75+
76+
test("handles errors when deleting from restricted collection", async () => {
77+
const restrictedDocRef = doc(firestore, "restrictedCollection", "someDoc");
78+
79+
const { result } = renderHook(
80+
() => useDeleteDocumentMutation(restrictedDocRef),
81+
{ wrapper }
82+
);
83+
84+
await act(() => result.current.mutate());
85+
86+
await waitFor(() => {
87+
expect(result.current.isError).toBe(true);
88+
});
89+
90+
expectFirestoreError(result.current.error, "permission-denied");
91+
});
92+
93+
test("calls onSuccess callback after deletion", async () => {
94+
const docRef = doc(firestore, "tests", "callbackTest");
95+
await setDoc(docRef, { foo: "callback" });
96+
97+
let callbackCalled = false;
98+
99+
const { result } = renderHook(
100+
() =>
101+
useDeleteDocumentMutation(docRef, {
102+
onSuccess: () => {
103+
callbackCalled = true;
104+
},
105+
}),
106+
{ wrapper }
107+
);
108+
109+
await act(() => result.current.mutate());
110+
111+
await waitFor(() => {
112+
expect(result.current.isSuccess).toBe(true);
113+
});
114+
115+
expect(callbackCalled).toBe(true);
116+
const snapshot = await getDoc(docRef);
117+
expect(snapshot.exists()).toBe(false);
118+
});
119+
120+
test("handles deletion of non-existent document", async () => {
121+
const nonExistentDocRef = doc(firestore, "tests", "doesNotExist");
122+
123+
const { result } = renderHook(
124+
() => useDeleteDocumentMutation(nonExistentDocRef),
125+
{ wrapper }
126+
);
127+
128+
await act(() => result.current.mutate());
129+
130+
await waitFor(() => {
131+
expect(result.current.isSuccess).toBe(true);
132+
});
133+
134+
const snapshot = await getDoc(nonExistentDocRef);
135+
expect(snapshot.exists()).toBe(false);
136+
});
137+
});
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { useMutation, type UseMutationOptions } from "@tanstack/react-query";
2+
import {
3+
deleteDoc,
4+
type FirestoreError,
5+
type DocumentData,
6+
type DocumentReference,
7+
} from "firebase/firestore";
8+
9+
type FirestoreUseMutationOptions<TData = unknown, TError = Error> = Omit<
10+
UseMutationOptions<TData, TError, void>,
11+
"mutationFn"
12+
>;
13+
14+
export function useDeleteDocumentMutation<
15+
AppModelType extends DocumentData = DocumentData,
16+
DbModelType extends DocumentData = DocumentData
17+
>(
18+
documentRef: DocumentReference<AppModelType, DbModelType>,
19+
options?: FirestoreUseMutationOptions<void, FirestoreError>
20+
) {
21+
return useMutation<void, FirestoreError>({
22+
...options,
23+
mutationFn: () => deleteDoc(documentRef),
24+
});
25+
}

0 commit comments

Comments
 (0)