I'm working on React based on NextJS code.
If I manually enter values in form fields and submit the form, I get all the values in the component function.
But if I dynamically display values in a form field like value={query.campaignid}, the component does not collect any value.
import { Button, Input } from "#chakra-ui/react";
import { Field, Form, Formik } from "formik";
import { useRouter } from "next/router";
export default function FormikExample() {
const { query } = useRouter();
return (
<>
<Formik
initialValues={
{
// campaignid: " ",
}
}
onSubmit={(values) => {
setTimeout(() => {
console.log(values);
});
}}
>
{(props) => (
<Form>
<Field name="campaignid">
<Input value={query.campaignid} />
</Field>
<Button id="submited" isLoading={props.isSubmitting} type="submit">
Submit
</Button>
</Form>
)}
</Formik>
</>
);
}
Resolved!
I passed the URL query to initialValues and added enableReinitialize={true} below:
...
initialValues={{
campaignId: query.campaignid,
}}
enableReinitialize={true}
...
In the input value I passed those props:
<Field name="campaignid">
<Input value={props.values.campaignId} />
</Field>
Related
I use Formik and react-bootstrap.
When the form is submitted, validation errors from the backend can occur.
I want to update the form feedback fields accordingly. I use the setErrors Method,
but the feedback element is not updated and the input element doesn't get red.
import React from 'react';
import { Formik, Field, ErrorMessage, useFormik } from 'formik';
import Form from 'react-bootstrap/Form';
import Row from 'react-bootstrap/Row';
import Button from 'react-bootstrap/Button';
const LoginForm = () => {
return (
<Formik
initialValues = {{ userid: '', password: ''}}
onSubmit = {(values, { setSubmitting, setErrors }) => {
console.log("Formsub: " + JSON.stringify(values, null, 2));
setErrors({userid: 'invalid login.'});
setSubmitting(false);
}}
>
{({
handleSubmit,
handleChange,
handleBlur,
isSubmitting,
values,
touched,
isValid,
errors,
}) => (
<Form onSubmit={handleSubmit}>
Errors: {errors.userid}
<Row className="mb-3">
<Form.Group md="4" controlId="validationUserid">
<Form.Label>Userid:</Form.Label>
<Form.Control type="text" name="userid"
onChange={handleChange} value={values.userid}
isValid={touched.userid && !errors.userid}
/>
<Form.Control.Feedback type="invalid">{errors.userid}</Form.Control.Feedback>
</Form.Group>
</Row>
<Button type="submit" disabled={isSubmitting}>Submit form</Button>
</Form>
)}
</Formik>
);
}
export default LoginForm;
Do I have to trigger the refresh of the form elements with the error marking manually?
If so, how?
Or is there a problem in my code that prevents the form from updating?
I made a reusable formik form, but I want to use ant select instead of formik select. the Error message is not working with ant design and I dont know how to configure that.
I need to show the error message when it's not validated. in console.log there is no problem it wworks perfect as i change the ant select. But the errortext does not shows
import React from "react";
import { Formik } from "formik";
import {Form} from 'antd'
import * as Yup from "yup";
import FormikController from "../components/Forms/FormikController";
const city = [
{ key: "option 1", value: "option1" },
{ key: "option 2", value: "option2" },
{ key: "option 3", value: "option3" },
{ key: "option 4", value: "option4" },
];
const Contact = () => {
const initialValues = {
whoYouAre: "",
email: "",
message: "",
};
const validationSchema = Yup.object({
whoYouAre :Yup.string().required(),
email :Yup.string().required()
});
return (
<Container className="contact">
<h5 className="mb-4">Contact</h5>
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
// onSubmit={handleAddData}
validateOnMount
>
{(formik) => (
<Form className="w-100">
<FormikController
control="selectsearch"
options={city}
formik={formik}
name="whoYouAre"
label="Please Tell Us Who You Are"
/>
<FormikController
control="input"
type="text"
name="email"
label="Email"
/>
<FormikController
control="textarea"
name="message"
rows="8"
label="Message"
/>
<div>
<Button variant="primary" type="submit">
Send
</Button>
</div>
</Form>
)}
</Formik>
</Container>
);
};
export default Contact;
and here the resusable select that i used from ant design
import React from "react";
import { Form,Select } from "antd";
import { ErrorMessage } from "formik";
import TextError from "./TextError";
const { Option } = Select;
const SearchSel = (props) => {
const { label, name, options ,formik, ...rest } = props;
console.log(formik);
return (
<Form.Item className="mb-3">
<Select
showSearch
size="large"
optionFilterProp="children"
placeholder={label}
name={name}
onChange={(value) => formik.setFieldValue("whoYouAre", value)}
>
{options.map((option) => {
return (
<Option key={option.value} value={option.value}>
{option.key}
</Option>
);
})}
</Select>
<ErrorMessage name={name} component={TextError} />
</Form.Item>
);
};
export default SearchSel;
Your default state is "" isn't right I believe. You probably want null.
Secondly, your Select is not correctly bound to the form state. You need to add value.
Moreover, you also need to add defaultActiveFirstOption={false} as that may be immediately selecting the first item on the initial state.
Finally, and probably most importantly, probably need to bind the fields blur such that it sets the field as touched. ErrorMessage won't show the error unless it thinks the user touched the field:
<Select
showSearch
size="large"
optionFilterProp="children"
placeholder={label}
name={name}
value={formik.values.whoYouAre}
defaultActiveFirstOption={false}
onChange={(value) => formik.setFieldValue("whoYouAre", value)}
onBlur={() => formik.setFieldTouched("whoYouAre", true)}
>
I have the following code which you can find here:
https://stackblitz.com/edit/react-d2fadr?file=src%2FApp.js
import { ErrorMessage, Field, Form, Formik } from 'formik';
import React from 'react';
import { Button } from 'react-bootstrap';
import * as Yup from 'yup';
let fieldName = 'hexColor';
const TextInput = ({ field, value, placeholder, handleChange }) => {
value = (field && field.value) || value || '';
placeholder = placeholder || '';
return (
<input
type="text"
placeholder={placeholder}
onChange={(e) => handleChange(e.target.value)}
value={value}
/>
);
};
export default () => {
const onSubmit = (values, { setSubmitting }) => {
console.log(values);
setSubmitting(false);
};
return (
<Formik
initialValues={{ [fieldName]: 'ff0000' }}
validationSchema={Yup.object({
hexColor: Yup.string().test(
fieldName,
'The Hex Color is Wrong.',
(value) => {
return /^[0-9a-f]{6}$/.test(value);
}
),
})}
onSubmit={onSubmit}
enableReinitialize
>
{(formik) => {
const handleChange = (value) => {
value = value.replace(/[^0-9a-f]/g, '');
formik.setFieldValue(fieldName, value);
};
return (
<Form>
<div>
<Field
component={TextInput}
name={fieldName}
placeholder="Hex Color"
handleChange={handleChange}
/>
<ErrorMessage name={fieldName} />
</div>
<Button
type="submit"
disabled={!formik.isValid || formik.isSubmitting}
>
Submit
</Button>
</Form>
);
}}
</Formik>
);
};
I want to know if is it any way to render the ErrorMessage element automatically?
The error message should be shown somewhere around the input text.
If you know how, you can fork the StackBlitz above with your suggestion.
Thanks!
Don't really know why ErroMessage is not rendering before you submit your form once but you can replace the line <ErrorMessage name={fieldName} /> by {formik.errors[fieldName]} to make it works
import { ErrorMessage, Field, Form, Formik } from 'formik';
import React from 'react';
import { Button } from 'react-bootstrap';
import * as Yup from 'yup';
let fieldName = 'hexColor';
const TextInput = ({ field, value, placeholder, handleChange }) => {
value = (field && field.value) || value || '';
placeholder = placeholder || '';
return (
<input
type="text"
placeholder={placeholder}
onChange={(e) => handleChange(e.target.value)}
value={value}
/>
);
};
export default () => {
const onSubmit = (values, { setSubmitting }) => {
console.log(values);
setSubmitting(false);
};
return (
<Formik
initialValues={{ [fieldName]: 'ff0000' }}
validationSchema={Yup.object({
hexColor: Yup.string().test(
fieldName,
'The Hex Color is Wrong.',
(value) => {
return /^[0-9a-f]{6}$/.test(value);
}
),
})}
onSubmit={onSubmit}
enableReinitialize
>
{(formik) => {
const handleChange = (value) => {
value = value.replace(/[^0-9a-f]/g, '');
formik.setFieldValue(fieldName, value);
};
return (
<Form>
<div>
<Field
component={TextInput}
name={fieldName}
placeholder="Hex Color"
handleChange={handleChange}
/>
{formik.errors[fieldName]}
</div>
<Button
type="submit"
disabled={!formik.isValid || formik.isSubmitting}
>
Submit
</Button>
</Form>
);
}}
</Formik>
);
};
the issue is validation schema. When I changed 6
return /^[0-9a-f]{6}$/.test(value);
to 3
return /^[0-9a-f]{3}$/.test(value);
and submitted with the initial value, ErrorMessage component is rendered
To reach your goal, I changed your code as below:
Since Formik's default component is Input, I deleted your TextInput component as there was nothing special in your component and handleChange function.
<Field name="hexColor" placeholder="Hex Color" onChange={(e) => handleChange(e.target.value)}/>
<ErrorMessage name="hexColor" />
As in mentioned in this answer, I changed your submit button condition to determine whether the button is disabled or not:
<Button type="submit" disabled={Object.keys(errors).length}>
Submit
</Button>
You can view my entire solution here.
Edit
If you want to keep your component, you should pass props as you might be missing something important, e.g. onChange,onBlur etc.
const TextInput = ({ field, ...props }) => {
return (
<input
{...field} {...props}
// ... your custom things
/>
);
};
<Field
component={TextInput}
name={fieldName}
placeholder="Hex Color"
onChange={(e) => handleChange(e.target.value)}
/>
Solution 2
I am having a form which is having 3 forms within and each child form will have its separate submit button and the parent will have its own submit button. I want to show the form one by one like initially only 1st form will be shown then on clicking continue 1st and 2nd form will be displayed and then after again clicking on continue 1st,2nd,3rd form will be displayed. All 3 forms are on the same page in 3 different components.
The code structure is like this: Codebox link
Also am facing an error
<form> cannot appear as a descendant of <form>
Because form within form, is there a way to achieve the same without this is error
You need to use a state to control it: https://codesandbox.io/s/elated-dawn-zlh5x?file=/src/App.js
export default function App() {
const [formNumber, setFormNumber] = useState(0);
const initialValues = { name: "", email: "", number: "" };
const onSubmit = async (values, { setSubmitting }) => {
console.log(" Final Values", values);
};
return (
<Formik initialValues={initialValues} onSubmit={onSubmit}>
{({ isSubmitting, values, setFieldValue }) => (
<Form className="space-y-7">
{formNumber >= 0 && <Form1 initialValues={initialValues} />}
{formNumber >= 1 && <Form2 initialValues={initialValues} />}
{formNumber >= 2 && <Form3 initialValues={initialValues} />}
<div className="flex flex-row-reverse">
<button type="button" onClick={() => setFormNumber(formNumber + 1)}>
Continue
</button>
</div>
</Form>
)}
</Formik>
);
}
I came up something like this, we can keep the count of form number and add one every time we click continue. But there needs to be validation before continue button enabled and plus, you need to handle the last step extra.
export default function App() {
const [formNumber, setFormNumber] = useState(0)
const initialValues = { name: "", email: "", number: "" };
const onSubmit = async (values, { setSubmitting }) => {
console.log(" Final Values", values);
};
return (
<Formik initialValues={initialValues} onSubmit={onSubmit}>
{({ isSubmitting, values, setFieldValue }) => (
<Form className="space-y-7">
{formNumber === 0 && <Form1 initialValues={initialValues} />}
{formNumber === 1 && <Form2 initialValues={initialValues} />}
{formNumber === 2 && <Form3 initialValues={initialValues} />}
<div className="flex flex-row-reverse">
<button onClick={()=> setFormNumber(formNumber + 1)} type="button">Continue</button>
</div>
</Form>
)}
</Formik>
);
}
Instead of using Form components, you can make it a single form with 3 inputs. That would be easier I believe.
You should use useState to save the index of current form, then create a function to change current index and pass it as a function to the child components!
use this:
import "./styles.css";
import React,{useState} from "react";
import { Formik, Form } from "formik";
import Form1 from "./Form1";
import Form2 from "./Form2";
import Form3 from "./Form3";
export default function App() {
const initialValues = { name: "", email: "", number: "" };
const onSubmit = async (values, { setSubmitting }) => {
console.log(" Final Values", values);
};
const [index, setIndex] = useState(0);
const nextIndex = ()=>{
setIndex(index+1);
}
return (
<Formik initialValues={initialValues} onSubmit={onSubmit}>
{({ isSubmitting, values, setFieldValue }) => (
<Form className="space-y-7">
{
index==0?<Form1 initialValues={initialValues} handleIndex={nextIndex}/>:
index==1?<Form2 initialValues={initialValues} handleIndex={nextIndex}/>:
<Form3 initialValues={initialValues} handleIndex={nextIndex}/>
}
<div className="flex flex-row-reverse">
<button type="button">Continue</button>
</div>
</Form>
)}
</Formik>
);
}
now, in each child use handleIndex function to go to the next form!
CodeSandbox:
https://codesandbox.io/s/kind-fire-q4o45
Question:
Click on reset button and clear a field with value "initial value"
Attempts:
There are too many variants to reset form via:
resetForm()
setFieldValue(<your_field_name>, '')
form.current.reset()
But this list doesn't helpful when you have initial value in formik field.
Snippet:
import React from 'react'
import {Formik, Form, Field} from 'formik'
const Search = () => (
<Formik onSubmit={({q}, {setSubmitting}) => {
setSubmitting(false)
}} initialValues={{q: 'initial value'}} render={({resetForm}) => (
<Form>
<Field name='q' />
<button type="reset" onClick={() => resetForm()}>Reset</button> {/* <== Reset */}
</Form>
)}/>
)
You're totally right - if you have some initial form state, the resetForm action will set the values to those initials. setFieldValue probably the only way to manually clear the field:
<button type="button" onClick={() => setFieldValue('q', '')}>
Drop field
</button>
notice, type='reset' not need here...
In case when you need to drop multiple fields, take a look at this method:
setValues({q: ''})
You can now reset formik by
formik.resetForm({
values: { name: 'Custom initial values', email: '' },
});
https://formik.org/docs/migrating-v2#resetform
yes when you reset form values it will reset it to default value
you can do the following
<Formik
enableReinitialize
onSubmit={(values, { setSubmitting }) => {
values.q = '';
setSubmitting(false);
}}
initialValues={{ q: "initial value" }}
render={({ resetForm }) => (
<Form>
<Field name="q" />
<button type="submit">
Reset form
</button>{" "}
{/* <== Reset */}
</Form>
)}
/>
Hope it helps