I'm trying to create a redux form (using redux-form) that can dynamically create it's own inputs. I am having trouble figuring out how to make redux-form aware of the new fields that have been created. Is it possible to dynamically change the fields prop that redux-form passes in within the form component itself? Am I thinking about this wrong? Here is what I am working with.
class AddCustomer extends Component {
render() {
class Form extends Component {
constructor(props, context) {
super(props, context)
this.state = {
inputsToAdd: []
};
}
handleAddInput() {
let inputsToAdd = this.state.inputsToAdd.slice();
inputsToAdd.push(this.state.inputsToAdd.length);
this.setState({ inputsToAdd });
}
submitForm(data) {
console.log(data)
this.setState({inputsToAdd: []});
this.props.dispatch(initialize('addCustomer', {}))
}
render() {
const { fields, handleSubmit } = this.props;
return (
<div>
<form onSubmit={handleSubmit(this.submitForm.bind(this))}>
<Input type='text' label='Company name' {...fields['companyName']}/>
<Input type='email' label='Admin user email' {...fields['adminEmail']}/>
</form>
{this.state.inputsToAdd.map((element, index) => {
return (
<Input key={index} type='text' />
)
})}
<Button onClick={() => this.handleAddInput()}>Add Input</Button>
<Button onClick={handleSubmit(this.submitForm.bind(this))}>Submit</Button>
</div>
)
}
}
Form = connectReduxForm({
form: 'addCustomer',
fields: ['companyName', 'adminEmail']
})(Form);
return (
<div>
<h1>Add Customer</h1>
<Form />
</div>
)
}
}
As of Redux Form 6.* you can achieve what you are trying to do using <FieldArray/>
See the example below (taken from Redux documentation and slightly simplified)
import React from 'react'
import { Field, FieldArray, reduxForm } from 'redux-form'
import validate from './validate'
const renderMembers = ({ fields, meta: { touched, error } }) => (
<ul>
<li>
<button type="button" onClick={() => fields.push({})}>Add Member</button>
{touched && error && <span>{error}</span>}
</li>
{fields.map((member, index) =>
<li key={index}>
<button
type="button"
title="Remove Member"
onClick={() => fields.remove(index)}/>
<h4>Member #{index + 1}</h4>
<Field
name={`${member}.firstName`}
type="text"
component={renderField}
label="First Name"/>
<Field
name={`${member}.lastName`}
type="text"
component={renderField}
label="Last Name"/>
</li>
)}
</ul>
)
const FieldArraysForm = (props) => {
const { handleSubmit, submitting } = props
return (
<form onSubmit={handleSubmit}>
<FieldArray name="members" component={renderMembers}/>
<div>
<button type="submit" disabled={submitting}>Submit</button>
</div>
</form>
)
}
export default reduxForm({
form: 'fieldArrays', // a unique identifier for this form
validate
})(FieldArraysForm)
For more info checkout the documentation
http://redux-form.com/6.1.1/examples/fieldArrays/
Related
In my react component I am trying to set a field called 'total'. I have imported the change action as a prop into my component:
import React, { Component, Fragment } from 'react'
import { Field, FieldArray, reduxForm, getFormValues, change } from 'redux-form'
import { connect } from 'react-redux'
import { CalcTotal } from './calculationHelper';
const renderField = ({ input, label, type, meta: { touched, error } }) => (
<div>
<label>{label}</label>
<div>
<input {...input} type={type} placeholder={label} />
{touched && error && <span>{error}</span>}
</div>
</div>
)
const renderMods = ({ fields, meta: { error, submitFailed } }) => (
<Fragment>
<ul>
<li>
<button type="button" onClick={() => fields.push({})}>
Add Modification
</button>
{submitFailed && error && <span>{error}</span>}
</li>
{fields.map((mod, index) => (
<li key={index}>
<button
type="button"
title="Remove Mod"
onClick={() => fields.remove(index)}
/>
<h4>Mod #{index + 1}</h4>
<Field
name={`${mod}.lastYear`}
type="number"
component={renderField}
label="Last Year"
/>
<Field
name={`${mod}.currentYear`}
type="number"
component={renderField}
label="Current Year"
/>
<Field name={`${mod}.type`} component="select" label="Type">
<option />
<option value="-">Expense</option>
<option value="+">Income</option>
<option value="-">Tax</option>
</Field>
</li>
))}
</ul>
<Field
name="total"
type="number"
component="input"
label="Total modifications"
text="0"
/>
</Fragment>
)
class FieldArraysForm extends Component {
render() {
const { handleSubmit, formValues, change } = this.props
if (formValues) {
console.log('formvalues', formValues);
const test = CalcTotal(2000);
console.log('calc=', test);
debugger
this.props.change('fieldArraysForm', 'total', 5000)
}
return (
<form onSubmit={handleSubmit}>
{/* <button onClick={this.changeStuff}>set total</button> */}
<FieldArray name="mods" component={renderMods} />
<div>
<button type="submit" >
Submit
</button>
</div>
</form>
)
}
}
const mapStateToProps = (state) => ({
formValues: getFormValues('fieldArraysForm')(state),
});
const mapDispatchToProps = {
change
};
// const Example = reduxForm({
// form: 'fieldArraysForm', // a unique identifier for this form
// })(FieldArraysForm)
// const ConnectedForm = connect(
// mapStateToProps,
// mapDispatchToProps,
// )(Example);
// export default ConnectedForm
export default reduxForm({
form: "fieldArraysForm"
})(
connect(
mapStateToProps,
mapDispatchToProps
)(FieldArraysForm)
);
The line where the code fall into an infinite loop:
this.props.change('fieldArraysForm', 'total', 5000)
How /where do I put this statement to make sure the 'total' field is changed and not get into a loop?Which React lifecycle event would suit? I want to fire this whenever there is a form change on any field.
You'll need to move your statement out of the render method and into the componentDidUpdate lifecycle method (you also need an if statement to prevent an infinite loop):
componentDidUpdate(prevProps) {
if (this.props.someValue !== prevProps.someValue) {
this.props.change("formName", "formField", "newFormValue");
}
}
Working example: https://codesandbox.io/s/r5zz36lqnn (selecting the Has Email? radio button populates the email field with test#example.com, unselecting the radio button resets the email field to "")
I'm trying to figure out how to use this.props.dispatch(change) in order use one field selector value to update another fields value within a FieldArray.
https://codesandbox.io/s/2p7k7jn930
I can't seem to get the syntax in this correct.
this.props.dispatch(
change("FieldArraysForm", `${props.member}.firstName`, this.props.hasLastName)
);
Any thoughts? Expected behavior would be to add a member and then have anything typed into the Last Name field be programmatically updated in the First Name field.
/**
The following can replace the file in the Field Arrays example
(https://github.com/erikras/redux-form/tree/master/examples/fieldArrays) to demonstrate this functionality.
**/
import React from "react";
import { connect } from "react-redux";
import {
Field,
FieldArray,
reduxForm,
formValueSelector,
change
} from "redux-form";
import validate from "./validate";
const selector = formValueSelector("fieldArrays");
const renderField = ({ input, label, type, meta: { touched, error } }) => (
<div>
<label>{label}</label>
<div>
<input {...input} type={type} placeholder={label} />
{touched && error && <span>{error}</span>}
</div>
</div>
);
class Member extends React.Component {
componentDidUpdate(prevProps, props) {
if (this.props.hasLastName !== prevProps.hasLastName) {
this.props.dispatch(
change("FieldArraysForm", `${props.member}.firstName`, this.props.hasLastName)
);
}
}
render() {
const { member, index, fields, hasLastName } = this.props;
return (
<li key={index}>
<button
type="button"
title="Remove Member"
onClick={() => fields.remove(index)}
/>
<h4>Member #{index + 1}</h4>
<Field
name={`${member}.firstName`}
type="text"
component={renderField}
label="First Name"
/>
<Field
name={`${member}.lastName`}
type="text"
component={renderField}
label="Last Name"
/>
{hasLastName && <p>{hasLastName}</p>}
</li>
);
}
}
Member = connect((state, props) => ({
hasLastName: selector(state, `${props.member}.lastName`)
}))(Member);
const renderMembers = ({ fields, meta: { touched, error } }) => (
<ul>
<li>
<button type="button" onClick={() => fields.push({})}>
Add Member
</button>
{touched && error && <span>{error}</span>}
</li>
{fields.map((member, index) => (
<Member member={member} fields={fields} index={index} key={index} />
))}
</ul>
);
const FieldArraysForm = props => {
const { handleSubmit, pristine, reset, submitting } = props;
return (
<form onSubmit={handleSubmit}>
<Field
name="clubName"
type="text"
component={renderField}
label="Club Name"
/>
<FieldArray name="members" component={renderMembers} />
<div>
<button type="submit" disabled={submitting}>
Submit
</button>
<button type="button" disabled={pristine || submitting} onClick={reset}>
Clear Values
</button>
</div>
</form>
);
};
export default reduxForm({
form: "fieldArrays", // a unique identifier for this form
validate
})(FieldArraysForm);
In order to access the FieldArray dynamically, you would want to change this:
change("FieldArraysForm", `${props.member}.firstName`, this.props.hasLastName)
to this:
change("FieldArraysForm", `members[${this.props.index}].firstName`, this.props.hasLastName)
Also, pass in the form selector specified:
const selector = formValueSelector("fieldArrays");
This would give you:
change("fieldArrays", `members[${this.props.index}].firstName`, this.props.hasLastName)
Had to get some help on this one - thanks goes to #Amanda Field.
You need to specify the index of the field in FieldArray you want to change. To do so, just use <fieldName>.<index>.<propertyName>, for instance:
this.props.change('members.0.firstName', 'Donald')
where member is the name of your FieldArray field, 0 is the index of the item in the array you want to change and firstName is the property of the object.
See sandbox here
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
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!
I'm unable to set a default value for the textField component.
I tried using default value, but as long as I'm using the redux-form-material-ui it just doesn't work.
I really don't understand what am I doing wrong (seems pretty basic)...
exmaple (just changed their fieldArray example a little):
import React from 'react'
import { Field, FieldArray, reduxForm } from 'redux-form'
import validate from './validate'
import {TextField} from 'redux-form-material-ui'
const renderField = (props) => {
console.log(props);
const { input, label, type, meta: { touched, error } } = props;
console.log(input);
return <div>
<label>{label}</label>
<div>
// Will not show "someValue", it will just be blank
<TextField defaultValue="someValue" {...input} type={type} placeholder={label}/>
{touched && error && <span>{error}</span>}
</div>
</div>
}
const renderMembers = ({ fields, meta: { touched, error, submitFailed } }) => (
<ul>
<li>
<button type="button" onClick={() => fields.push({})}>Add Member</button>
{(touched || submitFailed) && error && <span>{error}</span>}
</li>
{fields.map((member, index) =>
<li key={index}>
<button
type="button"
title="Remove Member"
onClick={() => fields.remove(index)}/>
<h4>Member #{index + 1}</h4>
<Field
name={`${member}.firstName`}
type="text"
component={renderField}
label="First Name"/>
<Field
name={`${member}.lastName`}
type="text"
component={renderField}
label="Last Name"/>
<FieldArray name={`${member}.hobbies`} component={renderHobbies}/>
</li>
)}
</ul>
)
const renderHobbies = ({ fields, meta: { error } }) => (
<ul>
<li>
<button type="button" onClick={() => fields.push()}>Add Hobby</button>
</li>
{fields.map((hobby, index) =>
<li key={index}>
<button
type="button"
title="Remove Hobby"
onClick={() => fields.remove(index)}/>
<Field
name={hobby}
type="text"
component={renderField}
label={`Hobby #${index + 1}`}/>
</li>
)}
{error && <li className="error">{error}</li>}
</ul>
)
const FieldArraysForm = (props) => {
const { handleSubmit, pristine, reset, submitting } = props
return (
<form onSubmit={handleSubmit}>
<Field name="clubName" type="text" component={renderField} label="Club Name"/>
<FieldArray name="members" component={renderMembers}/>
<div>
<button type="submit" disabled={submitting}>Submit</button>
<button type="button" disabled={pristine || submitting} onClick={reset}>Clear Values</button>
</div>
</form>
)
}
export default reduxForm({
form: 'fieldArrays', // a unique identifier for this form
validate
})(FieldArraysForm)
Thanks.
I was having the same issue. Just read through the documentation again and saw this
No Default Values
Because of the strict "controlled component" nature of redux-form, some of the Material UI functionality related to defaulting of values has been disabled e.g. defaultValue, defaultDate, defaultTime, defaultToggled, defaultChecked, etc. If you need a field to be initialized to a certain state, you should use the initialValues API of redux-form.
It's probably only one way to make form of editing.
It cannot be restored from initial redux Form values.
Input
export default function ({ input, label, meta: { touched, error }, ...custom }) {
if ( input.value === '' && custom.cvalue ) { // hack for redux form with material components
input.onChange(String(custom.cvalue));
}
return (
<TextField
{...input}
{...custom}
fullWidth={true}
hintText={label}
floatingLabelText={label}
errorText={touched && error}
/>
)
}
Select
export default function ({ input, label, meta: { touched, error }, onChange, children, ...custom }) {
if ( input.value === '' && custom.cvalue ) { // hack for redux form with material components
if ( is.function(onChange) ) {
onChange(custom.cvalue);
}
input.onChange(custom.cvalue);
}
return (
<SelectField
{...input}
{...custom}
fullWidth={true}
children={children}
floatingLabelText={label}
errorText={touched && error}
onChange={(event, index, value) => {
if ( is.function(onChange) ) { // and custom onChange for f....g ....
value = onChange(value);
}
input.onChange(value);
}}/>
)
}
then in template
its can be way to make form of editing existing entity ....
<Field
name="study"
label="Studies"
component={ FormSelect }
cvalue={this.state.study.id}
onChange={value => {
this.setState({study: _.find(studies, {id: value})||{id: 0}});
return value;
}}>
{studies.map( (study, key) => ( <MenuItem key={key} value={study.id} primaryText={study.officialTitle} /> ))}
</Field>