I'm trying to make a form with two fields using react hook form where the required value of the text field depends on the value of the select drop down.
Here is my code:
const { handleSubmit, control, errors } = useForm();
const [isPickupPoint, togglePickupPoint] = useState(false);
const handleDestinationTypeChange: EventFunction = ([selected]) => {
togglePickupPoint(selected.value === "PICKUP_POINT");
return selected;
};
<Grid item xs={6}>
<InputLabel>Destination type</InputLabel>
<Controller
as={Select}
name="destinationType"
control={control}
options={[
{ label: "Pickup point", value: "PICKUP_POINT" },
{ label: "Shop", value: "SHOP" },
]}
rules={{ required: true }}
onChange={handleDestinationTypeChange}
/>
{errors.destinationType && (
<ErrorLabel>This field is required</ErrorLabel>
)}
</Grid>
<Grid item xs={6}>
<Controller
as={
<TextField
label="Pickup Point ID"
fullWidth={true}
disabled={!isPickupPoint}
/>
}
control={control}
name="pickupPointId"
rules={{ required: isPickupPoint }}
/>
{errors.pickupPointId && (
<ErrorLabel>This field is required</ErrorLabel>
)}
</Grid>
<Grid item xs={12}>
<Button
onClick={onSubmit}
variant={"contained"}
color={"primary"}
type="submit"
>
Save
</Button>
</Grid>
The isPickupPoint flag changes properly because the disabled prop of the textfield works fine. Only when the PICKUP_POINT option is selected the text field is active. But the required prop is not working, it is always false. When I try submitting the form when its empty the destinationType error label appears, but when I try to submit the form with the PICKUP_POINT option and empty pickupPointId field it passes with no errors.
How can I make this dynamic required prop work?
Based on the code here, it looks like isPickUpPoint is working as expected since it works for disable. Since you are using the same property for required, it should flow through. I would suspect that the bug may lie in your Controller component. I would take a look there and make sure that the property is what you expected to be.
Also for disabled the condition is !isPickUpPoint, so it will trigger when it's false.
For required the condition is isPickUpPoint so it will trigger when it's true.
That's also a bit of a disconnect since it looks like it's the same input.
Related
I have a short code below, the date picker works fine, it changes when I select other dates.
But i can't figure it out why it always fail in required validation even I already select a date.
<Controller
control={control}
name="disclosureDate"
rules={{ required: 'This field is required' }}
errors={errors.disclosureDate}
value={disclosureDate}
render={({ field }) => (
<InputDate
className="mb-px-8"
onChange={(value) => setDisclosureDate(value)}
value={disclosureDate}
/>
)}
/>
I've been following of these links, but I can't make it work in my end.
react-hook-link-Controllers
stackoverflow-link
(by the way, there are other validations aside from required, for demonstration purposes only)
I don't think you are using the Controller correctly. You should use the value and onChange it provides in the render prop, try -
<Controller
control={control}
name="disclosureDate"
rules={{ required: 'This field is required' }}
errors={errors.disclosureDate}
value={disclosureDate}
render={({ value, onChange }) => (
<InputDate
className="mb-px-8"
onChange={onChange}
value={value}
/>
)}
/>
I have a Formik form where I take user input input & run a query after submitting.
When I click on the search button, a list is rendered. All list items have a button of their own. When I click on the green add button (from the list items) for the first time, the button does not work. Console log's content is not printed. Instead, the onBlur event of the inputField is triggered. However, if I click on the + button again, then it works and prints no. This problem is visible in simulators/phones, not on the web mode on the sandbox.
export const AddFriendScreen: React.FunctionComponent = () => {
const initialValues: FormValues = {
input: '',
};
const [showFlatList, setShowFlatList] = useState<UsersLazyQueryHookResult>(
'',
);
const handleSubmitForm = (
values: FormValues,
helpers: FormikHelpers<FormValues>,
) => {
loadUsers({
variables: {
where: {
OR: [
{ phoneNumber: newPhoneNumber },],
},
},
});
helpers.resetForm();
};
return (
<SafeAreaView style={styles.safeAreaViewContainer}>
<View style={styles.searchTopContainer}>
<View style={styles.formContainer}>
<Formik
initialValues={initialValues}
onSubmit={handleSubmitForm}
validationSchema={validationSchema}>
{({ handleChange, handleBlur, handleSubmit, values }) => (
<View style={styles.searchFieldContainer}>
<View style={styles.form}>
<FieldInput
handleChange={handleChange}
handleBlur={handleBlur}
value={values.input}
fieldType="input"
icon="user"
placeholderText="E-Mail oder Telefonnummer oder Name"
/>
<ErrorMessage
name="input"
render={(msg) => <ErrorText errorMessage={msg} />}
/>
</View>
<View style={styles.buttonContainer}>
<ActionButton buttonText="Suchen" onPress={handleSubmit} />
</View>
</View>
)}
</Formik>
</View>
<View style={styles.listHolder}>
{data && showFlatList !== null && (
<UsersFoundList
data={data}/>
)}
</View>
</View>
</SafeAreaView>
);
};
Snack Expo:
https://snack.expo.io/#nhammad/jealous-beef-jerky
[![enter image description here][1]][1]
Edit:
Found a workaround but still open to easier solutions. Still haven't figured out what's causing the issue exactly.
However, this causes the formik error to show up immediately after the form is submitted and data is returned/rendered. For instance, the error suggests that the input field should not be empty.
...
Why is the keyboard interfering in this?
Probably because when you call Keyboard.dismiss() you are actually unfocusing the input, which will trigger the validateOnBlur event.
So when you unfocus a field (blur), it will try to validate (validateOnBlur), and because the field is empty, it will show the validation.
The error should only show up when we are trying to resubmit the form without an input.
If the error should only show when you submit the form, you should pass to the Formik component validateOnBlur={false}. With this, it won't show the error message when you call Keyboard.dismiss(), because it will remove the validation on blur.
But it still validates when the input changes, triggering validateOnChange. You can also pass to the Formik component validateOnChange={false} to disable the validation on change and it will only validate when you submit the form (press the button).
Please notice that validateOnChange and validateOnBlur are true by default.
Edit:
You need to add validateOnBlur={false} but instead of calling Keyboard.dismiss() you need to call fieldRef.current.blur(). But for that to work, you need to use React.forwardRef in your input component.
So you need to create the fieldRef
const fieldRef = useRef();
Pass to the component
<FieldInput
ref={fieldRef} // passing the ref
handleChange={handleChange}
handleBlur={handleBlur}
value={values.input}
fieldType="input"
icon="user"
placeholderText="input"
/>
And you need to wrap around your FieldInput with React.forwardRef and pass the ref to the Input component.
// wrapping the component with React.forwardRef
export const FieldInput: React.FunctionComponent<FieldInputProps> = React.forwardRef(({
handleChange,
handleBlur,
fieldType,
placeholderText,
value,
style,
rounded,
//ref,
}, ref) => {
return (
<Item rounded={rounded} style={[styles.inputItem, style]}>
<Input
ref={ref} // passing the ref
autoFocus={true}
autoCapitalize="none"
style={styles.inputField}
placeholder={placeholderText}
keyboardType="default"
onChangeText={handleChange(fieldType)}
onBlur={handleChange(fieldType)}
value={value}
/>
</Item>
);
});
And now in the handleSubmitForm you call fieldRef.current.blur()
const handleSubmitForm = (
values: FormValues,
helpers: FormikHelpers<FormValues>,
) => {
// other logic from your submit
if (fieldRef && fieldRef.current)
fieldRef.current.blur();
};
With this, you will solve the problems of your question and from the comments.
Working example
Explanation about useRef
We need to use the useRef hook so we can get the element of the Input component to be able to call the blur function so don't be focused in the input and can click in the plus button.
We need to use the useRef hook because that is how you create a ref in functional components.
Could you please tell why my required check is not working in autocomplete .I am using material UI with react hook form.
Step to reproduce
Click Submit button it show field is required.
then select any element from list.
remove the selected element Then click again submit button.It should
show “required” field check.but it is not showing anything why ??
Here is my code
https://codesandbox.io/s/mui-autocomplete-with-react-hook-form-0wvpq
<Controller
as={
<Autocomplete
id="country-select-demo"
multiple
style={{ width: 300 }}
options={countries}
classes={{
option: classes.option
}}
autoHighlight
getOptionLabel={option => option.label}
renderOption={(option, { selected }) => (
<React.Fragment>
<Checkbox
icon={icon}
checkedIcon={checkedIcon}
style={{ marginRight: 8 }}
checked={selected}
/>
{option.label} ({option.code}) +{option.phone}
</React.Fragment>
)}
renderInput={params => (
<TextField
{...params}
label="Choose a country"
variant="outlined"
fullWidth
name="country"
inputRef={register({ required: true })}
// required
error={errors["country"] ? true : false}
inputProps={{
...params.inputProps,
autoComplete: "disabled" // disable autocomplete and autofill
}}
/>
)}
/>
}
onChange={([event, data]) => {
return data;
}}
name="country"
control={control}
/>
When the form loads initially, the value of your form is an empty object -
{}
When you select a country (say, 'Andorra') the value of your form becomes:
{"country":[{"code":"AD","label":"Andorra","phone":"376"}]}
And then when you deselect the country, the value of your form becomes:
{"country":[]}
An empty array technically meets the "required" criteria (it's not null, after all) so your required handler doesn't fire.
You can verify this is happening by showing the value of the form in your App class -
const { control, handleSubmit, errors, register, getValues } = useForm({});
return (
<form noValidate onSubmit={handleSubmit(data => console.log(data))}>
<Countries control={control} errors={errors} register={register} />
<Button variant="contained" color="primary" type="submit">
Submit
</Button>
<code>{JSON.stringify(getValues())}</code>
</form>
);
The simple fix is to NOT return an empty array as a value from your control - update your onChange handler as follows -
onChange={([event, data]) => {
return data && data.length ? data : undefined;
}}
I'm using a RadioGroup component to display a dynamic list of Radio options using FormControlLabel. The dynamic radio options are successfully getting displayed and I'm able to retrieve the selected radio option using onChange in RadioGroup. However, when I check a radio option, that particular option does not appear "checked".
Here's the code I'm using:
export default class Book extends Component {
state = {
slot: null,
};
...
onChange = (e) => {
this.setState({ [e.target.name]: e.target.value });
};
...
<FormControl component="fieldset" className={classes.formControl}>
<FormLabel component="legend">Select availability slot</FormLabel>
<RadioGroup
aria-label="Slot"
name="slot"
className={classes.group}
value={this.state.slot}
onChange={this.onChange}
>
{this.props.experience.availability !== null ? (
this.props.experience.availability.map(single => (
<FormControlLabel
key={single.id}
value={single.id}
control={<Radio color="primary" />}
label={single.title}
/>
))
) : ""}
// Some manual options in addition to the above dynamic list
<FormControlLabel
value="female"
control={<Radio color="primary" />}
label="Female"
/>
<FormControlLabel
value="male"
control={<Radio color="primary" />}
label="Male"
/>
</RadioGroup>
</FormControl>
...
}
this.props.experience.availability is an array of objects that I'm getting from a call I'm making to the backend. The call is being made in componentDidMount(). I use Redux, which makes the result available as a prop.
Now, if I manually add a few FormControlLabel components in the same RadioGroup, I can see that it's checked after selecting that option.
I've taken two screenshots - this is when the manual FormControlLabel is selected: https://ibb.co/pL5Fy6L and this is when one of my dynamic option is selected: https://ibb.co/Jq7mLN4
You can see that in the second one, the "Female" option gets unchecked but the option that I selected (20-06-2019) does not appear to be checked.
Can you please help me fix this?
Thanks in advance!
For anybody who might be facing the same issue, here's what I was doing wrong.
The problem was that I was passing an integer value to the value prop instead of a string like: value={single.id} so I added toString() to convert it to string like this: value={single.id.toString()} and it's working fine now.
I'm using Formik for validation in a React app.
Validation is working correctly, but my onChange handler does not fire:
<Field
type="text"
name="name"
placeholder="First Name"
component={Input}
onChange={() => console.log("gfdg")}
/>
Link to Sandbox
Why is this?
Inside Input, the way you have ordered the props passed to your input element means your onChange is being overwritten by Formik's onChange. When you create a Field with a custom component (i.e. Input in your case), Formik passes its FieldProps to the component. FieldProps contains a property field that contains various handlers including onChange.
In your Input component you do this (I've removed the irrelevant props):
<input
onChange={onChange}
{...field}
/>
See how your own onChange will just get replaced by Formik's onChange() inside field? To make it clearer ...field is basically causing this to happen:
<input
onChange={onChange}
onChange={field.onChange}
// Other props inside "field".
/>
If you were to reorder those the console message will now appear:
<input
{...field}
onChange={onChange}
/>
However now your input won't work now because you do need to call Formik's onChange to let Formik now when your input changes. If you want both a custom onChange event and for your input to work properly you can do it like this:
import React from "react";
import { color, scale } from "./variables";
const Input = React.forwardRef(
({ onChange, onKeyPress, placeholder, type, label, field, form }, ref) => (
<div style={{ display: "flex", flexDirection: "column" }}>
{label && (
<label style={{ fontWeight: 700, marginBottom: `${scale.s2}rem` }}>
{label}
</label>
)}
<input
{...field}
ref={ref}
style={{
borderRadius: `${scale.s1}rem`,
border: `1px solid ${color.lightGrey}`,
padding: `${scale.s3}rem`,
marginBottom: `${scale.s3}rem`
}}
onChange={changeEvent => {
form.setFieldValue(field.name, changeEvent.target.value);
onChange(changeEvent.target.value);
}}
onKeyPress={onKeyPress}
placeholder={placeholder ? placeholder : "Type something..."}
type={type ? type : "text"}
/>
</div>
)
);
export default Input;
See it here in action.
Although overall I'm not really sure what you're trying to do. Your form is working fine, you probably don't need a custom onChange but maybe you have some specific use case.
Let me first make it clear this answer is just for help purpose and I do know that this question has been accepted but I do have some modification for above answer with my version if the above solution doesn't work for anyone
Here onChangeText will return the value of the quantity field
<Formik
initialValues={{ product_id: '', quantity: '', amount: '' }}
onSubmit={(values, actions) => {
this.submitLogin(values);
}}
//some other elements ....
<Field placeholder='No. of Quantity' name='quantity' component={CustomInputComponent}
onChangeText={value => {
console.warn(value); // will get value of quantity
}}
/>
/>
Outside of your class you can define your component
const CustomInputComponent = ({
onChangeText,
field, // { name, value, onChange, onBlur }
form: { touched, errors, isValid, handleBlur, handleChange, values, setFieldValue }, // also values, setXXXX, handleXXXX, dirty, isValid, status, etc.
...props
}) => {
return (
<Input {...field} {...props} onBlur={handleBlur(field.name)}
onChangeText={value => {
setFieldValue(field.name, value);
onChangeText(value); // calling custom onChangeText
}}
/>
)
}