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

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.

Related

Reset input on uncheck checkbox (ReactJS + Formik)

I made this component to create a field if a checkbox is checked, but how can I reset this field value if I write something and then uncheck?
const InputCheckbox = ({name, size}) => {
const [checkAmount, setCheckAmount] = useState(false);
return(
<div>
<label htmlFor={size}>
<Field type="checkbox" name={name} value={size} onClick={()=> {checkAmount === false ? setCheckAmount(true) : setCheckAmount(false)}} />
{size}
</label>
{checkAmount === false ? null : <div className="form-control-amount">
<label htmlFor={`sizeamount.${size}`}>{size}</label>
<Field className="form-control-amount" type="number" name={`sizeamount.${size}`} />
</div>}
</div>
)
}
You can use Formik's setFieldValue function to reset the field when the checkbox is unchecked.
Your component:
const InputCheckbox = ({name, size, setFieldValue }) => {
const [checkAmount, setCheckAmount] = useState(false);
return(
<div>
<label htmlFor={size}>
<Field type="checkbox" name={name} checked={checkAmount} value={size} onClick={()=> {checkAmount === false ? setCheckAmount(true) : setCheckAmount(false); setFieldValue(`sizeamount.${size}`, '')}} />
{size}
</label>
{checkAmount === false ? null : <div className="form-control-amount">
<label htmlFor={`sizeamount.${size}`}>{size}</label>
<Field className="form-control-amount" type="number" name={`sizeamount.${size}`} />
</div>}
</div>
)
}
Usage:
<Formik
initialValues={{
check: true
}}
onSubmit={(values, actions) => {
alert('Form has been submitted');
actions.setSubmitting(false);
}}
>
{({setFieldValue}) => (
<Form>
<InputCheckbox name="check" size={10} setFieldValue ={setFieldValue} />
<button type="submit">Submit</button>
</Form>
)}
</Formik>
Reference: https://formik.org/docs/api/formik
You can
<button
class="btn btn-primary pull-right"
type="reset"
onClick={(e) => {
e.preventDefault();
props.resetForm()
}}
>Eliminar filtros</button>

How to validate dyanmic fields of fieldArray in Formik with Yup onSubmit

I am trying to validate Formik FieldArray with Yup, it does validate the initial fields , but does not validate the dyanmically generated one even if they are required.
Errormessage is only displayed in case of dyanmically generated one's when I click (2nd row insurance_company)on it and remove!
I am new to Reactjs/Formik world, any help is appreciated.
function VehicleForm(props) {
const { data, update, manuf, vmodel, vcat } = props
const initialValues = {
vehicle_manufacturer: data?.vehicle_manufacturer || 1,
vehicle_category: data?.vehicle_category || 1,
vehicle_model: data?.vehicle_model || 1,
origin_country: data?.origin_country || 1,
insurance_details_array: [{ policy_number: "", insurance_company: "", insured_amount: "", insurance_expiry: "", premium_term: "" }],
}
const validationSchema = Yup.object({
vehicle_manufacturer: Yup.string().required("Required"),
vehicle_category: Yup.string().required("Required"),
vehicle_model: Yup.string().required("Required"),
insurance_details_array: Yup.array(Yup.object({
policy_number: Yup.string().required("Required"),
insurance_company: Yup.string().required("Required"),
insured_amount: Yup.number().required("Required"),
insurance_expiry: Yup.string().required("Required"),
premium_term: Yup.number().required("Required")
})).required("Required"),
})
const onSubmit = async (values, formik) => {
}
return (<>
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={onSubmit}
>
{
formik => {
return (
<div className="container">
<Form >
<div className="row" >
<div className="col-md-6">
<FormikControl controls="select" name="vehicle_manufacturer" label="Vehicle Manufacturer" options={manuf.manuf} extraclass="" className="form-control" />
</div>
<div className="col-md-6">
<FormikControl controls="select" name="vehicle_category" label="Vehicle Category" options={vcat.vcat} extraclass="" className="form-control" />
</div>
</div>
<div className="row" >
<div className="col-md-6">
<FormikControl controls="select" name="vehicle_model" label="Vehicle Model" options={vmodel.vmodel} extraclass="" className="form-control" />
</div>
<div className="col-md-6">
{/* <FormikControl controls="select" name="origin_country" label="Origin Country" options={dropdown} extraclass="" className="form-control" /> */}
<Country name="origin_country" label="Origin Country" extraclass="form-control" />
</div>
</div>
<div className="col-md-12">
<label>Insurance Details</label>
<FieldArray name="insurance_details_array">
{
(fieldsArrayProps2) => {
const { push, remove, form } = fieldsArrayProps2
const { values } = form
const { insurance_details_array } = values
return (
<div>
{
values.insurance_details_array.map((ins_d, index) => (
<div className="row" key={index}>
<div className="col-md-2" >
<FormikControl className="form-control" placeholder="policy_number" name={`insurance_details_array.${index}.policy_number`} controls="input" id={`insurance_details_array.${index}.policy_number`} />
</div>
<div className="col-md-2" >
<FormikControl className="form-control" placeholder="insurance_company" name={`insurance_details_array.${index}.insurance_company`} controls="input" id={`insurance_details_array.${index}.insurance_company`} />
</div>
<div className="col-md-2" >
<FormikControl className="form-control" placeholder="insured_amount" name={`insurance_details_array.${index}.insured_amount`} controls="input" id={`insurance_details_array.${index}.insured_amount`} />
</div>
<div className="col-md-2" >
<FormikControl className="form-control" placeholder="insurance_expiry" name={`insurance_details_array.${index}.insurance_expiry`} controls="date" id={`insurance_details_array.${index}.insurance_expiry`} />
</div>
<div className="col-md-2" >
<FormikControl className="form-control" placeholder="premium_term" name={`insurance_details_array.${index}.premium_term`} controls="input" id={`insurance_details_array.${index}.premium_term`} />
</div>
<div className="col-md-2" >
{
index > 0 ? <button type="button" onClick={() => remove({ index })} className="btn btn-primary " >-</button> : ""
}
<button type="button" onClick={() => push("")} className="btn btn-primary m-1" >+</button>
</div>
</div>
))
}
</div>
)
}
}
</FieldArray>
</div>
<div className="row">
<button type="submit" className="btn btn-primary">Submit</button>
</div>
</Form>
</div>
)
}
}
</Formik>
</>)
}
export default VehicleForm;
I solved it, by using putting an empty field in push, that would give the Required errors.
<button
type="button"
onClick={() => push({ fieldname: "", fieldname2: "" })}
className="btn btn-primary m-1"
>
+
</button>

