deleting/updating select element options - javascript

I'm using material ui select element with added functionality such as multiple selection with checkbox, my question is, is there a way to delete and update name from select element itself ?
for example: by clicking the pen next to 'Oliver Hansen' i could update that name or by clicking recycle bin delete that name ?
code to try:
https://codesandbox.io/s/material-ui-multiple-select-with-select-all-option-forked-nrm6z4?file=/src/App.js
code:
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, options } from "./utils";
function App() {
const classes = useStyles();
const [selected, setSelected] = useState([]);
const handleChange = (event) => {
console.log("vals", event.target);
const value = event.target.value;
setSelected(value);
console.log("values", selected);
};
return (
<FormControl className={classes.formControl}>
<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) => (
<MenuItem key={option.id} value={option}>
<ListItemIcon>
<Checkbox checked={selected?.includes(option)} />
</ListItemIcon>
<ListItemText primary={option.title}>{option}</ListItemText>
<DeleteIcon />
<ListItemIcon>
<CreateIcon />
</ListItemIcon>
</MenuItem>
))}
</Select>
<p>{selected}</p>
</FormControl>
);
}
export default App;

Yes you can, you need to store your options in a state, and on the delete button you can stopPropagation and filter options of clicked option, and the same you can do to the edit.
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, options as rawOptions } from "./utils";
function App() {
const classes = useStyles();
const [selected, setSelected] = useState([]);
const [options, setOptions] = useState(rawOptions)
const handleChange = (event) => {
console.log("vals", event.target);
const value = event.target.value;
setSelected(value);
console.log("values", selected);
};
return (
<FormControl className={classes.formControl}>
<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={option.id} value={option}>
<ListItemIcon>
<Checkbox checked={selected?.includes(option)} />
</ListItemIcon>
<ListItemText primary={option.title}>{option}</ListItemText>
<DeleteIcon onClick={(e) => {
e.stopPropagation();
setOptions(options.filter(o => o !== option))
console.log('run');
}} />
<ListItemIcon>
<CreateIcon />
</ListItemIcon>
</MenuItem>
))}
</Select>
<p>{selected}</p>
</FormControl>
);
}
export default App;

You will need to declare another piece of state to represent options.
const [selectOptions, setSelectOptions] = useState(options);
Then from inside each element you could handle the delete or name change options by setting that state. For example, to handle deleting the element from the list:
{options.map((option, index) => (
...
<DeleteIcon
onClick={() =>
setSelectOptions((opts) => {
const newOpts = [...opts]; //create a shallow copy of selectOptions
newOpts.splice(index, 1); //remove the element at the current index
return newOpts;
})
}
/>
...
))}
This will push an update to the UI only. Additionally, if you wanted to also forever mutate the array options that you're importing from './utils', you could just include code inside the onClick event handler to make those updates as well.
Handling user input for changing the name is a little trickier, since you will also need to include a conditionally-displayed <TextField /> or similar for the user to perform the edits needed. You would probably also need to track some sort of state related to the editing process as well, something like:
const [isEditing, setIsEditing] = useState(false);
And then using that to conditionally render a text field.
<CreateIcon onClick={() => setIsEditing(true)} />
{isEditing && <TextField />}
That should hopefully get you started, from there you can also conditionally render a submit button, give it a onClick callback to update the selectOptions state, set the isEditing state back to false, and potentially mutate the original options array.

Related

Material-ui component Checkbox handler does not set the state - V.5.0

