Next js Form weird behavior

I am building a form where when i click on submit, the data does get updated but the ui starts to update every second, like an infinite loop I am using server actions with the new useFormState and useFormStatus
3 Replies
kaleembhatti
kaleembhatti10mo ago
Page
// Global Imports

// Local Imports

// Body

export default async function Subjects({ params }: { params: { subjectId: string } }) {
// Get all subjects
const currentSubject = await api.subjects.currentSubject.query({
id: params.subjectId
})

if (!currentSubject) {
return null
}

// JSX
return (
<>
<div className={`${pxGlobal} max-w-screen flex py-8 justify-between flex-col gap-y-12`}>
{/* Other Code */}

{/* Form */}
<div className="flex flex-col gap-y-4 w-full">
{/* Other Code */}

{currentSubject && <CurrentSubjectForm currentSubject={currentSubject} subjectId={params.subjectId} />}
</div>
</div>
</>
)
}
// Global Imports

// Local Imports

// Body

export default async function Subjects({ params }: { params: { subjectId: string } }) {
// Get all subjects
const currentSubject = await api.subjects.currentSubject.query({
id: params.subjectId
})

if (!currentSubject) {
return null
}

// JSX
return (
<>
<div className={`${pxGlobal} max-w-screen flex py-8 justify-between flex-col gap-y-12`}>
{/* Other Code */}

{/* Form */}
<div className="flex flex-col gap-y-4 w-full">
{/* Other Code */}

{currentSubject && <CurrentSubjectForm currentSubject={currentSubject} subjectId={params.subjectId} />}
</div>
</div>
</>
)
}
------------------------------------------
------------------------------------------
Form
"use client"

// Global Imports

// Local Imports

// NBody

const initialState = {
status: "" as string,
message: null as string | null,
}

export default function CurrentSubjectForm({ currentSubject, subjectId }: { currentSubject: Subject, subjectId: string }) {
const [state, formAction] = useFormState(MutateCurrentSubject, initialState);

// Other Code

const currentSubjectArray = Object.entries(currentSubject).map(([key, value]) => ({ "key": key, "value": value }))

// JSX
return (
<form className="flex flex-col gap-y-6 w-full box-border" action={formAction}>
{/* Other Code */}

{currentSubjectArray.map((propertyObj) => {
return (
<div key={`${propertyObj.key}`} className="flex flex-col gap-y-2">
<p>{propertyObj.key}</p>
{propertyObj.key === "id" && <CustomInputId name={propertyObj.key} defaultValue={rmFirstAndLastDoubleQuotes(JSON.stringify(propertyObj.value))} />}
{propertyObj.key === "subject" && <CustomInputSubject name={propertyObj.key} defaultValue={rmFirstAndLastDoubleQuotes(JSON.stringify(propertyObj.value))} />}
{/* Other input fields also generated like this */}
</div>
)
})}

{/* Custom Hidden Input */}
<input type="hidden" name="id" defaultValue={subjectId} />

<div className="flex gap-x-2">
<SaveCurrentSubjectButton />
<Button type="reset" variant={"outline"} className="w-32">Reset</Button>
</div>
</form>
)
}
"use client"

// Global Imports

// Local Imports

// NBody

const initialState = {
status: "" as string,
message: null as string | null,
}

export default function CurrentSubjectForm({ currentSubject, subjectId }: { currentSubject: Subject, subjectId: string }) {
const [state, formAction] = useFormState(MutateCurrentSubject, initialState);

// Other Code

const currentSubjectArray = Object.entries(currentSubject).map(([key, value]) => ({ "key": key, "value": value }))

// JSX
return (
<form className="flex flex-col gap-y-6 w-full box-border" action={formAction}>
{/* Other Code */}

{currentSubjectArray.map((propertyObj) => {
return (
<div key={`${propertyObj.key}`} className="flex flex-col gap-y-2">
<p>{propertyObj.key}</p>
{propertyObj.key === "id" && <CustomInputId name={propertyObj.key} defaultValue={rmFirstAndLastDoubleQuotes(JSON.stringify(propertyObj.value))} />}
{propertyObj.key === "subject" && <CustomInputSubject name={propertyObj.key} defaultValue={rmFirstAndLastDoubleQuotes(JSON.stringify(propertyObj.value))} />}
{/* Other input fields also generated like this */}
</div>
)
})}

{/* Custom Hidden Input */}
<input type="hidden" name="id" defaultValue={subjectId} />

<div className="flex gap-x-2">
<SaveCurrentSubjectButton />
<Button type="reset" variant={"outline"} className="w-32">Reset</Button>
</div>
</form>
)
}
------------------------------------------
------------------------------------------
Submit Button
'use client'

