Passing value to hidden input from dropdown menu in react - reactjs

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>

Related

How can I collect data by sending the register as props to the child component using React Hook Form?

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

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

How can I dynamically tie my form into Formik, in React functional component

I'm building a React component which loads form data dynamically from an external API call. I'll need to submit it back to another API endpoint after the user completes it.
here it is:
import React, { useEffect, useState } from "react";
import axios from "axios";
import TextField from "#material-ui/core/TextField";
import { useFormik } from "formik";
const FORM = () => {
const [form, setForm] = useState([]);
const [submission, setSubmission] = useState({});
const formik = useFormik({
initialValues: {
email: "",
},
});
useEffect(() => {
(async () => {
const formData = await axios.get("https://apicall.com/fakeendpoint");
setForm(formData.data);
})();
}, []);
return (
<form>
{form.map((value, index) => {
if (value.fieldType === "text") {
return (
<TextField
key={index}
id={value.name}
label={value.label}
/>
);
}
if (value.fieldType === "select") {
return (
<TextField
select={true}
key={index}
id={value.name}
label={value.label}
>
{value.options.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</TextField>
);
}
})}
<button type="submit">Submit</button>
</form>
);
};
export default FORM;
The API call is coming in ok (yeah i now i need some error handle on that) and I am able to populate the fields and get the form on the page. Where I am having trouble (and im newer to Formik so bear with me) is i need to do validation and submission. I do not really know how to handle the values, usually i'd write some type of static code for the variables, test them and then submit.
usually i'd set a field for "name" and one for "email" for example. In this case i can't write those fields in because they come from the API and we have no idea what they are until we get the call response.
My code handles the field creation but i need to wire for validation and submission and want to use Formik.
How can i accomplish a dynamic form (thru formik) which is wired for validation and submission?
I had the same problem, and I solved it with <Formik> component instead of useFormik() hook. not sure why useFormik fails on dynamic validation, but anyway, after switching to <Formik>, below is the code which worked for me. please check key points after the below code snippet as well.
<Formik
innerRef={formikRef}
initialValues={request || emptyRequest //Mostafa: edit or new: if request state is avaialble then use it otherwise use emptyRequest.
}
enableReinitialize="true"
onSubmit={(values) => {
saveRequest(values);
}}
validate={(data) => {
let errors = {};
if (!data.categoryId) {
errors.categoryId = 'Request Category is required.';
}
//Mostafa: Dynamic validation => as the component is re-rendered, the validation is set again after request category is changed. React is interesting and a lot of repetitive work is done.
if (requestCategoryFields)
requestCategoryFields.map(rcField => {
if (rcField.fieldMandatory) { //1- check Mandatory
if (!data.dynamicAttrsMap[rcField.fieldPropertyName]) {
if (!errors.dynamicAttrsMap) //Mostafa: Need to initialize the object in errors, otherwise Formik will not work properly.
errors.dynamicAttrsMap = {}
errors.dynamicAttrsMap[rcField.fieldPropertyName] = rcField.fieldLabel + ' is required.';
}
}
if (rcField.validationRegex) { //2- check Regex
if (data.dynamicAttrsMap[rcField.fieldPropertyName]) {
var regex = new RegExp(rcField.validationRegex); //Using RegExp object for dynamic regex patterns.
if (!regex.test(data.dynamicAttrsMap[rcField.fieldPropertyName])) {
if (!errors.dynamicAttrsMap) //Mostafa: Need to initialize the object in errors, otherwise Formik will not work properly.
errors.dynamicAttrsMap = {}
if (errors.dynamicAttrsMap[rcField.fieldPropertyName]) //add an Line Feed if another errors line already exists, just for a readable multi-line message.
errors.dynamicAttrsMap[rcField.fieldPropertyName] += '\n';
errors.dynamicAttrsMap[rcField.fieldPropertyName] = rcField.validationFailedMessage; //Regex validaiton error and help is supposed to be already provided in Field defintion by admin user.
}
}
}
});
return errors;
}}>
{({errors,handleBlur,handleChange,handleSubmit,resetForm,setFieldValue,isSubmitting,isValid,dirty,touched,values
}) => (
<form autoComplete="off" noValidate onSubmit={handleSubmit} className="card">
<div className="grid p-fluid mt-2">
<div className="field col-12 lg:col-6 md:col-6">
<span className="p-float-label p-input-icon-right">
<Dropdown name="categoryId" id="categoryId" value={values.categoryId} onChange={e => { handleRequestCategoryChange(e, handleChange, setFieldValue) }}
filter showClear filterBy="name"
className={classNames({ 'p-invalid': isFormFieldValid(touched, errors, 'categoryId') })}
itemTemplate={requestCategoryItemTemplate} valueTemplate={selectedRequestCategoryValueTemplate}
options={requestCategories} optionLabel="name" optionValue="id" />
<label htmlFor="categoryId" className={classNames({ 'p-error': isFormFieldValid(touched, errors, 'categoryId') })}>Request Category*</label>
</span>
{getFormErrorMessage(touched, errors, 'siteId')}
</div>
{
//Here goes the dynamic fields
requestCategoryFields && requestCategoryFields.map(rcField => {
return (
<DynamicField field={rcField} values={values} touched={touched} errors={errors} handleBlur={handleBlur}
handleChange={handleChange} isFormFieldValid={isFormFieldValid} getFormErrorMessage={getFormErrorMessage}
crudService={qaCrudService} toast={toast} />
)
})
}
</div>
<div className="p-dialog-footer">
<Button type="button" label="Cancel" icon="pi pi-times" className="p-button p-component p-button-text"
onClick={() => {
resetForm();
hideDialog();
}} />
<Button type="submit" label="Save" icon="pi pi-check" className="p-button p-component p-button-text" />
</div>
</form>
)}
Key points:
My dynamic fields also come from a remote API, by selecting the categoryId DropDown, and are set into the requestCategoryFields state.
I store the dynamic fields' values in a nested object called dynamicAttrsMap inside my main object which is called request as I have static fields as well.
I used validate props which includes one static validation for categoryId field, and then a loop (map) that adds dynamic validation for all other dynamic fields. this strategy works as with every state change, your component is re-rendered and your validation is created from scratch.
I have two kinds of validation, one checks the existence of value for mandatory fields. And the other checks a regex validation for the contents of the dynamic fields if needed.
I moved the rendering of dynamic fields to a separate component <DynamicField> for better modularization. please check my DynamicField component
you can move the validation props to a const for better coding; here my code is a bit messy.
I used two const, for better coding, for error messages and CSS as below:
const isFormFieldValid = (touched, errors, name) => { return Boolean(getIn(touched, name) && getIn(errors, name)) };
const getFormErrorMessage = (touched, errors, name) => {
return isFormFieldValid(touched, errors, name) && <small className="p-error">{getIn(errors, name)}</small>;
};
please check out my complete component at my GitHub Here for better understanding. and comment for any clarification if you need. because I know how tricky Formik can sometimes be. we have to help out each other.
Adding enableReinitialize to formik
useFormik({
initialValues: initialData,
enableReinitialize: true,
onSubmit: values => {
// Do something
}
});
FORMIK Doc

react-hook-form Controller with react-draft-wysiwyg

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

Reset selected values from react-select within react-hook-form on submit?

I'm in the process of replacing some select inputs in my app to use react-select. Prior to using react-select, I was clearing the selected options in the form on submit using the .reset() function, which does not appear to cause any effect now.
I tried to store a variable in the onSubmit function with null as the value to be set to the defaultValue prop in the Controller thinking it would reset things. However that didn't work. What would be a clean way to handle this?
Parent form component:
function AddActivityForm(props) {
const { register, handleSubmit, control, watch, errors } = useForm();
const onSubmit = (data) => {
//Additional logic here
document.getElementById("activity-entry-form").reset();
};
return (
<Form id="activity-entry-form" onSubmit={handleSubmit(onSubmit)}>
<ActivityPredecessorInput
activities={props.activities}
formCont={control}
/>
<Button variant="primary" type="submit" className="mb-2">
Submit
</Button>
</Form>
);
}
export default AddActivityForm;
Child using Select from react-select:
function ActivityPredecessorInput(props) {
const activities = props.activities;
const options = activities.map((activity, index) => ({
key: index,
value: activity.itemname,
label: activity.itemname,
}));
return (
<Form.Group controlId="" className="mx-2">
<Form.Label>Activities Before</Form.Label>
<Controller
as={
<Select
isMulti
options={options}
className="basic-multi-select"
classNamePrefix="select"
/>
}
control={props.formCont}
name="activitiesbefore"
defaultValue={""}
/>
</Form.Group>
);
}
export default ActivityPredecessorInput;
Answering my own question based on the example provided by #Bill in the comments in the original post. React-hook-form provides a reset method that takes care of the situation pretty simply.
Create a const to store default values (stored in the parent component in this case).
const defaultValues = {
activitiesbefore: "",
};
Include reset method in the useForm const.
const { register, handleSubmit, reset, control, watch, errors } = useForm();
Call the reset method in the onSubmit function passing defaultValues.
reset(defaultValues);
You can pass the onClick
onClick={()=> setValue("activitiesbefore", "")}
parameter with the setValue value from useForm
const { register, handleSubmit, errors, reset, control, setValue} = useForm();
to the reset button, or to the submit button

Resources