Redux-Form 7 Validation - Functional Component - reactjs

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

Related

I am unable to populate the form with initial values from the state

I am unable to populate the form fields with the supplied initial values. Got
stuck.
I am using "react": "^16.2.0" and redux-form": "7.4.0". Please help me on where I got stuck in the code. Even I tired hardcoded the initialValues but still no luck.
Can somebody please help me?
This is my code. Spent most of the time for this.
import React from 'react'
import { connect } from 'react-redux'
import { Field, reduxForm } from 'redux-form'
import { SubmissionError } from 'redux-form'
import { addStore } from "../actions/storeActions.js";
import { loadStore } from "../actions/storeActions.js";
//import submit from './submit'
import './bundle.css';
const required = value => (value ? undefined : 'Required')
const phoneNumber = value =>
value && !/^(0|[1-9][0-9]{9})$/i.test(value)
? 'Invalid phone number, must be 10 digits'
: undefined
const number = value =>
value && isNaN(Number(value)) ? 'Must be a number' : undefined
const renderField = ({ input, label, type, value, meta: { touched, error } }) => (
<div>
<label>{label}</label>
<div>
<input {...input} placeholder={label} type={type} value={value} />
{touched && error && <span>{error}</span>}
</div>
</div>
)
function throwError(msg) {
throw new SubmissionError({
_error: msg
})
}
function submit(values, props) {
console.log(values, props);
let msg;
if ((msg = required(values.storeName)) !== undefined) {
throwError("Store name is required")
} else if ((msg = required(values.address)) !== undefined) {
throwError("Address is required")
} else if ((msg = required(values.phone)) !== undefined) {
throwError("Phone number is required")
} else if ((msg = phoneNumber(values.phone)) !== undefined) {
throwError(msg)
} else {
props.dispatch(addStore(values, props.router));
}
//})
}
let StoreForm = props => {
const { error, handleSubmit, pristine, reset, submitting, } = props
return (
<form onSubmit={handleSubmit(values => { submit(values, props) })}>
<Field
name="storeName"
component={renderField}
type="text"
placeholder="Store Name"
label="Store Name"
/>
<Field
name="address"
component={renderField}
type="text"
placeholder="Address"
label="Address"
/>
<Field
name="description"
component={renderField}
type="text"
placeholder="Description"
label="Description"
/>
<Field
name="phone"
component={renderField}
type="text"
placeholder="Phone"
label="Phone"
/>
{error && <strong>{error}</strong>}
<div>
<button type="submit" disabled={submitting}>
Log In
</button>
<button type="button" disabled={pristine || submitting} onClick={reset}>
Clear Values
</button>
</div>
</form>
)
}
const mapDispatchToProps = dispatch => {
return {
initialValues: () => dispatch(loadStore())
};
};
const mapStateToProps = (state) => {
return {
initialValues: state.storeReducer.items
}
};
StoreForm = reduxForm({
form: 'initializeFromState' // a unique identifier for this form
})(StoreForm)
StoreForm = connect(
state => ({
initialValues: { storeName: "SURSH" },
})
)(StoreForm)
export default StoreForm
you havn't pass value in your Field components
It's caused by wrong usage of value in your renderField function.
As you can see in the official props docs, the value is part of the input property.
You have to refactor the function in the following way:
const renderField = ({ input, label, type, meta: { touched, error } }) => (
<div>
<label>{label}</label>
<div>
<input {...input} placeholder={label} type={type} />
{touched && error && <span>{error}</span>}
</div>
</div>
)

FieldArray re-registering fields after each char-input

