Redux form: change other field if validation success - reactjs

I'm using React Redux, Redux-form and reselect library (https://github.com/reactjs/reselect).
I have a component with two Fields: quota and amount. I want to update the amount field based on quota field. For the calculation of amount I'm using a selector with reselect.
I want to update the amount field only if is valid quota.
I have tried without success onChange field props because it's executed before the selector.
The best solution I've found is to use onChange reduxForm() property because it's executed after the selector but I can't run after validation.
This is my code:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { makeModalData } from '../selectors/Modal';
import { change, Field, reduxForm, formValueSelector } from 'redux-form';
const validate = values => {
const errors = {};
if (values.quota <= 1) {
errors.quota = "The quota must be greater than 1";
} else if (values.quota > 50) {
errors.quota = "The quota must be less than 50";
}
return errors;
}
const updateForm = (values, dispatch, props, previousValues) => {
if (props.valid) { // this not work because it shows the previous state of validation
if (values.quota !== previousValues.quota) {
let amount = props.modal_data.amount; // data with reselect
dispatch(change('modal', 'amount', amount));
}
}
}
class Modal extends Component {
constructor(props) {
super(props);
}
renderField = ({ input, label, type, meta: { touched, error } }) => (
<div>
<input {...input} type={type} placeholder={label} />
{touched && error && <span>{error}</span>}
</div>
)
// this not work because is executed before selector
/*
updateAmount(event) {
let amount = this.props.modal_data.amount;
this.props.dispatch(change('modal', 'amount', amount));
}*/
render() {
return (
<label>Quota</label>
<Field
name="quota"
component={this.renderField}
type="number"
label="Quota"
/*onChange={this.updateAmount.bind(this)} */
/>
<label>Amount</label>
<Field
name="amount"
component={this.renderField}
type="number"
label="Amount"
/>
)
}
}
const makeMapStateToProps = () => {
const modal_data = makeModalData(); // selector
const mapStateToProps = state => {
let my_modal_data = modal_data(state, state.modal)
return {
initialValues: {
quota: state.modal.quota,
amount: state.modal.amount,
},
}
}
return mapStateToProps;
}
Modal = reduxForm({
form: 'modal',
enableReinitialize: true,
touchOnChange: true,
validate,
onChange: updateForm
},makeMapStateToProps)(Modal);
Modal = connect(
makeMapStateToProps,
mapDispatchToProps
)(Modal);
export default Modal;

Here's a working CodeSandbox.
The answer is to use formValueSelector and update in componentWillReceiveProps (or componentDidUpdate). Here is the version of the working Modal.js. Notice that the quota validation is reused both in the validation function and also in the calculation.
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { Field, reduxForm, formValueSelector } from 'redux-form'
const validateQuota = quota => {
if (quota <= 1) {
return 'The quota must be greater than 1'
} else if (quota > 50) {
return 'The quota must be less than 50'
}
}
const validate = values => {
const errors = {}
errors.quota = validateQuota(values.quota)
return errors
}
/**
* Arbitrary function that takes quota and calcuates amount.
* For the purposes of this demo, I'm assuming that we're a
* mobile phone company and are charging $19.95 per 5 GB of quota.
*/
const calculateAmount = quota => Math.ceil(quota / 5) * 19.95
class Modal extends Component {
renderField = ({ input, label, type, meta: { touched, error } }) => (
<div>
<input {...input} type={type} placeholder={label} />
{touched && error && <span>{error}</span>}
</div>
)
componentWillReceiveProps(nextProps) {
const { change, quota } = nextProps
if (this.props.quota !== nextProps.quota && !validateQuota(quota)) {
// quota value is valid
change('amount', calculateAmount(quota))
}
}
render() {
return (
<div>
<label>Quota</label>
<Field
name="quota"
component={this.renderField}
type="number"
label="Quota"
/>
<label>Amount</label>
<Field
name="amount"
component={this.renderField}
type="number"
label="Amount"
/>
</div>
)
}
}
const valueSelector = formValueSelector('modal') // <-- form name
const makeMapStateToProps = () => {
const mapStateToProps = state => {
return {
quota: valueSelector(state, 'quota'),
initialValues: {
// Not implementing the modal reducer...
// quota: state.modal.quota,
// amount: state.modal.amount
}
}
}
return mapStateToProps
}
Modal = reduxForm(
{
form: 'modal',
enableReinitialize: true,
validate
},
makeMapStateToProps
)(Modal)
const mapDispatchToProps = undefined // not included in StackOverflow snippet
Modal = connect(makeMapStateToProps, mapDispatchToProps)(Modal)
export default Modal

