duhan
Blog

Inserting Shadcn Slider/DatePicker into react-hook-form

I could not find an example of using slider and date-picker in react-hook-form in Shadcn examples. So they need to be added in a custom way. Let’s start with slider example.

Slider

import { useEffect } from "react";
import { Slider } from "@/components/ui/slider";
type Props = {
  value: number;
  onChange: (value: number) => void;
};
export default function Ratio({ value, onChange }: Props) {
  useEffect(() => {
    if (!value) {
      onChange(0.5);
    }
  }, [onChange, value]);
  return (
      <Slider
        min={0}
        max={1}
        defaultValue={[value ?? 0.5]}
        step={0.01}
        onValueChange={(vals) => onChange(vals[0])}
      />
  );
}

And inside your form file you need to use it like below:

<FormField
control={form.control}
name="slider"
render={({ field, formState: { errors } }) => (
  <FormItem>
    <FormLabel>Slider</FormLabel>
    <FormControl>
      <Ratio
        value={field.value}
        onChange={(value) => {
          form.setValue("slider", value);
        }}
      />
    </FormControl>
  </FormItem>
)}
/>

DatePicker

This one is a little bit more complex. I keep my dateRange in two separate properties in an object.

First, you need to create a custom date picker component. You can copy-paste this from shadcn offical examples. I am going to skip this part. Then you need to create a wrapper component for this date picker.

import { useState } from "react";
import { DateRange } from "react-day-picker";
import DateRangePicker from "@/components/templates/DateRangePicker";

type Props = {
  formValue: {
    startDate: string;
    endDate: string;
  };
  currentGroup: "groupA" | "groupB";
  setValuesDate: (
    group: "groupA" | "groupB",
    value: DateRange | undefined
  ) => void;
};

export default function TestDate({
  formValue,
  currentGroup,
  setValuesDate,
}: Props) {
  const [date, setDate] = useState<DateRange | undefined>({
    from:
      formValue && formValue.startDate
        ? new Date(formValue.startDate)
        : undefined,
    to:
      formValue && formValue.endDate ? new Date(formValue.endDate) : undefined,
  });

  function handleSelectDate(selected: DateRange | undefined) {
    setDate(selected);
    setValuesDate(currentGroup, selected);
  }

  return (
    <DateRangePicker
      date={date}
      setDate={handleSelectDate}
      fromDate={new Date(Date.now())}
      fullWidth
    />
  );
}

And inside your form file you need to use it like below:

<FormField
  control={form.control}
  name={`redirects.groupA.dateRange`}
  render={({ field: { value }, formState: { errors } }) => (
    <FormItem className="w-full sm:flex-1">
      <FormLabel>Group A Date Range</FormLabel>
      <div>
        <TestDate
          formValue={value}
          currentGroup="groupA"
          setValuesDate={setValuesDate}
        />
      </div>
      <Form>
      <FormMessage>
      {/* your error path here */}
      </FormMessage>
    </FormItem>
  )}
/>

Finally, you need to create a function to set the values of the date picker.

const setValuesDate = useCallback((value: DateRange | undefined) => {
      if (!value || !value.from) {
        form.setValue("dateRange", {
          startDate: "1970-01-01",
          endDate: "1970-01-01",
        });
        return;
      }

      const startDateFormatted = format(value.from, "yyyy-MM-dd");
      const endDateFormatted = value.to
        ? format(value.to, "yyyy-MM-dd")
        : startDateFormatted;

      form.setValue("dateRange", {
        startDate: startDateFormatted,
        endDate: endDateFormatted,
      });
    },
    [form]
  );

I am sure there are better ways to implement this. But this is the way I implemented it. I hope it helps you.