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
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 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>
I am trying to use a more concise method of declaring the initial state of my form values by declaring them in an object within useState and having them be updated by user input.
However I get the error "Uncaught TypeError: setFieldValue.providerName is not a function".
Is my approach completely wrong? or is there a small detail I am missing?
code below
import { Container, FormControl, FormControlLabel, FormLabel, Grid, InputLabel, Paper, Radio, RadioGroup, TextField } from '#mui/material';
import Button from '#mui/material/Button';
import { useCallback, useContext, useEffect, useState } from 'react';
import { AppContext } from '../../context/context';
import DatePicker from '../../elements/form/DatePicker';
import MultipleSelect from '../../elements/form/Select';
import MultipleSelectChip from '../../elements/form/SelectMulti';
import MuiTextField from '../../elements/form/TextField';
const ProjectForm = () => {
const [fieldValue, setFieldValue] = useState({
projectName: "",
providerName: "",
clientName: "",
description: "",
budget: 0,
})
const [activeButtons, setActiveButtons] = useState({
maconomyMavenlink: false,
maconomy: false,
mavenlink: false
})
const [formActive, setFormActive] = useState(false)
const { mavenlinkConnected } = useContext(AppContext);
const { maconomyConnected } = useContext(AppContext);
const checkFormActive = () => {
if(maconomyConnected || mavenlinkConnected){
setFormActive(true);
}
}
const checkButtonsActive = useCallback(() => {
if (mavenlinkConnected){
setActiveButtons({
mavenlink: true
})
}
if (maconomyConnected){
setActiveButtons({
maconomy: true
})
}
if (mavenlinkConnected && maconomyConnected){
setActiveButtons({
maconomyMavenlink: true
})
}
}, [])
useEffect(() => {
checkButtonsActive();
},[])
useEffect(() => {
checkFormActive()
}, [])
const handleSubmit = () => {
console.log("form submitted")
}
const handleResetForm = () => {
setFieldValue.projectName("");
setFieldValue.providerName("");
setFieldValue.clientName("");
setFieldValue.description("");
setFieldValue.projectName("");
setFieldValue.budget(0);
}
//Dummy values for time being
const value = "value";
const handleChange = () => {
}
const templates = [
'template 1',
'template 2',
'template 3',
'template 4',
'template 5',
'template 6'
]
const groups = [
'group 1',
'group 2',
'group 3',
'group 4',
'group 5',
'group 6',
]
const currencies = [
'British Pound (£)',
'United States Dollar ($)'
]
const taskBilling = [
'Fixed Fee',
'Time & Materials'
]
const taskBillingModes = [
'Billable',
'Non-billable'
]
return(
<Container>
{formActive ?
<form onSubmit={handleSubmit} className="project-form-wrapper">
<Grid container spacing={1}>
<Grid item xs={12}>
<div className="button-container">
<Button disabled={!activeButtons.maconomyMavenlink} variant="contained">Mavenlink+Maconomy</Button>
<Button disabled={!activeButtons.mavenlink} variant="contained">Mavenlink</Button>
<Button disabled={!activeButtons.maconomy} variant="contained">Maconomy</Button>
</div>
</Grid>
<Paper className="project-form">
<Grid container>
<Grid item xs={12}>
<FormControl>
<FormLabel id="provider-client">I am the</FormLabel>
<RadioGroup
row
aria-labelledby="controlled-radio-buttons-group"
name="controlled-radio-buttons-group"
value={value}
onChange={handleChange}
>
<FormControlLabel value="provider" control={<Radio />} label="Provider" />
<FormControlLabel value="client" control={<Radio />} label="Client" />
</RadioGroup>
</FormControl>
</Grid>
<Grid item xs={12}>
<TextField
fullWidth
onChange={e => setFieldValue.projectName(e.target.value)}
id="project-name"
label="Project Name"
variant="outlined"
value={fieldValue.projectName}
error={fieldValue.projectName === "" }
helperText={fieldValue.projectName === "" ? "Required" : " "}
/>
</Grid>
<Grid item xs={6}>
<MuiTextField
onChange={e => setFieldValue.providerName(e.target.value)}
name="provider-name"
id="provider-name"
label="Provider Name"
width="99%"
value={fieldValue.providerName}
/>
</Grid>
<Grid item xs={6}>
<MuiTextField
onChange={e => setFieldValue.clientName(e.target.value)}
error
name="client-name"
id="client-name"
label="Client Name"
width="100%"
value={fieldValue.clientName}
/>
</Grid>
<Grid item xs={12}>
<MultipleSelect
options={templates}
label="Template"
width="100%"
/>
<InputLabel>If selected budget, currency, task billing mode will be auto configured from selected template</InputLabel>
</Grid>
<Grid item xs={12}>
<MultipleSelectChip
options={groups}
/>
<InputLabel>Assign to one or more groups</InputLabel>
</Grid>
<Grid item xs={6}>
<DatePicker
label="Start Date"
/>
</Grid>
<Grid item xs={6}>
<DatePicker
label="End Date"
/>
</Grid>
<Grid item xs={12}>
<TextField
fullWidth
onChange={e => setFieldValue.description(e.target.value)}
id="description"
label="Description"
variant="outlined"
value={fieldValue.description}
/>
</Grid>
<Grid item xs={6}>
<MuiTextField
onChange={e => setFieldValue.budget(e.target.value)}
name="budget"
id="budget"
label="Budget"
width="99%"
variant="outlined"
value={fieldValue.budget}
error={fieldValue.budget === "" }
helperText={fieldValue.budget === "" ? "Required" : " "}
/>
</Grid>
<Grid item xs={6}>
<MultipleSelect
options={currencies}
label="Currency"
width="100%"
/>
</Grid>
<Grid item xs={6}>
<MultipleSelect
options={taskBilling}
label="Task Billing Mode"
width="99%"
/>
</Grid>
<Grid item xs={6}>
<MultipleSelect
options={taskBillingModes}
width="100%"
label="Task Mode"
/>
</Grid>
<Grid item xs={6}>
<Button onClick={handleResetForm}>Reset</Button>
<Button type="submit">Submit</Button>
</Grid>
</Grid>
</Paper>
</Grid>
</form>
: <h1>Please connect to Maconomy or Mavenlink to use this feature</h1>}
</Container>
)
}
export default ProjectForm;
const handleResetForm = () => {
setFieldValue.projectName("");
setFieldValue.providerName("");
setFieldValue.clientName("");
setFieldValue.description("");
setFieldValue.projectName("");
setFieldValue.budget(0);
}
is wrong. use
setFieldValue({ projectName: "",
providerName: "",
clientName: "",
description: "",
budget: 0})
I am new to react and I have been trying to implement the bellow functionality. I am using two Autocomplete dropdowns with checkboxes from MUI. One is for departments and the second is for users. I need to filter users and to show only users that belongs to the chosen department from department dropdown.
I hope that the code sample is fine.
This is the checkbox component that I am using for both dropdowns:
import { styled, Box, Popper, Checkbox, TextField, Autocomplete, ClickAwayListener } from "#mui/material";
import React from "react";
import PropTypes from "prop-types";
const PopperStyledComponent = styled(Popper)(({ theme }) => ({
border: `1px solid ${
theme.palette.mode === "light" ? "rgba(149, 157, 165, 0.2)" : "rgb(1, 4, 9)"
}`,
width: "auto !important",
// marginBottom: "10px",
".MuiAutocomplete-popper .MuiBox-root .css-o5yjxw-MuiAutocomplete-popper": {
borderRadius: "0px !important",
},
}));
export default function CheckboxDropdown({ options, label }) {
const [value, setValue] = React.useState([]);
const [checkAll, setCheckAll] = React.useState(false);
const [open, setOpen] = React.useState(false);
console.log(value);
const checkAllChange = (event) => {
setCheckAll(event.target.checked);
if (event.target.checked) {
setValue(options);
} else {
setValue([]);
}
};
const handleClickAway = () => {
console.log("Handle Click Away");
setOpen(false);
};
const properComponent = (param) => (
<PopperStyledComponent {...param}>
<Box {...param} />
{/* <Divider /> */}
<Box
{...param}
sx={{
backgroundColor: "white",
height: "45px",
textOverflow: "ellipsis",
overflow: "hidden",
whiteSpace: "nowrap",
}}
>
<Checkbox
checked={checkAll}
onChange={checkAllChange}
id="check-all"
sx={{ marginRight: "8px" }}
onMouseDown={(e) => e.preventDefault()}
/>
Select All
</Box>
</PopperStyledComponent>
);
return (
<ClickAwayListener onClickAway={handleClickAway}>
<Box>
<Autocomplete
multiple
disableCloseOnSelect
limitTags={2}
id="checkboxes-tags-demo"
options={options}
value={value}
open={open}
onChange={(event, newValue, reason) => {
if (reason === "selectOption") {
setValue(newValue);
} else if (reason === "removeOption") {
setCheckAll(false);
setValue(newValue);
} else if (reason === "clear") {
setValue([]);
setCheckAll(false);
}
}}
onClose={(reason) => {
console.log("On Close: ", reason);
if (reason === "escape") {
setOpen(false);
}
}}
onOpen={() => {
setOpen(true);
}}
PopperComponent={properComponent}
getOptionLabel={(option) => option}
renderOption={(props, option, { selected }) => (
<li {...props}>
<Checkbox style={{ marginRight: 8 }} checked={selected || checkAll} />
{option}
</li>
)}
style={{ width: "100%" }}
renderInput={(params) => <TextField {...params} label={label} placeholder="Favorites" />}
/>
</Box>
</ClickAwayListener>
);
}
CheckboxDropdown.propTypes = {
options: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.number, PropTypes.string])).isRequired,
label: PropTypes.string.isRequired,
};
This is the component with data where I am using Checkboxes:
import React from "react";
// #mui material components
import { Grid } from "#mui/material";
// Soft UI Dashboard PRO React components
import SuiBox from "components/SuiBox";
import SuiButton from "components/SuiButton";
import SuiInput from "components/SuiInput";
import SuiTypography from "components/SuiTypography";
import CheckboxDropdown from "./CheckboxDropdown";
const users = [
{ name: "users 1", department: "department 3" },
{ name: "users 2", department: "department 2" },
{ name: "users 3", department: "department 2" },
{ name: "users 4", department: "department 1" },
];
const department = [
{ name: "department 1" },
{ name: "department 2" },
{ name: "department 3" },
{ name: "department 4" },
];
export default function TokensToUser() {
return (
<Grid item xs={12} md={12} lg={12}>
<Grid container xs={12} md={12} lg={12} spacing={2} alignItems="end">
<Grid item xs={12} md={12} lg={3}>
<SuiBox>
<SuiTypography variant="body2" fontWeight="bold">
Select Departments
</SuiTypography>
</SuiBox>
<SuiBox>
<SuiBox py={2}>
<CheckboxDropdown
options={department.map((el) => el.department_name)}
label="Select deparment"
/>
</SuiBox>
</SuiBox>
</Grid>
<Grid item xs={12} md={12} lg={3}>
<SuiBox>
<SuiTypography variant="body2" fontWeight="bold">
Select Users
</SuiTypography>
</SuiBox>
<SuiBox py={2}>
<CheckboxDropdown options={users.map((el) => el.email)} label="Select users" />
</SuiBox>
</Grid>
<Grid item xs={12} md={12} lg={3}>
<SuiBox>
<SuiTypography variant="body2" fontWeight="bold">
Tokens Amount
</SuiTypography>
</SuiBox>
<SuiBox py={2}>
<SuiInput placeholder="Type amount here" />
</SuiBox>
</Grid>
<Grid item lg={1}>
<SuiBox py={2} width="150px">
<SuiButton color="dark" variant="outlined">
Transfer now
</SuiButton>
</SuiBox>
</Grid>
</Grid>
</Grid>
);
}
export const data = [
{
size: "S",
colorMap: { Yellow: 10, Green: 5, Black: 50 },
productName: "Shirt",
price: 200
}
];
I wanted to show the initial values of the colorMapand then update its quantity. How can I update the quantities of the colors which are the values of the Object.entries(colorMap)?
Codesandbox: https://codesandbox.io/s/form-changehanlder-2-2repsp?file=/part2.js
The product here came from the parent component:
This is the child component
import React, { useState } from "react";
import { Grid, TextField } from "#mui/material";
const Part2 = ({ product }) => {
const [qty, setQty] = useState();
const handleSubmit = (e) => {
e.preventDefault();
console.log(qty);
};
return (
<div>
{product &&
product.map((prod, index) => (
<>
<Grid item key={index}>
<form onSubmit={handleSubmit}>
{Object.entries(prod.colorMap).map((color, index) => (
<Grid
container
rowSpacing={1}
columnSpacing={{ xs: 1, sm: 2, md: 3 }}
key={color[0]}
>
<Grid item xs={6}>
<TextField
type="text"
variant="outlined"
label="Color"
fullWidth
value={color[0]}
disabled
/>
</Grid>
<Grid item xs={6}>
<TextField
type="number"
variant="outlined"
fullWidth
label="Quantity"
value={color[1]}
onChange={(e) => console.log(index)}
/>
</Grid>
</Grid>
))}
</form>
</Grid>
</>
))}
</div>
);
};
export default Part2;
First make changeHandler in demo.js as you are using React State in demo.js so you have to make onChangeHandler in that file and pass it in props of part2.
Like:
const onChangeValues = (propertyName, index, value) => {
let item = product?.[index];
if (item) {
item.colorMap[propertyName] = value;
let prods = [...product];
prods[index] = item;
setProduct(prods);
}
};
And pass this function in props of Part2:
<Grid item>
<Part2 product={product} onChange={onChangeValues} />
</Grid>
In part2 Component you can consume it as follows:
<TextField
type="number"
variant="outlined"
fullWidth
label="Quantity"
value={color[1]}
onChange={({ target: { value } }) => {
onChangeHandler(color[0], index, value);
}}
/>
Codesandbox Link: https://codesandbox.io/s/form-changehanlder-2-forked-l9unl0?file=/part2.js:1131-1521