Related

How can I convert a Class Component which extends another Class component in a Functional Component in ReactJS?

How can I convert a Class Component which extends another Class component in a Functional Component in ReactJS?
input.jsx [Functional Component]
const Input = ({ name, label, error, ...rest }) => {
return (
<div className="mb-3">
<label htmlFor={name} className="form-label">
{label}
</label>
<input
autoFocus
{...rest}
id={name}
name={name}
className="form-control"
/>
{error && <div className="alert alert-danger">{error}</div>}
</div>
)
}
export default Input
form.jsx [Class Component]
import React, { Component } from "react"
import Input from "./input"
import Joi from "joi"
class Form extends Component {
state = {
data: {},
errors: {}
}
validate = () => {
const options = { abortEarly: false }
const schemaJoi = Joi.object(this.schema)
const { error } = schemaJoi.validate(this.state.data, options)
if (!error) return null
const errors = {}
error.details.map(item => (errors[item.path[0]] = item.message))
return errors
}
validateProperty = ({ name, value }) => {
const obj = { [name]: value }
const schema = {
[name]: this.schema[name]
}
const schemaJoi = Joi.object(schema)
const { error } = schemaJoi.validate(obj)
return error ? error.details[0].message : null
}
handleSubmit = e => {
e.preventDefault()
const errors = this.validate()
console.log(errors)
this.setState({ errors: errors || {} })
if (errors) return
this.doSubmit()
}
handleChange = ({ currentTarget: input }) => {
const errors = { ...this.state.errors }
const errorMessage = this.validateProperty(input)
if (errorMessage) errors[input.name] = errorMessage
else delete errors[input.name]
const data = { ...this.state.data }
data[input.name] = input.value
this.setState({ data, errors })
}
renderButton = label => {
return (
<button disabled={this.validate()} className="btn btn-primary">
{label}
</button>
)
}
renderInput = (name, label, type = "text") => {
const { data, errors } = this.state
return (
<Input
name={name}
label={label}
error={errors[name]}
type={type}
value={data[name]}
onChange={this.handleChange}
/>
)
}
}
export default Form
loginForm.jsx [Class Component which extends the other]
import Joi from "joi"
import Form from "./common/form"
class LoginForm extends Form {
state = {
data: { username: "", password: "" },
errors: {}
}
schema = {
username: Joi.string().required().label("Username"),
password: Joi.string().required().label("Password")
}
doSubmit = () => {
console.log("Submitted")
}
render() {
return (
<div>
<h1>Login</h1>
<form onSubmit={this.handleSubmit}>
{this.renderInput("username", "Username")}
{this.renderInput("password", "Password", "password")}
{this.renderButton("Login")}
</form>
</div>
)
}
}
export default LoginForm
I already know how to convert a simple Class Component to a Stateless Functional Component but what I don't know is how to convert a Class Component which extends another Class Component.
Please, may you explain me how?

Reference a Field's value in a FieldArray for className change

