Here i'm using select from material ui, and have a question: how to get an object (handleChange function) when user clicks from dropdown ? in my handleChange i want to have object and not just 'name', and those places where i'm using 'selected' i could just pick it from object like this selected.name (if needed). and another question: i'm getting data to 'selected' from api the thing is that that data shows on select itself but nothing shows in dropdown. any advices how to make this code simpler?
import React, { useState } from "react";
import Checkbox from "@material-ui/core/Checkbox";
import InputLabel from "@material-ui/core/InputLabel";
import ListItemIcon from "@material-ui/core/ListItemIcon";
import ListItemText from "@material-ui/core/ListItemText";
import MenuItem from "@material-ui/core/MenuItem";
import FormControl from "@material-ui/core/FormControl";
import Select from "@material-ui/core/Select";
import DeleteIcon from "@material-ui/icons/Delete";
import CreateIcon from "@material-ui/icons/Create";
import { MenuProps, useStyles } from "./utils";
import AddUser from "./AddUser";
import { Button } from "@material-ui/core";
import EditUser from "./EditUser";
function App() {
const rawOptions = [
{ id: 1, name: "Oliver Hansen" },
{ id: 2, name: "Van Henry" }
];
const classes = useStyles();
const [selected, setSelected] = useState([]);
const [options, setOptions] = useState(rawOptions);
const [openAddModal, setOpenAddModal] = useState(false);
const [openUpdateModal, setOpenUpdateModal] = useState(false);
const [edit, setEdit] = useState({
id: null,
name: ""
});
const handleChange = (event) => {
const value = event.target.value;
setSelected(value);
console.log("get id and name of selected here", value);
};
function addUser(newArray) {
const newTodos = [...options, newArray];
setOptions(newTodos);
}
const openAddUser = () => {
setOpenAddModal(true);
};
const openUpdateUser = (event, data) => {
event.preventDefault();
event.stopPropagation();
setOpenUpdateModal(true);
setEdit(data);
};
// console.log("id", edit);
const closeAddModal = () => {
setOpenAddModal(false);
};
const closeUpdateModal = () => {
setOpenUpdateModal(false);
};
const updateUser = (updateUser) => {
setOptions(
options.map((user) => (user.id === updateUser.id ? updateUser : user))
);
};
return (
<FormControl className={classes.formControl}>
<div>
<InputLabel id="mutiple-select-label">Multiple Select</InputLabel>
<Select
labelId="mutiple-select-label"
multiple
variant="outlined"
value={selected || []}
onChange={handleChange}
renderValue={(selected) => selected}
MenuProps={MenuProps}
>
{options.map((option, index) => (
<MenuItem key={index} value={option.name}>
<ListItemIcon>
<Checkbox checked={selected?.includes(option.name)} />
</ListItemIcon>
<ListItemText primary={option.name}>{option}</ListItemText>
<DeleteIcon
onClick={(e) => {
e.stopPropagation();
setOptions(options.filter((o) => o.id !== option.id));
}}
/>
<ListItemIcon>
<CreateIcon
onClick={(e) =>
openUpdateUser(e, { id: option.id, name: option.name })
}
/>
</ListItemIcon>
</MenuItem>
))}
</Select>
<Button onClick={openAddUser} style={{ backgroundColor: "#287B7A" }}>
Add User
</Button>
</div>
<p>{selected}</p>
<AddUser
openAddModal={openAddModal}
handleClose={closeAddModal}
array={options}
addUser={addUser}
/>
<EditUser
edit={edit}
openUpdateModal={openUpdateModal}
handleClose={closeUpdateModal}
array={options}
updateUser={updateUser}
/>
</FormControl>
);
}
export default App;
CodePudding user response:
First change value
to the id
<MenuItem key={option.id} value={option.id}>
Since you need that to be guaranteed to be unique.
And also change the checkbox:
<Checkbox checked={selected?.includes(option.id)} />
Then you will store an array of ids in the selected
state.
const handleChange = (event) => {
setSelected(event.target.value);
};
And if you ever need to get back to the options (it would be bad to store the whole option because it'd be duplicated state) you can do:
// If after the handlechange fn
selected.map((selectedId) => options.find(option => option.id === selectedId))
// If inside the handlechange fn
event.target.value.map((selectedId) => options.find(option => option.id === selectedId))
Also change renderValue
to show the names in the box when they are selected:
renderValue={(selected) =>
selected
.map(
(selectedId) =>
options.find((option) => option.id === selectedId)?.name
)
.join(", ")
}
You might want to break that map/find statement into a common function.
You also need to change the delete handler to clear out selected items that have been deleted:
<DeleteIcon
onClick={(e) => {
e.stopPropagation();
setSelected((prev) => prev.filter((id) => id !== option.id));
setOptions(options.filter((o) => o.id !== option.id));
}}
/>
As for the selected items being fetched from the server and them being a list of names, you will need to work back from the names to the id.
const initialSelectedValues = selectedNamesFromServer.map((selectedName) => options.find(option => option.name === selectedName).id)
Note this seems a little like a bad API design since you should always use ID only. What happens if there are 2 people with the same name? Probably you wouldn't be able to select the second one as it can not be disambiguated. The server should return ids really.
Presumably, you have endpoints to do the following. I have recommended what to do or each (I don't know your URL scheme):
- HTTP GET the selected items from the server. This should return an array of IDs from the back end store/database and not really the names. Well, it could return both, but you shouldn't use the name from there even if you do, since it will break the handling of duplicates.
- HTTP POST the selected items back to the server. This should be changed to ingest an array of selected IDs only.
- HTTP POST a new item to the list when they click "add item". Not necessarily selected. If you don't have a way of managing the list you wouldn't be able to have an item that is added, but not selected. This would accept an object containing just the name, and then the server would create this in the store, and whilst doing so, persist an ID as well against that name in the store/db.
- HTTP DELETE an item from the list when they click "delete item". Note this is not about removing a selected item, it's about removing an option, which may not necessarily be selected since you allow new options that are unchecked. This would just accept the ID to delete and the backend would remove it from the store.
- HTTP GET all the items in the list, including selected and unselected. This would return an array of objects, each containing an
id
andname
. This is what replacedrawArray
.
CodePudding user response:
const handleChange = (event) => {
const value = event.target.value;
setSelected(value);
const filterObjects = options.filter((o) => value.includes(o.name))
console.log("get id and name of selected here", filterObjects);
};