I'm using Formik and Yup when I create an entity (pany in this case) in order to check if the fields are correct and all of them are introduced (all are required).
When I create an entity it works fine: it only lets you create it if all fields are introduced and the rules are fulfilled (only one rule at the moment for the email).
This is the code which works to create a new pany with 2 fields name and email:
import React from 'react';
import { Redirect } from 'react-router-dom';
import { Formik, Form, Field } from 'formik';
import { Input, Button, Label, Grid } from 'semantic-ui-react';
import { connect } from 'react-redux';
import * as Yup from 'yup';
import { Creators } from '../../../actions';
import Layout from '../../Layout/Layout';
class CreateCompanyForm extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
name: '',
contactMail: '',
redirectCreate: false,
redirectEdit: false,
edit: false,
};
}
ponentDidMount() {
const {
getCompany,
location: { pathname },
} = this.props;
}
handleSubmit = values => {
const { createCompany, getCompanies } = this.props;
createCompany(values);
this.setState({ redirectCreate: true });
getCompanies(this.props.query);
};
render() {
let title = 'Create Company';
let buttonName = 'Create';
let submit = this.handleSubmitCreate;
const { redirectCreate, redirectEdit } = this.state;
if (redirectCreate) {
return <Redirect to="/panies" />;
}
const initialValues = {
name: '',
contactMail: '',
};
const requiredErrorMessage = 'This field is required';
const emailErrorMessage = 'Please enter a valid email address';
const validationSchema = Yup.object({
name: Yup.string().required(requiredErrorMessage),
contactMail: Yup.string()
.email(emailErrorMessage)
.required(requiredErrorMessage),
});
return (
<Layout>
<div>
<Button type="submit" form="amazing">
Create pany
</Button>
<Button onClick={() => this.props.history.goBack()}>Discard</Button>
<div>Create pany</div>
</div>
<Formik
htmlFor="amazing"
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={values => this.handleSubmit(values)}>
{({ values, errors, touched, setValues }) => (
<Form id="amazing">
<Grid columns={2}>
<Grid.Column>
<Label>Company Name</Label>
<Field name="name" as={Input} placeholder="Hello" />
<div>{touched.name && errors.name ? errors.name : null}</div>
</Grid.Column>
<Grid.Column>
<Label>Contact Mail</Label>
<Field
name="contactMail"
as={Input}
placeholder="[email protected]"
/>
<div>
{touched.contactMail && errors.contactMail
? errors.contactMail
: null}
</div>
</Grid.Column>
</Grid>
</Form>
)}
</Formik>
</Layout>
);
}
}
const mapStateToProps = state => ({
panies: statepaniespanies,
pany: statepanies.selectedCompany,
query: statepanies.query,
});
const mapDispatchToProps = {
getCompanies: Creators.getCompaniesRequest,
createCompany: Creators.createCompanyRequest,
getCompany: Creators.getCompanyRequest,
updateCompany: Creators.updateCompanyRequest,
};
export default connect(mapStateToProps, mapDispatchToProps)(CreateCompanyForm);
The problem appears when I want to edit the pany. So when someone click on a pany on edit button it should open the pany with all its fields containing the current values which should be editable.
To get those current values I'm using the state, for example the email can be accessed from this.state.email
and in order to change the value it was added onChange
method.
The values can be modified in the text input. However, it triggers the Yup message that says the field is required even if there is data in it - why is this happening? The field is not empty, that's the situation when it must show that message.
And of course, it doesn't update the entity when I click to save it because it requires those fields.
Here is the code:
import React from 'react';
...
class CreateCompanyForm extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
name: '',
contactMail: '',
redirectCreate: false,
redirectEdit: false,
edit: false,
};
}
ponentDidMount() {
const {
getCompany,
location: { pathname },
} = this.props;
if (pathname.substring(11) !== 'create') { // checks the URL if it is in edit mode
getCompany(pathname.substring(16));
this.setState({
edit: true,
});
this.setState({
name: this.propspany.name,
contactMail: this.propspany.contactMail,
});
}
}
onChange = (e, { name, value }) => { // method to update the state with modified value in input
this.setState({ [name]: value });
};
handleSubmit = values => {
const { createCompany, getCompanies } = this.props;
createCompany(values);
this.setState({ redirectCreate: true });
getCompanies(this.props.query);
};
handleSubmitEdit = e => {
e.preventDefault();
const { name, contactMail } = this.state;
const { updateCompany } = this.props;
updateCompany(this.propspany._id, {
name,
contactMail,
});
this.setState({ redirectEdit: true });
};
render() {
let title = 'Create Company';
let buttonName = 'Create';
let submit = this.handleSubmitCreate;
const { redirectCreate, redirectEdit } = this.state;
if (redirectCreate) {
return <Redirect to="/panies" />;
}
if (redirectEdit) {
return <Redirect to={`/panies/${this.propspany._id}`} />;
}
if (this.state.edit) {
title = 'Edit Company';
buttonName = 'Edit';
submit = this.handleSubmitEdit;
}
const initialValues = {
name: '',
contactMail: '',
};
const requiredErrorMessage = 'This field is required';
const emailErrorMessage = 'Please enter a valid email address';
const validationSchema = Yup.object({
name: Yup.string().required(requiredErrorMessage),
contactMail: Yup.string()
.email(emailErrorMessage)
.required(requiredErrorMessage),
});
return (
<Layout>
<div>
<Button type="submit" form="amazing">
Create pany
</Button>
<Button onClick={() => this.props.history.goBack()}>Discard</Button>
<div>Create pany</div>
</div>
<Formik
htmlFor="amazing"
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={values => this.handleSubmit(values)}>
{({ values, errors, touched, setValues }) => (
<Form id="amazing">
<Grid columns={2}>
<Grid.Column>
<Label>Company Name</Label>
<Field
name="name"
as={Input}
placeholder="Hello"
value={this.state.name || ''} // takes the value from the state
onChange={this.onChange} // does the changing
/>
<div>{touched.name && errors.name ? errors.name : null}</div>
</Grid.Column>
<Grid.Column>
<Label>Contact Mail</Label>
<Field
name="contactMail"
as={Input}
placeholder="[email protected]"
value={this.state.contactMail || ''} // takes the value from the state
onChange={this.contactMail} // does the changing
/>
<div>
{touched.contactMail && errors.contactMail
? errors.contactMail
: null}
</div>
</Grid.Column>
</Grid>
</Form>
)}
</Formik>
</Layout>
);
}
}
...
export default connect(mapStateToProps, mapDispatchToProps)(CreateCompanyForm);
Any ideas about how to solve this and make the fields editable and remove the 'This field is required'
message when the field already has data?
I'm using Formik and Yup when I create an entity (pany in this case) in order to check if the fields are correct and all of them are introduced (all are required).
When I create an entity it works fine: it only lets you create it if all fields are introduced and the rules are fulfilled (only one rule at the moment for the email).
This is the code which works to create a new pany with 2 fields name and email:
import React from 'react';
import { Redirect } from 'react-router-dom';
import { Formik, Form, Field } from 'formik';
import { Input, Button, Label, Grid } from 'semantic-ui-react';
import { connect } from 'react-redux';
import * as Yup from 'yup';
import { Creators } from '../../../actions';
import Layout from '../../Layout/Layout';
class CreateCompanyForm extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
name: '',
contactMail: '',
redirectCreate: false,
redirectEdit: false,
edit: false,
};
}
ponentDidMount() {
const {
getCompany,
location: { pathname },
} = this.props;
}
handleSubmit = values => {
const { createCompany, getCompanies } = this.props;
createCompany(values);
this.setState({ redirectCreate: true });
getCompanies(this.props.query);
};
render() {
let title = 'Create Company';
let buttonName = 'Create';
let submit = this.handleSubmitCreate;
const { redirectCreate, redirectEdit } = this.state;
if (redirectCreate) {
return <Redirect to="/panies" />;
}
const initialValues = {
name: '',
contactMail: '',
};
const requiredErrorMessage = 'This field is required';
const emailErrorMessage = 'Please enter a valid email address';
const validationSchema = Yup.object({
name: Yup.string().required(requiredErrorMessage),
contactMail: Yup.string()
.email(emailErrorMessage)
.required(requiredErrorMessage),
});
return (
<Layout>
<div>
<Button type="submit" form="amazing">
Create pany
</Button>
<Button onClick={() => this.props.history.goBack()}>Discard</Button>
<div>Create pany</div>
</div>
<Formik
htmlFor="amazing"
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={values => this.handleSubmit(values)}>
{({ values, errors, touched, setValues }) => (
<Form id="amazing">
<Grid columns={2}>
<Grid.Column>
<Label>Company Name</Label>
<Field name="name" as={Input} placeholder="Hello" />
<div>{touched.name && errors.name ? errors.name : null}</div>
</Grid.Column>
<Grid.Column>
<Label>Contact Mail</Label>
<Field
name="contactMail"
as={Input}
placeholder="[email protected]"
/>
<div>
{touched.contactMail && errors.contactMail
? errors.contactMail
: null}
</div>
</Grid.Column>
</Grid>
</Form>
)}
</Formik>
</Layout>
);
}
}
const mapStateToProps = state => ({
panies: state.panies.panies,
pany: state.panies.selectedCompany,
query: state.panies.query,
});
const mapDispatchToProps = {
getCompanies: Creators.getCompaniesRequest,
createCompany: Creators.createCompanyRequest,
getCompany: Creators.getCompanyRequest,
updateCompany: Creators.updateCompanyRequest,
};
export default connect(mapStateToProps, mapDispatchToProps)(CreateCompanyForm);
The problem appears when I want to edit the pany. So when someone click on a pany on edit button it should open the pany with all its fields containing the current values which should be editable.
To get those current values I'm using the state, for example the email can be accessed from this.state.email
and in order to change the value it was added onChange
method.
The values can be modified in the text input. However, it triggers the Yup message that says the field is required even if there is data in it - why is this happening? The field is not empty, that's the situation when it must show that message.
And of course, it doesn't update the entity when I click to save it because it requires those fields.
Here is the code:
import React from 'react';
...
class CreateCompanyForm extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
name: '',
contactMail: '',
redirectCreate: false,
redirectEdit: false,
edit: false,
};
}
ponentDidMount() {
const {
getCompany,
location: { pathname },
} = this.props;
if (pathname.substring(11) !== 'create') { // checks the URL if it is in edit mode
getCompany(pathname.substring(16));
this.setState({
edit: true,
});
this.setState({
name: this.props.pany.name,
contactMail: this.props.pany.contactMail,
});
}
}
onChange = (e, { name, value }) => { // method to update the state with modified value in input
this.setState({ [name]: value });
};
handleSubmit = values => {
const { createCompany, getCompanies } = this.props;
createCompany(values);
this.setState({ redirectCreate: true });
getCompanies(this.props.query);
};
handleSubmitEdit = e => {
e.preventDefault();
const { name, contactMail } = this.state;
const { updateCompany } = this.props;
updateCompany(this.props.pany._id, {
name,
contactMail,
});
this.setState({ redirectEdit: true });
};
render() {
let title = 'Create Company';
let buttonName = 'Create';
let submit = this.handleSubmitCreate;
const { redirectCreate, redirectEdit } = this.state;
if (redirectCreate) {
return <Redirect to="/panies" />;
}
if (redirectEdit) {
return <Redirect to={`/panies/${this.props.pany._id}`} />;
}
if (this.state.edit) {
title = 'Edit Company';
buttonName = 'Edit';
submit = this.handleSubmitEdit;
}
const initialValues = {
name: '',
contactMail: '',
};
const requiredErrorMessage = 'This field is required';
const emailErrorMessage = 'Please enter a valid email address';
const validationSchema = Yup.object({
name: Yup.string().required(requiredErrorMessage),
contactMail: Yup.string()
.email(emailErrorMessage)
.required(requiredErrorMessage),
});
return (
<Layout>
<div>
<Button type="submit" form="amazing">
Create pany
</Button>
<Button onClick={() => this.props.history.goBack()}>Discard</Button>
<div>Create pany</div>
</div>
<Formik
htmlFor="amazing"
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={values => this.handleSubmit(values)}>
{({ values, errors, touched, setValues }) => (
<Form id="amazing">
<Grid columns={2}>
<Grid.Column>
<Label>Company Name</Label>
<Field
name="name"
as={Input}
placeholder="Hello"
value={this.state.name || ''} // takes the value from the state
onChange={this.onChange} // does the changing
/>
<div>{touched.name && errors.name ? errors.name : null}</div>
</Grid.Column>
<Grid.Column>
<Label>Contact Mail</Label>
<Field
name="contactMail"
as={Input}
placeholder="[email protected]"
value={this.state.contactMail || ''} // takes the value from the state
onChange={this.contactMail} // does the changing
/>
<div>
{touched.contactMail && errors.contactMail
? errors.contactMail
: null}
</div>
</Grid.Column>
</Grid>
</Form>
)}
</Formik>
</Layout>
);
}
}
...
export default connect(mapStateToProps, mapDispatchToProps)(CreateCompanyForm);
Any ideas about how to solve this and make the fields editable and remove the 'This field is required'
message when the field already has data?
-
why are you not setting email in
initialValues
? so if you are editing: pass data with props and use them as initialValues – Tobias Lins Commented Sep 7, 2020 at 8:38 -
@TobiasLins it is set there:
const initialValues = { name: '', contactMail: '', };
– Leo Messi Commented Sep 7, 2020 at 8:42 - yeah but you are setting it to empty string and not to the value from editing – Tobias Lins Commented Sep 7, 2020 at 9:02
1 Answer
Reset to default 6 +100You need to do 3 little changes:
1. Your inital value is always set as:
const initialValues = {
name: '',
contactMail: '',
};
You need to change it to:
const initialValues = {
name: this.state.name,
contactMail: this.state.contactMail,
};
2. Add enableReinitialize
to Formik
Even with the change nº 1, your submit will still throwing errors, that´s because when the ponent is created, Formik
is rendered with the values from your constructor:
this.state = {
name: "",
contactMail: "",
redirectCreate: false,
redirectEdit: false,
edit: false,
};
And when you change state inside ponentDidMount
, Formik
is not reinitialize with the update values:
ponentDidMount() {
const {
getCompany,
location: { pathname },
} = this.props;
if (pathname.substring(11) !== 'create') { // checks the URL if it is in edit mode
getCompany(pathname.substring(16));
this.setState({
edit: true,
});
this.setState({
name: this.props.pany.name,
contactMail: this.props.pany.contactMail,
});
}
}
So, to formik reinitialize you need to add enableReinitialize
to it, like this:
<Formik
htmlFor="amazing"
/// HERE'S THE CODE
enableReinitialize
initialValues={initialValues}
....
3. With enableReinitialize
, Formik
will trigger validation on Blur and on Change. To avoid this you can add validateOnChange
and validateOnBlur
to false:
<Formik
htmlFor="amazing"
enableReinitialize
validateOnChange={false}
validateOnBlur={false}
initialValues={initialValues}
.....