React storing components in state with props not updating

{article.map((ArticleComp, i) =>
<div className="light-blue p-4 mb-3 mt-3" key={i}>
{ArticleComp}
<button type="button" className="red-button ml-2" onClick={() => removeArticle(i)}>Delete</button>
</div>
)}
<div className="mt-4">
<button type="button" className="blue-button" onClick={() => setAddArticle(true)}>Add Job</button>
<button type="button" className="blue-button ml-2" onClick={() => { setArticle([...article, <Article create="true" post={addArticle} updateError={props.updateError} updateArticle={UpdateArticleCallback}/>]) }}>Add Article</button>
<Link to="/job/all"><button type="button" className="red-button ml-2">Cancel</button></Link>
</div>
export default function Article(props) {
const [title, setTitle] = useState("")
const [content, setContent] = useState("")
if (props.edit === "true" || props.create === "true") {
if (props.post !== "no") {
axios.post('/api/v1/article/create.php', {
title: title,
content: content,
job_id: props.post
}).then((response) => {
if(response.data.Message === 'OK'){
props.updateError(null)
props.updateArticle()
} else {
props.updateError(response.data.Error)
}
})
}
return (
<Row>
{props.post}
<Col>
<Form.Group>
<Form.Label>Article Title</Form.Label>
<Form.Control key="articleTitle" type="text" placeholder="Title" value={title} onChange={e => setTitle(e.target.value)} maxLength="200" />
</Form.Group>
<Form.Group>
<Form.Label>Article Content</Form.Label>
<Form.Control as="textarea" rows={3} key="articleContent" placeholder="Alert Content" value={content} onChange={e => setContent(e.target.value)} maxLength="500" />
</Form.Group>
</Col>
</Row>
)
} else {
return (
<>
{props.article.map((article, i) =>
<div key={i} className="light-blue p-4">
<Row><Col><h3>{article.title}</h3><p>{article.content}</p></Col></Row>
</div>
)}
</>
)
}
}
I am passing the state variable addArticle as a prop however when I update the state, the component does not update. The component works as expected when not in the article state and then being mapped out. Shown is the Article component being added to the article state which is then being rendered. I rendered the props.post in the Article component so I could visually see the state updating. The props.post only takes the latest state when a new article is appended to the state
Thanks

props.mutators is deprecated

My code is working, but I got a warning :
Warning: As of React Final Form v3.3.0, props.mutators is deprecated
and will be removed in the next major version of React Final Form.
Use: props.form.mutators instead. Check your ReactFinalForm render
prop.
with this exemple :
https://codesandbox.io/s/kx8qv67nk5
return <Form
onSubmit={() => console.log('ok')}
mutators={{
...arrayMutators
}}
initialValues={ {customers: [{firstName: 'a', lastName: 'b'}]} }
render={({
handleSubmit,
mutators: { push, pop }, // injected from final-form-arrays above
pristine,
reset,
submitting,
values
}) => {
return (
<form onSubmit={handleSubmit}>
<div className="buttons">
<button
type="button"
onClick={() => push('customers', undefined)}>
Add Customer
</button>
</div>
<FieldArray name="customers">
{({ fields }) =>
fields.map((name, index) => (
<div key={name}>
<label>Cust. #{index + 1}</label>
<Field
name={`${name}.firstName`}
component="input"
placeholder="First Name"
/>
<Field
name={`${name}.lastName`}
component="input"
placeholder="Last Name"
/>
<GithubField name="user1" onSearch={(item) => {
this.api.getByFirstname(item).then(result => console.log(result));
}} />
<span
onClick={() => fields.remove(index)}
style={{ cursor: 'pointer' }}>❌</span>
</div>
))}
</FieldArray>
<div className="buttons">
<button type="submit" disabled={submitting || pristine}>
Submit
</button>
<button
type="button"
onClick={reset}
disabled={submitting || pristine}>
Reset
</button>
</div>
<pre>{JSON.stringify(values, 0, 2)}</pre>
</form>
)
}}
/>
How to do the right way ?
The warning already tells you what to do.
You must use form.mutators instead of just mutators
To do so, you can change your code from
mutators: { push, pop }
to
form: { mutators: { push, pop } }

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