Overview
I am using React-Hook-Form to manage validation on my input fields. I am leveraging react bootstrap so I have to make use of the Controller component provided with this library.
Documentation is not clear about how to use rules; just that its that same as register. I am able to get one validation rule working (required). For dropdowns, I always have at least one value selected ("select"), so required does not work, instead I am trying to use regex to see if the selection is this or not.
I have tried many ways to get this work but unfortunately I can't figure this out.
Question
Does any have experience in using React-Hook-Form with Controlled components, specifically dropdowns and regex? I am looking for the syntax on how what they define in register within the rules of the controller component. Only required seems to work
Here is what works (notice the rules prop)
<TextInputField
label="Operation Info"
name="operation_info"
control={control}
rules={{ required: "Operation is required" }}
error={errors?.name}
/>
This does not work (notice the rules prop)
I am trying this on another custom component except it a dropdown.
<Dropdown
label="Ratings"
name="ratings"
control={control}
rules={{ pattern: /^(?!select$).*/ }}
options={ratings}
optionsKey={"rating"}
error={errors?.indicator}
/>
I tried a copy paste of their actual pattern rule but this also does not work. I am able to hook into all errors in my forms but anything using pattern does not register as an error.
This second one does not work. I spread these props on the actual react-bootstrap components in another file. They are all the same, so the fact the text-input works, means the dropdown should work as well but it does not. Only difference I see it the rules I am applying; I must be doing it wrong but there is no documentation.
Here is a sample of the dropdown component:
import React from "react";
import { Form } from "react-bootstrap";
import { Controller } from "react-hook-form";
export const Dropdown = (props) => {
const { error, name, control, label, rules, options, optionsKey } = props;
return (
<Form.Group>
<Form.Label>{label}</Form.Label>
<Controller
name={name}
control={control}
rules={rules}
render={({ field }) => (
<Form.Control
{...field}
as="select"
className={error ? `is-invalid` : null}
>
<option selected disabled className="text-muted" value="select">
select
</option>
{options?.length > 0
? options.map((option, index) => {
return (
<option value={option[optionsKey]} key={index}>
{option[optionsKey]}
</option>
);
})
: null}
</Form.Control>
)}
/>
<Form.Control.Feedback type="invalid">
{error?.message}
</Form.Control.Feedback>
</Form.Group>
);
};
export default Dropdown;
here is the link to the controller documentation
https://react-hook-form.com/api/usecontroller/controller
I did a minimized test setup and if I understood your question to me it looks like the problem is just with your regex. Try
rules={{ required: "req", pattern: /^(?!select)/ }}
Click here for test sandbox and iIn case the sandbox goes away:
import * as React from "react"
import { useForm, Controller } from "react-hook-form"
const Dropdown = ({ name, control, rules, options }) => {
return (
<Controller
name={name}
control={control}
rules={rules}
render={({ field }) => (
<select {...field} rules={rules}>
<option value="select">select</option>
<option value=""></option>
{options.map(x => <option key={x} value={x}>{x}</option>)}
</select>
)}
/>
)
}
export default function App() {
const { handleSubmit, control, formState: {errors} } = useForm()
const onSubmit = (data) => console.log("Submit:", data)
console.log("Errors:", errors?.ratings)
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Dropdown
name="ratings"
control={control}
rules={{ required: "Requir", pattern: /^(?!select)/ }}
options={["potato", "tomato", "select", "greg"]}
/>
<input type="submit" />
{errors && errors.ratings && <p>
Error: {errors.ratings.type} {errors.ratings.message}</p>
}
</form>
)
}
Related
using react hooks form with react google autocomplete. Problem is on first selection, the react google autocomplete does not show the selected value, but just what the user types. Only if you select it the second time then it will say what you select. Subsequent selects are fine.
For example, user types in Disneyland and you select Disneyland Resort, Disneyland Drive, Anaheim, CA, USA from the autocomplete list, it still says Disneyland, but if you choose Disneyland Resort, Disneyland Drive, Anaheim, CA, USA again, it will now say that. anyone have any idea why this is happening and how to fix to get it to show the first time on select?
here is the code
import AutoComplete from "react-google-autocomplete";
import { useForm, Controller } from "react-hook-form";
const {
register,
handleSubmit,
control,
formState: { touchedFields, errors },
reset,
watch,
} = useForm();
<Controller
name="name"
control={control}
render={({ field, fieldState }) => (
<AutoComplete
apiKey={some_api_key}
options={{
types: ["establishment"],
fields: ["name"],
}}
{...field}
/>
)}
/>;
This worked for me:
import AutoComplete from "react-google-autocomplete";
import { useForm, Controller } from "react-hook-form";
const { ..., control } = useForm();
<Controller
control={control}
name="location"
render={({ field: { onChange } }) => (
<AutoComplete
apiKey={GOOGLE_API_KEY}
onPlaceSelected={(place) => onChange(place.formatted_address)}
/>
)}
/>
/>;
onChange sends the value to the form's registry.
Taken directly from the docs here.
Using react-hook-form: v 7.39.5
react-google-autocomplete: v 2.7.0
I am developing a React frontend to send form data back to my API. I have decided to use formik to create the required forms for my app. While testing the array field and trying to add validation errors only at the in question array element input field. I have run into this error.
Warning: A component is changing an uncontrolled input to be controlled. This is likely caused by the value changing from undefined to a defined value, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info:
I have successfully implemented my form and errors for other fields except the array field, so I tried to add the ErrorMessage tag with the correct array element index as the name and this is when the error showed its fangs first.
Here is my component code:
I tried to dig into the error and find the solution my self, but all the other stack overflow answers I saw discussing this error were too complicated for me to understand. If anyone can help would be much appreciated also if you have any tips on how I could clean up this code I'll take it its not the prettiest.
import { useDispatch } from "react-redux";
import {Formik, Form, Field, FieldArray, ErrorMessage} from 'formik'
import * as Yup from 'yup'
const Sign = () => {
const ciscoDomainRegex = new RegExp('.*\.cisco\.com')
const SignSchema = Yup.object({
hostname:Yup.string().matches(ciscoDomainRegex, 'Hostname Must Be A Cisco Domain').required('required'),
sans:Yup.array().of(Yup.string().matches(ciscoDomainRegex)),
csr:Yup.mixed()
})
const dispatch = useDispatch()
const showError = (errors, field)=>{
switch (field){
case 'hostname':
return <p>{errors.hostname}</p>
case 'sans':
return <p>{errors.sans}</p>
case 'csr':
return <p>{errors.csr}</p>
default:
return false
}
}
return (
<div>
Sign Page
<Formik
initialValues={{
hostname:'',
sans:[''],
csr:null
}}
validationSchema={SignSchema}
onSubmit={(values)=>console.log(values)}
>
{({errors, touched, setFieldValue})=>{
return(
<Form className="form-center">
<Field className="form-control mt-1" name='hostname' placeholder="Enter Hostname"/>
{/* {errors && touched.hostname ? showError(errors, 'hostname') : null} */}
<ErrorMessage name="hostname"/>
<FieldArray name="sans" placeholder='Enter Sans'>
{({push, remove, form})=>{
const {sans} = form.values
return (
<div>
{
sans.map((san, i)=>{
return (
<div className="input-group" key={i}>
<Field className="form-control mt-1" name={`sans${i}`} placeholder="Enter San"/>
{/* {errors && touched.sans ? showError(errors, 'sans') : null} */}
<ErrorMessage name={`sans${i}`}/>
<div className="input-group-append">
<button className="btn btn-secondary float-end" type="button" onClick={()=>remove(i)}>-</button>
<button className="btn btn-secondary" type="button" onClick={()=>push('')}>+</button>
</div>
</div>
)
})
}
</div>
)
}}
</FieldArray>
<input className="form-control mt-1" type="file" name='csr' onChange={(e)=>setFieldValue('csr',e.currentTarget.files[0])}/>
{errors && console.log(errors)}
<button className="btn btn-primary" type="submit">Submit</button>
</Form>
)
}}
</Formik>
</div>
);
}
export default Sign;
You are passing the wrong field name for each of the fields that will be added dynamucally.
Each field name should be name[index] like so...
<Field name={san[$(index)]/>
or u can as well use the dot notation to reference the particular field in the field array
PS: don't forget to use backticks
I have the following code which has a dropdown list with few values.
I want that when the component gets loaded, it selects the first option and let me submit right after.
I tried with the line below but no luck:
formik.setFieldValue(name, value);
Here you have the code:
import { ErrorMessage, Field, Form, Formik } from 'formik';
import { get } from 'lodash-es';
import React, { useEffect } from 'react';
import * as Yup from 'yup';
const DropdownListInput = (props) => {
useEffect(() => {
const firstOptionValue = get(props.children, '[0].props.value', '');
console.log({
name: props.field.name,
value: props.value,
firstOptionValue,
});
if (props.value === '' && firstOptionValue !== '') {
props.formik.setValues({
[props.field.name]: firstOptionValue,
});
}
}, []);
return (
<select
value={props.value}
onChange={(e) => props.handleChange(e.target.value)}
>
{props.children.map(({ props: { value, children: text } }, index) => (
<option value={value} key={index}>
{text}
</option>
))}
</select>
);
};
export default () => {
return (
<Formik
initialValues={{
email: '',
}}
validationSchema={Yup.object().shape({
email: Yup.string()
.required('Email is required.')
.email('Email is invalid.'),
})}
onSubmit={(values, { setSubmitting }) => {
console.log(values);
setSubmitting(false);
}}
enableReinitialize
validateOnMount
>
{(formik) => {
return (
<Form>
<div>
<Field
component={DropdownListInput}
formik={formik}
name="email"
value={formik.values.email}
handleChange={(value) => {
console.log(value);
formik.setFieldValue('email', value);
}}
>
<option value="bill.gates#microsoft.com">Bill Bates</option>
<option value="steve.jobs#apple.com">Steve Jobs</option>
<option value="elon.musk#tesla.com">Elon Musk</option>
</Field>
<ErrorMessage name="email">
{(error) => <div style={{ color: '#f00' }}>{error}</div>}
</ErrorMessage>
</div>
<input type="submit" value="Submit" disabled={!formik.isValid} />
</Form>
);
}}
</Formik>
);
};
Here you have the Stackblitz you can play with:
https://stackblitz.com/edit/react-formik-yup-example-uhdg-dt6cgk?file=Registration.js
Is there any way to select the first option automatically when the component gets loaded?
Requirements:
I need the Submit button to be enabled automatically.
Using initialValues is not an option for me because the dropdown is in the middle of a more complex context then it is the same dropdown who has to trigger the setting of that value.
If you want you can post your forked Stackblitz.
Thanks!
You should be able to just add the initial value as the default value. Since that would be selected by default, the initial value can reflect the same, and be changed on change.
initialValues={{
email: 'bill.gates#microsoft.com',
}}
This requirement is very straightforward where you always want to have a selected option in your dropdown, defaulting to the first one. To do that you have to set select attribute of tag just for first one. It's a pure HTML thing.
you can find the reference here:
<option value="bill.gates#microsoft.com" select>
Bill Bates
</option>
https://stackblitz.com/edit/react-formik-yup-example-uhdg-bgzdh7?file=Registration.js
Another scenario would be you want to preserve the user selection on rerenders, to do that you can rely on setting up initialValues to what user has selected.
In that way, if any selection is there reflect that or else default to the very first item of the dropdown.
I'm usign Formik to validate some data fields with Semantic UI in React. It works fine with input fields but doesn't work with selectors.
How it works with input fields:
import { Formik, Form, Field } from 'formik';
import { Input, Button, Select, Label, Grid } from 'semantic-ui-react';
import * as Yup from 'yup';
...
const initialValues = {
name: ''
};
const requiredErrorMessage = 'This field is required';
const validationSchema = Yup.object({ name: Yup.string().required(requiredErrorMessage) });
<Formik
htmlFor="amazing"
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={values => this.handleSubmit(values)}>
{({ errors, touched }) => (
<Form id="amazing">
<div>
<CreationContentContainer>
<Grid>
<Grid.Column>
<Label>Company name</Label>
<Field name="name" as={Input} placeholder="name" /> // here is the input
<div>{touched.name && errors.name ? errors.name : null}</div>
</Grid.Column>
</Grid>
</CreationContentContainer>
<Button type="submit" form="amazing">
Create company
</Button>
</div>
</Form>
)}
</Formik>;
However, when the as={Input} is replaced by as={Select}, it doesn't work. The dropdown gets opened when I click on the selector, it shows the options (company1 and company2), I click on one of them but it does not work -> the value shown is still the placeholder value.
import { Formik, Form, Field } from 'formik';
import { Input, Button, Select, Label, Grid } from 'semantic-ui-react';
import * as Yup from 'yup';
const companyOptions = [ // the select's options
{ text: 'company1', value: 'company1' },
{ text: 'company2', value: 'company2' },
];
<Formik
htmlFor="amazing"
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={values => this.handleSubmit(values)}>
{({ errors, touched }) => (
<Form id="amazing">
<div>
<CreationContentContainer>
<Grid>
<Grid.Column>
<Label>Company name</Label>
<Field
name="name"
as={Select} // here is changed to Select
options={companyOptions} // the options
placeholder="name"
/>
<div>{touched.name && errors.name ? errors.name : null}</div>
</Grid.Column>
</Grid>
</CreationContentContainer>
<Button type="submit" form="amazing">
Create company
</Button>
</div>
</Form>
)}
</Formik>;
The reason is because Formik passes an onChange function into the component. However, the Select (which is just syntactic sugar for dropdown https://react.semantic-ui.com/addons/select/) onChange prop uses a function of shape handleChange = (e: React.SyntheticEvent<HTMLElement>, data: SelectProps) => this.setState({value: data.value}) (https://react.semantic-ui.com/modules/dropdown/#usage-controlled) which is different than the conventional handleChange = (event: React.SyntheticEvent<HTMLElement>) => this.setState({value: event.target.value}) which formik uses.
So an easy solution would be to use the Formik function setFieldValue.
In your code, the implementation would be something like this:
const companyOptions = [ // the select's options
{ text: 'company1', value: 'company1' },
{ text: 'company2', value: 'company2' },
];
<Formik
htmlFor="amazing"
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={values => this.handleSubmit(values)}>
{({ errors, touched, setFieldValue, values }) => {
const handleChange = (e, { name, value }) => setFieldValue(name, value));
return (
<Form id="amazing">
<div>
<CreationContentContainer>
<Grid>
<Grid.Column>
<Label>Company name</Label>
<Field
name="name"
as={Select} // here is changed to Select
options={companyOptions} // the options
placeholder="name"
onChange={handleChange}
value={values.name}
/>
<div>{touched.name && errors.name ? errors.name : null}</div>
</Grid.Column>
</Grid>
</CreationContentContainer>
<Button type="submit" form="amazing">
Create company
</Button>
</div>
</Form>
)}
}
</Formik>;
Here's a somewhat working version using your code. The fields update (verified with a console.log), but the submit button isn't working 🤷♂️: https://codesandbox.io/s/formik-and-semanticui-select-gb7o7
This is a somewhat incomplete solution and does not handle the onBlur and other injected formik functions. It also defeats the purpose of Formik somewhat. A better solution would be to create a HOC that wraps semantic ui components to use Fromik correctly: https://github.com/formium/formik/issues/148.
And lastly, you can skip the Semantic UI Select and use the Semantic UI HTML controlled select: https://react.semantic-ui.com/collections/form/#shorthand-field-control-html. Notice the select vs Select. This will work natively with Formik without the need to pass in onChange and value.
I recently created a binding library for Semantic UI React & Formik.
Link: https://github.com/JT501/formik-semantic-ui-react
You just need to import Select from the library and fill in name & options props.
// Import components
import { Select, SubmitButton } from "formik-semantic-ui-react";
const companyOptions = [ // the select's options
{ text: 'company1', value: 'company1' },
{ text: 'company2', value: 'company2' },
];
<Formik
htmlFor="amazing"
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={values => this.handleSubmit(values)}>
{({ errors, touched }) => (
<Form id="amazing">
<div>
<CreationContentContainer>
<Grid>
<Grid.Column>
<Label>Company name</Label>
<Select
name="name"
selectOnBlur={false}
clearable
placeholder="name"
options={companyOptions}
/>
</Grid.Column>
</Grid>
</CreationContentContainer>
<SubmitButton>
Create company
</SubmitButton>
</div>
</Form>
)}
</Formik>;
i am developing a form in reactjs using formik plugin plugin link. when i submit form i am getting text fields values but dropdown values are coming empty...
this is my dropdown select
<div className="form-group">
<Field component="select" id="category" name="category" value={this.state.value} className={"form-control"} onChange={ this.handleChange }>
<option value="lokaler">Lokaler</option>
<option value="jobb">Jobb</option>
<option value="saker-ting">Saker & ting</option>
<option value="evenemang">Evenemang</option>
</Field>
</div>
handle submit function
export default withFormik({
enableReinitialize: true,
mapPropsToValues({ category }) {
return {
category: category || ''
}
},
handleSubmit(values, { setStatus, setErrors }){
console.log("data is this: ");
console.log(values); //here i am getting all form fields values except select value returning empty value
console.log("category: "+values.category);// here i always get empty value but not getting selected value
}
i am getting all text fields values in handle submit function but i am not able to get dropdown selected value....kindly tell me how to get dropdown select value in handle submit function ?
I also faced this problem yesterday. My problem was to submit form that contains ant design dropdown. I finally make it work after hours of:
revisiting the Formik Docs
watch Andrew Mead's video Better React Form with Formik again.
also viewing Jared Palmer's Working with 3rd-party inputs #1: react-select
The doc said you need to set onChange, onBlur events to setFieldValue, setFieldTouched props respectively like 3rd-party input example:
<MySelect
value={values.topics}
onChange={setFieldValue}
onBlur={setFieldTouched}
error={errors.topics}
touched={touched.topics}
/>
So to my problem, I need to change a bit:
<Select
{...field}
onChange={(value) => setFieldValue('fruitName', value)}
onBlur={()=> setFieldTouched('fruitName', true)}
value={values.fruitName}
...
>
...
</Select>
Hope this will help.
Here is my CodeSandbox
A more robust way to handle select components whilst using Formik would be to also use Jed Watsons react-select component. The two work together nicely and abstract away a lot of the boilerplate you would normally need to implement to get the component working seamlessly with Formik.
I have forked a simple example from Jared Palmer's react-select / Formik example on codesandbox and adjusted it to reflect your code above.
import "./formik-demo.css";
import React from "react";
import { render } from "react-dom";
import { withFormik } from "formik";
import Select from "react-select";
import "react-select/dist/react-select.css";
const options = [
{ value: "lokaler", label: "Lokaler" },
{ value: "jobb", label: "Jobb" },
{ value: "saker-ting", label: "Saker & ting" },
{ value: "evenemang", label: "Evenemang" }
];
const MyForm = props => {
const {
values,
handleChange,
handleBlur,
handleSubmit,
setFieldValue
} = props;
return (
<form onSubmit={handleSubmit}>
<label htmlFor="myText" style={{ display: "block" }}>
My Text Field
</label>
<input
id="myText"
placeholder="Enter some text"
value={values.myText}
onChange={handleChange}
onBlur={handleBlur}
/>
<MySelect value={values.option} onChange={setFieldValue} />
<button type="submit">Submit</button>
</form>
);
};
class MySelect extends React.Component {
handleChange = value => {
// this is going to call setFieldValue and manually update values.topcis
this.props.onChange("option", value);
};
render() {
return (
<div style={{ margin: "1rem 0" }}>
<label htmlFor="color">Select an Option </label>
<Select
id="color"
options={options}
onChange={this.handleChange}
value={this.props.value}
/>
</div>
);
}
}
const MyEnhancedForm = withFormik({
mapPropsToValues: props => ({
myText: "",
option: {}
}),
handleSubmit: (values, { setSubmitting }) => {
console.log(values);
}
})(MyForm);
const App = () => <MyEnhancedForm />;
render(<App />, document.getElementById("root"));
There are a few gotchas, you have to include the react select css for the component to display properly
import "react-select/dist/react-select.css";
the function mapPropsToValues option field should be initialised to an object
mapPropsToValues: props => ({
myText: "",
option: {}
Finally here is a link to the codesandbox example