s, I made a component "Checkbox" to be used in others forms of my app, but the state does not update after click. It works and get checked at the DOM, but the state doesn't change, it remains 'false'. I'm using Material-ui v.5.0 and React 18.0.1, all helps will be very appreciated. Thanks in advance. I don't know what mistake I'm doing. I'm a newbie in React. Below follows my codes. It should be a component, where I will use in another form. After checked, I want it to become disabled. Thanks folks.
// CardNotes Component
import React, {useState} from "react";
import Card from '#mui/material/Card';
import CardHeader from '#mui/material/CardHeader';
import CardContent from '#mui/material/CardContent';
import IconButton from '#mui/material/IconButton';
import { DeleteOutlined } from "#mui/icons-material";
import { Typography } from "#mui/material";
import { makeStyles } from "#mui/styles";
import Checkbox from './Checkbox';
import { Box } from '#mui/material';
const useStyles = makeStyles({
test: {
border: (note) => {
if (note.status == 'Urgente'){
return '1px solid red'
}
return '1px solid blue'
}
}
})
export default function CardNotes({ note, handleDelete }){
const [checked, setChecked] = useState(false);
console.log('checkbox', checked)
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setChecked(event.target.checked);
};
const classes = useStyles(note)
return( // receiving props from Notes
<div>
<Card elevation={3} className={classes.test}>
<CardHeader
titleTypographyProps={{
fontSize: 16,
}}
action={
<IconButton onClick={()=> handleDelete(note.id) }>
<DeleteOutlined />
</IconButton>
}
title={note.title}
subheader={note.status}
/>
<CardContent>
<Typography variant="display2" color="textSecondary">
{note.details}
</Typography>
</CardContent>
<Box ml={2}>
<Checkbox
label="Resolvido"
checked={checked}
onChange={ (e) => setChecked(e.target.checked)}>
</Checkbox>
</Box>
</Card>
</div>
)
}
// checkbox component
import React from 'react'
import {FormControl , FormControlLabel} from '#mui/material';
import Checkbox from '#mui/material/Checkbox';
export default function Chekbox (props) {
const { resolved, handleChange } = props;
return (
<FormControl>
<FormControlLabel
control={<Checkbox
checked={resolved}
color="primary"
onChange={handleChange}
/>}
label='Resolvido'
/>
</FormControl>
)
}
check names of props you pass to CheckBox Component.
there are differences between those and props you type in CheckBox file

Material-ui Autocomplete defaultValue not working

I tried setting the defaultValue to be "Chairs" and it is not working.
These are the codes:
import React, { useState } from "react";
import TextField from "#mui/material/TextField";
import Autocomplete from "#mui/material/Autocomplete";
import { Items2 } from "./Items2";
export default function ComboBox() {
const [selected, setSelected] = useState("");
console.log(selected);
return (
<>
you selected: {selected}
<br />
<br />
<Autocomplete
disablePortal
isOptionEqualToValue={(option, value) => option?.label === value?.label}
id="combo-box-demo"
options={Items2}
defaultValue="Chairs"
fullwidth
value={selected}
onChange={(event, value) => setSelected(value)}
renderInput={(params) => <TextField {...params} label="Items" />}
/>
</>
);
}
Also in codesandbox: https://codesandbox.io/s/combobox-material-demo-forked-g88fi?file=/demo.js:0-904
You just need to set default value for selected state and remove defaultValue from Autocomplete component:
import React, { useState } from "react";
import TextField from "#mui/material/TextField";
import Autocomplete from "#mui/material/Autocomplete";
import { Items2 } from "./Items2";
export default function ComboBox() {
const [selected, setSelected] = useState("Chairs");
console.log(selected);
return (
<>
you selected: {selected}
<br />
<br />
<Autocomplete
disablePortal
isOptionEqualToValue={(option, value) => option?.label === value?.label}
id="combo-box-demo"
options={Items2}
fullwidth
value={selected}
onChange={(event, value) => setSelected(value)}
renderInput={(params) => <TextField {...params} label="Items" />}
/>
</>
);
}

unable to update the state through custom react hook

