Next.js Discord

Discord Forum

Guidance for Forms for Nested Types

Unanswered
VoidPointer posted this in #help-forum
Open in Discord
Can someone please suggest a good resource for implementing forms for nested types, like for my Basics:
export type Basics = {
  name?: string;
  location?: Location;
  profiles?: Profile[];
};

I have, after many lessons, got some forms working using useActionState and zod-form-data for flat types and I'd like to extend that to nested ones. Gemini has been giving me a bit of a run around for a while, and other guides I've found seem to skirt the issue a bit. My first thought was, why nest vs. just using one form for each type, but having to handle updates to e.g. Location on it's own, then update Basics seems just wrong.

9 Replies

Nesting would require you to manage states coz you basically are not using the native FormData features.

"use client"

export function BasicsForm(props) {
  
  const [loc, setLoc] = useState(props.location)  
  const [prof, setProf] = useState(props.profiles)
  
  const [state, action, pending] = useActionState(
    async (prev, form) => {
      const name = form.get("name")
      const inputs: Basics = {
         name, locations: loc, profiles: prof
      }  
      const parsed = parseBasicsForm(inputs) // use zod
      const res = await serverActionMutateBasics(parsed)
      return res
    }, null
  )

  return (
    <form action={action}>
      <input name="name" defaultValues={props.name} />
      <LocationInput values={loc} onValueChange={setLoc} />

      <ProfilesInput values={prof} onValueChange={setProf} />    

      <button type="submit">Submit</button>
    </form>
  )
}
something like this
wdym?
Haha, sorry, I meant to delete that. I'll try your approach, thanks, merged with what I already have, and see what I can get.
I'm kind of stubbon, and having got somewhere with the nesting last night, I pushed a bit more and got something right. I'm doing this:
...
                    <HtmlTextField name={"dateOfBirth"} required={true} formState={state} />
                    <HtmlTextField name={"nationality"} required={true} formState={state} />
                    <HtmlTextField name={"workAuth"} required={true} label={"Work Authorization"} formState={state} />
                    <HtmlTextField name={"location.address"} required={true} label={"Address"} formState={state} />
                    <HtmlTextField name={"location.city"} required={true} label={"City"} formState={state} />
                    <HtmlTextField name={"location.postalCode"} required={true} label={"Postal Code"} formState={state} />
...

and this:
export async function updateBasics(
  prevState: EditBasicsFormState,
  formData: FormData,
): Promise<EditBasicsFormState> {
  const preprocessor = zfd.formData(basicsSchema);
  const result = preprocessor.safeParse(formData);
  console.log(util.inspect(result, { depth: null }));

  const values = Object.fromEntries(
    formData,
  ) as unknown as EditBasicsFormState["values"];

and the log output here has all the wanted data, as well as the log out output on the route that hosts the form component.
Nice
It's not that I think using separate components is out, I'll be moving the array to a reusable array component, and the Location to a separate dedicated component, just to group the inputs, but this little exercise gave me a good handle on formstate etc.