Getting the same value for dropwown on Oncahange event on react - reactjs

The below image shows duplicate value getting in two drop down i need to manage the
drop down one by one dynamically on add button click.My requirement is I have need to add multiple experience using the add button.
I have calling a function to createExpDetails() to add the section dynamically when plus button is clicked.The value filled in the drop down is getting from database .The present issue is I am getting the drop downs but when one value in one dropdown is changed it automatically reflects to all the other dropdowns. I need to change the values in dropdown seperately.
const [addfiledexp,setFildExp]=useState('')
const handleChangeexp= (event:SelectChangeEvent,i) =>
{
setFildExp(event.target.value);
}
const createExpDetails=() =>
{
return(addexplist.map((el,i) =>
<div key={i}>
<Box mt={4}>
<Grid container >
<Grid item xs={5} mb={2} mr={8} ml={4}>
<TextField
name='title'
id="title"
variant='outlined'
fullWidth
label="Title"
//value={address}
//onChange={(e: any) => setAddress(e.target.value)}
/>
</Grid>
<Grid item xs={5} mb={2} ml={4}>
<TextField
name='helorganisation'
id="helorganisation"
variant='outlined'
fullWidth
label="Health Care Organization"
// value={address}
//onChange={(e: any) => setAddress(e.target.value)}
/>
</Grid>
<Grid container>
<Grid item xs={5} mb={2} mr={8} ml={4}>
<FormControl fullWidth>
<InputLabel id="demo-simple-select-label">Type</InputLabel>
<Select
labelId="demo-simple-select-label"
id="demo-simple-select"
name='typeselect'
//value= {console.log(addfiledexp)}
value= {addfiledexp}
label="Type"
//onChange={handleChangeexp }
onChange={(e: any) => handleChangeexp(e,i)}
>
{addexptypelist.map((typelist, key) => {
return (<MenuItem value= {typelist.name} ><option key={key} >{typelist.name}</option></MenuItem>);
})}
</Select>
</FormControl>
</Select>
</FormControl>
</Grid>
</Grid>
variant='outlined'
{ addexplist.length >1 && (
<Button className="section-add-button" onClick={()=>handleServiceexpRemove(i)}><CancelIcon className="section-add-icon" /></Button>
)
}
</Box>
</div>
));
}
{createExpDetails()}
<Button className="section-add-button" onClick={() =>addbutton('addexperiance')}><AddCircleIcon className="section-add-icon" /></Button>

Related

ReactJS - How to utilize useRef for an array generated set of text fields?

I am creating a Poll with title and options. The title is stored using useRef and it works fine.
The options for the poll are generated uisng - state.pollOptions.map
export default function CreatePoll() {
const titleRef = useRef();
const [state, setState] = useState({
pollOptions: ['Yes', 'No'],
published: false,
});
...
const handleSubmit = async () => {
console.log(titleRef.current.value) // shows value entered in text field
...
<Box mb={2} mt={4} pt={4} px={3}>
<TextField
id="standard-multiline-flexible"
label="Poll Title"
name="pollTitle"
multiline
maxRows={4}
value={state.pollTitle}
inputRef={titleRef}
variant="standard"
fullWidth
/>
</Box>
<Box p={3}>
{state.pollOptions.map((option, index) => (
<Grid container spacing={3} mt={index==0?0:2}>
<Grid item xs={3}>
<TextField
size='small'
required
id={`value_field_${index}`}
label="Value"
defaultValue={index}
className="mb-1 mr-2"
fullWidth
/>
</Grid>
<Grid item xs={9}>
<TextField
required
size='small'
id={`value_field_${index}`}
label="Option Title"
defaultValue={option}
className="mb-1"
fullWidth
/>
</Grid>
</Grid>
))}
</Box>
The options for the poll are generated using an array. I am planning to add a button to add and remove options from array.
I am not sure how to track the values of these generated options in a state or a Ref.

stopPropogation not working consistently in React

I am working with 2 dropdown menus using stopPropogation to prevent the menu shifting up and down when items are checked and unchecked.
One menu works great but the other is still jumping up and down when checking and unchecking items, although I am confident that I'm using stopPropogation in the same way for both instances. I am wondering what I am doing wrong in the faulty dropdown menu that's causing this issue.
Working dropdown menu:
return (
<div onClick={(e) => {
e.stopPropagation();
}}>
<FormControl
variant="outlined"
size="small"
className={classes.formControl}
>
<InputLabel id="demo-mutiple-checkbox-label" align="left" margin="dense">
Select Models
</InputLabel>
<Select
multiple
labelId="demo-mutiple-checkbox-label"
label="SelectModels"
value={modelIds}
onChange={handleChange}
renderValue={(selected) =>
selected
.map(
(id) =>
deviceModelData.find((model) => model.alg_id === id).alg_name
)
.join(', ')
}
MenuProps={MenuProps}
className={classes.rootSelect}
>
{deviceModelData.map((model, index) => (
<MenuItem key={model.alg_id} value={model.alg_id}>
<Checkbox checked={modelIds.includes(model.alg_id)} />
<ListItemText primary={model.alg_name} />
</MenuItem>
))}
</Select>
</FormControl>
</div>
);
}
Problematic dropdown menu:
return (
<div
onClick={(e) => {
e.stopPropagation();
}}
>
<FormControl
variant="outlined"
size="small"
className={classes.formControl}
>
<InputLabel id="demo-mutiple-checkbox-label">Select Classes</InputLabel>
<Select
labelId="demo-mutiple-checkbox-label"
label={label}
id="demo-mutiple-checkbox"
multiple
value={classIds}
onChange={handleClassIdsChange}
renderValue={(selected) =>
selected
.map((id) => marketplaceModelClasses[id].original_label)
.join(', ')
}
MenuProps={MenuProps}
>
{allClassIds.map((classId) => {
return (
<MenuItem key={classId} value={classId}>
<Checkbox
value={classId}
checked={classIds.indexOf(classId) > -1}
/>
<ListItemText
primary={marketplaceModelClasses[classId].original_label}
/>
</MenuItem>
);
})}
</Select>
</FormControl>
</div>
);
}