Hi I'm quite new to React and I'm trying to learn how to work with redux-forms (https://redux-form.com/7.3.0/) and it mostly works, but I am encountering an issue which I cant solve. The issue is the same as this one: https://github.com/erikras/redux-form/issues/2412
The issue in short:
Whenever i try to write something in an input-field, my form constantly re-registers the field, which makes the form almost impossible to work with. The bad behavior happens because of validation of inputs.
Although Marnusw seems to have solved it with this comment:
<Field ... validate={[required, minLength(6)]}/>
Every time the form was rendered a new minLength instance would be
created which caused the field to UNREGISTER_FIELD and then
REGISTER_FIELD again.
The minLength instance should be created once outside of the render
function:
const minLength6 = minLength(6)
<Field ... validate={[required, minLength6]}/>
However I am not connecting my validation to the component like he is, so I am unsure of how I can declare my validation outside the render() function in my code.
I have tried doing:
...(imports)
const validation = validate //declaring a const and assigning 'validate' to it
//instead of directly using validate.
MenuInputFields = reduxForm({
form: 'inputItemList',
validation
})(MenuInputFields)
But this disabled validation alltogether, which (of course) also solves the problem, but this is not a solution, since I need to validate inputs.
My component:
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { reduxForm, Field, FieldArray } from 'redux-form'
import { menuSelector } from '../reducers/menuViewReducer'
import { postMenuRequest } from '../sagas/adminMenuSaga'
import validate from './validateForm'
const mapDispatchToProps = (dispatch) => {
return {
submitForm: (newMenu) => dispatch(postMenuRequest(newMenu))
}
}
export class MenuInputFields extends React.Component {
constructor(props){
super(props)
}
render() {
let currentDate = new Date()
let formattedDate = currentDate.toLocaleDateString('en-GB')
const renderField = ({ input, label, type, className, meta: { touched, error } }) => (
<div>
<input {...input} type={type} placeholder={label} className={className} />
{touched && error && <span>{error}</span>}
</div>
);
const renderMenuItem = ({fields, meta: {touched, error, submitFailed }}) => (
<ul className='adminInputFoodItem'>
{fields.map((MenuItem, index) => (
<li key={index}>
<p className='col-md-1'> {index+1} - </p>
<Field
name={`${MenuItem}.FoodItem`}
type='text'
component={renderField}
label='Titel'
className='col-md-2'
/>
<Field
name={`${MenuItem}.Description`}
type='text'
component={renderField}
label='Beskrivelse'
className='col-md-6'
/>
<Field
name={`${MenuItem}.Price`}
type='number'
component={renderField}
label='Pris'
className='col-md-1'
/>
<button
type='button'
title='Fjern'
onClick={() => fields.remove(index)}
className='btn btn-default btn-xs remItemRow'
>Fjern
</button>
</li>
))}
<li>
</li>
<li>
<button
type='button'
onClick={() => fields.push({})}
className='btn btn-default btn-sm addItemRow'
>Tilføj
</button>
{submitFailed && error && <span>{error}</span>}
</li>
</ul>
);
const { handleSubmit, pristine, reset, submitting, submitForm } = this.props
return (
<form className='adminInputForm' onSubmit={handleSubmit(submitForm)}>
<label className='col-md-2' >Dato: {formattedDate}</label>
<br />
<br />
<ul className='adminInputFoodItem'>
<li>
<label className='col-md-1'> Lukketid: </label>
<Field
name='ClosingTime'
type='text'
component={renderField}
label='TT:mm'
className='col-md-1'
/>
</li>
</ul>
<hr />
<div>
<label className='col-md-1'> # </label>
<label className='col-md-2'> Titel </label>
<label className='col-md-3'> Beskrivelse </label>
<label className='col-md-1'> Pris </label>
</div>
<br />
<br />
<FieldArray name='MenuItems' component={renderMenuItem} />
<br />
<div className=''>
<button
className='btn btn-default'
type='submit'
disabled={pristine || submitting}
>Gem Menu
</button>
<label>Submit besked her</label>
</div>
</form>
)
}
}
MenuInputFields = reduxForm({
form: 'inputItemList',
validate,
})(MenuInputFields)
export default connect(mapStateToProps, mapDispatchToProps)(MenuInputFields)
validation.js
//regex patterns to match against
const timeFormat = /^([0-9]|0[0-9]|1[0-9]|2[0-3]):[0-5][0-9]$/
const specialCharsTextInput = /^[^|<>\"&]*$/
const emptyTextInput = /^$/
//Error texts
const requiredErrText = 'Required'
const timeErrText = ' - Forkert input - Format: TT:MM'
const noItemsErrText = { _error: 'Tilføj mindst 1 menu' }
const validate = values => {
const errors = {}
if (!values.ClosingTime || emptyTextInput.test(values.ClosingTime)) {
errors.ClosingTime = requiredErrText
}
if(values.ClosingTime && !timeFormat.test(values.ClosingTime)){
errors.ClosingTime = timeErrText
}
if (!values.MenuItems || !values.MenuItems.length) {
errors.MenuItems = noItemsErrText
} else {
const MenuItemsArrayErrors = []
values.MenuItems.forEach((MenuItem, MenuItemIndex) => {
const MenuItemErrors = {}
if (!MenuItem || !MenuItem.FoodItem || !specialCharsTextInput.test(MenuItem.FoodItem) || !emptyTextInput.test(MenuItem.FoodItem)) {
MenuItemErrors.FoodItem = requiredErrText
MenuItemsArrayErrors[MenuItemIndex] = MenuItemErrors
} else if (!MenuItem || !MenuItem.Description || !specialCharsTextInput.test(MenuItem.Description) || !emptyTextInput.test(MenuItem.Description)) {
MenuItemErrors.Description = requiredErrText
MenuItemsArrayErrors[MenuItemIndex] = MenuItemErrors
} else if (!MenuItem || !MenuItem.Price || !specialCharsTextInput.test(MenuItem.Price) || !emptyTextInput.test(MenuItem.Price)) {
MenuItemErrors.Price = requiredErrText
MenuItemsArrayErrors[MenuItemIndex] = MenuItemErrors
}
})
if (MenuItemsArrayErrors.length) {
errors.MenuItems = MenuItemsArrayErrors
}
}
return errors
}
export default validate

pristine function not working redux form

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);

Redux-form FieldArray, handleSubmit' is not defined

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!

redux-form (version 6.4.3) does not display errors

below is the code for the redux-form. Everything works beautifully with the redux store but I can not display the error messages in the span element.
import React from 'react'
import { Button } from 'react-bootstrap'
import { Field, reduxForm } from 'redux-form'
const validate = (values) => {
const errors = {}
if (!values.firstname) {
errors.firstname = 'Required'
}
return errors
}
const renderInput = (field) => (
<div>
<label>{field.placeholder}</label>
<div>
<input {...field.input}/>
{field.error && <span>{field.error}</span>}
</div>
</div>
)
#reduxForm({
form: 'addUserForm',
validate
})
export default class CreateUserForm extends React.Component {
render() {
const {addUser, handleSubmit} = this.props
return (
<form onSubmit={handleSubmit(addUser)}>
<Field type="text" placeholder="First name" component={renderInput} name="firstname" />
<Button type="submit" className="btn btn-success">Submit</Button>
</form>
)
}
}
I can clearly see that the validation function works (see screen shot below)
but there is nothing in the <span></span> element, which means the field.error has no value. I also don't get an error message at all.
Does someone know what's going on here?
Thanks,
Thomas
Your renderInput is incomplete.
The official document shows:
const renderField = ({ input, label, type, meta: { touched, error, warning } }) => (
<div>
<label>{label}</label>
<input {...input} placeholder={label} type={type}/>
{
touched && (
(error && <span>{error}</span>) || (warning && <span>{warning}</span>)
)
}
</div>
)
Observe the object, parameters passed to renderField: meta: { touched, error, warning }
With that regards, shouldn't your renderInput be:
const renderInput = (field) => (
<div>
<label>{field.placeholder}</label>
<div>
<input {...field.input}/>
{field.meta.error && <span>{field.meta.error}</span>}
</div>
</div>
)
Missing => field.meta.error

Resources