ui icon indicating copy to clipboard operation
ui copied to clipboard

❌ DEFAULT VALUES DON'T WORK ❌

Open aymanechaaba1 opened this issue 1 year ago • 11 comments

'use client';

import { z } from 'zod';
import { Button } from './ui/button';
import {
  Form,
  FormControl,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from './ui/form';
import {
  Sheet,
  SheetContent,
  SheetHeader,
  SheetTitle,
  SheetTrigger,
} from './ui/sheet';
import useCustomForm from '@/hooks/useCustomForm';
import { Checkbox } from './ui/checkbox';
import { trpc } from '@/app/_trpc/client';
import { useEffect, useState } from 'react';
import { serverClient } from '@/app/_trpc/serverClient';

function AssignStudents({
  teacher_id,
  students,
}: {
  teacher_id: string;

  students: Awaited<ReturnType<(typeof serverClient)['getStudents']>>;
}) {
  const utils = trpc.useUtils();

  const teacher = trpc.getTeacher.useQuery({
    id: teacher_id,
  });

  const items = [
    ...students.map((student) => ({
      id: student?.id,
      label: student?.firstname,
    })),
  ] as const;

  const FormSchema = z.object({
    items: z.array(z.string().cuid()),
  });

  const [defaultStudents, setDefaultStudents] = useState<string[] | undefined>(
    []
  );

  useEffect(() => {
    const ids = teacher.data?.students.map((student) => student.student_id);
    setDefaultStudents(ids);
  }, [teacher.data?.students]);

  const [openSheet, setOpenSheet] = useState(false);

  const [form] = useCustomForm({
    formSchema: FormSchema,
    defaultValues: {
      items: defaultStudents,
    },
  });

  const assignStudentToTeacher = trpc.assignStudentToTeacher.useMutation({
    onSuccess() {
      utils.getTeacher.invalidate();
    },
  });
  const deleteStudentFromTeacher = trpc.deleteStudentFromTeacher.useMutation({
    onSuccess() {
      utils.getTeacher.invalidate();
    },
  });

  async function onSubmit(data: z.infer<typeof FormSchema>) {
    // get checked values
    const checkedStudents = data.items;

    // get unchecked values
    const uncheckedStudents = students
      .filter((student, i) => {
        const sId = data.items[i];
        return sId !== student?.id;
      })
      .map((student) => student?.id);

    console.log(checkedStudents);
    console.log(uncheckedStudents);

    // setOpenSheet(false);
  }

  return (
    <Sheet open={openSheet} onOpenChange={setOpenSheet}>
      <SheetTrigger asChild>
        <Button variant="default">Assign Students</Button>
      </SheetTrigger>
      <SheetContent className="">
        <SheetHeader>
          <SheetTitle>Assign Students</SheetTitle>
        </SheetHeader>
        <Form {...form}>
          <form
            onSubmit={form.handleSubmit(onSubmit)}
            className="space-y-8 mt-5"
          >
            <FormField
              control={form.control}
              name="items"
              render={() => (
                <FormItem>
                  {items.map((student) => (
                    <FormField
                      key={student.id}
                      control={form.control}
                      name="items"
                      render={({ field }) => {
                        return (
                          <FormItem
                            key={student.id}
                            className="flex flex-row items-start space-x-3 space-y-0"
                          >
                            <FormControl>
                              <Checkbox
                                checked={field.value?.includes(student.id)}
                                onCheckedChange={(checked) => {
                                  return checked
                                    ? field.onChange([
                                        ...field.value,
                                        student.id,
                                      ])
                                    : field.onChange(
                                        field.value?.filter(
                                          (value: string) =>
                                            value !== student.id
                                        )
                                      );
                                }}
                              />
                            </FormControl>
                            <FormLabel className="font-normal">
                              {student.label}
                            </FormLabel>
                          </FormItem>
                        );
                      }}
                    />
                  ))}
                  <FormMessage />
                </FormItem>
              )}
            />
            <Button type="submit">Submit</Button>
          </form>
        </Form>
      </SheetContent>
    </Sheet>
  );
}

export default AssignStudents;

aymanechaaba1 avatar Nov 17 '23 11:11 aymanechaaba1

May be worth checking what the first value passed for 'defaultStudents' is defaults is cached on first render, caught me out also, fixed by firing a useEffect that triggers form.reset

eg:

  useEffect(() => {
    form.reset();
  }, [defaultStudents]);

aronedwards91 avatar Nov 18 '23 10:11 aronedwards91

@aronedwards91 When I write hard coded default values, it works, but with dynamic data, doesn't work

aymanechaaba1 avatar Nov 18 '23 11:11 aymanechaaba1

Does sound like a cached value could be an issue, maybe provide .reset() with the new 'defaultValues' ? https://react-hook-form.com/docs/useform/reset

aronedwards91 avatar Nov 18 '23 11:11 aronedwards91

@aronedwards91 which data is cached?

aymanechaaba1 avatar Nov 18 '23 11:11 aymanechaaba1

Possibly the initial defaultValues see this thread https://github.com/shadcn-ui/ui/issues/1361

aronedwards91 avatar Nov 19 '23 09:11 aronedwards91

@aronedwards91 they're talking about the select component, I'm dealing with a checkbox

aymanechaaba1 avatar Nov 19 '23 11:11 aymanechaaba1

I have ran into a checkbox issue like this before but on a different UI library (Chakra), and I am not sure if it was specific to the UI library. In my case the checkbox was being controlled by a some condition based on user interactions somewhere else on the site - so not through a user manually clicking the checkbox.

My hack was to set the key of the checkbox to the toString() of the dynamic true or false value. Likely not the best/performant solution, but it may work for you!

rexfordessilfie avatar Nov 20 '23 00:11 rexfordessilfie

I'm facing the same problem. The input is of type text. The problem disappear when I provide a default Values, but it's very awful in a big form.

@aymanechaaba1 how did you deal with it?

maykon-oliveira avatar Nov 29 '23 22:11 maykon-oliveira

I got this problem, too.

jackypan1989 avatar Dec 11 '23 08:12 jackypan1989

Is this problem still going on? I think this is critical issue.

0Chan-smc avatar Jan 03 '24 09:01 0Chan-smc

@jackypan1989 @0Chan-smc That works for me now

aymanechaaba1 avatar Jan 03 '24 19:01 aymanechaaba1

This issue is related to how you are using react-hook-form, and not the component library.

defaultValues are cached, and need to be manually set if you want to change them, as you can read in their docs

NatanCieplinski avatar Jan 29 '24 09:01 NatanCieplinski

@NatanCieplinski You're right, the issue is a react-hook-form issue, and default values need to be defined.

aymanechaaba1 avatar Jan 29 '24 15:01 aymanechaaba1