Imagine you're creating a form that needs multiple discount codes for a checkout process. In React/React Native, you might use a FieldArray
from the redux-form library.
It could look something like this:
<form onSubmit={handleSubmit}>
<Field
name="firstname"
component={customInput}
type="text"
label="First Name"
validate={[required]}
normalize={capitalize}
/>
<FieldArray name="discountCodes" component={discounts} />
<button type="submit">Submit</button>
</form>
export const discounts = ({ fields }) => (
<div className="custom-field-array-container">
{fields.map((code, index) => (
<div key={index} className="field-array-item">
<Field
name={code}
type="text"
component={customInput}
label={`Discount Code #${index + 1}`}
autoFocus
/>
<button type="button" onClick={() => fields.remove(index)}>
×
</button>
</div>
))}
<button type="button" onClick={() => fields.push()}>
Add {!fields.length ? "Discount Code(s)" : "Another Discount Code"}
</button>
</div>
);
When you submit the form, redux-form will provide you a values object that contains the nested discount codes.
This all works great except for one thing. Notice that the button to add more discount codes is INSIDE the component that generates the discount code fields. Each time you click the button, it pushes more fields onto the passed in array. What if you want/need to have the "Add Another Discount Code" button OUTSIDE the fields. For example, your design my call for an "add more" button to be in the header or somewhere else in the form.
This is the problem I faced with my app. I did a lot of digging and finally found this Stack Overflow post that had a solution. The gist is the redux-form has action creators (arrayPush
, arrayUnshift
, arrayPop
, etc) to do various things with the fields in a FieldArray
. You just need to access these action creators from outside the FieldArray
.
You do this by adding the action creators to your props (see mapDispatchToProps
). When adding arrayPush
, you MUST alias it as another name or the action creator will not be added to your props.
import React, { Component } from "react";
import { connect } from "react-redux";
import { Field, FieldArray, reduxForm, arrayPush } from "redux-form";
import { customInput, customSelect, discounts } from "./fields";
import capitalize from "capitalize";
import {
required,
minLength,
maxLength,
matchesPassword,
asyncValidate
} from "../validation";
import "./RegisterForm.css";
class RegisterForm extends Component {
componentWillMount() {
this.props.initialize({ discountCodes: ["ABC200", "XYZ500"] });
}
render() {
const { handleSubmit } = this.props;
const addFieldsExternally = () => {
this.props.pushArray("register", "discountCodes", "");
};
return (
<form onSubmit={handleSubmit}>
<Field
name="firstname"
component={customInput}
type="text"
label="First Name"
validate={[required]}
normalize={capitalize}
/>
<FieldArray name="discountCodes" component={discounts} />
<button type="submit">Submit</button>
<button
type="button"
onClick={addFieldsExternally}
style={{ marginTop: 20 }}
>
Add Discount Codes from External Source
</button>
</form>
);
}
}
const mapDispatchToProps = {
// NOTE: This MUST be aliased or it will not work. Thanks Jack!
pushArray: arrayPush
};
RegisterForm = reduxForm({
form: "register",
asyncValidate,
asyncBlurFields: ["username"]
})(RegisterForm);
export default connect(
null,
mapDispatchToProps
)(RegisterForm);
Now, you can tap the external button to add discount codes to your form.
P.S. This example also shows how to initialize your FieldArray
with existing values.
Here's a working example: