I have a form where and I am validating the form using Formik, I want to multiply the value on the quantity input and unit cost input when there's an input and then automatically display it in the total input. I'm using Formik + Chakra_UI.
<Formik
initialValues={{
productName: "",
productNumber: "",
unitCost: 0,
totalCost: 0,
quantity: 0,
}}
>
{({ values }) => (
<Form>
<Field name="productName">
{() => (
<Grid templateColumns="repeat(2, 1fr)" gap={5}>
<Box>
<FormControl>
<FormLabel htmlFor="productName">Product Name:</FormLabel>
<Input id="productName" placeholder="Product Name" />
{/* <FormErrorMessage>{form.errors.name}</FormErrorMessage> */}
</FormControl>
</Box>
<Box>
<FormControl>
<FormLabel htmlFor="productNumber">
Product Number:
</FormLabel>
<Input id="productNumber" placeholder="Product Number" />
{/* <FormErrorMessage>{form.errors.name}</FormErrorMessage> */}
</FormControl>
</Box>
<Box>
<FormControl>
<FormLabel htmlFor="quantity">Quantity:</FormLabel>
<Input id="quantity" placeholder="Quanity" />
{/* <FormErrorMessage>{form.errors.name}</FormErrorMessage> */}
</FormControl>
</Box>
<Box>
<FormControl>
<FormLabel htmlFor="unitCost">Unit Cost:</FormLabel>
<Input id="unitCost" placeholder="Unit Cost" />
{/* <FormErrorMessage>{form.errors.name}</FormErrorMessage> */}
</FormControl>
</Box>
<Box>
<FormControl>
<FormLabel htmlFor="totalCost">Total Cost:</FormLabel>
<Input id="totalCost" placeholder="Total Cost" />
{/* <FormErrorMessage>{form.errors.name}</FormErrorMessage> */}
</FormControl>
</Box>
</Grid>
)}
</Field>
<Button isFullWidth mt={6} colorScheme="green" type="submit">
Submit
</Button>
</Form>
)}
</Formik>
To keep code for state management shorter you could just remove totalCost from values and compute it on use.
Updated code would look like this:
<Formik
initialValues={{
productName: "",
productNumber: "",
unitCost: 0,
quantity: 0,
}}
onSubmit={...}
>
{({ values }) => (
<Form>
<Grid templateColumns="repeat(2, 1fr)" gap={5}>
// ... other boxes stay same as before
<Box>
<FormControl>
<FormLabel htmlFor="totalCost">Total Cost:</FormLabel>
<Input id="totalCost" placeholder="Total Cost" value={values.quantity * values.unitCost} />
{/* <FormErrorMessage>{form.errors.name}</FormErrorMessage> */}
</FormControl>
</Box>
</Grid>
<Button isFullWidth mt={6} colorScheme="green" type="submit">
Submit
</Button>
</Form>)}
</Formik>
then you'll repeat same calculation for onSubmit.
Coult be also good idea to apply some rounding on as I assume you use it for currency value={values.quantity * values.unitCost}
maybe you could just simplify it to
<FormControl>
<FormLabel htmlFor="totalCost">Total Cost:</FormLabel>
<Box id="totalCost">{Math.round((values.quantity * values.unitCos + Number.EPSILON) * 100) / 100}</Box>
</FormControl>
rounding explained here: Round to at most 2 decimal places (only if necessary)
Related
const updateCreateFormField = (e) => {
const { name, value } = e.target;
setCreatForm({
...createForm,
[name]: value,
})
console.log({ name, value });
};
//The onChange variable in the fields is updated on the above code. I am unable to find the solution towards making the below fields function properly. I tried using formik's setFieldValue however It didn't work
const formik = useFormik({
initialValues: {
Name: "",
Address: "",
phoneNumber: "",
Email: "",
}
})
The below code is the return function:
return (<div className="App">
{updateForm._id && (<div>
<h2>Update Customer:</h2>
<Box height={20} />
<Formik initialValues={formik.initialValues}
validationSchema={object({
Name: string().required("Please enter a name").min(3, "Name is too short"),
Address: string().required("Please enter an address").min(3, "Address is too short"),
phoneNumber: number().required("Please enter a phone number").min(4, "Phone number is too short"),
Email: string().required("Please enter an email").email("Invalid email"),
})}
onSubmit={(values, formikHelpers) => {
console.log(values);
formikHelpers.resetForm();
}}
>
{({ errors, isValid, touched, dirty }) => (
<Form onSubmit={updateCustomer}>
<Field name="Name" type="name" as={TextField} variant="outlined" color="primary" label="Name" fullWidth
onChange={handleUpdateFieldChange}
value={updateForm.Name}
error={Boolean(errors.Name) && Boolean(touched.Name)}
helperText={Boolean(touched.Name) && errors.Name}
/>
<Box height={14} />
<Field name="Address" type="Address" as={TextField} variant="outlined" color="primary" label="Address" fullWidth
onChange={handleUpdateFieldChange}
value={updateForm.Address}
error={Boolean(errors.Address) && Boolean(touched.Address)}
helperText={Boolean(touched.Address) && errors.Address}
/>
<Box height={14} />
<Field name="phoneNumber" type="number" as={TextField} variant="outlined" color="primary" label="Phone Number" fullWidth
error={Boolean(errors.phoneNumber) && Boolean(touched.phoneNumber)}
helperText={Boolean(touched.phoneNumber) && errors.phoneNumber}
onChange={handleUpdateFieldChange}
value={updateForm.phoneNumber}
/>
<Box height={14} />
<Field name="Email" type="email" as={TextField} variant="outlined" color="primary" label="Email" fullWidth
error={Boolean(errors.Email) && Boolean(touched.Email)}
helperText={Boolean(touched.Email) && errors.Email}
onChange={handleUpdateFieldChange}
value={updateForm.Email}
/>
<Box height={16} />
<Button type="submit" variant="contained" color="primary" size="large" disabled={!dirty || !isValid} >Update Customer</Button>
</Form>
)}
</Formik>
</div>)}
{!updateForm._id && <div>
<h2>Create Customer:</h2>
<Box height={20} />
<Formik initialValues={formik.initialValues}
validationSchema={object({
Name: string().required("Please enter a name").min(3, "Name is too short"),
Address: string().required("Please enter an address").min(3, "Address is too short"),
phoneNumber: number().required("Please enter a phone number").min(4, "Phone number is too short"),
Email: string().required("Please enter an email").email("Invalid email"),
})}
onSubmit={(values, formikHelpers) => {
console.log(values);
formikHelpers.resetForm();
}}
>
{({ setFieldValue, errors, isValid, touched, dirty,handleBlur,handleSubmit}) => (
<Form onSubmit={createCustomer} >
<Field as={TextField} name="Name" type="name" variant="outlined" color="primary" label="Name" fullWidth
onChange={updateCreateFormField} value={createForm.Name}
error={Boolean(errors.Name) && Boolean(touched.Name)}
helperText={Boolean(touched.Name) && errors.Name}
/>
<Box height={14} />
<Field name="Address" type="Address" as={TextField} variant="outlined" color="primary" label="Address" fullWidth
error={Boolean(errors.Address) && Boolean(touched.Address)}
helperText={Boolean(touched.Address) && errors.Address}
onChange={updateCreateFormField} value={createForm.Address}
/>
<Box height={14} />
<Field name="phoneNumber" type="number" as={TextField} variant="outlined" color="primary" label="Phone Number" fullWidth
error={Boolean(errors.phoneNumber) && Boolean(touched.phoneNumber)}
helperText={Boolean(touched.phoneNumber) && errors.phoneNumber}
onChange={updateCreateFormField}
value={createForm.phoneNumber}
/>
<Box height={14} />
<Field name="Email" type="email" as={TextField} variant="outlined" color="primary" label="Email" fullWidth
error={Boolean(errors.Email) && Boolean(touched.Email)}
helperText={Boolean(touched.Email) && errors.Email}
onChange={updateCreateFormField}
value={createForm.Email}
/>
<Box height={16} />
<Button type="submit" variant="contained" color="primary" size="large" disabled={!dirty || !isValid} >Create Customer</Button>
</Form>
)}
</Formik>
</div>}
<Box height={40} />
</div>
)
Sandbox
Simply use Formik's handleChange event and values. You don't need to create custom functions or states to change values.
Working example here
Also, if you want to have custom function to have inside onChange you can use formik's setFieldValue.
More information related to formik events https://formik.org/docs/api/formik
I have a form built with Formik containing an array of objects. When I add a second element to the form with the add-button, I get the following error:
Warning: A component is changing a controlled input to be uncontrolled. This is likely caused by the value changing from a defined to undefined, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component.
I have made sure that initial values is defined for the the form. I use the FieldArray component from formik. If I remove the time and rate fields, so it's only the name input in each object, I don't get the error, but when I add the time and rate fields, the error occurs. The component contains the following:
const TimeItemsForm = () => {
const jiraItemsStore = useJiraItemsStore();
function jiraTimeImported(timeItemIndex: number, importedHoursTotal: number, checkedItems: CheckedTimeItems) {
// Add checked items to store
jiraItemsStore.setJiraTable(timeItemIndex, checkedItems, importedHoursTotal);
}
const TimeItemSchema = Yup.object().shape({
name: Yup.string().required('Required'),
time: Yup.number().min(0).required('Required'),
// rate: Yup.number().min(0).required('Required'),
});
const TimeItemsSchema = Yup.object().shape({
timeItems: Yup.array().of(TimeItemSchema),
})
const initialValues = {
timeItems: [
{
name: '',
time: 0,
rate: 0,
},
],
};
return (
<>
<Formik
validateOnChange={false}
initialValues={initialValues}
validationSchema={TimeItemsSchema}
onSubmit={(values) => console.log(values)}>
{({ values, errors, touched }) => (
<Form onChange={() => console.log("hs")}>
<FieldArray
name="timeItems"
render={arrayHelpers => (
<div>
{values.timeItems && values.timeItems.length > 0 ? (
values.timeItems.map((timeItem, index) => (
<React.Fragment key={index}>
<Stack gap={2}>
<Flex alignItems="end" gap={4}>
<FormControl>
<FormLabel htmlFor="timeItems[${index}].name">Name</FormLabel>
<Field as={Input} placeholder="Time Item Name" variant="filled" name={`timeItems[${index}].name`} />
</FormControl>
<FormControl>
<FormLabel htmlFor="timeItems[${index}].time">Time</FormLabel>
<InputGroup>
<Field as={Input} type="number" placeholder="0 Hours" variant="filled" name={`timeItems[${index}].time`} />
<InputRightAddon children='Hours' />
</InputGroup>
</FormControl>
<FormControl>
<FormLabel htmlFor="timeItems[${index}].rate">Rate</FormLabel>
<InputGroup>
<Field as={Input} type="number" placeholder="USD 0" variant="filled" name={`timeItems[${index}].rate`} />
<InputRightAddon children='Hours' />
</InputGroup>
</FormControl>
<Flex flexShrink="0" gap={3} direction="column">
<Heading fontWeight="normal" size="sm">Apply Taxes & Discounts</Heading>
<Flex mb={0.5} gap={4}>
<Tooltip label='Tax 1' fontSize='sm'>
<IconButton variant={true ? 'solid' : 'outline'} aria-label='Tax' icon={<TbReceipt />} />
</Tooltip>
<IconButton variant='outline' aria-label='Discount' icon={<TbDiscount />} onClick={() => arrayHelpers.insert(index, { name: "", email: "" })} />
</Flex>
</Flex>
</Flex>
<Flex py={2} gap={10} justifyContent="space-between">
<TimeItemsTable timeItemIndex={index} jiraTimeImported={jiraTimeImported} />
<Flex gap={4}>
<IconButton aria-label='Create Time Item' icon={<MinusIcon />} onClick={() => arrayHelpers.remove(index)} />
<IconButton aria-label='Create Time Item' icon={<AddIcon />} onClick={() => arrayHelpers.insert(index, { name: "", email: "" })} />
</Flex>
</Flex>
</Stack>
<Divider my={4} />
</React.Fragment>
))
) : (
<button type="button" onClick={() => arrayHelpers.push('')}>
Add a Time Item
</button>
)}
<Flex mt={6} gap={10} justifyContent="space-between">
<Button colorScheme="purple" type="submit">Save</Button>
{/* <TimeItemsStats /> */}
</Flex>
</div>
)}
/>
</Form>
)}
</Formik>
</>
)
}
Solution:
The arrayHelpers.push and arrayHelpers.insert didn't insert the values with the correct type, as seen in the code.
props.children is not working in my parent component, BoxCard. When I reference the child component directly instead of props.children, it works. Here's the code that's not working:
// AddUserForm.js
function AddUserForm(props) {
function handleSubmit(event) {
event.preventDefault();
}
return (
<BoxCard>
<Box component="form" onSubmit={handleSubmit}>
<TextField required id="name" label="Name" variant="filled" />
<TextField required type="number" id="age" label="Age" variant="filled" InputLabelProps={{ shrink: true }} InputProps={{inputProps: { min: 0, step: 1 }}}/>
<Button type="submit" variant="contained" startIcon={<FontAwesomeIcon icon={solid('circle-plus')}/>}>Add User</Button>
</Box>
</BoxCard>
)
}
export default AddUserForm;
// BoxCard.js
function BoxCard(props) {
return (
<Box sx={{ minWidth: 275 }}>
<Card variant="outlined">
<CardContent>
{props.children}
</CardContent>
</Card>
</Box>
)
}
export default BoxCard;
I need to validate like at list one field required date field either list tree dropdown so what my task is like if the user selects only one means if the user selects only list dropdown then user able to submit that if the user selects only date field then user able to submit it if the user does not select any one field user not able to submit it I added condition but not working properly need to set like one field needs to be selected and also not sure why I need this dependency ["list", "Start_Date"] if I am not adding this getting error Error: Cyclic dependency, a node was: "Start_Date".
const validationSchema = Yup.object().shape({
validation: Yup.object().shape(
{
list: Yup.array().when("Start_Date", {
is: "",
then: Yup.array()
.of(Yup.string())
.min(1, "Required!")
.required("Required!"),
otherwise: Yup.array(),
}),
Start_Date: Yup.string().when("list", {
is: (listItemId) => listItemId?.length === 0,
then: Yup.string().required("This field is required."),
otherwise: Yup.string(),
}),
},
[ "list", "Start_Date" ]
),
});
const DeltaForm = useFormik({
initialValues: {
list: [],
Start_Date: "",
End_Date: "",
},
validationSchema,
onSubmit: (values) => {
updateSanctionsFilter(values);
},
});
return (
<Card variant="outlined">
<form onSubmit={DeltaForm.handleSubmit}>
<CardContent>
<Box mb={2}>
<Box mb={1}>
<Typography color="primary" variant="subtitle1">
Delta Change
</Typography>
</Box>
<Divider></Divider>
</Box>
<Grid container display="flex" spacing={4}>
<Grid item xs={12} sm={6} md={6}>
<TextField
id="outlined-select-currency"
label="Start Date"
variant="outlined"
size="small"
type="date"
InputLabelProps={{
shrink: true,
}}
onChange={DeltaForm.handleChange}
value={DeltaForm.values.Start_Date}
error={Boolean(DeltaForm.errors.Start_Date)}
helperText={DeltaForm.errors.Start_Date}
name="Start_Date"
></TextField>
</Grid>
<Grid item xs={12} sm={6} md={6}>
<TextField
id="outlined-select"
label="End Date"
variant="outlined"
size="small"
type="date"
InputLabelProps={{
shrink: true,
}}
onChange={DeltaForm.handleChange}
value={DeltaForm.values.End_Date}
error={Boolean(DeltaForm.errors.End_Date)}
helperText={DeltaForm.errors.End_Date}
name="End_Date"
></TextField>
</Grid>
</Grid>
<Box mt={2}>
<MultiSelectWithTree
label="Select Sanctions list"
onChange={(newVal) => {
DeltaForm.setFieldValue("list", newVal);
}}
value={DeltaForm.values.list ? DeltaForm.values.list : []}
name="list"
error={DeltaForm.errors.list}
helperText={DeltaForm.errors.list}
variant="outlined"
size="small"
treeData={treeData?.length ? treeData : []}
/>
</Box>
<Box mb={2}>
<FormHelperText
error={Boolean(
DeltaForm.errors.selected || DeltaForm.errors.isCheck
)}
>
{DeltaForm.errors.selected || DeltaForm.errors.isCheck}
</FormHelperText>
</Box>
<Divider></Divider>
<Grid xs={12} item>
<Box
mt={2}
display="flex"
alignItems="flex-end"
flexDirection="column"
>
<Button variant="contained" color="primary" type="submit">
Submit
</Button>
</Box>
</Grid>
</CardContent>
</form>
</Card>
);
I am building an application using React and Electron-JS.
I am using form to display some information. To this form style is applied asstyle={{ overflow: 'scroll', height: '45vh' }}. When Form items overflows, scrollbar is added to it. But the problem is when I scroll down, the items were hidden till then will not be rendered fully. But when the screen is resized, they become visible again. This happens like once in 10 times.
I cannot reproduce it always.
I think React is not rendering the items which are not visible on the visible screen to gain performance.
Following is the code for the form component:
const style = { margin: '5px', height: '3.3vh', labelAlign: 'left' };
const inputStyle = { padding: '3px', height: '26px' };
export const ObjectPane = React.memo(({ objDetails }) => (
<Form
fields={Object.keys(objDetails).map(key => {
let value = null;
if (objDetails[key] === undefined) value = 'NA';
else if (objDetails[key] === null) value = 'No Value';
else value = objDetails[key];
return {
name: [`${key}`],
value
};
})}
style={{ overflow: 'scroll', height: '45vh' }}
onScroll={() => {}}
>
<Form.Item label="Object" name="obj" style={style}>
<Input style={inputStyle} disabled />
</Form.Item>
<Form.Item label="Object Id" name="objId" style={style}>
<Input style={inputStyle} disabled />
</Form.Item>
<Form.Item label="Distance Long. [m] " name="longitude" style={style}>
<Input style={inputStyle} disabled />
</Form.Item>
<Form.Item label="Distance Lat. [m] " name="lateral" style={style}>
<Input style={inputStyle} disabled />
</Form.Item>
<Form.Item label="Velocity Long. [m/s]" name="vel_long" style={style}>
<Input style={inputStyle} disabled />
</Form.Item>
<Form.Item label="Velocity Lat. [m/s]" name="vel_lat" style={style}>
<Input style={inputStyle} disabled />
</Form.Item>
<Form.Item label="2 Wheeler Confidence" name="conf_twowheel" style={style}>
<Input style={inputStyle} disabled />
</Form.Item>
<Form.Item label="4 Wheeler Confidence" name="conf_fourwheel" style={style}>
<Input style={inputStyle} disabled />
</Form.Item>
<Form.Item
label="Pedestrian Confidence"
name="conf_pedestrian"
style={style}
>
<Input style={inputStyle} disabled />
</Form.Item>
<Form.Item
label="Stationary Confidence"
name="conf_stationary"
style={style}
>
<Input style={inputStyle} disabled />
</Form.Item>
<Form.Item label="Object Type" name="obj_type" style={style}>
<Input style={inputStyle} disabled />
</Form.Item>
<Form.Item label="Type Confidence" name="conf_obj" style={style}>
<Input style={inputStyle} disabled />
</Form.Item>
<Form.Item label="long_cu_aeb" name="long_cu_aeb" style={style}>
<Input style={inputStyle} disabled />
</Form.Item>
<Form.Item label="cross_vru_aeb" name="cross_vru_aeb" style={style}>
<Input style={inputStyle} disabled />
</Form.Item>
<Form.Item label="long_cu_fcw" name="long_cu_fcw" style={style}>
<Input style={inputStyle} disabled />
</Form.Item>
<Form.Item label="cross_vru_fcw" name="cross_vru_fcw" style={style}>
<Input style={inputStyle} disabled />
</Form.Item>
<Form.Item label="cyc_ped_tipl" name="cyc_ped_tipl" style={style}>
<Input style={inputStyle} disabled />
</Form.Item>
</Form>
));
How can I make this work as expected? Please let me know if any more information needs to be added here.