I'm using Radio.Group in Ant Design form and depending on the option I would like to display a custom message below. I'm trying to use form.getFieldValue() and my code looks like this, but it doesn't work. How can I fix it?
const options = [
{
value: 1,
label: "Option 1"
},
{
value: 2,
label: "Option 2"
}
];
const Demo = () => {
const [form] = Form.useForm();
const { getFieldValue } = form;
return (
<Form
form={form}
initialValues={{
radio: 1
}}
>
<Form.Item
label="Radio Group"
name="radio"
>
<Radio.Group options={options} />
</Form.Item>
{getFieldValue("radio") === 1 && <div>This is option 1</div>}
{getFieldValue("radio") === 2 && <div>This is option 2</div>}
<Form.Item>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
);
};
Here is my codesandbox.
I think it's will help you to understand
const Demo = () => {
const [form] = Form.useForm();
const { getFieldValue, validateFields } = form;
const [radioValue, setRadioValue] = useState(1);
const onValidateForm = async () => {
const value = await validateFields();
window.alert(JSON.stringify(value, 2));
};
return (
<Form
{...layout}
form={form}
name="basic"
initialValues={{
radio: 1
}}
>
<Form.Item label="Options" name="radio">
<Radio.Group options={options} onChange={e => setRadioValue(e.target.value)} />
</Form.Item>
{radioValue === 1 && <div>This is option 1</div>}
{radioValue === 2 && <div>This is option 2</div>}
<Form.Item>
<Button type="primary" htmlType="submit" onClick={onValidateForm}>
Submit
</Button>
</Form.Item>
</Form>
);
};
Related
I have a Formik form that is using a progressive stepper and have multiple fields across different components, thus requiring the need to store the values in React Context. However none of the field values are being passed, so when I click submit, all values are empty strings and the validation fails. You can see on each Formik Field i am setting the value as {state.[field]}, which comes from the Context, so I believe something is going wrong here. Can anyone see what I'm doing wrong?
Thanks a lot
Here is my parent component
const AddSongPage = () => {
const { state, dispatch } = useUploadFormContext();
const initialValues = {
name: "",
};
const { mutate: handleCreateTrack } = useCreateSyncTrackMutation(
gqlClient,
{}
);
const handleSubmit = (values: any) => {
handleCreateTrack(
{
...values,
},
{
onSuccess() {
console.log("Track added succesfully");
},
}
);
};
const validate = Yup.object({
name: Yup.string().required("Song name is required"),
description: Yup.string().optional(),
});
return (
<Layout headerBg="brand.blue">
<Formik
onSubmit={(values) => handleSubmit(values)}
initialValues={initialValues}
validationSchema={validate}
>
<Form>
<Box> {state.activeStep === 1 && <Step1 />}</Box>
<Box> {state.activeStep === 2 && <Step2 />}</Box>
<Box> {state.activeStep === 3 && <Step3 />}</Box>
<Button type={"submit"}>Submit</Button>
</Form>
</Formik>
</Layout>
);
};
Here is step 1
const Step1 = () => {
const { state, dispatch } = useUploadFormContext();
const onInputChange = (e: FormEvent<HTMLInputElement>) => {
const inputName = e.currentTarget.name;
dispatch({
type: "SET_UPLOAD_FORM",
payload: {
[inputName]: e.currentTarget.value,
},
});
};
return (
<Stack spacing={4}>
<Field name={"name"}>
{({ field, form }: any) => (
<FormControl isInvalid={form.errors.name && form.touched.name}>
<Input
{...field}
onChange={onInputChange}
value={state.name}
/>
</FormControl>
)}
</Field>
<Field name={"description"}>
{({ field, form }: any) => (
<FormControl isInvalid={form.errors.name && form.touched.name}>
<Input
{...field}
onChange={onInputChange}
value={state.description}
/>
</FormControl>
)}
</Field>
</Stack>
);
};
export default Step1;
React beginner on testing, i'm testing form submits, i have tested already few form submits successfully, but this one has 'Accept' button AND 'Delete' button and both uses same 'handleSubmit' function so thats why i have conditions inside that function, depending on which clicked. I need some advice on how to test form submit when
”Accept” button is clicked ?
English is not my mother language so could be mistakes. If any question just ask me.
My test will pass only if i delete one of those buttons but i need both.
test:
import React from "react";
import {
render,
screen,
RenderResult,
cleanup,
fireEvent,
getByRole,
getByDisplayValue,
} from "#testing-library/react";
const onFormSubmit = jest.fn();
const currentCamera = "0-0-0-2";
describe("Testing component", () => {
const AddCamera = () =>
render(
<Provider store={store}>
<MemoryRouter>
<CameraForm onSubmit={onFormSubmit} key={"cameraform-" + currentCamera} />
</MemoryRouter>
</Provider>
);
test("Testing Add Camera", () => {
AddCamera();
const AddName = screen.getByTestId(/^AddName/i);
expect(AddName).toBeInTheDocument();
fireEvent.change(AddName, { target: { value: "Camera 2" } });
expect(AddName).toHaveValue("Camera 2");
fireEvent.submit(screen.getByTestId("renderAddForm"));
expect(onFormSubmit).toHaveBeenCalled();
});
});
code:
import {
...
} from "#material-ui/core/";
const handleSubmit = (event) => {
event.preventDefault();
if (state.button === 1) {
if (!Camera) {
return;
}
const { name} = state;
refresh();
dispatch(
addCamera(site.identifier, Camera.identifier, {
name
})
);
externalOnSubmit();
}
if (state.button === 2) {
event.preventDefault();
if (!Camera) {
return;
}
refresh();
dispatch(
deleteCamera(site.identifier, Camera.identifier)
);
externalOnSubmit();
}
};
const renderAdd = () => {
const { helperText, error, name } = state;
return (
<React.Fragment>
<Box sx={boxStyle} data-testid="CameraForm">
<form
data-testid="renderAddForm"
onSubmit={handleSubmit}>
<div className="handle">
<Box>
<Button
onClick={nulll}
aria-label="close-settings-popup">
<Close />
</Button>
</Box>
</div>
<div>
<FormGroup>
<Box>
<FormControl>
<TextField
helperText={helperText.name}
error={error.name}
inputProps={{
"data-testid": "AddName",
}}
InputProps={{
className: classes.underline,
}}
InputLabelProps={{
className: classes.inputLabelColor,
}}
required={true}
type="text"
id="name"
value={name}
onChange={handleChange}
label='Name'
></TextField>
</FormControl>
</Box>
</FormGroup>
</div>
{!state.readOnly ? (
<div>
<Button
onClick={() => (state.button = 2)}
type="submit"
>
<Trans i18nKey="form.delete">Delete</Trans>
</Button>
<Button
data-testid="submitButton"
onClick={() => (state.button = 1)}
type="submit"
variant="contained"
color="primary"
disabled={
!currentSite ||
Object.values(state.error).some((v) => {
return v === true;
})
}
className={classes.button_basic}
startIcon={<Done />}
>
Accept
</Button>
</div>
) : (
""
)}
</form>
</Box>
</React.Fragment>
);
};
I need to map dynamicaly on a array. This array can change with a search field and i need to know what checkbox are checked to send my data with a submit but i dont find the right way to do to check the checkbox with Formik. This is my code :
const initialValues = {
checked: [],
};
const onSubmit = (values) => {
console.log(values);
};
<Formik onSubmit={onSubmit} initialValues={initialValues}>
{({ values, handleSubmit }) => (
<form
onSubmit={handleSubmit}
noValidate={true}
>
{Listing.map((Search, index) => {
if (
Search.installationAddress.includes(
searchMeter
) ||
Search.deviceId.includes(searchMeter)
) {
return (
<div
key={index}
>
<Checkbox
color="text.standard"
label={`${Search.installationAddress}`+" Compteur N° " +`${Search.deviceId}`}
type="checkbox"
name="checked"
value={{
contractAccountId:
Search.contractAccountId,
deviceId: Search.deviceId,
}}
size="large"
/>
</div>
);
}
})}
<Checkbox
color="text.standard"
label="I confirm"
onChange={() => setValidationButton(!validationButton)}
value=""
checked={validationButton}
/>
<Button
type="submit"
disabled={!validationButton}
>
ACTIVATE
</Button>
</form>
)}
</Formik>
I am trying to disable the form submit button until the validation is fully passed.
I have come across the threads regarding this topic.
This thread helps to trigger validation without display of UI errors:
https://github.com/ant-design/ant-design/issues/25993
The below code works within the hoc of my footer button wrapper, but it is verifying for all fields to be touched and even applicable for non-required fields, which is not right and expected.
<Form.Item shouldUpdate>
{() => (
<Button
type="primary"
htmlType="submit"
disabled={
!!form
.getFieldsError()
.filter(({ errors }) => errors.length).length
}
>
Log in
</Button>
)}
</Form.Item>
Unfortunately, https://github.com/ant-design/ant-design/issues/23281 thread is full of Chinese and I cannot understand it.
My existing form.validateFields refers to formInstance, but it is referring to: InternalFormInstance.
How to import this and validate? Is it really supported in antd 4 version?
CodeSandbox link: https://codesandbox.io/s/gallant-merkle-21izz?file=/src/withFormWrapperHOC.tsx
Example code as a reference will be helpful!
The form validation miserably fails when select list is included. Form onChange or onChangeValues don't work; when we dig more the !form.isFieldsTouched(true) is always true even when there is no validation rule associated with Select List.
Ref ticket: https://github.com/ant-design/ant-design/issues/8436
It looks like Antd has some open issues with Rc-select api integrations and also which is not exposed.
Should we really consider Antd or any other form validation?
It looks like there is a bunch of bugs in existing Antd library for this validation and this is not supported unless they officially announce with a release. There are open tickets and none have addressed.
I believe ill not find a solution for this question for now.
For disabling validation on first render you can use Ref workaround
const isMounting = React.useRef(false);
useEffect(() => {
if (isMounting.current) {
const disableStatus = !!form
.getFieldsError()
.filter(({ errors }) => errors.length).length;
setIsDisabled(disableStatus);
}
else {
isMounting.current = true
}
}, [form]);
Another option would to disable login button till all the form inputs are filled and then validate with Login
import React, { useState } from "react";
import { Button, Form, Select } from "antd";
import "antd/dist/antd.css";
const { Option } = Select;
export function withFormWrapper(WrappedComponent: any) {
return (props: any) => {
const [form] = Form.useForm();
const [isDisabled, setIsDisabled] = useState(true);
function fieldIsEmpty(field) {
let fieldValue = form.getFieldValue(field.name.join("."));
return (
fieldValue === undefined || [].concat(fieldValue).join().trim() === ""
);
}
function fieldHasError(field) {
return field.errors.length > 0;
}
function isValid() {
const fields = form
.getFieldsError()
.filter((field) => fieldIsEmpty(field) || fieldHasError(field));
console.log(fields);
setIsDisabled(fields.length > 0);
}
const validate = () => {
form
.validateFields()
.then((values: any) => {
console.log("success");
})
.catch((errorInfo: any) => {
console.log("failure");
});
};
return (
<Form form={form} onChange={isValid}>
<WrappedComponent {...props} />
<Form.Item name="gender" label="Gender" rules={[{ required: true }]}>
<Select
placeholder="Select a option and change input text above"
onChange={isValid}
allowClear
>
<Option value="male">male</Option>
<Option value="female">female</Option>
<Option value="other">other</Option>
</Select>
</Form.Item>
<Form.Item>
<Button
type="default"
htmlType="submit"
onClick={() => form.resetFields()}
>
Cancel
</Button>
</Form.Item>
<Form.Item shouldUpdate>
{() => (
<Button
onClick={validate}
type="primary"
htmlType="submit"
disabled={isDisabled}
>
Log in
</Button>
)}
</Form.Item>
</Form>
);
};
}
Here working example
With Form.Provider onFormChange validation can be fired for Select Component
import React, { useState } from "react";
import { Button, Form, Select } from "antd";
import "antd/dist/antd.css";
const { Option } = Select;
export function withFormWrapper(WrappedComponent: any) {
return (props: any) => {
const [form] = Form.useForm();
const [isDisabled, setIsDisabled] = useState(true);
function fieldIsEmpty(field) {
let fieldValue = form.getFieldValue(field.name.join("."));
return (
fieldValue === undefined || [].concat(fieldValue).join().trim() === ""
);
}
function fieldHasError(field) {
return field.errors.length > 0;
}
function isValid() {
const fields = form
.getFieldsError()
.filter((field) => fieldIsEmpty(field) || fieldHasError(field));
setIsDisabled(fields.length > 0);
}
const validate = () => {
form
.validateFields()
.then((values: any) => {
console.log("success");
})
.catch((errorInfo: any) => {
console.log("failure");
});
};
return (
<Form.Provider onFormChange={isValid}>
<Form form={form}>
<WrappedComponent {...props} />
<Form.Item
name="gender"
label="Gender"
rules={[{ required: true }]}
shouldUpdate
>
<Select
placeholder="Select a option and change input text above"
allowClear
>
<Option value="male">male</Option>
<Option value="female">female</Option>
<Option value="other">other</Option>
</Select>
</Form.Item>
<Form.Item name="Tags" label="Tags" shouldUpdate>
<Select
mode="tags"
style={{ width: "100%" }}
placeholder="Tags Mode"
onChange={handleChange}
>
{children}
</Select>
</Form.Item>
<Form.Item>
<Button
type="default"
htmlType="submit"
onClick={() => form.resetFields()}
>
Cancel
</Button>
</Form.Item>
<Form.Item shouldUpdate>
{() => (
<Button
onClick={validate}
type="primary"
htmlType="submit"
disabled={isDisabled}
>
Log in
</Button>
)}
</Form.Item>
</Form>
</Form.Provider>
);
};
}
Here working example
I have an application where users can add their data in a form with 2 fields. They can add as many fields as they want.
const Demo = () => {
const onFinish = values => {
console.log("Received values of form:", values);
};
const firstDefaultOpen = {
name: 0,
key: 0,
isListField: true,
fieldKey: 0
};
const testHandler = a => {
console.log("result", a.concat(firstDefaultOpen));
return a.concat(firstDefaultOpen);
};
return (
<Form name="dynamic_form_nest_item" onFinish={onFinish} autoComplete="off">
<Form.List name="users">
{(fields, { add, remove }) => {
return (
<div>
{testHandler(fields).map(field => (
<Space
key={field.key}
style={{ display: "flex", marginBottom: 8 }}
align="start"
>
<Form.Item
{...field}
name={[field.name, "first"]}
fieldKey={[field.fieldKey, "first"]}
rules={[{ required: true, message: "Missing first name" }]}
>
<Input placeholder="First Name" />
</Form.Item>
<Form.Item
{...field}
name={[field.name, "last"]}
fieldKey={[field.fieldKey, "last"]}
rules={[{ required: true, message: "Missing last name" }]}
>
<Input placeholder="Last Name" />
</Form.Item>
<MinusCircleOutlined
onClick={() => {
remove(field.name);
}}
/>
</Space>
))}
<Form.Item>
<Button
type="dashed"
onClick={() => {
add();
}}
block
>
<PlusOutlined /> Add field
</Button>
</Form.Item>
</div>
);
}}
</Form.List>
<Form.Item>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
);
};
My target is to set a default pair of inputs as opened. Now you can see when you open the application that first name and last name inputs are open as default. This i made with:
const firstDefaultOpen = {
name: 0,
key: 0,
isListField: true,
fieldKey: 0
};
const testHandler = a => {
console.log("result", a.concat(firstDefaultOpen));
return a.concat(firstDefaultOpen);
};
and here i map() the new array:
{testHandler(fields).map(field => (...
The issue is when i click on Add field button, because there when i try to write something in one input also the same text appears on the another. This is happening because when i click on Add field button you can see in the console.log("result", a.concat(firstDefaultOpen));, that 2 objects are with the same values like:
[Object, Object]
0: Object
name: 0
key: 0
isListField: true
fieldKey: 0
1: Object
name: 0
key: 0
isListField: true
fieldKey: 0
Question: How to set the first object with all values 0, and the next values to be higher, and to get something like?:
[Object, Object]
0: Object
name: 0
key: 0
isListField: true
fieldKey: 0
1: Object
name: 1
key: 1
isListField: true
fieldKey: 1
2: Object
name: 2
key: 2
isListField: true
fieldKey: 2
...
demo: https://codesandbox.io/s/hungry-star-nu5ld?file=/index.js:783-819
You can do this by just simply changing your firstDefaultOption from variable to function like this
function firstDefaultOption(optionVal){
return {
name: optionVal,
key: optionVal,
isListField: optionVal,
fieldKey: optionVal
}
}
and then changing your test handler like this
const testHandler = a => {
console.log("A",a)
console.log("result", a.concat(firstDefaultOption(a.length)));
return a.concat(firstDefaultOption(a.length));
};
Kind of a messy solution but it works... Comments added throughout
import React from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./index.css";
import { Form, Input, Button, Space } from "antd";
import { MinusCircleOutlined, PlusOutlined } from "#ant-design/icons";
const Demo = () => {
const onFinish = values => {
console.log("Received values of form:", values);
};
const firstDefaultOpen = {
//Set default to null
name: null,
key: null,
isListField: true,
fieldKey: null
};
const testHandler = a => {
//Check for null and set first to 0
if(firstDefaultOpen.name == null){
firstDefaultOpen.name = 0;
firstDefaultOpen.key = 0;
firstDefaultOpen.fieldKey = 0;
}
//If not null.. then add 1 to each object item to make each unique
else{
firstDefaultOpen.name = firstDefaultOpen.name +1;
firstDefaultOpen.key = firstDefaultOpen.key +1;
firstDefaultOpen.fieldKey = firstDefaultOpen.fieldKey +1;
}
console.log("result", a.concat(firstDefaultOpen));
return a.concat(firstDefaultOpen);
};
return (
<Form name="dynamic_form_nest_item" onFinish={onFinish} autoComplete="off">
<Form.List name="users">
{(fields, { add, remove }) => {
return (
<div>
{testHandler(fields).map(field => (
<Space
key={field.key}
style={{ display: "flex", marginBottom: 8 }}
align="start"
>
<Form.Item
{...field}
name={[field.name, "first"]}
fieldKey={[field.fieldKey, "first"]}
rules={[{ required: true, message: "Missing first name" }]}
>
<Input placeholder="First Name" />
</Form.Item>
<Form.Item
{...field}
name={[field.name, "last"]}
fieldKey={[field.fieldKey, "last"]}
rules={[{ required: true, message: "Missing last name" }]}
>
<Input placeholder="Last Name" />
</Form.Item>
<MinusCircleOutlined
onClick={() => {
remove(field.name);
}}
/>
</Space>
))}
<Form.Item>
<Button
type="dashed"
onClick={() => {
add();
}}
block
>
<PlusOutlined /> Add field
</Button>
</Form.Item>
</div>
);
}}
</Form.List>
<Form.Item>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
);
};
ReactDOM.render(<Demo />, document.getElementById("container"));
You could declare the defaultOpen object with the let keyword and invoke an "update" function whenever you're ready to display an updated version, like:
let defaultOpen = getFirstDefaultOpen();
log(defaultOpen);
updateDefaultOpen(defaultOpen);
log(defaultOpen);
updateDefaultOpen(defaultOpen);
log(defaultOpen);
function updateDefaultOpen(currentState){
currentState.name++;
currentState.key++;
currentState.fieldKey++;
}
function getFirstDefaultOpen(){
return {
name: 0,
key: 0,
isListField: true,
fieldKey: 0
}
}
function log(currentState){
const output = Object.keys(currentState).reduce((str, key)=>{
str += `${key}:${currentState[key]}, `;
return str;
},"");
console.log(`{ ${output}}`);
}