I have a list of dynamically created inputs which are saved in state. When i remove an input field, it is removed correctly, However, when after deleting an input field i add a new input field, new value is not added to list, infact the input that was just deleted appears. I am using ingredient name as key, can it cause issue since it has spaces int it? Below is my code
const [savedingredients, setIngredients] = useStat([]);
const saveIngredient = async () => {
var name = document.getElementById("ing_name1").value;
reset({ ...getValues(), ing_name1: "" });
var ingredients_array = savedingredients;
ingredients_array.push({ name: name });
var newArray = [...ingredients_array];
setIngredients(newArray);
};
const deleteIngredient = (indexArray) => {
var idNum = parseInt(indexArray) + 1;
var nameId = "ing" + idNum;
var checkIngredients = [];
savedingredients.map((inst, i) => {
if (i != indexArray) {
checkIngredients.push({ name: inst.name });
}
});
setIngredients([...checkIngredients]);
enqueueSnackbar("Ingredient Deleted", {
variant: "success",
});
};
<List>
{savedingredients.map((ing, i) => (
<ListItem key={ing.name}>
<Grid container spacing={1}>
<Grid item xs={6} md={6}>
<Controller
name={`ing${i + 1}`}
defaultValue={ing.name ? ing.name : ""}
control={control}
rules={{
required: true,
minLength: 3,
}}
render={({ field }) => (
<TextField
variant="outlined"
fullWidth
id={`ing${i + 1}`}
inputProps={{ type: "text", disabled: true }}
{...field}
></TextField>
)}
></Controller>
</Grid>
<Grid item xs={3} md={2}>
<IconButton
onClick={() => deleteIngredient(i)}
aria-label="close"
sx={{
position: "absolute",
right: 8,
top: 8,
color: (theme) => theme.palette.grey[500],
}}
edge="end"
>
<CloseIcon></CloseIcon>
</IconButton>
<IconButton
onClick={(e) => editIngredient(e, i)}
aria-label="toggle action"
sx={{
position: "absolute",
right: 30,
top: 8,
color: (theme) => theme.palette.grey[500],
}}
edge="start"
>
{stepAction == "Edit" ? <Edit /> : <Save />}
</IconButton>
</Grid>
</Grid>
</ListItem>
))}
</List>
Related
I'm from Angular and new to React. Im doing well but here is a problem I'm stuck at. As you can see I have BasicLayout and AppointmentForm, both are in one file. BasicLayout is being used inside AppointmentForm but not like an element i.e <BasicLayout/> so I'm not able to understand how to pass props or its even possible now. I want to trigger commitChanges(inside AppointmentForm) function when onSubmit(inside Basic Layout) function is triggered. How can I pass props between these components?
const BasicLayout = (props) => {
const formik = useFormik({
initialValues: {
title: '',
agenda: '',
description: '',
participants: [],
host: user?.id,
guest: '',
location: '',
},
validationSchema,
onSubmit: async (values) => {
values.startDate = props.appointmentData.startDate;
values.endDate = props.appointmentData.endDate;
values.guest = values.guest?._id;
createAppointment(values);
console.log(values);
},
});
return (
<Container>
<Typography sx={{ fontSize: 24, fontWeight: 'bold' }} color="text.primary" gutterBottom>
Create Appointment
</Typography>
<Box sx={{ flexGrow: 1 }}>
<FormikProvider value={formik}>
<Form autoComplete="off" onSubmit={handleSubmit}>
<Grid container spacing={2}>
<Grid item xs={6} md={6}>
<TextField
label="Title"
color="secondary"
id="title"
type="text"
key="title"
value={formik.values.title}
onChange={formik.handleChange}
{...getFieldProps('title')}
error={Boolean(touched.title && errors.title)}
helperText={touched.title && errors.title}
fullWidth
/>
</Grid>
<Grid item container xs={12} md={12} direction="row" justifyContent="center" alignItems="center">
<LoadingButton size="medium" type="submit" variant="contained" loading={isSubmitting}>
Create
</LoadingButton>
</Grid>
</Grid>
</Form>
</FormikProvider>
</Box>
<ToastContainer />
</Container>
);
};
const AppointmentsDashboard = (props) => {
const commitChanges = ({ added, changed, deleted }) => {
console.log(props);
console.log({ added, changed, deleted });
if (added) {
if (!isValidate) {
notify('Please fill all required fields', 'error');
return;
}
const startingAddedId = data.length > 0 ? data[data.length - 1].id + 1 : 0;
setData([...data, { id: startingAddedId, ...added }]);
}
if (changed) {
setData(
data.map((appointment) =>
changed[appointment.id] ? { ...appointment, ...changed[appointment.id] } : appointment
)
);
}
if (deleted !== undefined) {
setData(data.filter((appointment) => appointment.id !== deleted));
}
return data;
};
return (
<>
<Paper>
<Scheduler data={data} height={660}>
<ViewState currentDate={currentDate} />
<EditingState
onCommitChanges={commitChanges}
addedAppointment={addedAppointment}
onAddedAppointmentChange={changeAddedAppointment}
appointmentChanges={appointmentChanges}
onAppointmentChangesChange={changeAppointmentChanges}
editingAppointment={editingAppointment}
onEditingAppointmentChange={changeEditingAppointment}
onAppointmentFormClosing={() => {
console.log('asdasd');
}}
allowAdding={true}
/>
<WeekView startDayHour={9} endDayHour={17} />
<AllDayPanel />
<EditRecurrenceMenu />
<ConfirmationDialog />
<Appointments />
<AppointmentTooltip showOpenButton showDeleteButton />
<AppointmentForm basicLayoutComponent={BasicLayout} />
</Scheduler>
</Paper>
</>
);
};
export default AppointmentsDashboard;
I'm displaying US region incidents from Google Cloud API, but the problem is that it displays also repeated data, example:enter image description here
How can I remove the repeated ones? Here's the code.
DATA RETRIEVING:
export const getGoogleStatus = async () => {
const response = await axios.get('https://status.cloud.google.com/incidents.json')
console.log('Raw Data: ', response.data)
const status = response.data.map((e) => {
return {
name: e.affected_products.map((e) => {
return e.title
}),
status: e.most_recent_update.status,
location: e.most_recent_update.affected_locations.map((e) => {
return e.title
}).filter((r) => r.includes("(us-"))
}
})
return status
}
DATA DISPLAYING IN FRONTEND:
export default function GoogleStatusSummary({ listStatus }) {
if (!listStatus) return <div>Loading...</div>
return (
<Grid container padding={2} justifyContent='center'>
<Grid item padding={2}>
<Typography variant='h1' marginLeft={5}>
<TwitterIcon sx={{ mr: 2, mt: 1 }} />
Google API Status
</Typography>
{listStatus.map((item, index) => {
const { name, status, location } = item
const colorStatus = status === 'AVAILABLE' ? 'success' : 'danger'
return (
location.length > 0 && (
<Grid display='flex' m={3} key={index}>
<RadioButtonCheckedIcon sx={{ ml: 2, mt: 1 }} color={colorStatus} />
<Grid ml={2}>
<Typography variant='h4'>
{name}
</Typography>
<Typography variant='h6' style={{ fontSize: 12 }}>
{status.charAt(0).toUpperCase() + status.slice(1)}
</Typography>
</Grid>
</Grid>
)
)
})
}
</Grid>
</Grid>
)
I have text fields that are being added dynamically on button click and mapping the items depending on state variable [inputList]. The problem is when I'm trying to delete a field using the splice or filter option it is always deleting the last item but in the console it is showing that it has deleted the specific item that I wanted but in UI it's removing the last field no matter which field I delete. For example, if there are 3 fields named 'A', 'B'& 'C'. If I try to delete B, it is deleting B but not affecting in the UI. In the UI it is showing only the last item is deleted that is C has been deleted but in console B has been deleted.
Here is my code:
<FormControl style={{ display: "flex", flex: 1 }}>
<FormGroup>
{inputList.map((x, i) => {
return (
<div key={i}>
<div className={styles.options}>
{!distributePoints ? (
<FormControlLabel
key={i}
value={x.isCorrect}
control={<Checkbox />}
checked={x.isCorrect}
onChange={(e) => handleIsCorrect(e, i)}
/>
) : (
""
)}
<div className="editor">
<BubbleEditor
handleAnswer={handleInputChange}
index={i}
theme="bubble"
/>
</div>
{distributePoints ? (
<TextField
variant="standard"
name="points"
InputProps={{
disableUnderline: true,
type: "number",
}}
value={x.points}
onChange={(e) => handlePointChange(e, i)}
className={styles.inputCounter}
/>
) : (
""
)}
{inputList.length !== 1 && (
<IconButton
onClick={() => handleRemoveClick(i)}
className={styles.icon}
>
<DeleteIcon
sx={{ fontSize: 24, color: "#3699FF" }}
/>
</IconButton>
)}
</div>
{inputList.length - 1 === i && (
<div
style={{
display: "flex",
justifyContent: "space-between",
}}
>
<button
onClick={handleAddClick}
type="button"
className={styles.addButton}
>
<img src={PlusSign} alt="" /> Add another answer
</button>
<div className={styles.label}>
<Checkbox
checked={shuffle}
onChange={handleShuffle}
style={{ color: "#00A3FF" }}
/>
Shuffle answer
</div>
</div>
)}
</div>
);
})}
</FormGroup>
</FormControl>
Remove function:
const handleRemoveClick = (index) => {
const list = [...inputList];
list.splice(index, 1);
setInputList(list);
};
Add function:
const handleAddClick = () => {
setInputList([...inputList, { answer: "", isCorrect: false, points: 0 }]);
};
State array of object that is being used:
const [inputList, setInputList] = useState([
{ answer: "", isCorrect: false, points: 0 },
]);
While updating a state property, using its previous value, the callback argument should be used.
This is due to the possible asynchronous nature of state updates
var handleRemoveClick = (index)=> {
setInputList((list)=> (
list.filter((o,i)=> (index != i))
);
};
var handleAddClick = ()=> {
setInputList((list)=> (
list.concat({answer:'', isCorrect: false, points: 0})
);
};
I am making an add sales component that requires an add button to add multiple products. I made it working with normal TextField with following code but when added Autocomplete, I am unable. Looking for help.
import React, { useState } from "react";
import {
Box,
TextField,
Grid,
InputLabel,
Tooltip,
IconButton,
Autocomplete
} from "#mui/material";
import { Add, Remove } from "#mui/icons-material";
const products = [
{ id: 1, name: "Green Apple" },
{ id: 2, name: "Red Cherry" },
{ id: 3, name: "Strawberry" },
{ id: 4, name: "Ground Apple" },
{ id: 5, name: "Dragon Fruit" },
{ id: 6, name: "White pear" }
];
function AddSale() {
const [inputList, setInputList] = useState([{ name: "", qty: "", rate: "" }]);
// handle input change
const handleInputChange = (e, index) => {
const { name, value } = e.target;
const list = [...inputList];
list[index][name] = value;
setInputList(list);
};
// handle click event of the Remove button
const handleRemoveClick = (index) => {
const list = [...inputList];
list.splice(index, 1);
setInputList(list);
};
// handle click event of the Add button
const handleAddClick = () => {
setInputList([...inputList, { name: "", qty: "", rate: "" }]);
};
return (
<Grid container spacing={2}>
<Grid item sx={{ py: 0 }} xs={5}>
<InputLabel>Select a item*</InputLabel>
</Grid>
<Grid item sx={{ py: 0 }} xs={2}>
<InputLabel>Qty (Piece)*</InputLabel>
</Grid>
<Grid item sx={{ py: 0 }} xs={3}>
<InputLabel>Rate (Rs)*</InputLabel>
</Grid>
<Grid item sx={{ py: 0 }} xs={2} />
{inputList.map((x, i) => {
return (
<React.Fragment key={i}>
<Grid item xs={5}>
<Autocomplete
value={x.name}
onChange={(e) => handleInputChange(e, i)}
inputValue={x.name}
onInputChange={(e) => handleInputChange(e, i)}
id="select-product"
options={products}
disableClearable
getOptionLabel={(option) => option.name}
renderOption={(props, option) => (
<li {...props}>{option.name}</li>
)}
renderInput={(params) => (
<TextField
name="name"
{...params}
placeholder="Select product"
/>
)}
/>
</Grid>
<Grid item xs={2}>
<Box>
<TextField
id="sale-qty"
name="qty"
value={x.qty}
onChange={(e) => handleInputChange(e, i)}
variant="outlined"
fullWidth
/>
</Box>
</Grid>
<Grid item xs={3}>
<Box>
<TextField
id="sale-rate"
name="rate"
value={x.rate}
onChange={(e) => handleInputChange(e, i)}
variant="outlined"
fullWidth
/>
</Box>
</Grid>
<Grid item xs={2} sx={{ display: "flex" }}>
{inputList.length !== 1 && (
<Tooltip title="Remove">
<IconButton
onClick={() => handleRemoveClick(i)}
color="error"
>
<Remove color="error" />
</IconButton>
</Tooltip>
)}
{inputList.length - 1 === i && (
<Tooltip title="Add new">
<IconButton onClick={handleAddClick}>
<Add />
</IconButton>
</Tooltip>
)}
</Grid>
</React.Fragment>
);
})}
</Grid>
);
}
export default AddSale;
Here is screenshot how it should look like
Also codesandbox of current state
https://codesandbox.io/s/ecstatic-sunset-hxrfy?file=/src/App.js:0-3803
I am creating the following component:
It will contain an array of objects, where each object is a prescription, with the medicine name from the select and a TextField for the Dosis.
My problem is that the TextField loses focus on every onChange() and is very frustrating because it cannot be edited on a single focus.
This is my component :
const MedicineSelect = ({ medications, setMedications, ...props }) => {
const { medicines } = useMedicines()
const classes = useStyles()
const handleChange = (index, target) => {
// setAge(event.target.value)
const newMedications = cloneDeep(medications)
newMedications[index][target.name] = target.value
setMedications(newMedications)
}
const handleAddMedicine = () => {
const newMedications = cloneDeep(medications)
newMedications.push({ medicine: '', dosis: '', time: '' })
setMedications(newMedications)
}
const handleDeleteMedicine = (index) => {
console.log('DELETE: ', index)
const newMedications = cloneDeep(medications)
newMedications.splice(index, 1)
setMedications(newMedications)
}
return (
<Paper style={{ padding: 5 }}>
<List>
{medications.map((medication, index) => (
<ListItem key={nanoid()} divider alignItems='center'>
<ListItemIcon>
<Tooltip title='Eliminar'>
<IconButton
className={classes.iconButton}
onClick={() => handleDeleteMedicine(index)}
>
<HighlightOffOutlinedIcon />
</IconButton>
</Tooltip>
</ListItemIcon>
<FormControl className={classes.formControl}>
<InputLabel
id={`${index}-select-${medication}-label`}
>
Medicamento
</InputLabel>
<Select
labelId={`${index}-select-${medication}-label`}
id={`${index}-select-${medication}`}
name='medicine'
value={medication.medicine}
onChange={(event) =>
handleChange(index, event.target)
}
>
{medicines.map((medicine) => (
<MenuItem
key={nanoid()}
value={medicine.name}
>
{medicine.name}
</MenuItem>
))}
</Select>
</FormControl>
<TextField
// fullWidth
id={`${index}-text-${medication}`}
label='Dosis'
name='dosis'
onChange={(event) =>
handleChange(index, event.target)
}
value={medication.dosis}
/>
</ListItem>
))}
<Button onClick={handleAddMedicine}>+ agregar</Button>
</List>
</Paper>
)
}
And here is where I set the component:
const [medications, setMedications] = useState([
{ medicine: '', dosis: '', time: '' },
])
...
<Grid item md={12} xs={12}>
<Accordion>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
aria-controls='panel1a-content'
id='panel1a-header'
>
<Typography variant='h4'>
Tratamiento:
</Typography>
</AccordionSummary>
<AccordionDetails>
<Container disableGutters>
<MedicineSelect
medications={medications}
setMedications={setMedications}
/>
</Container>
</AccordionDetails>
</Accordion>
</Grid>
...
Adding and removing objects from the array works perfect. selecting the medicine from the select, also works perfect. the only problem I have is when editing the Dosis TextField, with every character, the focus is lost and I have to click again on the TextField.
Please help me getting this fixed!!!
After searching a lot, finally I found the solution. Actually when using nanoid() to create unique keys, on every state update React re-renders all components and since the id of both the List and the TextField component are regenerated by nanoid on every render, React loses track of the original values, that is why Focus was lost.
What I did was keeping the keys unmuttable:
<ListItem key={`medication-${index}`} divider alignItems='center'>
and
<TextField
key={`dosis-${index}`}
fullWidth
// id={`${index}-dosis-${medication}`}
label='Dosis'
name='dosis'
onChange={(event) =>
handleChange(index, event.target)
}
value={medication.dosis}
/>