I have a React Component (originally written by someone else) that displays a form for person (redux-form). Recently, I've changed the component to be a FieldArray (component of redux-form).
I have a validation for the email Field that impacts className for the email Field (red colored when email is incorrectly formatted, black colored otherwise). It worked fine when it wasn't a FieldArray but now it validates all email Fields at once because of
// (in constructor)
this.email = React.createRef();
// (in Field)
ref={props.parent.email}
, i.e. props.parent.email is a global/static ref.
Example: There are two persons. One of them has an incorrectly formatted email, but both emails are displayed in red.
As I understand it, I'd need to have a dynamic ref but that didn't work the way I tried.
ref={`${person}.email.ref`}
Error is
"Function components cannot have refs. Did you mean to use React.forwardRef()?"
I didn't find anything helpful on forwardRef regarding FieldArray, besides the fact that it is a valid prop.
My objective is: When several persons are created by the user and/or loaded from Redux store, be able to show every correctly formatted email in black, and every incorrectly formatted email in red.
Any help is greatly appreciated!
import React from "react";
import { Field, reduxForm, FieldArray } from "redux-form";
import { connect } from "react-redux";
import classNames from 'classnames'
import { MAIL_PATTERN } from "../some_file";
import MoreGeneralComponent from "../some_other_file";
const renderField = ({ input, label, type, options, className, meta: { touched, error }, style, disabled, hidden }) => (
<div style={style}>
<label>{label}</label>
<div>
<input {...input} disabled={disabled ? true : false} hidden={hidden ? true : false} type={type} placeholder={label} className={className} />
</div>
</div>
);
const renderPerson = ({ fields, meta: { error, submitFailed }, ...props }) => {
setTimeout(props.validate(fields));
return (
<ul className="personlist">
{fields.map((person, index) => (
<li key={index} className="person">
<h4>Person #{index + 1}</h4>
<Field
component={renderField}
type="text"
ref={props.parent.email}
className={classNames({invalid: !props.parent.state.validEmail})}
validate={props.parent.validateEmail}
name={`${person}.email`}
label="Email"
key={`${person}.email`}
></Field>
<button type="button" onClick={() => fields.remove(index)}>
Remove
</button>
</li>
))}
{(!(fields.length >= props.max)) && (
<li className="addperson">
<button
type="button"
onClick={() => fields.push({})}
disabled={fields.length >= props.max}
>
Add Person
</button>
{submitFailed && error && <span>{error}</span>}
</li>)}
</ul>
);
};
class Person extends MoreGeneralComponent {
constructor(props) {
super(props);
if (this.state.ready) {
this.max = 4;
this.data = ["email"];
this.email = React.createRef();
}
}
validate = fields => {
if (!fields || !fields.getAll()) {
return;
}
let valid = true;
fields.getAll().forEach(field => {
for (let d of this.data) {
if (!field[d] || field[d].length < 2) {
valid = false;
return;
} else if (d === "email") {
valid = field[d] && MAIL_PATTERN.test(field[d]) ? valid : undefined;
}
}
});
if (valid !== this.state.valid) {
this.setState({
valid: valid
});
}
};
validateEmail = (value) => {
const valid = value && MAIL_PATTERN.test(value) ? value : undefined;
this.setState({validEmail: !!valid});
return valid
}
renderQuestion() {
return (
<div className={style.question}>
<fieldset>
<FieldArray
name="persons"
component={renderPerson}
props={{ max: this.max, validate: this.validate, parent: this }}
rerenderOnEveryChange={true}
/>
</fieldset>
</div>
);
}
}
const mapStateToProps = s => {
const persons = s.persons
var initialValuesPersons = []
persons.map(item => initialValuesPersons.push({
"email": item.email || ""
}))
var initialValues = { "persons": initialValuesPersons}
return {
initialValues,
formdata: s.form
}
}
export default connect(mapStateToProps, null)(reduxForm(
{
form: 'person',
destroyOnUnmount: false,
enableReinitialize: true,
keepDirtyOnReinitialize: true
})(Person))
With the help of a colleague I came up with a work-around that just uses JS (working around using setState).
toggleClass = (elementName, addInvalid) => {
const element = document.getElementsByName(elementName);
element.forEach(item => {
// remove class and add later, if necessary
item.classList.remove("invalid");
if (addInvalid) {
item.classList.add("invalid");
}
})
}
validateEmail = (value, person, form, nameField) => {
const valid = value && MAIL_PATTERN.test(value) ? value : undefined;
this.toggleClass(nameField, !valid);
return valid;
}

How to pass custom props to validate function in redux-form?

