I started working with redux-form along with react-bootstrap. I have give validation in my form my custom validation is working fine but I have given pristine condition from this doc it is not working for me. below is my code for that let me know where I went wrong? do I need to add anything?
If anyone let me know what render field does for me?
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>
);
class Duplicate extends React.Component {
constructor(...args) {
super(...args);
this.state = {
open: false,
showModal: false
};
}
saveDuplicate = value => {
if ('[default]'.includes(value.duplicateName)) {
throw new SubmissionError({
duplicateName: 'User does not exist',
_error: 'Login failed!'
});
}
console.log('value on submit', value);
};
close = () => this.setState({ showModal: false });
openModal = () => this.setState({ showModal: true });
render() {
console.log('this props in duplicate', this.props);
const required = value => (value ? undefined : 'Required');
const { handleSubmit, pristine, reset, submitting } = this.props;
return (
<div className="scenario_btn">
<Button
onClick={this.openModal}
bsStyle="danger"
className="scenario_mangt"
>
Duplicate
</Button>
<Modal
aria-labelledby="modal-label"
show={this.state.showModal}
onHide={this.close}
>
<form onSubmit={handleSubmit(this.saveDuplicate)}>
<Field
name="duplicateName"
type="text"
component={renderField}
label="name"
validate={[required]}
/>
<div>
<button type="submit" disabled={submitting}>
Save
</button>
<button
type="button"
disabled={pristine || submitting}
onClick={reset}
>
Cancel
</button>
</div>
</form>
</Modal>
</div>
);
}
}
export default reduxForm({
form: 'duplicatForm' // a unique identifier for this form
})(Duplicate);
Related
I am having some issues with setting the inital form field values using redux-form.
Here is the code I tried
import { Field, FieldArray, reduxForm, getFormValues, change } from 'redux-form'
const renderField = ({
input,
label,
type,
meta: { asyncValidating, touched, error }
}) => (
<div>
<label>{label}</label>
<div className={asyncValidating ? 'async-validating' : ''}>
<input {...input} type={type} placeholder={label}/>
{touched && error && <span>{error}</span>}
</div>
</div>
)
class Profile extends Component {
constructor(props) {
super(props);
this.state = {
firstName: null,
lastName: null,
}
}
componentDidMount() {
this.props.fetchProfile();
}
async handleChange(e) {
await this.setState({ 'initialValues': { [e.target.name] : e.target.value }});
await this.setState({ [e.target.name] : e.target.value });
}
onSubmit = (e) => {
this.props.saveProfile({
firstName: this.state.auth.firstName,
lastName: this.state.auth.lastName,
});
}
componentWillReceiveProps(nextProps) {
this.setState({
firstName : nextProps.initialValues.firstName,
lastName : nextProps.initialValues.LastName,
});
this.setState({ 'initialValues': {
firstName : nextProps.initialValues.firstName,
lastName : nextProps.initialValues.LastName,
}});
}
render() {
return (
<>
<form onSubmit={handleSubmit(this.onSubmit)}>
<div>
<Field
name="firstName"
type="text"
component={renderField}
label="firstName"
onChange={this.handleChange.bind(this)}
/>
</div>
<div>
<Field
name="lastName"
type="text"
component={renderField}
label="lastName"
onChange={this.handleChange.bind(this)}
/>
</div>
<div>
<button type="submit" disabled={pristine || submitting}>
Update Info
</button>
</div>
</form>
);
}
}
Profile = reduxForm({
form: 'Profile' ,
// fields,
validate,
asyncValidate,
enableReinitialize: true,
})(Profile);
function mapStateToProps(state, props){
let firstName = '';
let lastName = '';
return {
userData: state.auth.userData,
initialValues:{
firstName: state.auth.firstName,
lastName: state.auth.lastName,
}
}
}
const mapDispatchToProps = (dispatch) => {
return {
fetchProfile: () => dispatch(auth.actions.fetchProfile()),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Profile);
But it is not setting value in field when load. field is just empty
I guess Redux Form works little different.
You don't need to set explicit onChange handler or declare any state to hold form fields data in Redux Form. Update your code similar to below
import { Field, FieldArray, reduxForm, getFormValues, change } from 'redux-form'
const renderField = ({
input,
label,
type,
meta: { asyncValidating, touched, error }
}) => (
<div>
<label>{label}</label>
<div className={asyncValidating ? 'async-validating' : ''}>
<input {...input} type={type} placeholder={label}/>
{touched && error && <span>{error}</span>}
</div>
</div>
)
class Profile extends Component {
// No need constructor/to explicitly declare the state
componentDidMount() {
this.props.fetchProfile();
}
render() {
const { handleSubmit, pristine, submitting} = props; // You can have custom handleSubmit too
return (
<>
<form onSubmit={handleSubmit}>
<div>
<Field
name="firstName"
type="text"
component={renderField}
label="firstName"
/>
</div>
<div>
<Field
name="lastName"
type="text"
component={renderField}
label="lastName"
/>
</div>
<div>
<button type="submit" disabled={pristine || submitting}>
Update Info
</button>
</div>
</form>
);
}
}
Profile = reduxForm({
form: 'Profile' ,
// fields,
validate,
asyncValidate,
enableReinitialize: true,
})(Profile);
function mapStateToProps(state, props) {
return {
userData: state.auth.userData,
initialValues:{ // These are the initial values. You can give any name here for testing to verify the ReduxForm working
firstName: state.auth.firstName,
lastName: state.auth.lastName,
}
}
}
const mapDispatchToProps = (dispatch) => {
return {
fetchProfile: () => dispatch(auth.actions.fetchProfile()),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Profile);
The example in question and the answer would definitely help you to understand things better.
I am trying to implement async validation react-final-form-array. But its returning promise but not showing error at all.
I took reference of this code for asnyc validate and created my own version with final form array here.
Form:
<Form
onSubmit={onSubmit}
mutators={{
...arrayMutators
}}
initialValues={{ customers: [{ placeholder: null }] }}
validate={formValidate}
render={({
handleSubmit,
mutators: { push, pop }, // injected from final-form-arrays above
pristine,
reset,
submitting,
values,
errors
}) => {
return (
<form onSubmit={handleSubmit}>
<div>
<label>Company</label>
<Field name="company" component="input" />
</div>
<div className="buttons">
<button type="button" onClick={() => push("customers", {})}>
Add Customer
</button>
<button type="button" onClick={() => pop("customers")}>
Remove Customer
</button>
</div>
<FieldArray name="customers" validate={nameValidate}>
{({ fields }) =>
fields.map((name, index) => (
<div key={name}>
<label>Cust. #{index + 1}</label>
<Field
name={`${name}.firstName`}
component={testInput}
placeholder="First Name"
/>
<Field
name={`${name}.lastName`}
component={testInput}
placeholder="Last Name"
/>
<span
onClick={() => fields.remove(index)}
style={{ cursor: "pointer" }}
>
❌
</span>
</div>
))
}
</FieldArray>
<div className="buttons">
<button type="submit" disabled={submitting || pristine}>
Submit
</button>
<button
type="button"
onClick={reset}
disabled={submitting || pristine}
>
Reset
</button>
</div>
<pre>
nameValidate Function:{"\n"}
{JSON.stringify(nameValidate(values.customers), 0, 2)}
</pre>
<pre>
Form errors:{"\n"}
{JSON.stringify(errors, 0, 2)}
</pre>
<pre>
Form values:{"\n"}
{JSON.stringify(values, 0, 2)}
</pre>
</form>
);
}}
/>
Validate Function:
const duplicateName = async value => {
await sleep(400);
if (
~["john", "paul", "george", "ringo"].indexOf(value && value.toLowerCase())
) {
return { firstName: "name taken!" };
}
};
const nameValidate = values => {
if (!values.length) return;
const errorsArray = [];
values.forEach(value => {
if (value) {
let errors = {};
if (!value.firstName) errors.firstName = "First Name Required";
if (Object.keys(errors).length === 0) {
errors = duplicateName(value.firstName);
console.log("errors", errors);
}
errorsArray.push(errors);
}
});
return errorsArray;
};
Okay, you're mixing the validation in a way that's not allowed. You can't embed a Promise inside an array of errors, the whole validation function must return a promise. Here's the fixed version. The await duplicateName() is the important bit.
const nameValidate = async values => {
if (!values.length) return
return await Promise.all(
values.map(async value => {
let errors = {}
if (value) {
if (!value.firstName) errors.firstName = 'First Name Required'
if (Object.keys(errors).length === 0) {
errors = await duplicateName(value.firstName)
}
}
console.error(errors)
return errors
})
)
}
so I'm trying to separate redux-form to stateless-component and container-component, but when I'm trying to do syncValidation, for some reason there is no validation called. what am I missing?
the error and warning in the renderField returns undefined.
and I'm planning to move the renderField function from the stateless component
container -
let EditMovie = (props) => {
const { updateMovie, editModal, editModalStateChange, invalid, initialValues, handleSubmit, pristine } = props;
return (
<MovieModal
modalTitle={initialValues.Title}
initialValues= {initialValues}
invalid= {invalid}
validators= {Validators}
exit= {editModalStateChange}
isOpen= {editModal}
handleSubmit= {handleSubmit}
onSubmit= {updateMovie}
pristine={pristine}
/>
);
};
const validate = values => {
const errors = {}
if (!values.Title) {
errors.username = 'Required'
}
return errors
}
const warn = values => {
const warnings = {
Title: 'bla bla'
}
return warnings
}
const mapStateToProps = (state) => ({
initialValues: state.movies.selectedMovie,
editModal: state.movies.editModal,
});
EditMovie = reduxForm({ form: 'editMovie', validate, warn })(EditMovie);
export default connect(mapStateToProps, { editModalStateChange, updateMovie } )(EditMovie);
stateless -
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 MovieModal = (props) => {
const { pristine, handleSubmit, onSubmit, isOpen, exit, validators, invalid, modalTitle } = props;
return (
<Modal visible={isOpen} onClickBackdrop={() => exit()}>
<div className="modal-body">
<form onSubmit={handleSubmit(onSubmit)}>
<div className="form-group">
<Field component={renderField} name="Title" label="Movie Title" />
</div>
<div className="modal-footer">
<button type="button" className="btn btn-secondary" onClick={() => exit()}>close</button>
<button type="submit" className="btn btn-primary" disabled={invalid || pristine}>Save</button>
</div>
</form>
</div>
</Modal>
);
}
you need to provide validate function in Field Component
Well I Found the problem !
the issue was with the Bootstrap Modal module named - react-bootstrap4-modal
when i wrapped MovieModal in the Modal Class and not inside of it, everything worked fine
thanks for the help
I would like to conditionally display errors in my form.
The way formik works is that if you change one field all validations are ran and all errors returned even thought you changed just one.
I would like to display the error only if the field was TOUCHED and I would like a field to be TOUCHED onChange. The first change to the field should make it touched.
At the moment formik is touching fields just on submit. How would I be able to touch it onChange?
This is my current form:
const optionsForSelect = (collection) => {
return collection.map(item => ({
value: item.id,
label: item.name
}))
}
const validationSchema = yup.object().shape({
length: yup
.number()
.min(1, 'Length should be a positive non-zero integer')
.required(),
frame_rate: yup
.string()
.required()
})
class SpecificationsForm extends React.PureComponent {
render() {
const {
values,
handleChange,
handleInputChange,
handleSelectChange,
handleBlur,
errors,
touched
} = this.props;
const debouncedHandleChange = debounce(handleChange, 200)
console.log(errors)
console.log('TOUCHED')
console.log(touched)
return (
<div className="panel panel-default specifications-panel" id="js-turbosquid-product-specifications-panel">
<div className="panel-heading">
<a href="#" className="js-more-info" data-toggle="collapse" data-target="#specifications-panel-instructions" tabIndex="-1">
Specifications
<i className="fa fa-question-circle" />
</a>
</div>
<div className="panel-body panel-collapse collapse in" id="specification-panel-body">
<div className="panel-body-container">
<div id="specifications-panel-instructions" className="panel-instructions collapse" />
<div className="row">
<div className="col-xs-6">
<PanelInputField
label='Length'
value={ values.length }
onChange={ (e) => handleInputChange(e, debouncedHandleChange) }
formName='turbosquid_product_form_length'
fieldName='length'
/>
<div className="form-field-error">{errors.length ? errors.length : "No Error"}</div>
<PanelSelectField
label='Frame Rate'
value={ values.frame_rate }
onChange={ ({value}) => handleSelectChange('frame_rate', value) }
formName='turbosquid_product_form_frame_rate'
fieldName='frame_rate'
options={ optionsForSelect(frameRateDropdownData) }
searchable={ false }
clearable={ false }
/>
</div>
<div className="col-xs-6">
<PanelCheckBox
label='Biped'
checked={ values.biped }
onChange={ (e) => handleInputChange(e, debouncedHandleChange) }
fieldName='biped'
formName='turbosquid_product_form_biped'
/>
<PanelCheckBox
label='Loopable'
checked={ values.loopable }
onChange={ (e) => handleInputChange(e, debouncedHandleChange) }
fieldName='loopable'
formName='turbosquid_product_form_loopable'
/>
</div>
</div>
</div>
</div>
</div>
)
}
}
const ProductSpecificationsMotionCapturePanel = withFormik({
validationSchema,
enableReinitialize: true,
mapPropsToValues: (props) => (props),
handleInputChange: (props) => (props.handleInputChange),
handleSelectChange: (props) => (props.handleSelectChange),
})(SpecificationsForm)
export default ProductSpecificationsMotionCapturePanel
To touch a Formik field onChange, you can do this:
<Formik
initialValues={initialValues}
onSubmit={(values) => {
//submit form
}}>
{({ setFieldTouched, handleChange }) => {
return (
<Form>
<Field
name="type"
onChange={e => {
setFieldTouched('type');
handleChange(e);
}} />
</Form>
)
}}
Hi I think it's not doable onChange but you can do so when the input is blurred and you need to use the handleBlur function: onBlur={handleBlur}.
Also errors being an object you can display it only when a given [input name] has one.
Take a look at when validations are ran here in the docs: https://jaredpalmer.com/formik/docs/guides/validation#when-does-validation-run
A workaround would be to use formik's method getFieldMeta and pass your field's name and call the value prop which isn't null when you type something.
errorMessage={
formikProps.getFieldMeta("username").value
? formikProps.errors.username
: ""
}
It's possible to set the touched value without invoking validation again and one can do so by using the useFormik hook available in React 18+.
import { useFormik } from "formik";
const Component = () => {
const { setFieldTouched, handleChanged } = useFormik({
validateOnChange: true,
validateOnBlur: true,
validateOnMount: true,
});
const handleInput = (e) => {
setFieldTouched(e.target.name, true, false);
handleChanged && handleChanged(e);
};
return <input name="email" onInput={handleInput} />;
};
I'm working to use a redux-form FieldArray. I'm having challenges connecting the form w the actual react component. When I try to submit the form, I get the following error: error 'handleSubmit' is not defined
The file is below. Am I building this form correctly in my React component? Any idea why handleSubmit is erroring?
import React from 'react';
import {Field, FieldArray, reduxForm} from 'redux-form'
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import * as inviteActions from '../../actions/inviteActions';
let renderEmailField = ({input, label, type, meta: {touched, error}}) =>
<div>
<label>{label}</label>
<div>
<input {...input} name="email" type="email"/>
{touched && error && <span>{error}</span>}
</div>
</div>
let renderEmails = ({fields, meta: {submitFailed, error}}) =>
<ul>
<li>
<button type="button" onClick={() => fields.push()}>Add Email</button>
</li>
{fields.map((email, index) =>
<li key={index}>
{index > 2 && <button
type="button"
title="Remove Email"
onClick={() => fields.remove(index)}
/>}
<Field
name={email}
component={renderEmailField}
label={`Email #${index + 1}`}
/>
</li>
)}
{submitFailed && error && <li className="error">{error}</li>}
</ul>
let EmailsForm = ({handleSubmit, pristine, reset, submitting}) =>
<form onSubmit={handleSubmit}>
<FieldArray name="emails" component={renderEmails} />
<div>
<button type="submit" disabled={submitting}>Submit</button>
<button type="button" disabled={pristine || submitting} onClick={reset}>
Clear Values
</button>
</div>
</form>
class InvitePage extends React.Component {
handleSubmit(data) {
console.log(data)
this.props.actions.createInvites(data);
}
render() {
return (
<div>
<h1>Invites</h1>
<EmailsForm onSubmit={handleSubmit}/>
</div>
);
}
}
EmailsForm = reduxForm({
form: 'emailsForm',
initialValues: {
emails: ['', '', '']
},
validate(values) {
const errors = {}
if (!values.emails || !values.emails.length) {
errors.emails = {_error: 'At least one email must be entered'}
}
else {
let emailsArrayErrors = []
values.emails.forEach((email, emailIndex) => {
const emailErrors = {}
if (email == null || !email.trim()) {
emailsArrayErrors[emailIndex] = 'Required'
}
})
if (emailsArrayErrors.length) {
errors.emails = emailsArrayErrors
}
}
return errors
}
})(EmailsForm)
const mapStateToProps = state => {
return {
currentUser: state.currentUser
};
};
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(inviteActions, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(InvitePage);
You have not provided onSubmit function as a prop to EmailsForm. You can pass it in two ways:
EmailsForm = reduxForm({
form: 'emailsForm',
...
onSubmit: () => {}
...
})(EmailsForm)
or you can pass onSubmit as a prop while calling EmailsForm
<EmailsForm onSubmit={() => {}} />
In your case, you have to write like this:
<EmailsForm onSubmit={this.handleSubmit.bind(this)}/>
According to me, if you can re-use these small components renderEmailField, renderEmails, EmailsForm, then you can create them as a separate component as you have done now.
I would recommend, you should not separate EmailsForm from InvitePage class as you will have to pass all props from InvitePage to EmailsForm as your requirement grows. InvitePage is not serving any other purpose as of now other than passing onSubmit function.
class InvitePage extends React.Component {
handleSubmit = data => {
console.log(data)
this.props.actions.createInvites(data);
}
render() {
const { pristine, reset, submitting } = this.props
return (
<div>
<h1>Invites</h1>
<form onSubmit={this.handleSubmit}> // react recommends we should not bind function here. Either bind that in constructor or write handleSubmit like I have written.
<FieldArray name="emails" component={renderEmails} />
<div>
<button type="submit" disabled={submitting}>Submit</button>
<button type="button" disabled={pristine || submitting} onClick={reset}>
Clear Values
</button>
</div>
</form>
</div>
)
}
}
InvitePage = reduxForm({
form: 'emailsForm',
initialValues: {
emails: ['', '', ''],
},
validate(values) {
...
}
})(InvitePage)
Hope it works.
Try to use const rather than let if you are not changing the value of any variable.
You have to explicitly pass a callback function to handleSubmit like this.
render() {
const { handleSubmit } = this.props;
return (
<form onSubmit={handleSubmit(this.onSubmit.bind(this))}>
<Field name="firstName" component={this.renderField} type="text" className="curvedBorder" label="First Name" />
...
<button type="submit" className="btn btn-primary">Submit</button>
</form>
);
}
onSubmit(values) {
// handle form submission here.
console.log(values);
}
Hope this helps. Happy coding!