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
Related
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'm trying to reset the inputs in my formik Form on submit. It seems I'm supposed to use resetForm() to do that but i get the error:
src\components\CommentSubmition\inCommentSubmition.js
Line 19:13: 'resetForm' is not defined no-undef
Here's my component:
import React from 'react';
import { Formik, Field, Form, ErrorMessage } from 'formik';
import {createComment} from '../../services/CommentLocalStorage.js'
import * as Yup from 'yup';
function CommentForm(props){
return (
<Formik
initialValues={{ autor: '', content: ''}}
validationSchema={Yup.object({
autor: Yup.string().required('Required'),
content: Yup.string().required('Required')
})}
onSubmit={(values, { setSubmitting }) => {
setTimeout(() => {
createComment(props.pageEnum, props.articleId, values.autor, values.content)
setSubmitting(false);
},400);
resetForm();
}}
>
<Form>
<label htmlFor="autor">Nome</label>
<Field name="autor" type="autor" placeholder="Nome"/>
<ErrorMessage name="autor" />
<br/>
<label htmlFor="content">Comentário</label>
<Field name="content" type="content" placeholder="Comentário" />
<ErrorMessage name="content" />
<br/>
<button type="submit">Submit</button>
</Form>
</Formik>
);
};
export default CommentForm;
It seems most people make something like this:
const formik = some configuration
And then they use it like
formik.resetForm()
And instead I'm using the Formik component with all the stuff inside it (I did it based on an example available on the official tutorials). If possible i'd like to keep it like that and still make the form reset.
Pass resetForm as a parameter to your onSubmit function. That should give your function access to the resetForm method from Formik thereby getting rid of the error and successfully reset the form. If you want to use any methods from the formik library inside your onSubmit function, first pass a parameter to the function so you can have access to the formik method. Let me know if this helps
import React from 'react';
import { Formik, Field, Form, ErrorMessage } from 'formik';
import {createComment} from '../../services/CommentLocalStorage.js'
import * as Yup from 'yup';
function CommentForm(props){
return (
<Formik
initialValues={{ autor: '', content: ''}}
validationSchema={Yup.object({
autor: Yup.string().required('Required'),
content: Yup.string().required('Required')
})}
onSubmit={(values, { setSubmitting, {resetForm }) => { //<--- See here for code added
setTimeout(() => {
createComment(props.pageEnum, props.articleId, values.autor, values.content)
setSubmitting(false);
},400);
resetForm();
}}
>
<Form>
<label htmlFor="autor">Nome</label>
<Field name="autor" type="autor" placeholder="Nome"/>
<ErrorMessage name="autor" />
<br/>
<label htmlFor="content">Comentário</label>
<Field name="content" type="content" placeholder="Comentário" />
<ErrorMessage name="content" />
<br/>
<button type="submit">Submit</button>
</Form>
</Formik>
);
};
export default CommentForm;
I am using Formik form in my React application to create a new post and I have an input field of type "file". The problem I face now is I cannot implement a cancelation of the action. With the code I am proving I do update the form values and the preview image disappears but I still can see the name of the file on the screen which should be "no file chosen" instead. I went through Formik documentation but did not find a solution. Any ideas will help. Thank you.
Setting value={values.image} gives these errors:
Failed to set the 'value' property on 'HTMLInputElement': This input element accepts a filename, which may only be programmatically set to the empty string
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.
import { useHistory } from "react-router-dom";
import { useDispatch } from "react-redux";
import { Formik, Field } from "formik";
import { createPost } from "../actions";
const NewPost = () => {
const dispatch = useDispatch();
const history = useHistory();
const handleImageUpload = async (event, setFieldValue) => {
// .... some code where I obtain the image URLs
setFieldValue("image", file.secure_url);
setFieldValue("largeImage", file.eager[0].secure_url);
};
return (
<div>
<Formik
initialValues={{ image: "", largeImage: "", title: "", body: "" }}
validate={(values) => {
const errors = {};
//... validation
return errors;
}}
onSubmit={(values, { setSubmitting }) => {
const { title, body, image, largeImage } = values;
dispatch(
createPost(
{
title,
body,
image,
largeImage,
},
history
)
);
setSubmitting(false);
}}
>
{({
values,
errors,
touched,
handleChange,
handleBlur,
handleSubmit,
setFieldValue,
isSubmitting,
/* and other goodies */
}) => (
<form onSubmit={handleSubmit} className="new__post-form">
<div>
<label htmlFor="image">Image</label>
<div>
<div>
<label htmlFor="image">Upload File</label>
<input
type="file"
type="file"
name="image"
id="image"
placeholder="Upload an image"
onChange={(e) => handleImageUpload(e, setFieldValue)}
onBlur={handleBlur}
/>
</div>
<div>
{values.image && (
<>
<img src={values.image} alt="Upload Preview" />
<button
type="button"
onClick={() => {
setFieldValue("image", "");
setFieldValue("largeImage", "");
}}
>
X
</button>
</>
)}
</div>
</div>
</div>
<button type="submit" disabled={isSubmitting}>
Submit
</button>
</form>
)}
</Formik>
</div>
);
};
export default NewPost;
I have a weird requirement. I need to do a redux-form validation client side as well as on the server side. I am able to do it on the client side but not sure how can I do for both client and server side. Checked redux-form documentation where it is done either client or server but not for both at once.
Here is the Code
import React from 'react'
import { Field, reduxForm } from 'redux-form'
const validate = values => {
const errors = {}
if (!values.username) {
errors.username = 'Required'
} else if (values.username.length > 15) {
errors.username = 'Must be 15 characters or less'
}
if (!values.email) {
errors.email = 'Required'
} else if (!/^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.email)) {
errors.email = 'Invalid email address'
}
if (!values.age) {
errors.age = 'Required'
} else if (isNaN(Number(values.age))) {
errors.age = 'Must be a number'
} else if (Number(values.age) < 18) {
errors.age = 'Sorry, you must be at least 18 years old'
}
return errors
}
const renderField = ({
input,
label,
type,
meta: { touched, error, warning }
}) => (
<div>
<label>{label}</label>
<div>
<input {...input} placeholder={label} type={type} />
{touched &&
((error && <span>{error}</span>) ||
(warning && <span>{warning}</span>))}
</div>
</div>
)
const SyncValidationForm = props => {
const { handleSubmit, pristine, reset, submitting } = props
return (
<form onSubmit={handleSubmit}>
<Field
name="username"
type="text"
component={renderField}
label="Username"
/>
<Field name="email" type="email" component={renderField} label="Email" />
<Field name="age" type="number" component={renderField} label="Age" />
<div>
<button type="submit" disabled={submitting}>
Submit
</button>
<button type="button" disabled={pristine || submitting} onClick={reset}>
Clear Values
</button>
</div>
</form>
)
}
export default reduxForm({
form: 'syncValidation',
validate
})(SyncValidationForm)
Now onSubmit I have to do an API request and show the errors coming from the server for each field.
Can anyone explain me how can I add sever side validation while I keep client side validation also working?
Thanks in advance.
Well, you need to write server-side code for this. It depends on what language you want to use, but I guess node.js is good to achieve this. So for validating data on the server you should create a nodejs server, and pass your data (that validated already on client-side) then in node.js server validate data again however you want.
So in summary, you should start node.js which is simple because both react/node.js are almost the same.
Hope it helps you
Redux-form has a function you can use to throw submissionErrors inside your onSubmit function. https://redux-form.com/8.2.2/docs/api/submissionerror.md/
This can be done after an asynchronous call.
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.