Skip to content

Commit 550c31c

Browse files
committed
fix(mobile/submissions): stop form resetting mid-photo-upload
Opening the native picker backgrounded the app and retriggered the useEffect that seeded the form, wiping in-progress edits. Split into outer loader + inner form that mounts with submission as defaultValues.
1 parent 47859b3 commit 550c31c

1 file changed

Lines changed: 111 additions & 101 deletions

File tree

Lines changed: 111 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { revalidateLogic, useForm, useStore } from "@tanstack/react-form";
22
import { router, useLocalSearchParams } from "expo-router";
33
import { StatusBar } from "expo-status-bar";
4-
import { useEffect, useState } from "react";
4+
import { useState } from "react";
55
import { ScrollView, View } from "react-native";
66
import { SafeAreaView } from "react-native-safe-area-context";
77
import { Button } from "@/components/ui/button";
@@ -17,24 +17,82 @@ import {
1717
} from "../hooks/use-submissions";
1818
import { collectInvalidLabels } from "../lib/collect-invalid-labels";
1919
import {
20-
EMPTY_SUBMISSION,
2120
type MosqueSubmissionInput,
2221
mosqueSubmissionSchema,
2322
} from "../lib/schemas";
2423
import { SubmissionFormFields } from "./submission-form-fields";
2524