How to pass Formik form values via an intermediate function to the handleSubmit event?

Ok, so far I have successfully used the handleSubmit() and could manage to access the updated (user edited) form values but what if you want to have a messagebox prompt user to Save\Discard changes and when user selects the Save button on the messagebox, you want the form's handleSubmit() even gets called and you want to access the form data inside your handleSubmit event? so, for example
const [formValues, setFormValues] = useState({
emailMessageText: emailMessage,
});
const handleSubmit = (initialValues, {e, setSubmitting }) => {
//here in the below messageText I can only access the original value of the
//emailMessageText. i.e. can't get what user had updated post form rendered
let req = {
campusID: campusID,
messageText: initialValues.emailMessageText
};
}
<Formik
enableReinitialize
initialValues={formValues}
onSubmit={handleSubmit}
>
{({
submitForm,
handleBlur,
isSubmitting,
isValid,
touched,
values,
errors,
}) => (
<Form>
<div>
<Grid
container
direction="row"
justify="center"
alignItems="center"
>
<Grid item lg={3} md={5} xs={12}>
<FormControl fullWidth>
<InputLabel htmlFor="primaryCampusID">
Campus
</InputLabel>
<Field
component={Select}
type="text"
name="primaryCampusID"
validate={handleCampusSelection}
inputProps={{
id: "primaryCampusID",
}}
value={campusID}
>
<MenuItem value={0} key={0}>
Select Campus...
</MenuItem>
{campuses.map((option) => (
<MenuItem
value={option.value}
key={option.value}
>
{option.key}
</MenuItem>
))}
</Field>
<FormHelperText error>
<ErrorMessage name="primaryCampusID" />
</FormHelperText>
</FormControl>
</Grid>
<Grid
container
item
lg={12}
md={12}
xs={12}
spacing={5}
justify="space-between"
className={classes.rowSpacing}
>
<Grid item lg={12} md={12} xs={12}>
<FormControl fullWidth>
<Field
component={TextField}
multiline
fullWidth={true}
//label="Email Message"
validate={validateData}
placeholder="Email Message"
name="emailMessageText"
rows="15"
></Field>
<FormHelperText error>
<ErrorMessage name="emailMessageText" />
</FormHelperText>
</FormControl>
</Grid>
<Grid item lg={12} md={12} xs={12}>
<Button
variant="contained"
color="primary"
size="medium"
className={classes.buttonGreen}
startIcon={<EmailIcon />}
disabled={!isFormDataValid}
onClick={() => {
setConfirmOpen(true);
}}
>
Email
</Button>
</Grid>
</Grid>
</Grid>
</div>
</Form>
)}
</Formik>
<ConfirmDialog
title="Send Notification emails?"
open={confirmOpen}
setOpen={setConfirmOpen}
onConfirm={handleSubmit}
>
<Typography style={{ whiteSpace: "pre-line" }}>
{"This job will send notice emails to " +
currentPermitHolders.length +
" members." +
".\n\n" +
" This is a non reversible action. Do you still want to Proceed with the submission?"}
</Typography>
</ConfirmDialog>

Dynamically adding or removing items within a react-hook-forms form and registering the inputs?

