Reputation: 2126
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;
Upvotes: 0
Views: 1896
Reputation: 7272
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
Upvotes: 2