I am trying to create a validation function that returns errors if there is a input error clientside or if there is an error returned from the server.
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Form, submit, reduxForm, Field } from 'redux-form';
import Modal from '../../ui/modal';
import { ACCOUNT_REGISTER_MODAL_ID } from './constants';
import registerRequest from './actions';
import CField from '../../ui/form/field';
function validate(values, props) {
const errors = {};
console.log('-');
console.log(values);
console.log(props);
console.log('-');
if (!errors.description && (!values.description || values.description.trim() === '')) {
errors.description = 'Enter a Description';
}
if (!errors.username && (!values.username || values.username.trim() === '')) {
errors.username = 'Enter a Username';
}
return errors;
}
class RegisterModal extends Component {
static propTypes = {
handleSubmit: PropTypes.func,
fields: PropTypes.array,
register: PropTypes.shape({
requesting: PropTypes.bool,
successful: PropTypes.bool,
messages: PropTypes.array,
errors: PropTypes.array,
fieldErrors: PropTypes.array,
}),
dispatch: PropTypes.func,
};
onSubmit = (values) => {
console.log(this.props);
console.log(values);
}
getForm = () => {
this.props.dispatch(submit('register'));
}
render() {
const {
handleSubmit,
fields,
register: {
requesting,
successful,
messages,
errors,
fieldErrors,
},
} = this.props;
console.log(fieldErrors);
const required = value => value ? undefined : 'Required';
return (
<Modal
modalID={ACCOUNT_REGISTER_MODAL_ID}
header={'Connect Account'}
submitText={'Connect'}
onSubmitClick={this.getForm}
register={this.register}
>
<Form
className="ui form register"
onSubmit={handleSubmit(this.onSubmit)}
fieldErrors={fieldErrors}
>
<Field
name="description"
type="text"
component={CField}
label="Please give a recognizable name to this account"
required
placeholder="My main Account"
/>
<Field
name="username"
type="text"
component={CField}
label="Please enter your username"
required
placeholder="foobar2017"
/>
</Form>
</Modal>
);
}
}
const mapStateToProps = state => ({
register: state.RegisterModal,
});
const connected = connect(mapStateToProps, { registerRequest })(RegisterModal);
const formed = reduxForm({
form: 'register',
fields: ['description', 'username'],
validate
})(connected);
export default formed;
None of the values passed to the validate function seems to contain the "fieldErrors" prop I passed into the Form component. I need to be able to pass the prop into the validate function so I can access the responses from the server received via redux.
Is there a different way that I should be creating my validation function?
I came across this Scenario where I needed values from state and
validate the redux form data with them.
Solution that I implemented is to take Global variable in file and assign props to them eg:
let TicketList = [] // global variable
function () {
TicketList = this.props.tickets;
return (
<Field
validate={validationHandler}
/>
)
}
validationHandler(value){
if(TicketList.includes(value)){
return "error message"
}
}
This worked for me!!

React Redux - Uncaught TypeError: Cannot read property 'setState' of undefined

