I have a table showing school name and its fee or rates. There is button in table which opens a drawer containing this component or form.There is a button in table which toggles the state of drawer. As long as i am only reading values in the form by clicking on the button my initalValue updates. But if i update price and submit form for any 1 entry , then initial value my price or school doesn't change for other entries or schools. While my state is changing accordingly which I did check by rendering state. But my initialValue of form doesn't updating accordingly
import React, { useState, useEffect } from 'react'
import { Table, Button, Popconfirm, Drawer, Form, Input, Select, notification } from 'antd'
const { Option } = Select
const layout = {
labelCol: {
span: 10,
},
wrapperCol: {
span: 10,
},
}
const tailLayout = {
wrapperCol: {
offset: 7,
span: 14,
},
}
function SchoolRates({ form, relevantData }) {
const [rates, setRates] = useState(null)
useEffect(() => {
setRates(relevantData)
}, [someCondition])
const updateRates = e => {
e.preventDefault()
form.validateFields((error, values) => {
console.log(error, values, 'forms')
})
return 'Data Updated'
}
return (
<>
<div>
{`${rates?.schoolName}, ${rates?.Price}`}
</div>
<Form {...layout} initialValue={rates} onSubmit={e => updateRates(e)}>
<Form.Item label="Country" style={{ marginBottom: '5px' }} size="small">
{form.getFieldDecorator('schoolName', {
initialValue: rates?.schoolname,
rules: [{ required: true, message: 'Please select a school!' }],
})(
<Select
placeholder="School"
allowClear
size="small"
style={{ borderRadius: 0 }}
>
{schoolList.map(item => (
<Option value={item}>{item}</Option>
))}
</Select>,
)}
</Form.Item>
<Form.Item label="Price / Learner" style={{ marginBottom: '5px' }} size="small">
{form.getFieldDecorator('price', {
initialValue: rates?.price,
rules: [{ required: true, message: 'Please enter price/learner' }],
})(
<Input
size="small"
type="number"
style={{ borderRadius: 0 }}
/>,
)}
</Form.Item>
<Form.Item {...tailLayout}>
<Button type="primary" htmlType="submit" size="small">
Save
</Button>
</Form.Item>
</Form>
</>
)
}
export default Form.create()(UpdateRates)
I just want to re render the complete form to update data as soon as I click on other school from my table
To update initialValue form input fields need to be cleaned or reset.
So add form.resetFields()(to reset data in input fields) after successful submission of form.
Related
I build a simple form using formik. I have a situation that one field is hidden after I choose in radio buttun 'no'.
But if I enter value to this field and then choose 'no' to hide it, and submit the form I will get in the final json also the value of the hidden field.
My expectation is when field isn't show to the screen, remove it from the final json.
My form:
import React from "react";
import { Formik } from "formik";
import { Form } from "react-bootstrap";
import { Radio } from "antd";
const CustomFormik = () => {
return (
<>
<Formik
initialValues={{
input1: null,
radio: null,
input2: null
}}
onSubmit={(values) => {
console.log(values);
}}
>
{(formik) => (
<>
<Form
onSubmit={formik.handleSubmit}
className="custom-formik-container"
>
<Form.Group>
<span>input1: </span>
<Form.Control
id={"input1"}
name={"input1"}
{...formik.getFieldProps("input1")}
/>
</Form.Group>
{formik.getFieldProps("radio").value !== 2 && (
<Form.Group>
<span>input2: </span>
<Form.Control
id={"input2"}
name={"input2"}
{...formik.getFieldProps("input2")}
/>
</Form.Group>
)}
<Form.Group>
<span>radio: </span>
<Radio.Group {...formik.getFieldProps("radio")}>
<Radio value={1}>yes</Radio>
<Radio value={2}>no</Radio>
</Radio.Group>
</Form.Group>
</Form>
<button
style={{
width: 200,
height: 30,
background: "#113776",
color: "#fff"
}}
onClick={() => formik.handleSubmit()}
>
S U B M I T
</button>
</>
)}
</Formik>
</>
);
};
export default CustomFormik;
For example enter:
'abc' to the first input.
'efg' to the second input.
'no' to the radio button.
The final json in console.log will be:
{input1: "abc",input2: "efg", radio: 2}
But my expectation in to be:
{input1: "abc", radio: 2}
codesandbox
Thank you guys!
I don't know if this fits your needs but I had a similar case in my project using redux-form and what i did was to delete the property before submiting it
sth like this
onSubmit={(values) => {
if (values.radio === 2) {
let clonedValues = { ...values };
delete clonedValues.input2;
console.log(clonedValues);
} else {
console.log(values);
}
}}
this will do the job.
onSubmit={(values) => {
if(values.radio === 2){
delete values['input2'];
}
console.log(values);
}}
I dynamically create checkboxes in item forms, when I submit a form in item forms, I get an array of checkbox IDs. How can I either bind true / false to these IDs or overwrite the object, create a new one or something else by clicking on the checkbox? How to understand which checkbox was clicked and bind a value to it true / false?
const [checkboxData, setCheckboxData] = useState(undefined);
useEffect(() => {
(async () => {
const data = await request(`/document-type/typeDocuments`, { method: 'get', cancelTokenKey: 'typeDocuments' });
if (data) { setCheckboxData(data.content); }
})();
}, []);
function onChangeCheckbox(checkedValues) {
setCheckboxData(prevState => {
prevState.map(item => ...)
}) // tried this, but then when you click on the checkbox everything breaks
}
<Form
onFinish={onFormFinish}
onFinishFailed={onFormFailed}
>
<Form.Item
name="typesDocuments"
valuePropName="checked"
>
<Checkbox.Group style={{ width: '100%' }}>
<Row>
{checkboxData?.map(item => <Col span={12}><Checkbox key={item?.id} value={item?.id} onChange={onChangeCheckbox}>{item?.Name}</Checkbox></Col>)}
</Row>
</Checkbox.Group>
</Form.Item>
<Form.Item>
<Space style={{ display: 'flex', justifyContent: 'end' }}>
<Button type="primary" htmlType="submit"> {intl("modal-btn-ok")} </Button>
</Space>
</Form.Item>
</Form>
For ease of viewing, I created an example of a project in a
sandbox
So I have a wrapper parent component that sets the layout of the application, menu and also has a save button. Child components render according to routes ( only 2-3 for now ).
I'm developing something where the position of the SAVE button is fixed ( in the parent component) and whenever the save button is pressed it should execute the save method of the child component.
I'm able to call child methods from parents using refs for now (looking for a better solution) but then I lose the onFininsh functionality provided by ANT Design Form and hence losing all validations, values of items of form, and other helpful features.
This is my Parent.
class Layout extends React.Component {
constructor(props) {
super(props);
this.child = React.createRef();
}
render() {
return(
<button onClick = {this.child.current.save }>Save</button>
<Two ref={this.child} />
)
}
}
This is my child
const ChildComponent = forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({
save() {
alert("Child Function Called");
},
}));
function saveData(values) {
// How to execute this function from parent
}
return (
<Form
form={form}
{...formItemLayout}
// onFinishFailed={finishFailed}
onFinish={saveData}
>
-----
----
</Form>
)
})
I can get alerts from the save function of the child component. Also, there is a router in between both components. This may not be the best approach or maybe anti react approach but there is no harm in trying or learning anything new.
Yes, it is still possible. Using the Form API docs, there is a submit method that is exposed that you can call. It will validate fields so there is no need to call validateFields either.
There is no need to create a custom save method since submit will do the job. The thing is that you have to pass the ref down to the Form component.
class Layout extends React.Component {
constructor(props) {
super(props);
this.child = React.createRef();
}
render() {
return (
<>
<button onClick={() => this.child.current.submit()}>Save</button>
<DemoForm ref={this.child} />
</>
);
}
}
const DemoForm = forwardRef((props, ref) => {
const onFinish = (values) => {
console.log('Success:', values);
};
const onFinishFailed = (errorInfo) => {
console.log('Failed:', errorInfo);
};
return (
<Form
ref={ref}
name="basic"
labelCol={{
span: 8,
}}
wrapperCol={{
span: 16,
}}
initialValues={{
remember: true,
}}
onFinish={onFinish}
onFinishFailed={onFinishFailed}
autoComplete="off"
>
<Form.Item
label="Username"
name="username"
rules={[
{
required: true,
message: 'Please input your username!',
},
]}
>
<Input />
</Form.Item>
<Form.Item
label="Password"
name="password"
rules={[
{
required: true,
message: 'Please input your password!',
},
]}
>
<Input.Password />
</Form.Item>
<Form.Item
name="remember"
valuePropName="checked"
wrapperCol={{
offset: 8,
span: 16,
}}
>
<Checkbox>Remember me</Checkbox>
</Form.Item>
<Form.Item
wrapperCol={{
offset: 8,
span: 16,
}}
>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
);
});
DEMO
I have a project on ReactJS which you can find here (see develop-branch) or check it out on our web site.
As you can see, I use formik to handle forms.
Now I have only one submit button which handles all forms however it does not link with forms by form attribute. It was OK.
Unfortunately, I've faced a problem when having a go to implement form validation. I still prefer using formik validation, but the thing is that it demands a direct connection between form and submit button like this:
export function GenerateButton(props) {
return (
<Button id="genButton"
form="form1"
type="submit"
onClick={props.onClick}>
Generate
</Button>
);
}
Any ideas how I can link all forms with submit button?
Or I have to just use fictitious buttons in every form (position: absolute; left: -9999px;) and imitate their click after pushing generate button?
P.S. now there is id="forms" in html form tag, it is just stupid mistake, must be class attribute. I can generate unique id this way: id={"form"+(props.index + 1)}.
P.S.S. I am so sorry for my English.
I think you can handle this with fieldarray of Formik very easily.
you will have an array and a list of forms in it then you can simply add and remove forms.
you won't have any problem with using validation of Formik too.
here is an example that exactly does what you want:
import React from "react";
import { Formik, Form, Field, FieldArray } from "formik";
const formInitialValues = { name: "", lastName: "" };
const FormList = () => (
<Formik
initialValues={{ forms: [formInitialValues] }}
onSubmit={values =>
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
}, 500)
}
render={({ values }) => (
<Form>
<FieldArray
name="forms"
render={arrayHelpers => (
<div>
{values.forms.map((formItem, index) => (
<div key={index}>
<Field name={`forms.${index}.name`} />
<Field name={`forms.${index}.lastName`} />
<button
type="button"
onClick={() => arrayHelpers.remove(index)} // remove a form from the list of forms
>
-
</button>
<button
type="button"
onClick={() =>
arrayHelpers.insert(index, formInitialValues)
} // insert an empty string at a position
>
+
</button>
</div>
))}
<div>
<button type="submit">Submit</button>
</div>
</div>
)}
/>
</Form>
)}
/>
);
export default FormList;
I have provided a code sandbox version for you too
please let me know if you still have any problem
more reading:
https://jaredpalmer.com/formik/docs/api/fieldarray#fieldarray-array-of-objects
The fieldArray solution looks quite complex and I think useFormik is a better option here.
For example:
import React from 'react'
import { Button, Grid, TextField } from '#mui/material'
import clsx from 'clsx'
import { useFormik } from 'formik'
export function MyFormComponent(props: any) {
const formA = useFormik({
initialValues: {
age: 23
},
onSubmit: values => {
alert(JSON.stringify(values, null, 2))
}
})
const formB = useFormik({
initialValues: {
name: 'bar'
},
onSubmit: values => {
alert(JSON.stringify(values, null, 2))
}
})
const formC = useFormik({
initialValues: {
description: 'A flat object'
},
onSubmit: values => {
alert(JSON.stringify(values, null, 2))
}
})
const submitForm = () => {
//get your values here
const values1 = formA.values
const values2 = formB.values
const values3 = formC.values
//data access for storing the values
}
return (
<div>
<form onSubmit={formA.handleSubmit}>
<TextField
style={{ marginBottom: 20 }}
fullWidth
type='number'
label='Age'
id='age'
name='age'
InputProps={{
inputProps: {
min: 0
}
}}
onChange={formA.handleChange}
value={formA.values.age}
/>
</form>
<form onSubmit={formB.handleSubmit}>
<Grid container spacing={2}>
<Grid item xs={6}>
<TextField
onChange={formB.handleChange}
value={formB.values.name}
style={{ marginRight: 20 }}
fullWidth
name='name'
type='text'
label='Name'
InputProps={{
inputProps: {
min: 0
}
}}
/>
</Grid>
</Grid>
</form>
<form onSubmit={formC.handleSubmit}>
<Grid container spacing={2}>
<Grid item xs={6}>
<TextField
onChange={formC.handleChange}
value={formC.values.description}
style={{ marginRight: 20 }}
fullWidth
name='description'
type='text'
label='Description'
InputProps={{
inputProps: {
min: 0
}
}}
/>
</Grid>
</Grid>
</form>
<Button color='secondary' variant='contained' onClick={submitForm}>
Submit
</Button>
</div>
)
}
I'm created a special utility library to work with multiple forms (including repeatable forms) from one place. I see that you are using Formik component (instead of useFormik hook), but maybe it still be useful.
Check this example
https://stackblitz.com/edit/multi-formik-hook-basic-example?file=App.tsx
You can find the library here
I am using antd to create a form and use a fieldDecorator to decorate the various form elements which gives me some extra functionality later.
Everything works fine but I want to create the FormItems in separate files/components and have no idea how to parse this to a new component class.
Here is my code so far which works, but how would I extract and use the existing fieldDecorator in a separate component such as the ProcessSelect component for example.
class ZalosForm extends React.Component {
constructor(...args) {
super(...args);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(e) {
e.preventDefault();
this.props.form.validateFields((err, values) => {
if (!err) {
console.log('Received values of form: ', values);
}
})
}
render() {
const { getFieldDecorator } = this.props.form;
const formItemLayout = {
labelCol: { span: 6 },
wrapperCol: { span: 14 }
};
return (
<div>
<Form inline style={{ marginTop: 10, marginBottom: 10 }} onSubmit={this.handleSubmit}>
<FormItem
{...formItemLayout}
hasFeedback
>
{getFieldDecorator('qualityLabel', {
rules: [{
min: 11, max: 11, message: 'Invalid QL'
}]
})(
<Input style={{ width: '200px' }} size="default" placeholder="QL" />
)}
<FormItem>
<ProcessSelect />
</FormItem>
<FormItem>
<ButtonGroup size="default">
<Button htmlType="submit" size="default" icon="search" onSubmit={this.handleSubmit} />
<Button size="default" icon="close" />
</ButtonGroup>
</FormItem>
</Form>
</div>
)
}
}