-
-
Notifications
You must be signed in to change notification settings - Fork 40
Description
i use remix-hook-fom in my project. I struggled trying to use it with React Select. here is my controlled React Select code:
import { useId } from 'react'
import { Controller, FieldError, get } from 'react-hook-form'
import ReactSelect from 'react-select'
import { useRemixFormContext } from 'remix-hook-form'
import { useHydrated } from 'remix-utils/use-hydrated'
import { twMerge } from 'tailwind-merge'
import { CustomOption } from './custom-option'
import { SelectProps } from './type'
export const Select = <T extends Record<string, unknown>>(
props: SelectProps<T>
) => {
const isHydrated = useHydrated()
const {
name,
id,
label,
onChange,
className,
containerClassName,
errorClassName,
disabled,
labelClassName,
required,
isSearchable = false,
size = 'md',
...rest
} = props
const generatedId = useId()
const {
control,
formState: { errors }
} = useRemixFormContext()
const error: FieldError = get(errors, name)
return (
<div
className={twMerge([
'relative flex w-full flex-col gap-1.5',
containerClassName
])}
>
{label && (
<label
htmlFor={id ?? generatedId}
className={twMerge(['text-gray-700', labelClassName])}
>
{label} {required && <span className="text-rose-500">*</span>}
</label>
)}
{isHydrated ? (
<Controller
name={name}
control={control}
render={({ field }) => {
return (
<ReactSelect
instanceId={id ?? generatedId}
components={{ Option: CustomOption }}
onChange={(newValue, actionMeta) => {
if (onChange) {
onChange(newValue, actionMeta)
}
field.onChange(newValue)
}}
value={field.value}
isSearchable={isSearchable}
isDisabled={disabled}
className={className}
{...rest}
/>
)
}}
/>
) : (
<div className="h-8 w-full animate-pulse rounded-md bg-gray-300" />
)}
{error && (
<span
className={twMerge([
errorClassName,
'absolute -bottom-4 text-xs text-rose-500'
])}
>
{error?.message?.toString()}
</span>
)}
</div>
)
}and here is my implementation:
export const createDepositSchema = z.object({
bank: z.object({
label: z.string(),
value: z.string()
})
}) const fetcher = useFetcher()
const formMethods = useRemixForm<TCreateDeposit>({
mode: 'onSubmit',
resolver: zodResolver(createDepositSchema),
stringifyAllValues: false,
fetcher
})
const {
bank: watchedBank,
} = watch()
console.log(watchedBank) // the value is captured here
<RemixFormProvider {...formMethods}>
<fetcher.Form
method="POST"
action="/actions/deposit"
>
<Select<TCreateDeposit>
required
labelClassName="text-white"
label="Bank Transfer"
name="bank"
options={[
{label: 'BNI', value: '1'},
{label: 'BCA', value: '2'},
{label: 'BRI', value: '3'},
{label: 'Mandiri', value: '4'},
]}
/>
</fetcher.Form>
</RemixFormProvider>and here is my action:
import { zodResolver } from '@hookform/resolvers/zod'
import { ActionFunctionArgs } from '@remix-run/node'
import { getValidatedFormData } from 'remix-hook-form'
import { createDepositSchema, TCreateDeposit } from '@/schemas/deposit'
export const action = async ({ request }: ActionFunctionArgs) => {
const {
errors,
data: formData,
receivedValues: defaultValues
} = await getValidatedFormData<TCreateDeposit>(
request,
zodResolver(createDepositSchema),
true
)
if (errors) {
console.log(errors)
return Response.json(
{ success: false, errors, defaultValues },
{ status: 400 }
)
}
console.log(formData)
return null
}when i try to console.log the value in the client, it is there as expected. but once i submit the form, it is not being captured in the action by getValidatedFormData and returns validation error. I try to debug it by adding .optional() to the schema, it turns out that the data is empty. the bank field didn't event sent to the action.
before submitting this issue, i have tried all possible options in. stringifyAllValues as well as in preserveStringified, but nothing happens.
in another remix roject where i use plain useForm with useFormContext from React Hook Form, it works just fine.