I need some help. I was using react-final-form in my project but I've decided to change for react-hook-form. By the way, I love it, but I got stuck. :/
On my page, I'm using an editor to collect some info from a user. At the moment I'm using react-draft-wysiwyg.
Here the code:
parentComponent.js
/**
* react-hook-form library
*/
const {
register,
handleSubmit,
control
} = useForm({
mode: 'onChange'
});
<form onSubmit={handleSubmit(onSubmit)}>
<Controller
as={<WYSIWYGEditor />}
name='editor'
control={control}
onChange={([ event ]) => event.target.value}
/>
</form>
WYSIWYGEditor.js
const WYSIWYGEditor = ({ input }) => {
const [ editorState, setEditorState ] = useState(EditorState.createEmpty());
const onEditorStateChange = editorState => {
setEditorState(editorState);
};
return (
<React.Fragment>
<div className={classes.root}>
<Editor
editorState={editorState}
wrapperClassName='wrapper-class'
editorClassName='editor-class'
onEditorStateChange={onEditorStateChange}
/>
</div>
</React.Fragment>
);
};
export default WYSIWYGEditor;
PLEASE NOTE:
The input prop comes from the coding with react-final-form. The input was passing the characters I was typing. So, if I leave input as props it fails because it doesn't exist. React-hook-form doesn't pass an input.
I've changed that with props:
const WYSIWYGEditor = props=> {
console.log(props)
and I get the following in the console.log when I type anything:
{name: "editor", value: undefined, onChange: ƒ}
As you can see, value is undefined. How can I structure the Controller in order to pass a value each time I type something in the editor?
Thanks for your help
I found a solution.
value is undefined because obviously on component load there is nothin' to load. If you don't want to see undefined just pass defaultValue='' from the controller:
<Controller
as={<WYSIWYGEditor />}
name='editor'
control={control}
onChange={([ event ]) => event.target.value}
defaultValue='' <== here
/>
Now, the issue that doesn't allow to return any typed value, is because I have declared the onChange from the controller. So, the right code would be:
<Controller
as={<WYSIWYGEditor />}
name='editor'
control={control}
defaultValue=''
/>
Also, in the WYSIWYGEditor.js file, you need to replace what before was input with props. The reason is that they are passing exactly the same Object, which contains a value, onChange, and onBlur function.
Here a code sandbox with the working code:
https://codesandbox.io/s/trusting-mountain-k24ys?file=/src/App.js
Related
Using React Hook Form, when I want to collect data by sending register as props to child component to take input value from child component, it shows 'register is not a function' error.
How can I solve this?
const { register, formState: { errors }, handleSubmit } = useForm();
const onSubmit = (data) => console.log(data);
<form onSubmit={handleSubmit(onSubmit)}>
<fieldset>
<legend className='text-[#666666]' >Status</legend>
{
statusData.map(status => <CheckboxFilter register={register} key={status._id} status={status}/>)
}
</fieldset>
</form>
here child
//CheckboxFilter component
const CheckboxFilter = ({ status, register }) => {
return (
<>
<p className='text-[#858585] mt-2 text-[14px]' >
<label htmlFor={status?.name} className='cursor-pointer' >
<input {...register("checkBoxData")} type="checkbox" name="status" id={status?.name} value={"status?.name"} /> {status?.name}
</label>
</p>
</>
);
};
I created a sandbox here codesandbox and it works perfectly.
I took your code and only changed the CheckboxFilter component:
Removed the name property (register function returns the name of the input based in the string you pass to it as a parameter, you should not override it)
Removed the value property (that was making the value of the checkbox constant, because there wasn't onChange handler that was modifying it)
Changed ...register("checkBoxData") to ...register(checkBoxData${name}) so this way you can have every checkbox value individually in the form.
Anyway, if you want to have a different behaviour than what I have assumed, let me know and I will help.
-Ado
I have react-select dropdown menu and hidden input which I pass to form when submiting...
using useState hook I created variable which tracks changes to react-select dropdown menu.
Hidden input has this variable as value also. I thought this would be enough.
But when I submit the form, console. log shows me that value of input is empty despite that variable that was selected from dropdown menu is actually updated.
I mean variable that I have chosen console logs some value, but hidden input thinks that it is still empty.
Does it means I have to rerender manually page each time I change that variable so input gets it's new value using useEffect ? Which is bad solution for me, I don't like it, thought it would be done automatically.
Or instead of useState I must create and use variable via Redux ? Which I also don't like, use redux for such small thing fills overcomplicated.
Isn't there any nice elegant solution ? :)
import { useForm } from 'react-hook-form';
const [someVar,setSomeVar]=useState('');
const {
register,
handleSubmit,
formState: { errors },
} = useForm({ mode: 'onBlur' });
const handleFormSubmit = (data) => {
console.error('success');
};
const handleErrors = (errors) => {
console.error(errors);
console.log(document.getElementsByName('hiddenInput')[0].value);
};
const options = {
hiddenInput: {
required: t('hiddenInput is required'),
},
};
.......
<form onSubmit={handleSubmit(handleFormSubmit, handleErrors)}>
<Select
options='...some options list'
onChange={(value) => setSomeVar(value)}
/>
<input
name='hiddenInput'
value={someVar}
{...register('hiddenInput', options.hiddenInput)}
/>
<button>submit</button>
</form>
UPDATED
Its because getElementsByName returns an array of elements.
You probably want
document.getElementsByName('hiddenInput')[0].value
I should add that really you should use a ref attached to the input and not access it via the base DOM API.
const hiddenRef = useRef(null)
// ...
cosnt handleSubmit =(e)=>{
console.log(hiddenRef.current.value);
}
// ...
<input
name='hiddenInput'
value={someVar}
ref={hiddenRef}
/>
However as you are using react-hook-form you need to be interacting with its state store so the library knows the value.
const {
register,
handleSubmit,
formState: { errors },
setValue
} = useForm({ mode: 'onBlur' });
// ...
<form onSubmit={handleSubmit(handleFormSubmit, handleErrors)}>
<Select
options='...some options list'
onChange={(value) => setValue('hiddenInput', value)}
/>
<input
name='hiddenInput'
{...register('hiddenInput', options.hiddenInput)}
/>
<button>submit</button>
</form>
You can remove const [someVar,setSomeVar]=useState('');
However, this hidden input is not really necessary as you mention in comments. You just need to bind the dropdown to react hook form.
// controller is imported from react hook form
<form onSubmit={handleSubmit(handleFormSubmit, handleErrors)}>
<Controller
control={control} // THIS IS FROM useForm return
name="yourDropdown"
rules={{required: true}}
render={({
field: { onChange, value, name, ref }
}) => (
<Select
options={options}
inputRef={ref}
value={options.find(c => c.value === value)}
onChange={val => onChange(val.value)}
/>
)}
/>
<button>submit</button>
</form>
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
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} />} ... />
I get it that Field has an onChange attribute where you can pass the own Formik onChange prop from here: https://jaredpalmer.com/formik/docs/api/formik#handlechange-e-reactchangeevent-any-void.
However, I am struggling to understand where these value[key] is passed, so I can handle the data passed in the form. Found in withFormik(): How to use handleChange that I can pass two callbacks to Formik's onChange prop, but I wonder if there is a better way to handle this.
edit after comments from folks that replied, thanks for that:
My code using these 2 callbacks in the onChange prop in Field:
export default function FormikForm() {
const onSubmitHandler = (formValues, actions) => {
console.log(formValues);
console.log(actions);
};
const onChangeHandler = (e) => {
console.log(e.target.value);
};
return (
<div>
<h1>This is the Formik Form</h1>
<Formik
initialValues={{
name: "",
email: "",
age: ""
}}
onSubmit={onSubmitHandler}
render={props => {
return (
<Form>
<label>
Name
<Field
name="name"
type="text"
placeholder="name"
onChange={e => {props.handleChange(e); onChangeHandler(e)}}
/>
</label>
<button type="submit">Submit</button>
</Form>
);
}}
/>
</div>
);
}
Is there a way to do a similar thing as in onSubmitHandler, where Formik automagically outputs the value of the input without having to call these 2 functions in the onChange?
Thanks
Every field component receives a field prop which has a value prop containing the value for that field, as well as a form prop containing helper methods that you can use to set the value. I'd need to see the structure of your code to give specific suggestions on how to implement what you want, but you can emulate the default functionality by calling form.setFieldValue(field.name, field.value). In addition, the field prop has this handler built in by default in the field.onChange prop.