// Global Imports

// Local Imports

// Body

export function SaveCurrentSubjectButton() {
const { pending } = useFormStatus()

if (pending) {
return (
<Button type="submit" variant={"primaryDisabled"} className="w-32">Submit</Button>
)
}

// JSX
return (
<Button type="submit" variant={"primary"} className="w-32">Submit</Button>
)
}
'use client'

// Global Imports

// Local Imports

// Body

export function SaveCurrentSubjectButton() {
const { pending } = useFormStatus()

if (pending) {
return (
<Button type="submit" variant={"primaryDisabled"} className="w-32">Submit</Button>
)
}

// JSX
return (
<Button type="submit" variant={"primary"} className="w-32">Submit</Button>
)
}
------------------------------------------
------------------------------------------
Server Action
"use server"

// Global Imports

// Local Imports

// Body

const subjectsSchema = z.object({
subject: z.string().trim().max(255, "Subject must be less than 255 characters long"),
overview: z.string().trim().max(10000000, "Overview must be less than 10,000,000 characters long").optional(),
educationLevel: z.enum(["OLevel", "ALevel"]),
visibility: z.enum(["Pending", "Public", "Private"]),
id: z.string().cuid(),
})

export async function MutateCurrentSubject(prevState: unknown, formData: FormData) {
try {
const validatedFields = subjectsSchema.safeParse({
subject: formData.get('subject'),
educationLevel: formData.get('educationLevel'),
overview: formData.get('overview'),
visibility: formData.get('visibility'),
id: formData.get('id'),
})

// // Return early if the form data is invalid
if (!validatedFields.success) {
return {
status: "error",
message: "The form data is invalid. Please try again later."
}
}

const { status, message } = await api.subjects.updateCurrentSubject.mutate(validatedFields.data)

revalidatePath(`/admin/subjects`)

return {
status: status ? status : "success",
message: message ? message : "Subject created successfully."
}

} catch (error) {
return {
status: "fail",
message: "Something went wrong. Please try again later."
}
}
}
"use server"

// Global Imports

// Local Imports

// Body

const subjectsSchema = z.object({
subject: z.string().trim().max(255, "Subject must be less than 255 characters long"),
overview: z.string().trim().max(10000000, "Overview must be less than 10,000,000 characters long").optional(),
educationLevel: z.enum(["OLevel", "ALevel"]),
visibility: z.enum(["Pending", "Public", "Private"]),
id: z.string().cuid(),
})

export async function MutateCurrentSubject(prevState: unknown, formData: FormData) {
try {
const validatedFields = subjectsSchema.safeParse({
subject: formData.get('subject'),
educationLevel: formData.get('educationLevel'),
overview: formData.get('overview'),
visibility: formData.get('visibility'),
id: formData.get('id'),
})

// // Return early if the form data is invalid
if (!validatedFields.success) {
return {
status: "error",
message: "The form data is invalid. Please try again later."
}
}

const { status, message } = await api.subjects.updateCurrentSubject.mutate(validatedFields.data)

revalidatePath(`/admin/subjects`)

return {
status: status ? status : "success",
message: message ? message : "Subject created successfully."
}

} catch (error) {
return {
status: "fail",
message: "Something went wrong. Please try again later."
}
}
}
I have minified the code i cant find a reason why this is happenning when i remove the revalidatePath inside the server actions the bug no longer occurs but then the user would have to refresh every time as well i added useOptimistic that solved the issue but the entire form should work fine even without it still no idea what the problem is
Josh
Josh10mo ago
I'm guessing it's cause you're calling a tRPC route inside your backend if the API object wasn't created via createCaller your doing another post request to yourself which could cause some interesting side effects
kaleembhatti
kaleembhatti10mo ago
ohh ill look into that thanks
Want results from more Discord servers?
Add your server