I am trying to create a custom hook which returns a custom drawer and a button(to toggle the state of drawer). I am managing the state in custom hook itself.
This is my custom hook for returning the drawer and a button
import React from "react";
import clsx from "clsx";
import { makeStyles } from "#material-ui/core/styles";
import Drawer from "#material-ui/core/Drawer";
import List from "#material-ui/core/List";
import Divider from "#material-ui/core/Divider";
import ListItem from "#material-ui/core/ListItem";
import ListItemIcon from "#material-ui/core/ListItemIcon";
import ListItemText from "#material-ui/core/ListItemText";
import InboxIcon from "#material-ui/icons/MoveToInbox";
import MailIcon from "#material-ui/icons/Mail";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import { faBars } from "#fortawesome/free-solid-svg-icons";
import { Button } from "#material-ui/core";
const useStyles = makeStyles({
list: {
width: 250
},
fullList: {
width: "auto"
}
});
export default function useTemporaryDrawer(toggle) {
const classes = useStyles();
const [state, setState] = React.useState({
right: false
});
const toggleDrawer = (anchor, open) => (event) => {
if (
event.type === "keydown" &&
(event.key === "Tab" || event.key === "Shift")
) {
return;
}
alert("setting");
setState({ ...state, [anchor]: open });
};
const onButtonClick = (e) => {
toggleDrawer("right", true)(e);
};
const Toggler = (
<Button
style={{
marginTop: "10px"
}}
onClick={onButtonClick}
startIcon={<FontAwesomeIcon icon={faBars} style={{ color: "#fff" }} />}
></Button>
);
const list = (anchor) => (
<div
className={clsx(classes.list, {
[classes.fullList]: anchor === "top" || anchor === "bottom"
})}
role="presentation"
onClick={toggleDrawer(anchor, false)}
onKeyDown={toggleDrawer(anchor, false)}
>
<List>
{["Inbox", "Starred", "Send email", "Drafts"].map((text, index) => (
<ListItem button key={text}>
<ListItemIcon>
{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}
</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
<Divider />
<List>
{["All mail", "Trash", "Spam"].map((text, index) => (
<ListItem button key={text}>
<ListItemIcon>
{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}
</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
</div>
);
const CustomDrawer = (
<Drawer
anchor={"right"}
open={state["right"]}
onClose={toggleDrawer("right", false)}
>
{list("right")}
</Drawer>
);
return {
CustomDrawer,
setState,
state,
Toggler
};
}
Then, I am trying to update the state through Toggle button which I am using in Header. This is my header
import React from "react";
import styles from "./Header.module.css";
import useTemporaryDrawer from "../Drawer/useTemporaryDrawer";
const Header = (props) => {
const { Toggler } = useTemporaryDrawer();
return <div className={styles.headerForApp}>{Toggler}</div>;
};
export default Header;
When I press the toggle button, it does update the state but it does not update the open property of drawer. In short I am not able to open the drawer through toggle button. Please help.
Here is the sandbox
https://codesandbox.io/s/customhooksnotupdatingstate-d7mfz?file=/src/components/Drawer/useTemporaryDrawer.js
If I have 2 components: A and B, and each of them have setState hook to update variable state, you won't expect setState('a') inside A to make any difference to state in B, will you? But then you create 2 components, each of them with their independent state and expect those states to be shared somehow. Toggle in Header changes state of Header, and Drawer in Dashboard had no visibility of these changes. As an option you can pass Toggler to the Header as a prop
<Header Toggler={Toggler} />
const Header = (props) => {
return <div className={styles.headerForApp}>{props.Toggler}</div>;
};

How to get my withStyles classes to work in my export class component in my React app

I'm trying to apply styles to my React form. I'm using withStytles from Material UI.
The styles however are not taking into affect. I tried testing the code in my first <DialogContentText> in the code below, but it's not working.
EDIT: edited my code to reflect David's answer.
import React, { Component, Fragment } from 'react';
import { withStyles } from '#material-ui/core/styles';
import Button from '#material-ui/core/Button';
import Dialog from '#material-ui/core/Dialog';
import DialogActions from '#material-ui/core/DialogActions';
import DialogContent from '#material-ui/core/DialogContent';
import DialogContentText from '#material-ui/core/DialogContentText';
import DialogTitle from '#material-ui/core/DialogTitle';
import IconButton from '#material-ui/core/IconButton';
import AddCircleIcon from '#material-ui/icons/AddCircle';
import TextField from '#material-ui/core/TextField';
import FormHelperText from '#material-ui/core/FormHelperText';
import Select from '#material-ui/core/Select';
import MenuItem from '#material-ui/core/MenuItem';
import InputLabel from '#material-ui/core/InputLabel';
import FormControl from '#material-ui/core/FormControl';
const styles = theme => ({
formControl: {
width: 500
},
selectEmpty: {
marginTop: theme.spacing(2),
},});
export default class extends Component {
state = {
open: false,
bucket: {
name:'',
category: '',
about: '',
}
}
handleToggle = () => {
this.setState({
open: !this.state.open
})
}
handleChange = name => ({ target: { value } }) => {
this.setState({
bucket:{
...this.state.bucket,
[name]: value,
}
})
}
render() {
const { open, bucket: { name, category, about } } = this.state;
const { classes } = this.props;
return <Fragment>
<IconButton color="primary" size="large" onClick={this.handleToggle}>
<AddCircleIcon/>
</IconButton>
<Dialog open={open} onClose={this.handleToggle} aria-labelledby="form-dialog-title">
<DialogTitle id="form-dialog-title">Create your Bucket</DialogTitle>
<DialogContent>
<DialogContentText>
Get started! Make your bucket by telling us a little bit more.
</DialogContentText>
<form>
<TextField
id="filled-password-input"
label="Bucket Title"
value={name}
variant="filled"
onChange={this.handleChange('name')}
margin="normal"
required = "true"
className = {classes.formControl}
/>
<br/>
<br/>
<FormControl>
<InputLabel htmlFor="category"> What type of Bucket </InputLabel>
<Select
labelId="demo-simple-select-helper-label"
id="demo-simple-select-helper"
value={category}
onChange={this.handleChange('category')}
required = "true"
>
<MenuItem value={'personal'}> Personal </MenuItem>
<MenuItem value={'social'}> Social </MenuItem>
</Select>
</FormControl>
<FormHelperText>Is this your personal or social Bucket?</FormHelperText>
<TextField
id="filled-password-input"
label="About"
multiline
rows="5"
value={about}
variant="filled"
onChange={this.handleChange('about')}
margin="normal"
/>
<FormHelperText>Will this be your tech stock watchlist?</FormHelperText>
<br/>
<br/>
</form>
</DialogContent>
<DialogActions>
<Button
color="primary"
variant="raised"
onClick={this.handleSubmit}
>
Create Bucket
</Button>
</DialogActions>
</Dialog>
</Fragment>
}
}
export withStyles(styles)(YourComponent);
How would I be able to apply these styles to my code below? Thank you for assistance.
TL;DR: You are trying to use a React Hook const classes = useStyles; within a class component. Also, useStyles is a function, not an object.
NL;PR: Hooks only work on functional components (useStyles() hooks are obtained via makeStyles() instead of withStyles()). You apply HOC withStyles(YourComponent) to your class component not your styles, so you can access const {classes, ...rest} = this.props; in the render() method.
It should look like this:
const styles = theme => ({
formControl: {
width: 500
},
selectEmpty: {
marginTop: theme.spacing(2),
},});
class YourComponent extends PureComponent {
//...
render(){
const {classes, ...rest} = this.props;
// now your can access classes.formControl ...
}
}
export withStyles(styles)(YourComponent);

How to set a HTML element ID on a material-ui component?

I have a website built with Gatsby.js using the Material-UI.
Specific problem is this: I want to use the Google Tag Manager "Element Visibility" triggers. If some HTML element becomes visible, GTM should fire some GA tag.
Question is this: how can I specify the HTML ID for a material-ui component for GTM (or anything else) to find it?
First example:
// ...react imports omitted...
import makeStyles from '#material-ui/core/styles/makeStyles';
import Box from '#material-ui/core/Box';
import Grid from '#material-ui/core/Grid';
import CloseIcon from '#material-ui/icons/Close';
import Link from '~components/Link';
import ButtonSubmit from '~components/form-buttons/ButtonSubmit';
import Container from '~components/Container';
// ... all other imports are in-house code
const useStyles = makeStyles(theme => ({ /* ...styles... */}));
const GuestUserSoftSaleSecondPopup = ({ which, ...rest }) => {
const classes = useStyles();
// ...setup code omitted...
return (
<Box bgcolor="#474d5c" width="100%" py={4} className={classes.banner}>
<Container>
<Grid container direction="row" justify="space-between" alignItems="center" spacing={2}>
<Grid item xs={12} sm={1} md={3} lg={4}>
<CloseIcon onClick={handleClose} size="large" className={classes.closeIcon} />
</Grid>
<Grid item xs={12} sm={7} md={5} lg={4}>
<Link to="/subscribe" variant="h5" className={classes.linkStyle}>
Become a member for full access
</Link>
</Grid>
<Grid item xs={12} sm={4} className={classes.buttonPosition}>
<Link to="/subscribe" underline="none" className={classes.linkStyle}>
<ButtonSubmit type="button" fullWidth={false}>
See my option
</ButtonSubmit>
</Link>
</Grid>
</Grid>
</Container>
</Box>
);
};
// ...proptypes and `export` clause
Second example:
// ...react imports omitted...
import makeStyles from '#material-ui/core/styles/makeStyles';
import MuiDialog from '#material-ui/core/Dialog';
const useStyles = makeStyles(() => ({ /* ...styles... */ }));
const Dialog = ({ children, background, backdrop, isOpen, ...rest }) => {
const classes = useStyles({ background });
return (
<MuiDialog
open={isOpen}
maxWidth="sm"
fullWidth
disableBackdropClick
disableEscapeKeyDown
BackdropProps={{
className: backdrop ? classes.backdropBM : classes.backdrop
}}
PaperProps={{
className: classes.paper
}}
scroll="body"
{...rest}
>
{children}
</MuiDialog>
);
};
If you look at the API documentation for almost any of the Material-UI components, you will find at the end of the "Props" section a statement like the following example from Dialog:
Any other props supplied will be provided to the root element (Modal).
This means that any props not explicitly recognized by this component will be passed along eventually to whatever HTML element is the outermost element rendered. So for most Material-UI components, in order to add an id property, you just specify it.
My example below (a modification of the Simple Dialog demo) includes three different ids: one on the Dialog element which will be placed on the outermost div of the Modal, one specified via the PaperProps which will go on the main Paper div of the visible content of the dialog, and one on the Box wrapped around the dialog content.
import React from "react";
import PropTypes from "prop-types";
import { makeStyles } from "#material-ui/core/styles";
import Button from "#material-ui/core/Button";
import Avatar from "#material-ui/core/Avatar";
import List from "#material-ui/core/List";
import ListItem from "#material-ui/core/ListItem";
import ListItemAvatar from "#material-ui/core/ListItemAvatar";
import ListItemText from "#material-ui/core/ListItemText";
import DialogTitle from "#material-ui/core/DialogTitle";
import Dialog from "#material-ui/core/Dialog";
import PersonIcon from "#material-ui/icons/Person";
import Typography from "#material-ui/core/Typography";
import { blue } from "#material-ui/core/colors";
import Box from "#material-ui/core/Box";
const emails = ["username#gmail.com", "user02#gmail.com"];
const useStyles = makeStyles({
avatar: {
backgroundColor: blue[100],
color: blue[600]
}
});
function SimpleDialog(props) {
const classes = useStyles();
const { onClose, selectedValue, open } = props;
const handleClose = () => {
onClose(selectedValue);
};
const handleListItemClick = value => {
onClose(value);
};
return (
<Dialog
onClose={handleClose}
aria-labelledby="simple-dialog-title"
open={open}
PaperProps={{ id: "MyDialogPaperID" }}
id="ThisIDWillBeOnTheModal"
>
<DialogTitle id="simple-dialog-title">Set backup account</DialogTitle>
<Box id="MyBoxID">
<List>
{emails.map(email => (
<ListItem
button
onClick={() => handleListItemClick(email)}
key={email}
>
<ListItemAvatar>
<Avatar className={classes.avatar}>
<PersonIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary={email} />
</ListItem>
))}
</List>
</Box>
</Dialog>
);
}
SimpleDialog.propTypes = {
onClose: PropTypes.func.isRequired,
open: PropTypes.bool.isRequired,
selectedValue: PropTypes.string.isRequired
};
export default function SimpleDialogDemo() {
const [open, setOpen] = React.useState(false);
const [selectedValue, setSelectedValue] = React.useState(emails[1]);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = value => {
setOpen(false);
setSelectedValue(value);
};
return (
<div>
<Typography variant="subtitle1">Selected: {selectedValue}</Typography>
<br />
<Button variant="outlined" color="primary" onClick={handleClickOpen}>
Open simple dialog
</Button>
<SimpleDialog
selectedValue={selectedValue}
open={open}
onClose={handleClose}
/>
</div>
);
}
Material UI components don't let you set an id for them since the implementation inside should be a black box and may contain multiple html element. See if you can wrap the element in a div and put the id on that instead.
Another option would be to add a class (via the classes prop) to the element instead but I'm not sure if Google Tag Manager can use those instead of ids.

Categories