Skip to content

Commit a36a87c

Browse files
0xdevcollinsclaude
andcommitted
fix(submit-page): hydrate Phase A fields + stop wiping user input on re-render
The submit page mapped `initialData` inline on every render, which (a) recreated the object reference on every render so the form's reset effect fired continuously and wiped values the user was typing, and (b) dropped the Phase A fields entirely (tagline, builtWith, screenshots, license, codeAttestedAt) plus trackEntries. The combined effect was the documented symptom — only logo and videoUrl survived the save because those round-tripped through the type-narrowed object literal, while tagline / builtWith / license kept appearing to "switch to empty". - Memoize `initialData` against `mySubmission` so the reference only changes when the underlying submission actually changes. - Pass through Phase A fields and trackEntries so the form can hydrate the saved values, and so a follow-up save doesn't write back empties. - Widen SubmissionFormContent's `initialData` prop to accept the raw server-side extras (trackEntries, codeAttestedAt) that the form already consumes via cast — keeps the parent's hydration explicit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 8a9b4ad commit a36a87c

2 files changed

Lines changed: 53 additions & 20 deletions

File tree

app/(landing)/hackathons/[slug]/submit/page.tsx

Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client';
22

3-
import { use, useEffect, useState } from 'react';
3+
import { use, useEffect, useMemo, useState } from 'react';
44
import { useRouter } from 'next/navigation';
55
import { useHackathon } from '@/hooks/hackathon/use-hackathon-queries';
66
import { useAuthStatus } from '@/hooks/use-auth';
@@ -94,6 +94,40 @@ export default function SubmitProjectPage({
9494
router.push(`/hackathons/${hackathonSlug}?tab=submission`);
9595
};
9696

97+
// Stable initialData reference so the form doesn't re-reset (and wipe
98+
// the user's typed-but-unsaved input) on every parent re-render. The
99+
// form's reset effect depends on this object identity, so it MUST only
100+
// change when the underlying submission actually changes.
101+
//
102+
// We also pass through the Phase A polish fields (tagline, builtWith,
103+
// screenshots, license, codeAttestedAt) and trackEntries — if these
104+
// are missing, the form initialises them to empty and any save then
105+
// clobbers the server values with empties.
106+
const initialData = useMemo(() => {
107+
if (!mySubmission) return undefined;
108+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
109+
const s = mySubmission as any;
110+
return {
111+
projectName: mySubmission.projectName,
112+
category: mySubmission.category,
113+
description: mySubmission.description,
114+
logo: mySubmission.logo,
115+
banner: mySubmission.banner,
116+
videoUrl: mySubmission.videoUrl,
117+
introduction: mySubmission.introduction,
118+
links: mySubmission.links,
119+
participationType: s.participationType,
120+
teamName: s.teamName,
121+
teamMembers: s.teamMembers,
122+
tagline: s.tagline,
123+
builtWith: s.builtWith,
124+
screenshots: s.screenshots,
125+
license: s.license,
126+
codeAttestedAt: s.codeAttestedAt,
127+
trackEntries: s.trackEntries,
128+
};
129+
}, [mySubmission]);
130+
97131
const [hasInitialLoaded, setHasInitialLoaded] = useState(false);
98132

99133
useEffect(() => {
@@ -149,24 +183,7 @@ export default function SubmitProjectPage({
149183
hackathonSlugOrId={hackathonId}
150184
organizationId={orgId}
151185
submissionId={mySubmission?.id}
152-
initialData={
153-
mySubmission
154-
? {
155-
projectName: mySubmission.projectName,
156-
category: mySubmission.category,
157-
description: mySubmission.description,
158-
logo: mySubmission.logo,
159-
banner: mySubmission.banner,
160-
videoUrl: mySubmission.videoUrl,
161-
introduction: mySubmission.introduction,
162-
links: mySubmission.links,
163-
participationType: (mySubmission as any)
164-
.participationType,
165-
teamName: (mySubmission as any).teamName,
166-
teamMembers: (mySubmission as any).teamMembers,
167-
}
168-
: undefined
169-
}
186+
initialData={initialData}
170187
onSuccess={handleSuccess}
171188
onClose={handleClose}
172189
/>

components/hackathons/submissions/SubmissionForm.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,23 @@ type SubmissionFormDataLocal = z.infer<typeof baseSubmissionSchema>;
185185
interface SubmissionFormContentProps {
186186
hackathonSlugOrId: string;
187187
organizationId?: string;
188-
initialData?: Partial<SubmissionFormDataLocal>;
188+
/**
189+
* Pre-populates the form when editing an existing submission. Accepts
190+
* the raw submission shape from the API so server-only fields like
191+
* trackEntries and codeAttestedAt can be hydrated alongside the Zod-
192+
* typed form fields (which the form reads via a typed cast).
193+
*/
194+
initialData?: Partial<SubmissionFormDataLocal> & {
195+
trackEntries?: Array<{
196+
trackId: string;
197+
trackAnswers?: {
198+
promptAnswer?: string;
199+
customAnswers?: Record<string, string>;
200+
artifacts?: Record<string, string>;
201+
};
202+
}>;
203+
codeAttestedAt?: string | null;
204+
};
189205
submissionId?: string;
190206
onSuccess?: () => void;
191207
onClose?: () => void;

0 commit comments

Comments
 (0)