Add multiple form fields but show only one at a time - reactjs

I need final-form to be able to add array of form field records. But want to display only one field of array at a time. Like on left side I will have customer id or index which user will select and on right side i have to show customer corresponding to that index. I am able to add reac-final-form-array, but it always show all array elements. What should be the right approach to show only selected customer.
Please check the below code for reference. Hope my question is clear, if not please let me know, will add more explanation.
<FieldArray name="customer">
{({ fields }) => (
fields.map((name, index) => (
<div key={index}>
<Field name={`${name}.firstName`} />
<Field name={`${name}.lastName`} />
</div>
))
)}
</FieldArray>
To add new customer:
<div className="buttons">
<button
type="button"
onClick={() => push('customers', undefined)}>
Add Customer
</button>
</div>
Currently its looking like:
I need it to look like this:

In the fields array, you can add one more key isVisible.
It will look like this:
fields = [
{
firstName: 'John',
lastName: 'Doe',
isVisible: true,
},
{
firstName: 'Jane',
lastName: 'Doe',
isVisible: false,
}
];
Now while showing, only render the field when isVisible is true,
<FieldArray name="customer">
{({ fields }) => (
fields.map((name, index) => {
if(name.isVisible){
return (
<div key={index}>
<Field name={`${name}.firstName`} />
<Field name={`${name}.lastName`} />
</div>
);
))
)}
</FieldArray>
You can control isVisible key by clicking Cust # button.

Find the below code.
import React from "react";
import { Form, Field } from "react-final-form";
import arrayMutators from "final-form-arrays";
import { FieldArray } from "react-final-form-arrays";
const onSubmit = () => {
console.log("submitted");
};
const validate = () => {
// console.log("validated");
};
const MyForm = () => (
<Form
onSubmit={onSubmit}
mutators={{
// potentially other mutators could be merged here
...arrayMutators
}}
validate={validate}
render={({ handleSubmit, pristine, invalid }) => (
<form onSubmit={handleSubmit}>
<FieldArray name="customers">
{({ fields }) => (
<div>
<button
type="button"
onClick={() =>
fields.push({ firstName: "", lastName: "", isVisible: true })
}
>
Add Customer
</button>
{fields.map((name, index) => (
<div key={name}>
<a
onClick={() =>
(fields.value[index].isVisible = !fields.value[index]
.isVisible)
}
>{`Cust #${index}`}</a>
{fields.value[index].isVisible ? (
<div>
<div>
<Field name={`${name}.firstName`} component="input" />
</div>
<div>
<Field name={`${name}.lastName`} component="input" />
</div>
</div>
) : null}
<button type="button" onClick={() => fields.remove(index)}>
Remove
</button>
</div>
))}
</div>
)}
</FieldArray>
</form>
)}
/>
);
export default MyForm;
Check the codesandbox link here

Related

How to use Radio button to select a different form in each array of useFieldArray of react-hook-form

I have a basic react hook form with field array. With append the form fields replicates as expected. For each array, I want the user to choose some field to enter without entering the other. I am using radio button and useState to achieve this. However, when i change the selection in an array, the selections in the other arrays changes as well. Please how do i correct this ? Or is there a better way to achieve this functionality. Thanks in advance for your help. The code is found below. I also have codeSandbox: https://codesandbox.io/s/usefieldarray-react-hook-form-2yp3vb?file=/src/App.js:0-3753
export default function App() {
const { handleSubmit, control } = useForm({
defaultValues: {
Detail: [
{
userName: {},
officeAddress: {},
homeAddress: {}
}
]
}
});
const { fields, append, remove } = useFieldArray({
control,
name: "Detail"
});
const [checked, setChecked] = useState();
// onChange function for the address forms
const changeAddressForm = (e) => {
setChecked(e.target.value);
};
const onSubmit = async (data) => {};
return (
<div className="App">
<h1>Selecting a different form in each field array</h1>
<form onSubmit={handleSubmit(onSubmit)}>
<ul>
{fields.map((field, index) => {
return (
<li
key={field.id}
className="w3-border w3-border-green w3-padding"
>
<div>
<div className="w3-padding-large">
<label>Username</label>
<Controller
name={`Detail.${index}.userName`}
control={control}
render={({ field }) => (
<Input
onChange={(value) => field.onChange(value)}
style={{ width: 200 }}
/>
)}
/>
</div>
<div>
<Radio.Group onChange={changeAddressForm} value={checked}>
<Radio value={1}>Office address</Radio>
<Radio value={2}>Home address</Radio>
</Radio.Group>
</div>
<div className="w3-padding-large">
{checked === 1 && (
<div>
<label>Office address</label>
<Controller
name={`Detail.${index}.officeAddress`}
control={control}
render={({ field }) => (
<Input
onChange={(value) => field.onChange(value)}
style={{ width: 200 }}
/>
)}
/>
</div>
)}
</div>
<div className="w3-padding-large">
{checked === 2 && (
<div>
<label>Home address</label>
<Controller
name={`Detail.${index}.homeAddress`}
control={control}
render={({ field }) => (
<Input
onChange={(value) => field.onChange(value)}
style={{ width: 200 }}
/>
)}
/>
</div>
)}
</div>
</div>
</li>
);
})}
</ul>
<section>
<button
type="button"
onClick={() =>
append({
userName: {},
homeAddress: {},
officeAddress: {}
})
}
>
Append
</button>
</section>
</form>
</div>
);
}

How to get values from react FieldArray in formik form with other fields?

I have created a Formik form that contains a field array, form and fieldArray is in two separate classes as separate components.
My form:
<Formik onSubmit = {(values, { setSubmitting }) => { setSubmitting(false);}}
enableReinitialize>
{({handleSubmit, errors})=> (
<Form onSubmit= { handleSubmit }>
<Form.Group as= { Row } controlId= "cpFormGroupTitle" className="required">
<Form.Label className="post-create-label" column sm={ 2 } >
Title
</Form.Label>
<Col sm={ 10 }>
<Field name="title" component={ renderTextField } type="text"
isinvalid={ !!errors.title ? "true": "false" }
placeholder="Title *" />
</Col>
</Form.Group>
<Form.Group as= { Row } controlId= "cpFrmGroupShortDesc" className="required">
<Form.Label className="post-create-label" column sm={ 2 } >
Short Description
</Form.Label>
<Col sm={ 10 }>
<Field name="short-desc" component={ renderTextArea } type="text"
isinvalid={ !!errors.shortDescription ? "true": "false" }
placeholder="Short Description *" />
</Col>
</Form.Group>
<Form.Group as= { Row } controlId= "cpFormGroupFeatures">
<Form.Label className="post-create-label" column sm={ 2 }>
Features
</Form.Label>
<Col sm={ 10 }>
<TextFieldArray initialValues={{ features: [] } } name="features"/>
</Col>
</Form.Group>
<Form.Group as={ Row }>
<Col sm= { { span: 2, offset:2 } }>
<Button type="submit" variant="primary">Submit</Button>
</Col>
<Col sm={ 2 }>
<Button variant="secondary">Save as draft</Button>
</Col>
</Form.Group>
</Form>
)}
</Formik>
Here, <TextFieldArray> is field array , I need to get values from field array when form is submitted.
TextFieldArray:
export const TextFieldArray = (props) => (
<React.Fragment>
<Formik initialValues= { props.initialValues } render={({ values }) => (
<Form>
<FieldArray name= { props.name } render={arrayHelper => (
<div>
{ values[props.name] && values[props.name].length > 0 ?
(
values[props.name].map((item, index) => (
<div key={index}>
<Form.Group as= { Row }>
<div className="col-md-8">
<Field name={`${props.name}.${index}`}
className="form-control"/>
</div>
<div className="col-md-2">
<Button type="button" variant="outline-secondary"
onClick={() => arrayHelper.remove(index)}>
Remove
</Button>
</div>
<div className="col-md-2">
<Button type="button" variant="outline-secondary"
onClick={() => arrayHelper.insert(index, '')}>
Add
</Button>
</div>
</Form.Group>
</div>
))
) : (
<Button type="button" variant="outline-secondary"
onClick={() => arrayHelper.push('')} >
{`Add ${ props.name }`}
</Button>
)
}
</div>
)} />
</Form>
)} />
</React.Fragment>
);
I'm a beginner to ReactJS, so someone help me please, that will be huge help from you all.
Thanks.
To have field array be part of same form as other fields, only have one <Formik> and one <Form>. Then make initialValues on Formik that describes all the fields:
<Formik
initialValues={{ friends: someFriends, random: randomText }}
As seen in the following code from Formik FieldArray docs, with another form field added that is not part of the array:
import React, { useState } from "react";
import ReactDOM from "react-dom";
import { Formik, Form, Field, useField, FieldArray } from "formik";
const someFriends = ["jared", "ian", "brent"];
const randomText = "Four score and seven years ago...";
function MyTextInput({ label, ...props }) {
// useField() returns [formik.getFieldProps(), formik.getFieldMeta()]
// which we can spread on <input> and alse replace ErrorMessage entirely.
const [field, meta] = useField(props);
return (
<>
<label
htmlFor={props.id || props.name}
css={{ backgroundColor: props.backgroundColor }}
>
{label}
</label>
<input className="text-input" {...field} type="text" {...props} />
{meta.touched && meta.error ? (
<div className="error">{meta.error}</div>
) : null}
</>
);
}
// Here is an example of a form with an editable list.
// Next to each input are buttons for insert and remove.
// If the list is empty, there is a button to add an item.
export const FriendList = () => (
<div>
<h1>Friend List</h1>
<Formik
initialValues={{ friends: someFriends, random: randomText }}
onSubmit={values =>
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
}, 500)
}
render={({ values }) => (
<Form>
<MyTextInput label="Random comment" name="random" />
<FieldArray
name="friends"
render={arrayHelpers => (
<div>
{values.friends &&
values.friends.length > 0 &&
values.friends.map((friend, index) => (
<div key={index}>
<Field name={`friends.${index}`} />
<button
type="button"
onClick={() => arrayHelpers.remove(index)} // remove a friend from the list
>
-
</button>
<button
type="button"
onClick={() => arrayHelpers.insert(index, "")} // insert an empty string at a position
>
+
</button>
</div>
))}
{/* Add a new empty item at the end of the list */}
<button type="button" onClick={() => arrayHelpers.push("")}>
Add Friend
</button>
<div>
<button type="submit">Submit</button>
</div>
</div>
)}
/>
</Form>
)}
/>
</div>
);
ReactDOM.render(<FriendList />, document.getElementById("root"));
Code in codesandbox.
I don't think you need to create second form for child component.
You need to just pass the values from the parent to the TextFieldArray
<TextFieldArray values={values.myArr} name="features"/>
And the child component just receive the values and render them (as if it was in the parent component)
export const TextFieldArray = (props) => {
return (
<React.Fragment>
<FieldArray
name= { props.name }
render={arrayHelper => (
<div>
{
props.values[props.name] && props.values[props.name].length > 0 ?
(
props.values[props.name].map((item, index) => (
<div key={index}>
<Form.Group as= { Row }>
<div className="col-md-8">
<Field name={`${props.name}.${index}`} className="form-control"/>
</div>
<div className="col-md-2">
<Button type="button" variant="outline-secondary"
onClick={() => arrayHelper.remove(index)}>
Remove
</Button>
</div>
<div className="col-md-2">
<Button type="button" variant="outline-secondary"
onClick={() => arrayHelper.insert(index, '')}
>
Add
</Button>
</div>
</Form.Group>
</div>
))
) : (
<Button type="button" variant="outline-secondary" onClick={() => arrayHelper.push('')} >
{`Add ${ props.name }`}
</Button>
)
}
</div>
)}
/>
</React.Fragment>
)
Of course don't forget to add the initial values of the array to the parent component.
And finally when you click on the submit button it would give you the values.

How to access field data in other field

I have a modal form with a form that uses Formik. Here are two pictures that show two states of that form that can be toggled with a switch.Initially I fill text into fields which can be added dynamically and stored as an array with .
The second picture shows how I toggled to textarea. There you can also add text with commas that will be turned into an array.
Is there any way to fill data in input fields from the first screen, toggle into textarea and access already inputted data.
I understand formik keeps that state somewhere. But at the moment these fields have a separate state.
Here is my component:
class ModalForm extends React.Component {
constructor(props) {
super(props);
this.state = {
disabled: true,
};
}
onChange = () => {
this.setState({
disabled: !this.state.disabled,
});
};
render() {
var {
visible = false,
onCancel,
onRequest,
submitting,
setSubscriberType,
editing,
subscriptionTypeString,
tested,
selectedGates,
} = this.props;
const { gateId } = selectedGates.length && selectedGates[0];
const handleSubmit = values => {
console.log(values);
onRequest && onRequest({ gateId, ...values });
};
const { disabled } = this.state;
return (
<Modal
footer={null}
closable
title="Список абонентов для выбранного гейта"
visible={visible}
onCancel={onCancel}
onOk={handleSubmit}
destroyOnClose
width="600px"
>
<StyledDescription>
<Switch onChange={this.onChange} />
<StyledLabel>массовый ввод</StyledLabel>
</StyledDescription>
<Formik
initialValues={{ abonents: [''] }}
onSubmit={handleSubmit}
render={({ values, handleChange }) => (
<Form>
{disabled ? (
<FieldArray
name="abonents"
render={arrayHelpers => {
return (
<div>
{values.abonents.map((value, index) => (
<div key={index}>
<MyTextInput
placeholder="Абонент ID"
name={`abonents.${index}`}
value={value}
onChange={handleChange}
/>
<Button
shape="circle"
icon="delete"
onClick={() => {
arrayHelpers.remove(index);
}}
/>
</div>
))}
<Button type="dashed" onClick={() => arrayHelpers.push('')}>
<Icon type="plus" />Добавить абонента
</Button>
</div>
);
}}
/>
) : (
<StyledField
placeholder="Введите ID абонентов через запятую"
name="message"
component="textarea"
/>
)}
<Footer>
<Button type="primary" htmlType="submit">
Запросить
</Button>
</Footer>
</Form>
)}
/>
</Modal>
);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.0.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.0.0/umd/react-dom.production.min.js"></script>
Pretty easy, formik stores values inside values.abonents, hence you can use it inside textarea
let { Formik, Form, Field, ErrorMessage, FieldArray } = window.Formik;
function App () {
const [disabled, setDisabled] = React.useState(false) // some boilerplate code
function submit (values) {
console.log('submit', values)
}
return (
<Formik
initialValues={{ abonents: [] }}
onSubmit={submit}
render={({ values, handleChange, setFieldValue }) => (
<Form>
<FieldArray
name='abonents'
render={arrayHelpers => {
if (!disabled) {
return (
<textarea onChange={(e) => {
e.preventDefault()
setFieldValue('abonents', e.target.value.split(', '))
}} value={values.abonents.join(', ')}></textarea>
)
}
return (
<div>
{
values.abonents.map((value, index) => (
<div key={index}>
<input
placeholder='Абонент ID'
name={`abonents.${index}`}
value={value}
onChange={handleChange}
/>
<button onClick={(e) => {
e.preventDefault()
arrayHelpers.remove(index)
}}>
-
</button>
</div>
))
}
<button onClick={(e) => {
e.preventDefault()
arrayHelpers.push('')
}}>
+
</button>
</div>
)
}}
/>
<button type='submit'>Submit</button>
<button onClick={e => {
e.preventDefault()
setDisabled(!disabled)
}}>toggle</button>
</Form>
)}
/>
)
}
ReactDOM.render(<App />, document.querySelector('#root'))
<script src="https://unpkg.com/react#16.9.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.9.0/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/formik/dist/formik.umd.production.js"></script>
<div id='root'></div>
I found a solution. You have got to give the same name to the input and textarea, so whe you add text in input will automatically change text in textarea
<FieldArray
name="abonents"
render={arrayHelpers => {
and
<StyledField
placeholder="Введите ID абонентов через запятую"
name="abonents"
component="textarea"
/>
These two fields have same name so they are rendered interchangeably but have common text inside them

How do I combine `final-form-calculate` with `final-form-array`

I have a form which uses react-final-form-array in order to have many "Attendees". I'm wanting each of the Attendee.Email inputs to do a web lookup and then set the Attendee.FirstName and Attendee.Surname based off the result.
I'm not sure how this should be expressed with a final-form-calculate decorator. Specifically, I don't know how to reference the individual form item within the array.
Here's what I've got as a start:
const MyForm = props => {
const calculator = createDecorator(
{
field: 'Attendees.Email', // <-- this needs to happen for each Email
updates: {
// ... do some lookups and set FirstName and Surname based off database query
Attendees.FirstName: (email, allValues) =>
someLookup(email).FirstName
}
},
);
const submit = values => {
console.log(values);
};
return (<Form onSubmit={submit}
mutators={{
...arrayMutators,
}}
decorators={[calculator]}
render={({handleSubmit, pristine, invalid, mutators: {push, pop}}) => {
return (
<form onSubmit={handleSubmit}>
<fieldset>
<legend>Attendees</legend>
<div className="fieldset">
<p><Button bsStyle="primary" onClick={() => push('Attendees', undefined)}>
Add attendee
</Button></p>
<FieldArray name="Attendees">
{({fields}) =>
fields.map((name, index) => (
<div key={name}>
<label>Attendee #{index + 1} {index > 0 && <span
onClick={() => {
const attendee = fields.remove(index);
}}
style={{cursor: 'pointer'}}
role="img"
aria-label="delete">❌</span>}</label>
<Field
name={`${name}.Email`}
component={TextField}
label="Email address"
validate={required}
/>
<Field
name={`${name}.FirstName`}
component={TextField}
label="First name"
validate={required}
/>
<Field
name={`${name}.Surname`}
component={TextField}
label="Surname"
validate={required}
/>
</div>
))}
</FieldArray>
</div>
</fieldset>
<Button type="submit" bsStyle="primary"
disabled={invalid}>Next</Button>
</form>
);
}}
/>);
};
export default MyForm;
Took me a bit of hunting but it's actually pretty simple - from the website i noticed they have it listed (but not clearly)
So for my case, i just picked up the name and changed the last part of the text to reflect the object property i wanted to modify.
Below, i use regex to match a field, then replace the last part of the name value to match a different field (stored in fieldName) and then return an array containing the new field i am changing and the new value.
{
field: /fields\[\d+\]\.field\.text/, // when a field matching this pattern changes...
updates: (value, name, allValues) => {
console.log('Update called', value, name);
const fieldName = name.replace('.text', '.name');
return {
[fieldName]: value.replace(/\W+/gi, '-').toLowerCase()
};
}
}

How to programmatically initialize individual redux-form field?

Struggling with this for two days already. In a 'redux-form' form, I need to prepopulate an order field with a value that comes from array.map iteration index. Here is a complete code for my form (please see comments):
const renderField = ({ input, label, type, meta: { touched, error } }) => {
let color = 'normal';
if (touched && error) {
color = 'danger';
}
return (
<FormGroup color={color}>
<Label>{label}</Label>
<div>
<input {...input} type={type} placeholder={label} />
{touched && (error && <FormFeedback>{error}</FormFeedback>)}
</div>
</FormGroup>
);
};
const renderChoices = ({ fields, meta: { error } }) => (
<ul>
<li>
<button type="button" onClick={() => fields.push()}>
Add Choice
</button>
</li>
{fields.map((choice, index) => (
<li key={index}>
<button type="button" title="Remove Choice" onClick={() => fields.remove(index)}>
x
</button>
<Field name={choice} type="text" component={renderField} label={`Choice #${index + 1}`} />
</li>
))}
{error && <li className="error">{error}</li>}
</ul>
);
const renderQuestion = ({ fields, meta: { error, submitFailed } }) => (
<ul>
<li>
<Button type="button" onClick={() => fields.push({})}>
Add Question
</Button>
{submitFailed && error && <span>{error}</span>}
</li>
{fields.map((question, index) => (
<li key={index}>
<button type="button" title="Remove Question" onClick={() => fields.remove(index)}>
x
</button>
<h4>Question #{index + 1}</h4>
<Field // this is the field that needs to be prepopulated
name={`${question}.order`}
type="text"
component={renderField}
label="Order"
/>
<Field name={`${question}.prompt`} type="text" component={renderField} label="Prompt" />
<FieldArray name={`${question}.choices`} component={renderChoices} />
</li>
))}
</ul>
);
const QuizStepAddForm = props => {
const { handleSubmit, pristine, reset, submitting } = props;
return (
<Form onSubmit={handleSubmit}>
<Field name="order" type="number" component={renderField} label="Quiz Order" />
<Field name="title" type="text" component={renderField} label="Quiz Title" />
<FieldArray name="questions" component={renderQuestion} />
<div>
<Button style={{ margin: '10px' }} color="primary" type="submit" disabled={submitting}>
Submit
</Button>
<Button type="button" disabled={pristine || submitting} onClick={reset}>
Clear Values
</Button>
</div>
</Form>
);
};
export default reduxForm({
form: 'quizStepAddForm',
})(QuizStepAddForm);
I have tried to use redux-form Field API meta props, meta:initial to initialize the field, but by just setting it in the Field tag does not change anything. I also tried to set input:defaultValue in the following manner <Field input={{ defaultValue: '${index + 1}' }}.... This attempt though changes initial value of the wrapped input component yet still, somehow, effects the state of the field, any changes in the field do not have any effect on the form state.
What am I missing?
In order to set the initial state of values inside of a redux form, you need to provide initialValues property into redux form wrapped component:
//... your code above
export default compose(
connect(state => ({
// here, you are having an access to the global state object, so you can
// provide all necessary data as the initial state of your form
initialValues: {
someField: "initial value"
}
})),
reduxForm({
form: "quizStepAddForm"
})
)(QuizStepAddForm);

Resources