How to use RadixUI with React Hook Form's Controller - reactjs

I'm unable to use Radix UI's select component with React Hook Form's Controller and don't understand why the following doesn't work in this CodeSandbox. Any ideas?
For example, the Radix UI documentation shows passing value and onChange props to <SelectPrimitive.Root> as well as the ref to <SelectPrimitive.Trigger>
export const Select = React.forwardRef(
({ children, ...props }, forwardedRef) => {
return (
<SelectPrimitive.Root {...props}>
<SelectPrimitive.Trigger ref={forwardedRef}>
<SelectPrimitive.Value />
...
</SelectPrimitive.Root>
How I implemented it:
const SelectItem = forwardRef(({ value, onValueChange }, forwardedRef) => {
return (
<Select.Root value={value} onValueChange={onValueChange}>
<Select.Trigger
ref={forwardedRef}
className="text-md border-solid border-2 border-slate-500 px-2"
>
...
</Select.Root>
How I pass value and onChange in WrapperSelect using RHF's Controller
<Controller
control={control}
name={name}
render={({ field: { onChange, value, ref, ...props } }) => (
<SelectItem
valueOnChange={onChange}
value={value}
forwardedRef={ref}
/>
)}
/>

I had similar problem, also with Radio button and I found out that you need to use custom <Select.Value> component with asChild prop
<Select.Value asChild>
<span>{props.value ?? "Select something..."}</span>
</Select.Value>
Here is a working sandbox: https://codesandbox.io/s/admiring-sun-xwupmz?file=/src/atoms/SelectItem.tsx

Related

react hook form's getValues() returning undefined

The Question:
How do I access the form's values using react-hook-form's Controller without using setValue() for each input?
The Problem:
I'm creating my own set of reusable components and am trying to use React Hook Form's Controller to manage state, etc. I'm having trouble accessing an input's value and keep getting undefined. However, it will work if I first use setValue().
CodeSandbox example
return (
<form onSubmit={onSubmit}>
<WrapperInput
name="controllerInput"
label="This input uses Controller:"
type="text"
rules={{ required: "You must enter something" }}
defaultValue=""
/>
<button
type="button"
onClick={() => {
// getValues() works if use following setValue()
const testSetVal = setValue("controllerInput", "Hopper", {
shouldValidate: true,
shouldDirty: true
});
// testGetVal returns undefined if setValue() is removed
const testGetVal = getValues("controllerInput");
console.log(testGetVal);
}}
>
GET VALUES
</button>
<button type="submit">Submit</button>
</form>
);
More info:
I don't understand why getValues() is returning undefined. When I view the control object in React dev tools I can see the value is saved. I get undefined on form submission too.
My general approach here is to use an atomic Input.tsx component that will handle an input styling. Then I use a WrapperInput.tsx to turn it into a controlled input using react-hook-fom.
Lift the control to the parent instead and pass it to the reusable component as prop.
// RegistrationForm.tsx
...
const {
setValue,
getValues,
handleSubmit,
formState: { errors },
control,
} = useForm();
...
return (
...
<WrapperInput
control={control} // pass it here
name="controllerInput"
label="This input uses Controller:"
type="text"
rules={{ required: "You must enter something" }}
defaultValue=""
/>
)
// WrapperInput.tsx
const WrapperInput: FC<InputProps> = ({
name,
rules,
label,
onChange,
control, /* use control from parent instead */
defaultValue,
...props
}) => {
return (
<div>
<Controller
control={control}
name={name}
defaultValue={defaultValue}
render={({ field }) => <Input label={label} {...field} />}
/>
</div>
);
};
Codesandbox

React.forwardRef warning but no ref passed or how to pass custom component to another component

I want to pass a custom input proponent to another proponent this way:
<FormTextField
name="startTime"
label="startTime"
type="time"
error={!!errors.startTime}
CustomTextField={TextFieldColorTimeIcon} <-- custom TextField
...
/>
The code for FormTextField:
export default function FormTextField({ name, CustomTextField, ...other }) {
const { control } = useFormContext();
let LocalCustomTextField = CustomTextField ? CustomTextField : TextField;
return (
<Controller
name={name}
control={control}
render={
({ field, fieldState: { error } }) => (<LocalCustomTextField {...field} fullWidth error={!!error} helperText={error?.message} {...other} />)
}
/>
);
}
And the code for TextFieldColorIcon:
export function TextFieldColorIcon({ sx, ...other }: TextFieldColorIconProps) {
return <TextField
sx={{
...sx,
'& input[type="time"]::-webkit-calendar-picker-indicator': {
filter:
'invert(41%) sepia(24%) saturate(301%) hue-rotate(166deg) brightness(101%) contrast(89%)',
},
}}
{...other} />
}
But I've got warning:
Warning: Function proponents cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?
Check the render method of Controller.
at TextFieldColorIcon
at Controller
at FormTextField
at div
despite of the fact that there is no ref passed.
What is the reason of this warning and am I passing custom TextFieldColorIcon right way?

Function components cannot be given refs. Attempts to access this ref will fail in select component

Here I defined a select component, and I wanted to display it if a condition is true. This select field appears when one of the values ​​of the previous select input is selected. But here is when the new select field (i.e. my select component), and I choose a value from this select, this value is not submitted by my form, and is not present in my data when I do a console log after submitting my form, but the name of the value field is there, but not the value. And i have a warning in my console stating:
Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?
Check the render method of Controller
My Select Component
export const SelectCompanyField = () => {
// const [page, setPage] = useState(1);
// const [perPage, setPerPage] = useState(10);
const { data, error } = useSWR<ServerResponse, any>(companyUrls.base, url => getDataByParams(companyUrls.base));
console.log("Data", data, "Error", error);
console.log(data?.entities);
return (
<Select
showSearch
placeholder="Choisir une compagnie"
optionFilterProp="children"
filterOption={(input, option: any) =>
option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
>
{data?.entities.map(d => (
<option value={d.index} key={d.id} >
{d.name}
</option>
))}
</Select>
);
};
The select component to display if the condition is true
<Col md={24} lg={24} sm={24}>
{firstOptionValue &&
<div className="ant-form-item">
<label className="label">Compagnie <span className="text-danger">*</span></label>
<Controller
as={<SelectCompanyField />}
name="company"
control={control}
defaultValue={""}
rules={{ required: false }}
{errors.company && "Company is required"}
</div>
}
</Col>
The console log of my data submit
{
"firstName": "Atom",
"lastName": "Proton",
"username": "xyz#test.ml",
"password": "00789",
"phoneNumber": "44258569",
"profile": "ADMIN",
"userType": "B2B_CLIENT",
"company": ""
}
The company part with the empty quotes, is the part where it should have the value of the field I chose in my select component.
I would just like the selected value of the field or the option, appear in my data so that I can submit my form.
Thanks
SelectCompanyField needs to be:
export const SelectCompanyField = React.forwardRef(() => {
...
});
When rendering using Controller's as prop, it passes a ref prop to the as prop. React see's a ref being passed, but you aren't using a forwardRef component.
In addition to this, then you need to actually use the ref (and props) which you don't appear to be doing now in SelectCompanyField that are being provided by Controller
The docs will help you out
☝️ Please read the docs, but your SelectCompanyField receives props like this:
export const SelectCompanyField = React.forwardRef(({field,meta}) => {
const { onChange, onBlur, value, ref } = field
return <Select onChange={onChange} value={value} .../>
});
It Is your job to add the onChange handler and the value to the Select component you are rendering. I don't know what Select component it is - is it frame a component framework? is it just a regular select? - so I can't tell you how to do it, but the hook form docs are pretty clear.
For anyone with this problem and a component that you can't use ref (because you haven't created the component can't change it, or it doesn't need a ref prop, or you are using typescript with a generic component and used a different/custom name for the ref prop), you can use the react-hook-mask Controller render prop (https://stackoverflow.com/a/69671594/4850646), instead of as, which allows you to customize which props are passed:
Instead of:
<Controller as={<SelectCompanyField />} ... />
You can use:
<Controller render={({ field: { ref, ...field } }) => <SelectCompanyField {...field} />} ... />

How can we set default value for React RRule Generator in react-admin?

I am using react-admin and react-rrule-generator (https://github.com/Fafruch/react-rrule-generator). Create / Adding records is working fine while using rrule widget. But whenever I try to edit a record, the widget should have its values automatically filled based on the record's values. But the value is always the default one provided by the widget itself. Here is my code:
main_file.jsx
export const JobCreate = (props) => {
return (
<Create {...props}>
<SimpleForm>
<CustomRRuleInput name="recurrency" label="Recurrency" />
</SimpleForm>
</Create>
)
}
recurrency_field.jsx
export const CustomRRuleInput = (props) => {
const {
input: { onChange },
meta: { touched, error },
} = useInput(props)
return (
<Labeled label={props.label}>
<RRuleGenerator
onChange={onChange}
name={props.name}
/>
</Labeled>
)
}
If I add value={props.record.recurrency} in RRuleGenerator component, I can't change values because I kind of fixed / hardcoded its value which is constant even if I try to change them. If this widget had a prop called defaultValue then it would have worked out!
How can I achieve this?
If you check closely the documentation's Inputs/Writing your own input part you will notice that custom input compoenents using either useField or useInput hooks still receive the source prop which is passed inside the input as part of the hook parameters.
Try this:
Inside main_file.jsx
<CustomRRuleInput source="recurrency" label="Recurrency" />
Inside recurrency_field.jsx
const {
input: { name, onChange },
meta: { touched, error },
} = useInput(props)
return (
<Labeled label={props.label}>
<RRuleGenerator
onChange={onChange}
name={name}
/>
</Labeled>
)
Never mind I did it! I can use this for creation as well as updating records. I also used rrule library for converting rrule to human readable text which gets displayed in TextInput field just below RRule widget. The text dynamically changes when you change data in RRule widget.
recurrency_field.jsx
import RRuleGenerator from "react-rrule-generator"
import React, { useState } from "react"
import { useInput, Labeled, TextInput } from "react-admin"
import { rrulestr } from "rrule"
export const CustomRRuleInput = (props) => {
const record = props.record
const {
input: { onChange },
} = useInput(props)
const [state, setState] = useState(record[props.name])
return (
<>
<Labeled label={props.label}>
<RRuleGenerator
onChange={(val) => {
setState(val)
onChange(val)
}}
value={state}
name={props.name}
/>
</Labeled>
<TextInput
fullWidth
disabled
label={"RRule Text"}
value={state ? rrulestr(state).toText() : ""}
/>
</>
)
}
main_file.jsx
<CustomRRuleInput name="recurrency" label="Recurrency(r rule)" />

How to use custom Input with Formik in React?

I'm trying to use DatePicker within Formik. But when I click DatePicker's date its form value is not changed. Instead, I got this error:
Uncaught TypeError: e.persist is not a function
at Formik._this.handleChange (formik.es6.js:5960)
I shortify code, the code is below
const SomeComponent = () => (
<Formik
render={({
values,
handleSubmit,
handleChange,
setFieldValue
}) => {
return (
<div>
<form onSubmit={handleSubmit}>
<DatePicker
name={'joinedAt'}
value={values['joinedAt']}
onChange={handleChange}
/>
</form>
</div>
)
}}
/>
)
I googled few documents, https://github.com/jaredpalmer/formik/issues/187 and https://github.com/jaredpalmer/formik/issues/86
So I tried like below, but it's not working.
...setFieldValue
<DatePicker
name={'joinedAt'}
value={values['joinedAt']}
onChange={setFieldValue}
/>
I resolve this like
<DatePicker
name={'joinedAt'}
value={values['joinedAt']}
onChange={e => setFieldValue('joinedAt', e)}
/>
Update on 2020-03-08:
You can use e.target.value on setFieldValue's second prop, depends on your custom input design. Thanks to Brandon.
If you have deeper nesting, you should use Formik Field.
Example:
<Formik
validationSchema={schema}
initialValues={initialValues}
onSubmit={(values, actions) => {}}
>
<Field name="colors" component={ColorsEditor} colors={colorArray}/>
</Formik>
const ColorsEditor = ({ field, colors, form, ...props }) => {
return (
<div>
<Button onClick={() => form.setFieldValue('colors', "myValue")}>
</Button>
</div>
)
}
So the Field component already include the form, where live the setFieldValue that you can use where you need. It also include the errors and needed fields for additional customization.
For html primitive input fields handleChange works like a charm, but for custom components, you've to use setFieldValue to change the value imperatively.
onChange={e => setFieldValue('joinedAt', e)}
The accepted answer didn't work for me and didn't show the selected date. It has been almost a year so there might be some changes with the implementation. Though this can be fix by using toDateString() like this
<DatePicker
name={'joinedAt'}
value={values['joinedAt']}
onChange={e => setFieldValue('joinedAt', e.toDateString())}
/>
The Formik solution for this is the useField() method. You may need to create a wrapper for components like DatePicker that you may not have source access to.
The documentation provides an example:
const MyTextField = ({ label, ...props }) => {
const [field, meta, helpers] = useField(props);
return (
<>
<label>
{label}
<input {...field} {...props} />
</label>
{meta.touched && meta.error ? (
<div className="error">{meta.error}</div>
) : null}
</>
);
};
In short, the method takes in the custom props which describe the field. Then you can expand the assigned field value and props in your custom field.
https://github.com/jaredpalmer/formik/issues/692
you need to set value manually if you are using setFieldValue method

Resources