New to this.
I have looked for answers here and here.
am using Redux as well. As per good practice I have a container "AddressContainer" and its component "Address".
The AddressContainer is as follows -
import React, { Component, PropTypes } from 'react'
import { connect } from 'react-redux'
import { Field, change } from 'redux-form'
import { Col, Panel, Row } from 'react-bootstrap'
import Select from 'react-select'
import Address from './address'
import { ensureStateData, getSuburbs } from './actions'
import { CLIENT_FORM_NAME } from '../clients/client/client'
export class AddressContainer extends Component {
static contextTypes = {
_reduxForm: PropTypes.object.isRequired,
}
constructor(props, context) {
super(props, context)
this.state = {
selectedSuburb: null,
}
}
componentDidMount() {
this.props.ensureStateData()
}
// Manage asyncSelect for new data request - for suburbs.
handleSuburbSearch = (query) => {
const { addressData } = this.props
const companyStateId = addressData.companyStateId
if (!query || query.trim().length < 2) {
return Promise.resolve({ options: [] })
}
const queryString = {
query: query,
companyStateId: companyStateId,
}
return getSuburbs(queryString)
.then(data => {
return { options: data }
})
}
render() {
const {
initialValues,
addressData,
updatePostcodeValue,
} = this.props
//const { value } = this.state
const sectionPrefix = this.context._reduxForm.sectionPrefix
if (addressData.isLoading || !addressData.states.length) {
return (
<p>Loading</p>
)
}
if (addressData.error) {
return (
<p>Error loading data</p>
)
}
const companyStateId = addressData.companyStateId
// initialValues = {
// ...initialValues.Address=null,
// state: addressData.states.find(option => option.stateId === companyStateId),
// }
return (
<Address
initialValues={initialValues}
addressData={addressData}
handleSuburbSearch={this.handleSuburbSearch}
/>
)
}
}
const mapStateToProps = (state) => ({
initialValues: state.address,
companyStateId: state.companyStateId,
addressData: state.addressData,
})
const mapDispatchToProps = (dispatch) => ({
ensureStateData: () => dispatch(ensureStateData()),
getSuburbs: (values) => dispatch(getSuburbs(values)),
updatePostcodeValue: (postcode, sectionPrefix) => dispatch(change(CLIENT_FORM_NAME, `${sectionPrefix ? (sectionPrefix + '.') : ''}postcode`, postcode))
})
export default connect(mapStateToProps, mapDispatchToProps)(AddressContainer)
The Address component is as follows -
import React, { Component, PropTypes } from 'react'
import { connect } from 'react-redux'
import { Field, reduxForm, change } from 'redux-form'
import { Col, Panel, Row } from 'react-bootstrap'
import Select from 'react-select'
import FormField from '../formComponents/formField'
import TextField from '../formComponents/textField'
import StaticText from '../formComponents/staticText'
export const ADDRESS_FORM_NAME = "Address"
export const Address = (props) => {
const { addressData, handleSuburbSearch } = props
const { reset } = props
return (
<Panel header={<h3>Client - Address Details</h3>}>
<Row>
<Field component={TextField}
name="address1"
id="address1"
type="text"
label="Address Line 1"
placeholder="Enter street 1st line..."
fieldCols={6}
labelCols={3}
controlCols={9}
/>
<Field component={TextField}
name="address2"
id="address2"
type="text"
label="Address Line 2"
placeholder="Enter street 2nd line..."
fieldCols={6}
labelCols={3}
controlCols={9}
/>
</Row>
<Row>
<Field
component={props => {
const { input, id, placeholder, type } = props
const { fieldCols, labelCols, controlCols, label, inputClass } = props
// just the props we want the inner Select textbox to have
const { name, onChange } = input
const onStateChange = (state) => {
console.log('onStateChange', state)
onChange(state)
}
return (
<FormField
id={id}
label={label}
fieldCols={fieldCols}
labelCols={labelCols}
controlCols={controlCols}
inputClass={inputClass}
>
<Select
name={name}
onChange={onStateChange}
placeholder="Select state"
valueKey="id"
options={addressData.states}
labelKey="stateLabel"
optionRenderer={option => `${option.stateShortName} (${option.stateName})`}
value={input.value}
selectValue={Array.isArray(input.value) ? input.value : undefined}
/>
</FormField>
)
}}
name="state"
id="state"
label="State."
fieldCols={6}
labelCols={3}
controlCols={6}
/>
</Row>
<Row>
<Field
component={props => {
const { input, id, placeholder, type } = props
const { fieldCols, labelCols, controlCols, label, inputClass } = props
const { name, value, onChange, onBlur, onFocus } = input
const inputProps = {
name,
value,
onChange,
onBlur,
onFocus,
}
const onSuburbChange = (value) => {
console.log('onSuburbChange: ', value)
this.setState({ selectedSuburb: value }, () => {
input.onChange(value)
updatePostcodeValue(value ? value.postcode : null, sectionPrefix)
})
}
return (
<FormField
id={id}
label={label}
fieldCols={fieldCols}
labelCols={labelCols}
controlCols={controlCols}
inputClass={inputClass}
>
<Select.Async
{...inputProps}
onChange={onSuburbChange}
valueKey="id"
labelKey="suburbName"
loadOptions={handleSuburbSearch}
backspaceRemoves={true}
/>
</FormField>
)
}}
name="suburb"
id="AddressLocation"
label="Suburb."
fieldCols={6}
labelCols={3}
controlCols={9}
/>
</Row>
<Row>
<Field component={StaticText}
name="postcode"
id="postcode"
label="Postcode."
fieldCols={6}
labelCols={3}
controlCols={9}
/>
</Row>
</Panel>
)
}
Address.propTypes = {
handleSuburbSearch: PropTypes.func.isRequired,
}
const AddressForm = reduxForm({
form: ADDRESS_FORM_NAME,
})(Address)
export default AddressForm
The problem is with the following function in the address component below and with setState which it says is undefined -
const onSuburbChange = (value) => {
console.log('onSuburbChange: ', value)
this.setState({ selectedSuburb: value }, () => {
input.onChange(value)
updatePostcodeValue(value ? value.postcode : null, sectionPrefix)
})
}
You will note there is a console.log for "value". This produces the result:
onSuburbChange: Object {id: 6810, suburbName: "Eaglehawk", postcode: "3556", state: "VIC"}
I am using React-Select as the async dropdown. This all works. If I select an option I get dropdown options but select one and it gives me the error.
I am using react state here for selectSuburb options as I dont need to update redux with this - just react state.
It seems all right but I still get the error. Why am I getting this error and how do I fix it?
This specific error is caused by the fact that the <Address /> component is a stateless functional component and cannot have a this.state object or a setState function in it. However, more generally it looks like you are expecting state and functions from the <AddressContainer /> component to be available to the child <Address /> component, but that cannot happen. In this case, you are wanting to modify the state of the parent by calling setState on the child.
A child React component (in this case <Address />) will only have state/functions/properties from its parent that are explicitly passed down as props to that component. If you want to change the local state of a component it must happen on the component with the local state. If you want to have a child component trigger some type of function call on the parent, then that function must be passed down as a prop to the child and the child can call it.
If I understand your code correctly, you want 3 things to happen when the Suburbs FormField is changed, in this order.
The selectedSuburb state on <AddressContainer /> is updated.
The onChange of the Redux-Form <Field /> in <Address /> is triggered.
The updatePostCode action is fired off.
If that is correct, then you will need to move your onSuburbChange to the <AddressContainer /> and pass it to <Address /> as a prop. However, you cannot call the onChange of the Redux-Form <Field /> inside <AddressContainer />. Therefore, you can make that function expect to receive a callback function that will be fired off after the state updates. Then you can define the callback in the child component but the state-changing function in the parent. As long as you pass down needed props on the parent, such as updatePostCode and sectionPrefix, you'll be golden. Here's what it would look like:
AddressContainer
export class AddressContainer extends Component {
/* Everything else in this component */
onSuburbChange = (value, callback) => {
this.setState({ selectedSuburb: value }, callback);
}
render() {
/* Other stuff inside render */
return (
<Address
initialValues={initialValues}
addressData={addressData}
handleSuburbSearch={this.handleSuburbSearch}
onSuburbChange={this.onSuburbChange}
updatePostcodeValue={this.props.updatePostcodeValue}
sectionPrefix={sectionPrefix}
/>
);
}
}
Address
export const Address = (addressProps) => {
return (
/* All other JSX */
<Field
component={props => {
const { input } = props;
const handleSuburbChange = (value) => {
addressProps.onSuburbChange(value, () => {
input.onChange(value);
addressProps.updatePostcodeValue(value ? value.postcode : null, addressProps.sectionPrefix)
});
}
return (
<Select.Async
onChange={handleSuburbChange}
/>
)
}}
);
}
As you can see, there is going to be a naming conflict between the different props variables in the <Address /> component, so I call the main props addressProps to avoid this.