I'm trying to implement a rather complicated form that has and date picker and input which the user can add multiple of (or delete). That data then gets added to the overall form on submit. How do I get react-hook-forms to register that little dynamic faux form within the real form?
Here's the faux form inputs:
<AddPackagesStyle>
<Label htmlFor="addedPackages" label="Add Packages" />
<DatePicker
id="dateRange"
selected={startDate}
selectsRange
startDate={startDate}
endDate={endDate}
placeholderText="select dates"
onChange={onDateChange}
/>
<PackageInput
id="PackageSelect"
placeholder="Select Package"
type="text"
value={name}
// #ts-ignore
onChange={(e) =>
// #ts-ignore
setName(e.target.value)
}
/>
<ButtonContainer>
<button type="button" onClick={clearAll}>
Clear
</button>
<button
type="button"
// #ts-ignore
onClick={addPackage}
>
Add
</button>
</ButtonContainer>
</AddPackagesStyle>
These entries get added to an array in a useState hook:
const [addedPackages, setAddedPackages] = useState<any[]>([])
Then this gets rendered in JSX as the add packages:
<ContentSubscriptionWrapper>
{addedPackages.length !== 0 &&
addedPackages.map((addedPackage, idx) => (
// #ts-ignore
<>
<ContentSubscriptionColumn>
{addedPackage.name && addedPackage.name}
</ContentSubscriptionColumn>
<ContentSubscriptionColumn>
{addedPackage.startDate &&
addedPackage.startDate.toString()}
</ContentSubscriptionColumn>
<ContentSubscriptionColumn>
{addedPackage.endDate && addedPackage.endDate.toString()}
</ContentSubscriptionColumn>
<button type="button" onClick={() => removePackage(idx)}>
X
</button>
</>
))}
</ContentSubscriptionWrapper>
So before the form gets submitted, the 'add packages' has to be set. Where do I add the {...register} object to add to the larger form object for submission?
const {
control,
register,
handleSubmit,
formState: { errors },
} = useForm<any>()
const onSubmit = (data: any) => {
console.log(data)
}
I created a CodeSandbox trying to reproduce your use case and used Material UI to get it done quickly, but you should get the idea and can modify it with your own components.
you should let RHF handle all the state of your form
use RHF's useFieldArray for managing (add, remove) your packages/subscriptions - there is no need to use watch here
use a separate useForm for your <AddPackage /> component, this has the benefit that you will have form validation for this sub form (in case it should be a requirement that all fields of <AddPackage /> need to be required) - i added validation in the demo to demonstrate this
AddPackage.tsx
export const AddSubscription: React.FC<AddSubscriptionProps> = ({ onAdd }) => {
const {
control,
reset,
handleSubmit,
formState: { errors }
} = useForm<Subscription>({
defaultValues: { from: null, to: null, package: null }
});
const clear = () => reset();
const add = handleSubmit((subscription: Subscription) => {
onAdd(subscription);
clear();
});
return (
<Card variant="outlined">
<LocalizationProvider dateAdapter={AdapterDateFns}>
<Grid container spacing={1} p={2}>
<Grid item container spacing={1} xs={12}>
<Grid item xs={6}>
<Controller
name="from"
control={control}
rules={{ required: "Required" }}
render={({ field }) => (
<DatePicker
{...field}
label="From"
renderInput={(params) => (
<TextField
{...params}
fullWidth
error={!!errors.from}
helperText={errors.from?.message}
/>
)}
/>
)}
/>
</Grid>
<Grid item xs={6}>
<Controller
name="to"
control={control}
rules={{ required: "Required" }}
render={({ field }) => (
<DatePicker
{...field}
label="To"
renderInput={(params) => (
<TextField
{...params}
fullWidth
error={!!errors.to}
helperText={errors.to?.message}
/>
)}
/>
)}
/>
</Grid>
</Grid>
<Grid item xs={12}>
<Controller
name="package"
control={control}
rules={{ required: "Required" }}
render={({ field: { onChange, ...field } }) => (
<Autocomplete
{...field}
options={packages}
onChange={(e, v) => onChange(v)}
renderInput={(params) => (
<TextField
{...params}
label="Package"
fullWidth
error={!!errors.package}
helperText={errors.package && "Required"}
/>
)}
/>
)}
/>
</Grid>
<Grid item xs={12}>
<Stack spacing={1} direction="row" justifyContent="end">
<Button variant="outlined" onClick={clear}>
Clear
</Button>
<Button variant="contained" onClick={add} type="submit">
Add
</Button>
</Stack>
</Grid>
</Grid>
</LocalizationProvider>
</Card>
);
};
Form.tsx
export default function Form() {
const { control, handleSubmit } = useForm<FormValues>({
defaultValues: {
seats: "",
addOns: false
}
});
const { fields, append, remove } = useFieldArray({
control,
name: "subscriptions"
});
const onSubmit = (data) => console.log("data", data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Grid container spacing={2}>
<Grid item xs={12}>
<Box display="flex" justifyContent="end" gap={1}>
<Button variant="outlined">Cancel</Button>
<Button variant="contained" type="submit">
Save
</Button>
</Box>
</Grid>
<Grid item xs={12}>
<Controller
name="seats"
control={control}
render={({ field }) => (
<TextField {...field} fullWidth label="Seats" />
)}
/>
</Grid>
<Grid item xs={12}>
<AddSubscription onAdd={append} />
</Grid>
<Grid item xs={12}>
<List>
{fields.map((field, index) => (
<ListItem
key={field.id}
secondaryAction={
<IconButton
edge="end"
aria-label="delete"
onClick={() => remove(index)}
>
<DeleteIcon />
</IconButton>
}
>
<ListItemText
primary={field.package.label}
secondary={
<span>
{formatDate(field.from)} - {formatDate(field.to)}
</span>
}
/>
</ListItem>
))}
</List>
</Grid>
<Grid item xs={12}>
<Controller
name="addOns"
control={control}
render={({ field: { value, onChange } }) => (
<FormControlLabel
control={<Checkbox checked={!!value} onChange={onChange} />}
label="Add-ons"
/>
)}
/>
</Grid>
</Grid>
</form>
);
}

