Skip to content

Commit 76f75ef

Browse files
committed
fix(propose): guard TemplateDetailsForm effect against render loop
useWatch returns a fresh object reference on every render even when the watched values are structurally unchanged. The effect's dependency array then triggered setValue → parent re-render → fresh useWatch ref → infinite loop, surfacing as "Maximum update depth exceeded". Serialize watched values and short-circuit when the snapshot is unchanged.
1 parent ee3d7b2 commit 76f75ef

1 file changed

Lines changed: 9 additions & 1 deletion

File tree

src/components/proposals/TemplateDetailsForm.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client";
22

3-
import { forwardRef, useEffect, useImperativeHandle } from "react";
3+
import { forwardRef, useEffect, useImperativeHandle, useRef } from "react";
44
import { zodResolver } from "@hookform/resolvers/zod";
55
import { FormProvider, useForm, useFormContext, useWatch, type Resolver } from "react-hook-form";
66
import { Label } from "@/components/ui/label";
@@ -42,8 +42,16 @@ export const TemplateDetailsForm = forwardRef<TemplateDetailsFormHandle, Templat
4242

4343
const watchedValues = useWatch({ control: templateForm.control }) as TemplateValues;
4444

45+
// useWatch returns a fresh object reference on every render even when values
46+
// are unchanged. Guard setValue with a serialized snapshot so the effect is a
47+
// no-op until a field actually changes (otherwise parent.setValue re-renders
48+
// us, we re-read useWatch, get a new ref, run the effect, loop forever).
49+
const lastSerializedRef = useRef<string | null>(null);
4550
useEffect(() => {
4651
if (!schema) return;
52+
const serialized = JSON.stringify(watchedValues);
53+
if (serialized === lastSerializedRef.current) return;
54+
lastSerializedRef.current = serialized;
4755
parent.setValue("templateFields", watchedValues, { shouldDirty: true });
4856
const compiled = compileTemplate(slug, watchedValues);
4957
parent.setValue("description", compiled, { shouldDirty: true, shouldValidate: false });

0 commit comments

Comments
 (0)