I have a custom radio button component in React looking like this:
export default function SingleSelectionButton(props) {
const {
values, name, required, errors, defaultData,
xs, md, register, infoBox, infoBoxContent, validateField } = props;
return (
<>
<Form.Group>
<Form.Label>
<FormattedMessage
id={name}
/>
</Form.Label>
{values.map((value) => (
<span key={value} className="SingleSelectionButton">
<input
{...register(name, { required: required}
id={name + value}
type="radio"
name={name}
value={value}
defaultChecked={defaultData.primary[name] === value}
/>
<label htmlFor={name + value}>
<FormattedMessage id={value} />
</label>
</span>
))}
</Form.Group>
</>
);
};
I call it like this, using an array for the different values:
<SingleSelectionButton
name="us_person"
md={6}
values={["yes", "no"]}
register={register}
required={true}
errors={errors}
validateField="no"
/>
The validation with required is working fine.
The problem is that I don't manage to validate a value in particular.
I have tried the following:
<input
{...register(name, { required: required, validate: value => value === validateField })}
id={name + value}
type="radio"
name={name}
value={value}
defaultChecked={defaultData.primary[name] === value}
/>
And also:
<input
{...register(name, { required: required, pattern: validateField })}
id={name + value}
type="radio"
name={name}
value={value}
defaultChecked={defaultData.primary[name] === value}
/>
So far no joy. What am I missing?
Related
I have a form where two fields have to have the same value to be able to continue with the process, for this I have created a component that is in charge of the comparison, the problem is that it does not show me the value of the input to be able to make the comparison . this is my component.
const UIInput = ({ register, rules, name, errors, label, type, options, equals, getValues, ...props }) => {
return (
<div>
<label >{label}</label>
{type === 'select' ?
<select
{...(register && register(name, rules))}>
{options.map(o => (
<option key={o}>{o}</option>
))}
</select>
:
<input
name={name}
{...props}
placeholder={label}
/* {...(register && register(name, rules))} */
{...(register && register({validate:{
name:value=>(value === getValues().email) || console.log('are not the same')
}}))}
/>
/* register && register(name, rules)) */
}
{errors[name] && <ErrorMessaje>{errors[name]?.message}</ErrorMessaje>}
</div>
)
}
export default UIInput
And this is how I use it:
<UIInput
name="email"
register={register}
errors={errors}
label="Email"
defaultValue={dataForm.email}
type="email"
rules={{ required: 'Enter an email' }}
/>
<UIInput
name="email2"
register={register}
errors={errors}
label="Confirmar Email"
type="email"
rules={{ required: 'Has to be the same value' }}
getValues={getValues}
/>
when I run the code the error it sends me is "path.split is not a function"
i am new to react, can anyone explain to me how can i manipulate or repopulate my existing form from backend data in react.
I am trying to edit and existing item in the inventory that i wanna change values for. i am using formik with react and formik grid. for data i am using AXIOS.
What i am trying to do is to get edit a specific entry from my database which has changed values. it's like trying to update values
<Formik
validationSchema={schema}
initialValues={{
name: "",
numberOfSets: "0",
sortValue: "0",
boxType: "",
price: "",
photo: "",
}}
onSubmit={({
name,
numberOfSets,
sortValue,
boxType,
price,
photo,
}) => {
boxService.addBox(
name,
numberOfSets,
sortValue,
boxType,
price,
photo
);
alert("Box added Successfully!"); // Box or Gift it's same thing
window.location.reload(false);
}}
>
{({
values,
errors,
touched,
handleBlur,
handleSubmit,
setFieldValue,
}) => {
return (
<Form
form={form}
name="edit-gift"
onFinish={handleSubmit}
{...layout}
labelAlign="left"
>
// These are the fields i am trying to manipulate
<Form.Item name="name" label="Name">
<Input
name="name"
title="Product Name"
dataIndex="name"
key="productName"
value={values.name}
onChange={(e) => setFieldValue("name", e.target.value)}
onBlur={handleBlur}
placeholder="Please enter Box Name"
/>
{errors?.name && touched?.name && (
<Text type="danger">{errors?.name}</Text>
)}
</Form.Item>
<Form.Item name="numberOfSets" label="Number of Sets">
<Input
name="numberOfSets"
type="number"
value={values.numberOfSets}
onChange={(e) =>
setFieldValue("numberOfSets", e.target.value)
}
onBlur={handleBlur}
placeholder="Please enter Number of Sets"
/>
{errors?.numberOfSets && touched?.numberOfSets && (
<Text type="danger">{errors?.numberOfSets}</Text>
)}
</Form.Item>
<Form.Item name="sortVlaue" label="Sort Value">
<Input
name="sortVlaue"
type="number"
value={values.sortValue}
onChange={(e) => setFieldValue("sortVlaue", e.target.value)}
onBlur={handleBlur}
placeholder="Please enter soring value"
/>
{errors?.numberOfBoxes && touched?.numberOfBoxes && (
<Text type="danger">{errors?.numberOfBoxes}</Text>
)}
</Form.Item>
<Form.Item name="boxType" label="Type">
<Select
value={values.boxType}
onChange={(value) => setFieldValue("boxType", value)}
onBlur={handleBlur}
placeholder="Please enter Box Type"
>
<Select.Option value="reward">Reward</Select.Option>
<Select.Option value="doublerandom">
Double Random
</Select.Option>
</Select>
{errors?.boxType && (
<Text type="danger">{errors?.boxType}</Text>
)}
</Form.Item>
<Form.Item name="price" label="Price">
<Input
name="price"
title="Product Price"
dataIndex="price"
key="price"
value={values.price}
onChange={(e) => setFieldValue("price", e.target.value)}
onBlur={handleBlur}
placeholder="Please enter Box Price"
/>
{errors?.price && touched?.price && (
<Text type="danger">{errors?.price}</Text>
)}
</Form.Item>
<Form.Item name="photo" label="Product Picture">
<div className="dropzone-container">
{values.photo && <img src={values.photo} alt=""></img>}
{!values.photo && (
<Dropzone
onDrop={(acceptedFiles) => {
acceptedFiles.map((file) => {
console.log(file);
});
let fr = new FileReader();
fr.onload = function () {
setFieldValue("photo", fr.result);
setFieldValue("imgName", acceptedFiles[0].name);
console.log(acceptedFiles[0]);
};
fr.readAsDataURL(acceptedFiles[0]);
}}
accept={{ "image/*": [] }}
maxSize={1000000}
>
{({ getRootProps, getInputProps }) => (
<section>
<div {...getRootProps({ style })}>
<input {...getInputProps()} />
<p>
Drag 'n' drop some files here, or click to
select files
</p>
<aside>
Tips: The size ratio of the photo is: 750:216,
the maximum recommended size is 1M, and the
format is: .jpg, .jpeg, .png
</aside>
</div>
</section>
)}
</Dropzone>
)}
</div>
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">
Cofirm save
</Button>
</Form.Item>
</Form>
);
}}
</Formik>
You could either:
rerender the Formik component when your data is available and pass it to the initialValues prop by doing something like
results?.data && <Formik initialValues={{...}}>...</Formik>
or set the values manually with setValues after the data is available
const {setValues, values, ...} = useFormik({...});
useEffect(() => {
setValues(results?.data); // use any fields from your data
}, [results]} // this is the data from your api
hello I'm trying to show and hide specific elements with checkbox material and what is happening now is when one checkbox is checked all the hidden divs are showing up.
You can see the problem here: https://stackblitz.com/edit/react-1ecdqb?file=demo.tsx
edit: I know that I need more variables in the state but I ask if there is a way to do it without state for each checkbox because there are gonna be 10 more checkboxes
const UninstallView = () => {
const [isChecked, setIsChecked] = useState(false);
const handleChange = event => {
if (event.target.checked) {
setIsChecked(true);
}
else {
setIsChecked(false);
}
}
return (
<div>
<FormGroup>
<FormControlLabel control={<Checkbox onChange={handleChange} />} label="simple isn't what I expected" />
{isChecked ? <TextField
id="filled-multiline-static"
label="What did you expect from simple?"
multiline
rows={4}
defaultValue=""
variant="filled"
/>
: '' }
</FormGroup>
<FormGroup>
<FormControlLabel control={<Checkbox onChange={handleChange} />} label="simple isn't working correctly" />
{isChecked ?
<div>
<h1>hello</h1>
</div>
: '' }
</FormGroup>
</div>
);
You are sharing 1 state across 2 checkboxes. You should have a separate state that holds the state for each checkbox.
This code may help:
const UninstallView = () => {
const [isFirstChecked, setIsFirstChecked] = useState(false);
const [isSecondChecked, setIsSecondChecked] = useState(false);
return (<div>
<FormGroup>
<FormControlLabel
control={<Checkbox onChange={() => setIsFirstChecked(!isFirstChecked)}/>}
label="simple isn't what I expected"/>
{isFirstChecked ? <TextField
id="filled-multiline-static"
label="What did you expect from simple?"
multiline
rows={4}
defaultValue=""
variant="filled"
/> : ''}
</FormGroup>
<FormGroup>
<FormControlLabel
control={<Checkbox onChange={() => setIsSecondChecked(!isSecondChecked)}/>}
label="simple isn't working correctly"/>
{isSecondChecked ? <div>
<h1>hello</h1>
</div> : ''}
</FormGroup>
</div>);
}
Sandbox example
I am creating an invoice generator with Formik. The issue is that the Fields Component prints out the new state of the form when I click save changes. But when I return to the form again, the old field persists. If I click on something or return, then the new field replaces the old field. I looked at other solutions like enableReinitialize and passing a handleChange but I have the same issue.
Edit Invoice Form Component
<Formik
initialValues={{
senderAddress: invoice.senderAddress,
clientName: invoice.clientName,
clientEmail: invoice.clientEmail,
clientAddress: invoice.clientAddress,
createdAt: new Date(invoice.createdAt),
paymentTerms: invoice.paymentTerms,
description: invoice.description,
items: invoice.items,
invoice: invoice.paymentTerms
}}
validationSchema={validationSchema}
onSubmit={onSubmit}
>
{formik => (
<Form setIsOpen={setIsOpen}
enableReinitialize>
<Heading>Edit <span>#</span>{invoice.id}</Heading>
<Fields client={client} getClientsChild={getClientsChild} setDraftId={setDraftId} />
<Buttons>
<Button type="button" secondary onClick={() => setIsOpen(false)}>Cancel</Button>
<Button type="submit" onClick={() => addDraft(formik.values)}>Save Changes</Button>
</Buttons>
</Form>
)}
Fields Component
useEffect(() => {
console.log(client)
//setFieldValue(businessID, client.draft.business_id)
//setFormikContext const { setFieldValue } = useFormikContext()
//setFieldValue(`items[${index}].total`, rounded || '0');
//onChange={value => setFieldValue(name, value)}
if (client) {
console.log("Client in child", client.draft)
setDraftId(client.draft.id)
formik.values.businessID = client.draft.businessID
formik.values.clientName = client.restaurant.first_name + client.restaurant.last_name;
formik.values.clientEmail = client.restaurant.email;
formik.values.clientAddress.street = client.restaurant.address;
formik.values.clientAddress.postCode = client.restaurant.postal_code;
formik.values.paymentTerms = client.draft.terms;
formik.values.businessID = client.draft.business_id;
console.log("Client in child", client.draft)
}
}, [client])
return (
<TouchScrollable>
<Wrapper>
<FieldSet>
<Legend>Bill From</Legend>
<BillFrom>
<SelectClient update={update} id="client" label="Select Client" name="clientOptions" options={clients ? clients : [{ name: '', value: '' }]} />
<Input label="Street Address" name="senderAddress.street" />
<Input label="City" name="senderAddress.city" />
<Input label="Post Code" name="senderAddress.postCode" />
<Input label="Country" name="senderAddress.country" />
</BillFrom>
</FieldSet>
<FieldSet>
<Legend>Bill To</Legend>
<BillTo>
<Input label="Business ID ( If financing Net 30 terms )" name="businessID" />
<Input label="Client's Name" name="clientName" />
<Input label="Client's Email" name="clientEmail" placeholder="e.g. email#example.com" />
<Input label="Street Address" name="clientAddress.street" />
<Input label="Postal Code" name="clientAddress.postCode" />
</BillTo>
</FieldSet>
<FieldSet>
<OtherFields>
<DatePicker label="Invoice Date" name="createdAt" />
<Select label="Payment Terms" name="paymentTerms" options={dropdownOptions} />
<Input label="Description" name="description" placeholder="e.g. Graphic Design Service" />
</OtherFields>
</FieldSet>
<Items name="items" />
{formik.submitCount > 0 && formik.errors &&
<Errors>
{reduceErrors(formik.errors).map((item, index) => (
<Error key={index}>{item}</Error>
))}
</Errors>
}
</Wrapper>
</TouchScrollable>
)
}
I am trying to reset my form with the reset() method and, it is working partially. All of the form's fields get reset, except for the checkboxes group.
I have tried three different ways of implementing an MUI CheckboxesGroup but, I always get the same result.
You can see a working example in this sandbox
Checkbox one implementation
<FormControl error={error} required={required} component="fieldset">
<FormLabel component="legend">{label}</FormLabel>
<Controller
name={name}
render={({ field: { onBlur: rhfonBlur, onChange: rhfonChange } }) => (
<FormGroup onBlur={rhfonBlur}>
{Object.keys(options).map((key) => {
return (
<FormControlLabel
label={key}
key={key}
control={
<Checkbox onChange={(e) => rhfonChange(handleCheck(key))} />
}
/>
);
})}
</FormGroup>
)}
control={control}
/>
<FormHelperText>{helperText}</FormHelperText>
</FormControl>
Checkbox two implementation
<FormControl error={error}>
<FormLabel component="legend">{label}</FormLabel>
<FormGroup>
{Object.keys(options).map((key) => {
return (
<FormControlLabel
key={key}
control={<Checkbox {...register(name)} />}
value={key}
label={key}
/>
);
})}
</FormGroup>
<FormHelperText>{helperText}</FormHelperText>
</FormControl>
Checkbox three implementation
<FormControl required error={error} component="fieldset" variant="standard">
<FormLabel component="legend">{label}</FormLabel>
<Controller
control={control}
name={name}
render={({ field: { onChange: rfhonChange, value } }) => {
// console.log("value:", value);
return (
<FormGroup>
{Object.keys(state).map((key) => {
return (
<FormControlLabel
label={key}
key={key}
control={
<Checkbox
onChange={(e) => {
rfhonChange(handleChange(e));
}}
checked={state[key]}
name={key}
/>
}
/>
);
})}
</FormGroup>
);
}}
/>
<FormHelperText>{helperText}</FormHelperText>
</FormControl>
Your third implementation was the correct way of doing it by using RHF's <Controller /> component. The reason why it wasn't working was because of the following:
you're not updating RHF's internal state for your field as you are only passing a function to the onChange handler. There is no need to use an additional useState here
your initial value is a boolean (false) but you are using an object inside the <CheckboxThree /> component
So basically you can simplify the component to the following (in you're example it isn't clear if the field value should be an object or an array containing the selected options - so the example i made is using an array):
const CheckboxThree = ({
control,
error,
helperText,
label,
name,
options
}) => {
return (
<FormControl required error={error} component="fieldset" variant="standard">
<FormLabel component="legend">{label}</FormLabel>
<Controller
control={control}
name={name}
render={({ field: { onChange, value, ref, ...field } }) => (
<FormGroup>
{Object.keys(options).map((key) => {
return (
<FormControlLabel
label={key}
key={key}
control={
<Checkbox
{...field}
name={key}
checked={value.some((option) => option === key)}
onChange={(event, checked) => {
if (checked) {
onChange([...value, event.target.name]);
} else {
onChange(
value.filter((value) => value !== event.target.name)
);
}
}}
inputRef={ref}
/>
}
/>
);
})}
</FormGroup>
)}
/>
<FormHelperText>{helperText}</FormHelperText>
</FormControl>
);
};