how can I change the layout of this material UI form?

I have a checkout form which I made using material UI looking like this:
It seems minor but I'd like to change the layout so that First Name and Last Name etc. are all on the same row, looking more like this (but I'm not sure how to go about it):
Here's the code for my address form component:
return (
<>
<Typography variant="h6" gutterBottom>
Shipping address
</Typography>
<FormProvider {...methods}>
<form
onSubmit={methods.handleSubmit((data) =>
test({
...data,
shippingCountry,
shippingSubdivision,
shippingOption,
})
)}
>
<Grid container spacing={3}>
<FormInput required name="firstName" label="First name" />
<FormInput required name="lastName" label="Last name" />
<FormInput required name="address1" label="Address line 1" />
<FormInput required name="email" label="Email" />
<FormInput required name="city" label="City" />
<FormInput required name="zip" label="Zip / Postal code" />
<Grid item xs={12} sm={6}>
<InputLabel>Shipping Country</InputLabel>
<Select
value={shippingCountry}
fullWidth
onChange={(e) => setShippingCountry(e.target.value)}
>
{Object.entries(shippingCountries)
.map(([code, name]) => ({ id: code, label: name }))
.map((item) => (
<MenuItem key={item.id} value={item.id}>
{item.label}
</MenuItem>
))}
</Select>
</Grid>
<Grid item xs={12} sm={6}>
<InputLabel>Shipping Subdivision</InputLabel>
<Select
value={shippingSubdivision}
fullWidth
onChange={(e) => setShippingSubdivision(e.target.value)}
>
{Object.entries(shippingSubdivisions)
.map(([code, name]) => ({ id: code, label: name }))
.map((item) => (
<MenuItem key={item.id} value={item.id}>
{item.label}
</MenuItem>
))}
</Select>
</Grid>
<Grid item xs={12} sm={6}>
<InputLabel>Shipping Options</InputLabel>
<Select
value={shippingOption}
fullWidth
onChange={(e) => setShippingOption(e.target.value)}
>
{shippingOptions
.map((sO) => ({
id: sO.id,
label: `${sO.description} - (${sO.price.formatted_with_symbol})`,
}))
.map((item) => (
<MenuItem key={item.id} value={item.id}>
{item.label}
</MenuItem>
))}
</Select>
</Grid>
</Grid>
<br />
<div style={{ display: "flex", justifyContent: "space-between" }}>
<Button component={Link} variant="outlined" to="/cart">
Back to Cart
</Button>
<Button type="submit" variant="contained" color="primary">
Next
</Button>
</div>
</form>
</FormProvider>
</>
);
};
And my custom text field component:
import React from "react";
import { TextField, Grid } from "#material-ui/core";
import { useFormContext, Controller } from "react-hook-form";
const FormInput = ({ name, label, required }) => {
const { control } = useFormContext();
const isError = false;
return (
<>
<Controller
control={control}
name={name}
render={({ field }) => <TextField fullWidth label={label} required />}
/>
</>
);
};
export default FormInput;
Looks like you're already using Mui Grid so I think you need to place your <FormInput/> components within a <Grid item/> component. Like this:
<Grid container>
<Grid container direction="row">
<Grid item xs={6} sm={6}>
<FormInput required name="firstName" label="First name" />
</Grid>
<Grid item xs={6} sm={6}>
<FormInput required name="lastName" label="Last name" />
</Grid>
</Grid>
...Another Grid row, and so on...
</Grid>

Resources