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?
Related
On the code below there is a text input that shows an error message when:
type invalid email (or leave blank) and hit enter
type invalid email (or leave blank) and blur (or clicking outside)
import { ErrorMessage, Field, Form, Formik } from 'formik';
import React from 'react';
import * as Yup from 'yup';
const TextInput = ({ value, handleChange }) => (
<input
type="text"
value={value}
onChange={(e) => handleChange(e.target.value)}
/>
);
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
>
{({ setFieldValue }) => (
<Form>
<div>
<Field
type="text"
name="email"
onChange={({ target: { value } }) => {
console.log(value);
setFieldValue('email', value);
}}
/>
<ErrorMessage name="email">
{(error) => <div style={{ color: '#f00' }}>{error}</div>}
</ErrorMessage>
</div>
<input type="submit" value="Submit" />
</Form>
)}
</Formik>
);
};
Now I want to keep the same behavior when doing the following change:
from
<Field
type="text"
name="email"
onChange={({target:{value}}) => {
console.log(value);
setFieldValue("email", value);
}}
/>
to:
<Field
component={TextInput}
name="email"
handleChange={(value) => {
console.log(value);
setFieldValue("email", value);
}}
/>
I mean, basically I'm changing from: type="text" to component={TextInput} which is basically a text input as you can see above.
After doing that change the error happens when:
[SUCCESS] type invalid email (or leave blank) and hit enter
but not when:
[FAIL] type invalid email (or leave blank) and blur (or clicking outside)
Do you know how can I get the error displayed on the second situation?
I'm looking for a standard way to do it and not tricky workarounds.
Here you have a StackBlitz you can play with and if you want you can post your forked one.
Thanks!
The way I handle this is to pass name and id between the component and wrapper, otherwise, Formik has no relationship and therefore no knowledge of what to error.
Here's a fork
import { ErrorMessage, Field, Form, Formik } from 'formik';
import React from 'react';
import * as Yup from 'yup';
const TextInput = ({ value, handleChange, name, id }) => (
<Field
id={id} // <- adding id
name={name} // <- adding name
type="text"
value={value}
onChange={handleChange} // You don't need to capture the event, you've already done the work in the wrapper
/>
);
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
>
{({ setFieldValue }) => (
<Form>
<div>
<Field
component={TextInput}
id="email" // <- id
name="email" // <- name
handleChange={e => {
const value = e.target.value;
console.log(value);
setFieldValue('email', value);
}}
/>
<ErrorMessage name="email">
{(error) => <div style={{ color: '#f00' }}>{error}</div>}
</ErrorMessage>
</div>
<input type="submit" value="Submit" />
</Form>
)}
</Formik>
);
};
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>
I have a problem with react-hook-form and reactstrap. I have this component List.jsx:
import { useContext, useEffect } from "react";
import { ListContext, ADD_LIST } from '../providers/ShoppingListProvider';
import { Link } from "react-router-dom";
import { useForm } from 'react-hook-form';
import { ListGroup, ListGroupItem, Form, FormGroup, Button, Input, Label, Container, Row, Col } from 'reactstrap';
export const Lists = () => {
const [store, dispatch] = useContext(ListContext);
const { register, handleSubmit, formState: { errors }, getValues } = useForm();
const onSubmit = data => addList(data);
const addList = (data) => {
console.log(data);
//dispatch({ type: ADD_LIST, store: store, listName: data.name });
}
return (
<Container>
<Row>
<Col>
<ListGroup>
{store.lists.map((item, key) => {
return <ListGroupItem key={key} ><Link to={"/list/" + key}>{item.name}</Link></ListGroupItem>
})}
</ListGroup>
</Col>
<Col>
<Form onSubmit={handleSubmit(onSubmit)}>
<FormGroup>
<Label >Name of new list</Label>
<Input type="text" placeholder="name of list" {...register("name", { required: true })} ></Input>
</FormGroup>
<Button type="submit">Přidat list</Button>
</Form>
</Col>
</Row>
</Container>
);
}
the problem is, that when I submit the form, nothing happens (addList won't happen). But when I replace Input (from reactstrap) with normal input from classic html, everything seems working. So the problem is Input from reactstrap. Does anyone knows how to fix this issue?
Thank you very much!
Try like this, look at the innerRef in input
const { register, handleSubmit, formState: { errors } } = useForm();
const { ref, ...rest } = register('name')
const onSubmit = data => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Input type="text" placeholder="Name" innerRef={ref} {...rest} />
<Input type="submit" />
</form>
);
i'm quite new with react and i'm building a form with react-hook and useState to manage my datas after the submit.
I'm not able to use textfield as they are blocked. I think that i make some errors into value/onChange parameters but i don't know what type of error.
import React, { useState } from "react";
import {
TextField,
MenuItem,
Typography,
Checkbox,
Divider,
Button,
} from "#mui/material";
import { MdError } from "react-icons/md";
import { BsArrowRight } from "react-icons/bs";
import "../style/contactform.scss";
import { useForm } from "react-hook-form";
const initialState = {
name: "",
email: "",
};
const ContactForm = () => {
const {
register,
handleSubmit,
formState: { errors },
} = useForm();
const [state, setState] = useState(initialState);
const { name, email } = state;
const handleInputChange = (e) => {
const { name, value } = e.target;
setState({ ...state, [name]: value });
};
const onSubmit = (e) => {
e.preventDefault();
console.log("form submit");
setState(initialState);
};
return (
<form className="contact-form" onSubmit={handleSubmit(onSubmit)}>
<Typography variant="h4" className="form-title">
Be the first.
</Typography>
<div className="form-component">
<TextField
id="standard-basic"
label="Nome*"
variant="standard"
name="nome"
value={name}
onChange={handleInputChange}
{...register("nome", {
required: true,
})}
/>
{errors?.nome?.type === "required" && (
<MdError className="form-validation-icon" />
)}
</div>
<Divider className="form-hr" />
<div className="form-component">
<TextField
id="standard-basic"
label="Email*"
variant="standard"
name="email"
value={email}
onChange={handleInputChange}
{...register("email", {
required: true,
pattern: {
value:
/^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+#[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/,
},
})}
/>
{errors?.email?.type === "required" && (
<MdError className="form-validation-icon" />
)}
{errors?.email?.type === "pattern" && (
<Typography variant="p" className="form-validation-email">
Inserisci un indirizzo email valido.
</Typography>
)}
</div>
<Divider className="form-hr" />
<Button className="form-submit" type="submit" variant="contained">
<BsArrowRight />
</Button>
</form>
);
};
export default ContactForm;
Textfields are completely block but initial state is actually working, do i miss something?
Can you help me?
To assign initial values using the useForm hook, you pass it under the defaultValues parameter passed to the hook like so:
const {
register,
handleSubmit,
reset
formState: { errors },
} = useForm({
defaultValues: initialState
});
Then just pass the ...register name and email to the inputs. There is no need to assign values to them again:
<TextField
id="standard-basic"
label="Name*"
variant="standard"
name="name"
{...register("name", {
required: true,
})}
/>
// for the email..
<TextField
id="standard-basic"
label="Email*"
variant="standard"
name="email"
{...register("email", {
required: true,
pattern: {
value: /^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+#[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/,},
})}
/>
If you'll notice, the values are off the text fields already and there's also no need for the handleInputChange function. The useForm hook takes care of that.
Edit:
In addition to the onSubmit function, the handleSubmit from useForm passes a data object into the function like this:
const onSubmit = (data) => {
console.log("form submitted", data);
reset(); // this can be destructured of the `useForm` hook.
};
For more info check their documentation
I currently have a form I am building using formik. I am able to update each of the fields(name, email, phone) as expected. The issue I am running into is when I provide one of the formik intial values with a state value and update that state. The act of updating the state causes the form to completely reset the input fields Is there a way to update the test state without resetting the entire form?? My code is as follows:
import { useState } from "react";
import { Formik, FieldArray } from "formik";
import "./styles.css";
export default function App() {
const [test, setTest] = useState(null);
return (
<Formik
enableReinitialize
initialValues={{
test: test,
name: "",
email: "",
phone: ""
}}
>
{({
values,
touched,
errors,
handleChange,
handleBlur,
handleSubmit
}) => (
<>
<button onClick={() => setTest("something")}>Update State</button>
<br />
<input
placeholder="name"
type="text"
name="name"
value={values.name}
onChange={handleChange}
/>
<br />
<input
placeholder="email"
type="text"
name="email"
value={values.email}
onChange={handleChange}
/>
<br />
<input
placeholder="phone"
type="text"
name="phone"
value={values.phone}
onChange={handleChange}
/>
<>
<pre style={{ textAlign: "left" }}>
<strong>Values</strong>
<br />
{JSON.stringify(values, null, 2)}
</pre>
<pre style={{ textAlign: "left" }}>
<strong>Errors</strong>
<br />
{JSON.stringify(errors, null, 2)}
</pre>
</>
</>
)}
</Formik>
);
}
In order to replicate what I am talking about in the codesandbox first update the name, email, and phone number. Then hit the udate state button and everything will be reset
codesandbox https://codesandbox.io/s/wispy-sun-mzeuy?file=/src/App.js:0-1567
This is because you are initializing the Formik component with enableReinitialize set to true:
enableReinitialize?: boolean
Default is false. Control whether Formik should reset the form if initialValues changes (using deep equality).
When the state variable test is updated, the formik-values are reinitialized to the values set in initialValues.
If you want this to work, I've forked your sandbox and modified it to use the formik hook (without enableReinitialize) in combination with a useEffect to manually update the value from state:
const [test, setTest] = useState(null);
const { values, errors, handleChange, setFieldValue } = useFormik({
initialValues: {
test: test,
name: "",
email: "",
phone: ""
}
});
useEffect(() => {
setFieldValue("test", test);
}, [test]);
My solution for this problem was to use the Formik values as state and set it directly with setFieldValue, so enableInitialize would not be triggered.
export default function App() {
//const [test, setTest] = useState(null); // This removed
return (
<Formik
enableReinitialize
initialValues={{
test: "",
name: "",
email: "",
phone: ""
}}
>
...
<button onClick={() => setFieldValue("test", "something")}>Update State</button>
Mostly the reason you would want to use states outside of Formik is to use it outside of Formik (ok clear), but this way you still can trigger a function from outside of Formik which you pass the values and the setFieldValue- Function to do whatever when triggered e.g. at onChange of a Formik field:
const handlerDefinedOutside = (values: any, setFieldValue: (field: string, value: any, shouldValidate?: boolean) => void): void => { /* do stuff */ };
<TextField
onChange={(e) => {
handleChange(e);
handlerDefinedOutside(values, setFieldValue);
}}
/>