How to change a field in a redux-form? - reactjs

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

Related

redux form dynamic categories

i am using redux form and what i do here is when user selects category the next select field should have all subcategories depending of category selected.
what i did was i created api for fetching all categories, i trigger action via componentWillMount and load all categories in first categories select field, then i use formValueSelector of redux-form to get selected category to the state/this.props, then i use componentWillReceiveProps() to trigger fetching subcategories with that e.g. "this.props.categoryId" that i put to state with formValueSelector and that works.
and my question is, is this the right aporoach, is there a better way?
and the second question, is how do i reset categoryChildId field to, lets say blank when the categoryId field is changed?
import React from 'react';
import moment from 'moment';
import { Field, reduxForm, formValueSelector } from 'redux-form';
import { connect } from 'react-redux';
import { Link, NavLink } from 'react-router-dom';
import {CopyToClipboard} from 'react-copy-to-clipboard';
import * as actions from '../../actions/category';
const renderField = ({ input, label, type, meta: { touched, error } }) => (
<div>
<input {...input} placeholder={label} type={type} />
{touched &&
error &&
<div className="error">{error}</div>}
</div>
)
const renderTextArea = ({ input, label, type, meta: { touched, error } }) => (
<div>
<textarea {...input} placeholder={label} type={type} />
{touched &&
error &&
<div className="error">{error}</div>}
</div>
)
class AddProduct extends React.Component {
constructor(props) {
super(props);
this.state = {
value: `${process.env.SITE_URL}/user/${props.user.username}`,
copied: false,
isLoading: false
};
}
componentWillMount() {
this.props.startSetCategories()
}
componentWillReceiveProps(nextProps) {
this.props.startSetCategoryChildren(nextProps.categoryId)
console.log(nextProps)
}
renderCategorySelector = ({ input, meta: { touched, error } }) => {
return (
<div>
<select {...input}>
<option value="">select category</option>
{!this.props.categories ? (
<option value="">loading...</option>
) : (
this.props.categories.map(category => <option value={category._id} key={category._id}>{category.name}</option>)
)
}
</select>
{touched && error && <span>{error}</span>}
</div>
)
}
renderCategoryChildSelector = ({ input, meta: { touched, error } }) => {
return (
<div>
<select {...input}>
<option value="">select sub category</option>
{!this.props.categoryChildren ? (
<option value="">loading...</option>
) : (
this.props.categoryChildren.categoryChildren.map(categoryChild => <option value={categoryChild._id} key={categoryChild._id}>{categoryChild.name}</option>)
)
}
</select>
{touched && error && <span>{error}</span>}
</div>
)
}
submitForm = values => {
console.log(values)
}
render() {
const username = localStorage.getItem('username');
const { user } = this.props;
const { handleSubmit, pristine, submitting, categoryId } = this.props;
return (
<div className="profile-wrapper">
<div className="profile">
<form className="profile-addproduct-left" onSubmit={handleSubmit(this.submitForm.bind(this))}>
<div className="profile-addproduct-title">
<h2>New Product</h2>
<p>Fill out the form.</p>
</div>
<div className="profile-form-group">
<div className="profile-form-item">
<p>Title</p>
<Field
name="title"
type="text"
label="title of a product"
component={renderField}
/>
</div>
<div className="profile-form-item">
<p>Category</p>
<Field
name="categoryId"
type="text"
component={this.renderCategorySelector}
label="category"
/>
{this.props.categoryId ?
<Field
name="categoryChildId"
type="text"
component={this.renderCategoryChildSelector}
label="categoryChild"
/> :
''
}
</div>
<div className="profile-form-item">
<p>Description</p>
<Field
name="description"
type="text"
label="Write some interesting..."
component={renderTextArea}
/>
</div>
</div>
<div className="profile-addproduct-form-submit">
<button className="button button--register" type="submit" disabled={this.state.isLoading || pristine}>Submit New Product</button>
</div>
</form>
</div>
</div>
)
}
};
AddProduct = reduxForm({
form: 'addproduct-form'
})(AddProduct)
const selector = formValueSelector('addproduct-form')
AddProduct = connect(state => {
const categoryId = selector(state, 'categoryId')
return {
categoryId,
categories: state.category.categories,
categoryChildren: state.category.categoryChildren
}
}, actions)(AddProduct)
export default AddProduct
You should not call the startSetCategoryChildren (or any other api) in componentWillReceiveProps... because it would call every time whenever componentWillReceiveProps called
componentWillReceiveProps(nextProps) {
this.props.startSetCategoryChildren(nextProps.categoryId)
console.log(nextProps)
}
Instead of this you can to do this on handleChange of the Field
<Field
name="categoryId"
type="text"
component={this.renderCategorySelector}
label="category"
onChange={(e) => this.props.startSetCategoryChildren(e.target.value)}
/>

Programatically changing a FieldArray value in Redux-Form

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

Control the Redux-form with the button

I am using the Redux-form to do a task.
This form in a form container.
In the form container or in the form component.
There are two buttons. An add button and a subtract button.
The form component is:
import React from 'react'
import { Field, reduxForm } from 'redux-form'
import TextField from 'material-ui/TextField'
import RaisedButton from 'material-ui/RaisedButton'
const renderTextField = ({ input, label, meta: { touched, error }, ...custom }) => (
<TextField hintText={label}
floatingLabelText={label}
errorText={touched && error}
{...input}
{...custom}
/>
)
const ActivityDetailForm = props => {
const { handleSubmit, pristine, reset, submitting,} = props
return (
<form onSubmit={handleSubmit}>
<div>
<RaisedButton
type="submit"
disabled={pristine || submitting}
label="saveChange"
fullWidth={true}
secondary={true}
/>
</div>
</form>
)
}
export default reduxForm({
form: 'ActivityDetailForm', // a unique identifier for this form
})(ActivityDetailForm)
Now, I face a problem. When I click the add button,
<div>
<Field name="field1" component={renderTextField} label="text1: "/>
</div>
the code above will be created in the form element.
When I click the add button again, the div element which includes the Field named field2 will be created in the form element.
... Field named field3
... Field named field4
... Field named field5
...
When I click the subtract button. The last Field element will be destroyed.
Do you know the method to solve this problem?
The following (untested) is a pretty basic example on how to achieve dynamic inputs with a FieldArray. You'd have to tweak this a bit to tailor it to your specific scenario.
const renderTextField = ({ input, label, meta: { touched, error }, ...custom }) => (
<TextField hintText={label}
floatingLabelText={label}
errorText={touched && error}
{...input}
{...custom}
/>
)
const ActivityDetailForm = props => {
const { handleSubmit, pristine, reset, submitting,} = props
const renderFieldArray = ({ fields }) => (
<div>
<div>
<RaisedButton
onTouchTap={() => fields.push({})}
label="Add"
/>
</div>
{fields.map((field, index) => (
<div>
<div key={index}>
<Field
name={`${field}.name`}
label={`Text ${index + 1}`}
component={renderTextField}
/>
</div>
<div>
<RaisedButton
onTouchTap={() => fields.remove(index)}
label="Remove"
/>
</div>
</div>
))}
</div>
);
return (
<form onSubmit={handleSubmit}>
<div>
<FieldArray
name="textFields"
component={renderFieldArray}
/>
<RaisedButton
type="submit"
disabled={pristine || submitting}
label="saveChange"
fullWidth={true}
secondary={true}
/>
</div>
</form>
)
}
export default reduxForm({
form: 'ActivityDetailForm', // a unique identifier for this form
})(ActivityDetailForm)

unable to set defaultValue in redux-form-material-ui TextField

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>

Redux form with self creating inputs

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/

Resources