backend_extra_exercise_start
  1. formDatePicker.tsx
import type {FunctionComponent, InputHTMLAttributes} from 'react'
import {useId, useState} from 'react'
import {CalendarIcon} from 'lucide-react'
import {Popover, PopoverContent, PopoverTrigger} from '@/components/ui/popover'
import {Button} from '@/components/ui/button'
import {format} from 'date-fns'
import {Calendar} from '@/components/ui/calendar'
import {cn} from '@/lib/utils'
import {Controller, useFormContext} from 'react-hook-form'
import {Label} from '@/components/ui/label'
import FormError from '@/components/form/formError'

interface DatePickerProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'defaultValue'> {
  name: string
  label: string
  defaultValue?: Date
}

const DatePicker: FunctionComponent<DatePickerProps> = ({defaultValue, label, ...inputProps}) => {
  const form = useFormContext()
  const formDefault = form.getValues(inputProps.name) ? new Date(form.getValues(inputProps.name) as string) : undefined
  const [date, setDate] = useState<Date | undefined>(defaultValue ?? formDefault ?? undefined)
  const id = useId()

  return (
    <div className={cn('flex flex-col gap-2 mb-2 grow', inputProps.className)}>
      <input id={id} type="hidden" value={(date ?? new Date()).toISOString()} {...inputProps} onChange={() => {}} />
      <Label htmlFor={id}>{label}</Label>
      <Popover>
        <PopoverTrigger asChild>
          <Button
            variant="outline"
            className={cn('w-full justify-start text-left font-normal', !date && 'text-muted-foreground')}>
            <CalendarIcon className="mr-2 h-4 w-4" />
            {date ? format(date, 'PPP') : <span>{inputProps.placeholder}</span>}
          </Button>
        </PopoverTrigger>
        <PopoverContent className="w-auto p-0">
          {/**
           * De eerste keer dat het formulier gesubmit wordt, controleert Hook Form (via Zod) of er validatiefouten
           * zijn. Als dit het geval is, wordt de input een controlled component in plaats van een uncontrolled
           * component. Zo kan hookform validatiefouten verwijderen terwijl de gebruiker aan het typen is.
           * Dit werkt echter enkel als we de onChange handler van Hook Form gebruiken.
           * Deze wordt dan automatisch toegevoegd via de register functie.
           * Omdat we hier met een complexe component zitten, wordt die onChange functie echter nooit uitgevoerd.
           *
           * Een value change triggert de onChange niet als deze vanuit React gebeurd in plaats van via de browser,
           * bovenstaande input zal dus nooit een onChange triggeren.
           *
           * Om dit probleem op te lossen, gebruiken we de Controller uit Hook Form, dit is een wrapper die rond
           * complexere input elementen gezet kan worden om deze toch te koppelen aan Hook Form.
           */}
          <Controller
            name={inputProps.name}
            render={({field}) => (
              <Calendar
                mode="single"
                selected={field.value ? new Date(field.value as string) : undefined}
                onSelect={newDate => {
                  setDate(newDate)
                  field.onChange(date ? new Date().toISOString() : newDate?.toISOString())
                }}
                autoFocus
              />
            )}
          />
        </PopoverContent>
      </Popover>
      <FormError path={inputProps.name} />
    </div>
  )
}

export default DatePicker