Redux form does not reset

i have a component which based upon props renders a form with different components.
class Feedback extends Component {
submitMyForm(data) {
const { onSubmit, reset } = this.props;
reset();
return onSubmit(data);
//
// do other success stuff
}
render() {
const { handleSubmit } = this.props;
let component;
if(this.props.data.feedbackType == "likert")
component = Likert;
else if(this.props.data.feedbackType == "single choice")
component = SingleChoice;
else if(this.props.data.feedbackType == "multiple choice")
component = MultipleChoice;
return (
<div>
<h1>Feedback zu Aufgabe {this.props.id}</h1>
<form onSubmit={handleSubmit(this.submitMyForm.bind(this))}>
<Field
name="feedback"
component={component}
heading={this.props.data.description}
items={this.props.data.options}
required={true}
/>
<button type="submit">Submit</button>
</form>
</div>
);
}
}
// Decorate the form component
Feedback = reduxForm({
form: 'feedback', // a unique name for this form,
validate,
enableReinitialize:true
})(Feedback);
function validate(formProps) {
const errors = {};
if (!formProps.feedback) {
errors.feedback = 'please select an option';
}
return errors;
}
export default Feedback;
import React, { PropTypes } from 'react';
const SingleChoice = ({ input, disabled, heading, required, className, items, name, meta: { touched, error } }) => (
<fieldset className={`form__field ${className || ''}`}>
<legend className="form__label">
{heading}{required ? (<span>*</span>) : null}
{ (touched && error) ? (
<span className="form__error"> {error}</span>
) : null }
</legend>
<div>
{ items.map((item, i) => (
<div className="form__segmented-control width-1/2#small" key={ i }>
<input
{...input}
name={ name }
type="radio"
value={ item.value }
disabled={ disabled }
className="segmented-control__input u-option-bg-current"
id={ `${name}-${item.value}` }
/>
<label className="segmented-control__label u-adjacent-current" htmlFor={ `${name}-${item.value}` }>
{item.label}
</label>
</div>
))
}
</div>
</fieldset>
);
SingleChoice.propTypes = {
input: PropTypes.object.isRequired,
name: PropTypes.string.isRequired,
className: PropTypes.string,
items: PropTypes.arrayOf(PropTypes.shape({
label: PropTypes.string.isRequired,
value: PropTypes.any.isRequired,
})).isRequired,
heading: PropTypes.string,
meta: PropTypes.object,
required: PropTypes.bool,
disabled: PropTypes.bool,
};
export default SingleChoice;
The first time the form renders everything is fine. All radio buttons are unchecked and if i try to submit it i get an validation error as intended. But when my Feeback component receives new props and the form is updated. The old values still remain selected when the form component for the new props is the same as the one for the old props.
When the form component for the new props is different all values are not selected as intended, but i can submit the form without selecting anything, which should be prevented by validation.
I hope you got any suggestions, i am totally out of ideas at this point.
I searched for hours trying to find a resolution to this problem. The best way I could fix it was by using the plugin() API to teach the redux-form reducer to respond to the action dispatched when your submission succeeds. Exactly like the first step here How can I clear my form after my submission succeeds?
const reducers = {
// ... your other reducers here ...
form: formReducer.plugin({
nameOfTheForm: (state, action) => { // <- 'nameOfTheForm' is name of form
switch(action.type) {
case ACCOUNT_SAVE_SUCCESS:
const values = undefined;
const fields = {
fields: {
input_field_name: {
visited: false,
touched: false
}
// repeat for each input field
}
};
const newState = { ...state, values, fields };
return newState;
default:
return state;
}
}
})
}
You will have to change a couple things in your component.
onSubmit(values) {
this.props.postForm(values, () => {});
}
render(){
const { handleSubmit } = this.props;
}
return (
<form onSubmit={handleSubmit(this.onSubmit.bind(this))}></form>
)
In your actions file:
export function postForm(values, callback) {
const request = axios.get(`${ROOT_URL}`, config).then(() => callback());
return {
type: ACCOUNT_SAVE_SUCCESS,
payload: request
};
}
The best way I found:
import 'initialize'...
import {initialize} from 'redux-form';
and then when you call the action, call another action right after passing an empty object to the 'initialize' function...
yourFunction = (data) => {
this.props.dispatch(yourAction(data))
.then(
result => {
if (result) {
this.props.dispatch(initialize('nameOfTheForm', {}));
}
}
);
when my Feeback component receives new props and the form is updated, the old values still remain selected when the form component for the new props is the same as the one for the old props.
This is because the values for the feedback form are stored in your Redux store.
You should implement componentWillReceiveProps and test whether your form should be reset or not.
class Feedback extends Component {
componentWillReceiveProps ( nextProps ) {
if ( nextProps.blabla !== this.props.blabla ) {
// oh cool, props changed, let's reset the form
// checkout more available form props at http://redux-form.com/6.4.3/docs/api/ReduxForm.md/
this.props.reset();
}
}
render () {
// your normal rendering function
}
}

Resources