25+
type Submission = NonNullable<ReturnType<typeof useMySubmission>["data"]>;
26+
2627
export function SubmissionEditScreen() {
2728
const { id } = useLocalSearchParams<{ id: string }>();
2829
const { data: submission, isLoading } = useMySubmission(id);
30+
const scheme = useThemeScheme();
31+
32+
return (
33+
<View className="flex-1 bg-cream">
34+
<StatusBar style={scheme === "dark" ? "light" : "dark"} />
35+
<SafeAreaView edges={["top"]} className="bg-cream">
36+
<View className="flex-row items-center justify-between px-s-5 py-s-2">
37+
<IconButton
38+
icon="back"
39+
size="sm"
40+
variant="ghost"
41+
onPress={() => router.back()}
42+
accessibilityLabel="Back"
43+
/>
44+
<Text variant="display-sm">Edit submission</Text>
45+
<View className="h-10 w-10" />
46+
</View>
47+
</SafeAreaView>
48+
49+
{isLoading || !submission || !id ? (
50+
<View className="flex-1 items-center justify-center">
51+
<Text variant="body" tone="muted">
52+
Loading…
53+
</Text>
54+
</View>
55+
) : (
56+
<EditSubmissionForm id={id} submission={submission} />
57+
)}
58+
</View>
59+
);
60+
}
61+
62+
function submissionToDefaults(submission: Submission): MosqueSubmissionInput {
63+
return {
64+
name: submission.name,
65+
subtitle: submission.subtitle ?? null,
66+
about: submission.about ?? null,
67+
address: submission.address ?? null,
68+
street: submission.street ?? null,
69+
area: submission.area ?? null,
70+
city: submission.city,
71+
lat: submission.lat,
72+
lng: submission.lng,
73+
open: submission.open,
74+
tags: submission.tags ?? [],
75+
facilities: submission.facilities ?? [],
76+
photos: submission.photos ?? [],
77+
};
78+
}
79+
80+
function EditSubmissionForm({
81+
id,
82+
submission,
83+
}: {
84+
id: string;
85+
submission: Submission;
86+
}) {
2987
const update = useUpdateMySubmission();
3088
const del = useDeleteMySubmission();
3189
const [errorMessage, setErrorMessage] = useState<string | null>(null);
3290
const dialog = useAppDialog();
3391
const toast = useAppToast();
34-
const scheme = useThemeScheme();
92+
const readOnly = submission.status !== "pending";
3593

3694
const form = useForm({
37-
defaultValues: EMPTY_SUBMISSION,
95+
defaultValues: submissionToDefaults(submission),
3896
validationLogic: revalidateLogic({
3997
mode: "submit",
4098
modeAfterSubmission: "change",
@@ -52,7 +110,6 @@ export function SubmissionEditScreen() {
52110
});
53111
},
54112
onSubmit: async ({ value }) => {
55-
if (!id) return;
56113
try {
57114
await update.mutateAsync({
58115
id,
@@ -71,33 +128,9 @@ export function SubmissionEditScreen() {
71128
},
72129
});
73130

74-
// biome-ignore lint/correctness/useExhaustiveDependencies: form from useForm is stable; re-running on form identity would loop
75-
useEffect(() => {
76-
if (!submission) return;
77-
form.reset({
78-
name: submission.name,
79-
subtitle: submission.subtitle ?? null,
80-
about: submission.about ?? null,
81-
address: submission.address ?? null,
82-
street: submission.street ?? null,
83-
area: submission.area ?? null,
84-
city: submission.city,
85-
lat: submission.lat,
86-
lng: submission.lng,
87-
open: submission.open,
88-
tags: submission.tags ?? [],
89-
facilities: submission.facilities ?? [],
90-
photos: submission.photos ?? [],
91-
});
92-
setErrorMessage(null);
93-
}, [submission]);
94-
95131
const isSubmitting = useStore(form.store, (s) => s.isSubmitting);
96132

97-
const readOnly = submission && submission.status !== "pending";
98-
99133
const handleDelete = () => {
100-
if (!id) return;
101134
dialog.show({
102135
title: "Withdraw submission?",
103136
body: "This removes the pending row. You can submit again later.",
@@ -124,83 +157,60 @@ export function SubmissionEditScreen() {
124157
};
125158

126159
return (
127-
<View className="flex-1 bg-cream">
128-
<StatusBar style={scheme === "dark" ? "light" : "dark"} />
129-
<SafeAreaView edges={["top"]} className="bg-cream">
130-
<View className="flex-row items-center justify-between px-s-5 py-s-2">
131-
<IconButton
132-
icon="back"
133-
size="sm"
134-
variant="ghost"
135-
onPress={() => router.back()}
136-
accessibilityLabel="Back"
137-
/>
138-
<Text variant="display-sm">Edit submission</Text>
139-
<View className="h-10 w-10" />
140-
</View>
141-
</SafeAreaView>
160+
<>
161+
<ScrollView
162+
contentContainerStyle={{
163+
paddingHorizontal: 24,
164+
paddingTop: 12,
165+
paddingBottom: 48,
166+
}}
167+
keyboardShouldPersistTaps="handled"
168+
showsVerticalScrollIndicator={false}
169+
>
170+
{readOnly ? (
171+
<View className="mb-s-4 rounded-md border border-line bg-surface p-s-4">
172+
<Text variant="label">
173+
This mosque is already {submission.status}
174+
</Text>
175+
<Text variant="caption" tone="muted" className="mt-s-1">
176+
Approved and hidden mosques can only be changed by an admin. You
177+
can still view the details below.
178+
</Text>
179+
</View>
180+
) : null}
142181

143-
{isLoading || !submission ? (
144-
<View className="flex-1 items-center justify-center">
145-
<Text variant="body" tone="muted">
146-
Loading…
147-
</Text>
148-
</View>
149-
) : (
150-
<ScrollView
151-
contentContainerStyle={{
152-
paddingHorizontal: 24,
153-
paddingTop: 12,
154-
paddingBottom: 48,
155-
}}
156-
keyboardShouldPersistTaps="handled"
157-
showsVerticalScrollIndicator={false}
158-
>
159-
{readOnly ? (
160-
<View className="mb-s-4 rounded-md border border-line bg-surface p-s-4">
161-
<Text variant="label">
162-
This mosque is already {submission.status}
163-
</Text>
164-
<Text variant="caption" tone="muted" className="mt-s-1">
165-
Approved and hidden mosques can only be changed by an admin. You
166-
can still view the details below.
167-
</Text>
168-
</View>
169-
) : null}
182+
{errorMessage ? (
183+
<View className="mb-s-4 rounded-md border border-danger/40 bg-danger-tint p-s-3">
184+
<Text variant="body-sm" className="text-danger">
185+
{errorMessage}
186+
</Text>
187+
</View>
188+
) : null}
170189

171-
{errorMessage ? (
172-
<View className="mb-s-4 rounded-md border border-danger/40 bg-danger-tint p-s-3">
173-
<Text variant="body-sm" className="text-danger">
174-
{errorMessage}
175-
</Text>
176-
</View>
177-
) : null}
190+
<SubmissionFormFields
191+
form={form}
192+
isSubmitting={isSubmitting || readOnly}
193+
/>
178194

179-
<SubmissionFormFields
180-
form={form}
181-
isSubmitting={isSubmitting || Boolean(readOnly)}
182-
/>
183-
184-
{!readOnly ? (
185-
<View className="mt-s-6 gap-s-3">
186-
<Button
187-
label={isSubmitting ? "Saving…" : "Save changes"}
188-
onPress={() => form.handleSubmit()}
189-
disabled={isSubmitting}
190-
/>
191-
<Button
192-
label="Withdraw submission"
193-
variant="outline"
194-
onPress={handleDelete}
195-
disabled={del.isPending}
196-
/>
197-
</View>
198-
) : null}
199-
</ScrollView>
200-
)}
195+
{!readOnly ? (
196+
<View className="mt-s-6 gap-s-3">
197+
<Button
198+
label={isSubmitting ? "Saving…" : "Save changes"}
199+
onPress={() => form.handleSubmit()}
200+
disabled={isSubmitting}
201+
/>
202+
<Button
203+
label="Withdraw submission"
204+
variant="outline"
205+
onPress={handleDelete}
206+
disabled={del.isPending}
207+
/>
208+
</View>
209+
) : null}
210+
</ScrollView>
201211

202212
{dialog.element}
203213
{toast.element}
204-
</View>
214+
</>
205215
);
206216
}

0 commit comments

Comments
 (0)