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
Related
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>
)
}
so it's been a month i'm working with react.
there is a problem i didn't know what to do with it.
in onChange event handlers EVEN in the smallest page and project there is a delay in typing and specially Delay in Removing text.
why is this happenig ?
i mean react is popular because of Fast UI right? so what we should do to handle Forms without this Perfomance Problem ?
why there is a lag in this form ?
const LoginForm = () => {
// State & Validate Schema
const formik = useFormik({
initialValues: {
email: "",
password: "",
rememberMe: false,
},
validationSchema: Yup.object({
blah blah some validation
}),
onSubmit: (value) => {
console.log(value, "Submitted");
},
});
return (
<>
<Container maxWidth="sm" disableGutters>
<Box>
<h3>Log to the Account</h3>
<form onSubmit={formik.handleSubmit}>
<TextField
error={formik.touched.email && formik.errors.email !== undefined}
name='email'
label='Email'
value={formik.values.name}
onBlur={formik.handleBlur} // If Input is Touched Show Error
onChange={formik.handleChange}
helperText={formik.touched.email && formik.errors.email}
autoComplete="on"
/>
<TextField
error={formik.touched.password && formik.errors.password!== undefined}
name='password'
label='Password'
value={formik.values.password}
onBlur={formik.handleBlur}
onChange={formik.handleChange}
helperText={formik.touched.password&& formik.errors.password}
autoComplete="on"
/>
<Button type="submit" variant="contained" size="medium">
Login
</Button>
<span>Remember Me</span>
<Checkbox
value={formik.values.rememberMe}
name="rememberMe"
onChange={formik.handleChange}
/>
</form>
</Box>
</Container>
</>
);
};
I think this is not a problem of react rather than Formik. In your example you have a variable named formik to pass the current value to your TextField component you define the prop value={formik.values.someName} and to update the field value you specify an onChange={formik.handleChange} handler. To be able to pass the new value after a change to your input field, the formik variable needs to be updated and this causes your whole form component to be rerendered on every key stroke.
To be honest, I switched for similar reasons from Formik to React Hook Form some while ago and hadn't used Formik for a long time. But I think your performance problems are caused by the design of Formik.
I using useFieldArray from react-hooks-form. But when i append new field with a required rules; required error doesnt show on my screen when i submit it. But after that , if i append new field then delete it then i can see that error on my screen. So its not working in first render. So what do i making wrong ? How can i solve it ? My codes :
const { fields, append, remove } = useFieldArray(
{
control,
name: "Taxes",
}
);
Selecting something from select and finding my model from my list then appending it as a input :
const value=e.target.value;
const model = otherTaxesList.find((x) => x.Id === value);
append(model);
FormInput is simply a Controller with a material UI OutlinedInput
{
fields.map((item, index, arr) => {
return (
<React.Fragment key={item.id}>
<div className="flex ">
<FormInput
className="flex-1"
control={control}
name={`Taxes[${index}].model`}
label={item.Name}
type="number"
rules={{ required: customRules.required }}
error={errors.Taxes?.[index]?.model && errors.Taxes?.[index]?.model}
placeholder={item.Name}
/>
<button type="button" onClick={() => remove(index)}>
Delete
</button>
</div>
</React.Fragment>
);
});
}
I am in the process of learning React. I tried the Formik package https://www.npmjs.com/package/formik for React. It seems to work nice.
However, I tried to understand how it actually works with no luck.
Here is an example of simple form made with Formik:
import React from 'react';
import { Formik, Form, Field } from 'formik';
const FormikShort = () => (
<div>
<h1>Formik example</h1>
<Formik
initialValues={{email: '', password: ''}}
validate={values => {
let errors = {};
if (!values.email) {
errors.email = 'Required field';
} else if (
!/^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(values.email)
) {
errors.email = 'Not valid email address';
}
return errors;
}}
onSubmit={(values, { setSubmitting }) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
}, 400);
}}
>
{({ errors, touched, isSubmitting}) => (
<Form noValidate>
<div className="form-group">
<label htmlFor="emailLabel">Input email address</label>
<Field className="form-control" type="email" name="email" />
{errors.email && touched.email && errors.email}
</div>
<button type="submit" disabled={isSubmitting}>
Submit
</button>
</Form>
)}
</Formik>
</div>
);
export default FormikShort;
So in the example above there is an anonymous function call between Formik-element opening and closing tags. What makes this possible?
If I create my own simple component, such as
function Dummy(props) {
return <h1>Dummy component: {props.text}</h1>;
}
And then have it rendered like this
<Dummy
text="hello"
>
<div>Some text</div>
</Dummy>
the text "Some text" is not rendered at all. What do I have to do in order to be able insert stuff inside my own component and have it show up?
What about the function call inside the Formik-elements opening and closing tags? Where do the values for parameters (errors, touched, isSubmitting) come from?
I tried to look at the source files at node_modules/formik but I don't know exactly what file to look at. There are a lot of files, such as:
formik.cjs.development.js
formik.cjs.production.js
formik.esm.js
formik.umd.development.js
I am trying to use redux-form to generate a quiz form. My data source for an individual redux-form field component comes from an array - questions in my case. Everything works as expected except validation. Any thoughts how this can be fixed?
import React from 'react';
import { Field, reduxForm } from 'redux-form';
import { Input, Button } from 'reactstrap';
const validate = values => {
const errors = {};
if (!values.question) { // this is just an example of what I am trying to do, validation does not work
errors.question = 'Required';
} else if (values.question.length < 15) {
errors.question = 'Must be 15 characters or more';
}
return errors;
};
const renderField = ({ input, label, type, meta: { touched, error } }) => (
<div>
<label>{label}</label>
<div>
<Input {...input} type={type} />
{touched && (error && <span>{error}</span>)}
</div>
</div>
);
const renderQuestions = questions => {
return questions.map(question => {
return (
<Field key={question.id} name={question.prompt} type="textarea" component={renderField} label={question.prompt} />
);
});
};
const QuizStepForm = props => {
const { handleSubmit, pristine, reset, submitting, questions } = props;
return (
<form onSubmit={handleSubmit}>
<Field name="username" type="textarea" component={renderField} label="username" />
{renderQuestions(questions)}
<div>
<br />
<Button color="primary" style={{ margin: '10px' }} type="submit" disabled={submitting}>
Submit
</Button>
<Button type="button" disabled={pristine || submitting} onClick={reset}>
Clear Values
</Button>
</div>
</form>
);
};
export default reduxForm({
form: 'quizStepForm',
validate
})(QuizStepForm);
Your validation function assumes there is one field named "question." But your code creates a set of fields whose name is set by {question.prompt}. If you stick with this implementation, your validation code will need to know about all the question.prompt array values and check values[question.prompt] for each one, then set errors[question.prompt] for any failures. That would probably work, though it seems like a suboptimal design.
This might be a good use case for a FieldArray. In FieldArrays, the validation function is called for you on each field; your validation code doesn't have to know the names of all the fields.