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

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.

Related

How do I get the className of a event.target in React?

I am trying to get the user.id of the user that I clicked on, so that I can use it as a query parameter in a fetch(). However, what setClickedUserId is passing/assigning to my clickedUserId state is the following: MuiButtonBase-root%20MuiListItem-root%201%20MuiListItem-gutters%20MuiListItem-button
----My code:
import React from 'react';
import {makeStyles} from '#material-ui/core/styles';
import List from '#material-ui/core/List';
import ListItem from '#material-ui/core/ListItem';
import ListItemIcon from '#material-ui/core/ListItemIcon';
import ListItemText from '#material-ui/core/ListItemText';
import Collapse from '#material-ui/core/Collapse';
import ExpandLess from '#material-ui/icons/ExpandLess';
import ExpandMore from '#material-ui/icons/ExpandMore';
import PersonIcon from '#material-ui/icons/Person';
import AddBoxOutlinedIcon from '#material-ui/icons/AddBoxOutlined';
// Global context
import globalContext from './globalContext';
const useStyles = makeStyles((theme) => ({
root: {
marginTop: theme.spacing(-2.0),
width: '100%',
maxWidth: 360,
backgroundColor: '#3F0E40',
color: 'white',
},
nested: {
marginLeft: theme.spacing(-3.5),
},
nestedAdd: {
marginLeft: theme.spacing(3.5),
},
nestedAddText: {
marginLeft: theme.spacing(0.5),
},
}));
/**
*#return{any} nestedList
*/
export default function DmsList() {
const classes = useStyles();
const [open, setOpen] = React.useState(true);
console.log('I am inside DmsList!!');
// Global Context
const {setMobileOpen} = React.useContext(globalContext);
const {currentDmdWith} = React.useContext(globalContext);
const {setChannel} = React.useContext(globalContext);
const {clickedDms, setClickedDms} = React.useContext(globalContext);
const {setClickedUserId} = React.useContext(globalContext);
const handleClickUser = (event) => {
setMobileOpen(false);
setClickedDms('true');
setChannel(event.target.innerText);
setClickedUserId(event.target.className);
console.log(clickedDms);
};
const handleClickAdd = () => {
setMobileOpen(false);
};
const handleClick = () => {
setOpen(!open);
};
return (
<List
component="nav"
aria-labelledby="nested-list-subheader"
className={classes.root}
>
<ListItem button
onClick={handleClick}>
{open ? <ExpandLess /> : <ExpandMore />}
<ListItemText primary="Direct Messages" />
</ListItem>
<Collapse in={open} timeout="auto" unmountOnExit>
<List component="div" disablePadding className={classes.nested}>
{currentDmdWith.map((user) => (
<ListItem button className={user.id} onClick={handleClickUser}>
<ListItemIcon className={user.id}>
</ListItemIcon>
<PersonIcon className={user.id} color='white'/>
<ListItemText className={user.id} primary={user.name} />
</ListItem>
))}
<ListItem button
onClick={handleClickAdd}
className={classes.nested}>
<ListItemIcon>
</ListItemIcon>
<AddBoxOutlinedIcon className={classes.nestedAdd}/>
<ListItemText
className={classes.nestedAddText} primary="Add Teammate" />
</ListItem>
</List>
</Collapse>
</List>
);
}
I would use event.target.id, because it actually gets the div's id and not a bunch of mumbo jumbo, but when I do put id = {user.id} in a ListItem I have to click on a very specific spot, the leftmost side, because if I click on an a person icon or the area with text, event.target.id is a assigned a blank and makes my fetch() return a 404 error.
Instead of trying to read the information out of the Event object, pass the info that you need in the handleClickUser function through the onClick prop.
<ListItem
button
className={user.id}
onClick={() => handleClickUser(user.id)}
>
const handleClickUser = (userId) => {
setClickedUserId(userId);
};

how to perform parent action from child in react

I have parent & its 2 child component
Parent code:
import React from 'react';
import { Grid, CircularProgress } from '#mui/material';
import { useSelector } from 'react-redux';
import Angle from './Angle/Angle';
import CustomSnackbar from '../Snackbar/CustomSnackbar';
import useStyles from './styles';
const Angles = ({ setCurrentId }) => {
const angles = useSelector((state) => state.angles);
const [snackbarOpenProp, setSnackbarOpenProp] = React.useState(false);
const classes = useStyles();
const handleSnackBarCloseAction = () => {
setSnackbarOpenProp(false)
}
const handleAddToCartAction = () => {
console.log('click');
setSnackbarOpenProp(true)
}
return (
!angles.length ? <CircularProgress /> : (
<Grid className={classes.container} container alignItems="stretch" spacing={3}>
{angles.map((angle) => (
<Grid key={angle._id} item xs={12} sm={6} md={6}>
<Angle angle={angle} setCurrentId={setCurrentId} handleAddToCart={handleAddToCartAction}/>
</Grid>
))}
<CustomSnackbar openState={snackbarOpenProp} handleSnackBarCloseProp={handleSnackBarCloseAction}/>
</Grid>
)
);
};
export default Angles;
Child 1 code:
import React from 'react';
import { Card, CardActions, CardContent, CardMedia, Button, Typography } from '#mui/material';
import Add from '#mui/icons-material/Add';
import { useDispatch } from 'react-redux';
import useStyles from './styles';
const Angle = ({ angle, setCurrentId,handleAddToCart }) => {
const dispatch = useDispatch();
const classes = useStyles();
return (
<Card className={classes.card}>
<CardMedia className={classes.media} image={angle.image} />
<div className={classes.overlay}>
<Typography variant="h6">{angle.qualityName}</Typography>
<Typography variant="body2">{angle.colors}</Typography>
</div>
<div className={classes.overlay2}>
<Button onClick={handleAddToCart} style={{ color: 'white' }} size="small" onClick={() => setCurrentId(angle._id)}><Add fontSize="large" /></Button>
</div>
</Card>
);
};
export default Angle;
Child 2 code:
import * as React from 'react';
import Button from '#mui/material/Button';
import Snackbar from '#mui/material/Snackbar';
import IconButton from '#mui/material/IconButton';
import CloseIcon from '#mui/icons-material/Close';
export default function CustomSnackbar(props) {
const action = (
<React.Fragment>
<Button color="secondary" size="small" onClick={props.handleSnackBarCloseProp}>
UNDO
</Button>
<IconButton
size="small"
aria-label="close"
color="inherit"
onClick={props.handleSnackBarCloseProp}
>
<CloseIcon fontSize="small" />
</IconButton>
</React.Fragment>
);
return (
<div>
<Snackbar
open={props.openState}
autoHideDuration={6000}
onClose={props.handleSnackBarCloseProp}
message="Note archived"
action={action}
/>
</div>
);
}
As you can see, I am trying to attach the onClick event which is in Child1 Angle code & based on its click I am trying to change a state value of another child2 CustomSnackbar and sending it as a prop, but on clicking I am not getting a response, How can I do it, also is there any more simple way to achieve this ?
There are two onClick methods on the button component. Please change according below example and try it.
Before:
<Button onClick={handleAddToCart} style={{ color: 'white' }} size="small" onClick={() => setCurrentId(angle._id)}><Add fontSize="large" /></Button>
After:
<Button
style={{ color: 'white' }}
size="small"
onClick={() => {
handleAddToCart();
setCurrentId(angle._id)
}
}>
<Add fontSize="large" />
</Button>

React.js: How to convert class based component to functional?

I'm building an app using function based component. I found the sidebar menu template from Material Ui in classes and want to convert it to functional component. But after converting click button doesn't work. I've only changed the menu icon to another.
Any help will be appreciated.
Here is the default component in classes
import React from "react";
import AppBar from "#material-ui/core/AppBar";
import Toolbar from "#material-ui/core/Toolbar";
import Typography from "#material-ui/core/Typography";
import Button from "#material-ui/core/Button";
import IconButton from "#material-ui/core/IconButton";
import MenuIcon from "#material-ui/icons/Menu";
import { NavDrawer } from "./NavDrawer";
class NavBar extends React.Component {
constructor(props) {
super(props);
this.state = {
drawerOpened: false
};
}
toggleDrawer = booleanValue => () => {
this.setState({
drawerOpened: booleanValue
});
};
render() {
return (
<div className="App">
<AppBar position="static">
<Toolbar>
<IconButton
color="secondary"
aria-label="Menu"
onClick={this.toggleDrawer(true)}
>
<MenuIcon />
</IconButton>
<Typography variant="h6" color="inherit">
News
</Typography>
<Button color="inherit">Login</Button>
</Toolbar>
</AppBar>
<NavDrawer
drawerOpened={this.state.drawerOpened}
toggleDrawer={this.toggleDrawer}
/>
</div>
);
}
}
export default NavBar
Here I'm trying to convert
import React, { useState } from 'react'
import AppBar from '#material-ui/core/AppBar'
import Toolbar from '#material-ui/core/Toolbar'
import Typography from '#material-ui/core/Typography'
import IconButton from '#material-ui/core/IconButton'
import NavDrawer from './NavDrawer'
import AddShoppingCartIcon from '#material-ui/icons/AddShoppingCart'
function NavBar(props) {
const [drawerOpened, setDrawerOpened] = useState(false)
const toggleDrawer = booleanValue => () => {
setDrawerOpened(booleanValue)
}
return (
<div className="App">
<AppBar position="static">
<Toolbar>
<IconButton
aria-label="AddShoppingCartIcon"
onClick={() => toggleDrawer(true)}
>
<AddShoppingCartIcon style={{ fontSize: 30 }} color="secondary" />
</IconButton>
<Typography variant="h6" color="inherit"></Typography>
</Toolbar>
</AppBar>
<NavDrawer drawerOpened={drawerOpened} toggleDrawer={toggleDrawer} />
</div>
)
}
export default NavBar
Have a look at React hooks, there ae two approaches:
const [toggleDrawer, setToggleDrawer] = useState(false); // set variable
<button onClick={() => setToggleDrawer(!toggleDrawer}>
Of you can useEffect to perform some logic after the component is initially rendered, preventing a max error:
const toggleDrawer = false;
useEffect(() => { // update variable
checkDrawOpened(toggleDrawer)
}, toggleDrawer);]
With the one click
onClick={toggleDrawer} // use variable
You can do this instead for toggling actions.
const toggleDrawer = () => {
setDrawerOpened(!drawerOpened)
}
And in the return
onClick={toggleDrawer}
Your function is stacking. On onclick, you try to call function to call function. Just use the const instead.
on toggleDrawer const, you should set setDrawerOpened to whenever the opposite of value it is to get toggling effect.

Not able to copy to clipboard from Popper which is inside Dialogue in material-UI

Steps to reproduce the bug here: (Try to open in Firefox, I was almost crashed the chrome :P) https://codesandbox.io/s/73z5293391
Click on the OPEN SIMPLE DIALOGUE button. Now you see a dialogue, Click on the TOGGLE POPPER button.
Now you see a Popper which has an input box and a COPY button.
You need to copy on the text inside the input box in this case hello.
So I am not able to copy to clipboard actually.
First I thought it might be a problem with Dialogue. But no. In just a Dialogue it works. But not on Popper which pops up from the Dialogue(Only for Popper also it works).
Can you help me to copy to clipboard in this situation?
Once again the source code:
import React from "react";
import PropTypes from "prop-types";
import { withStyles } 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 AddIcon from "#material-ui/icons/Add";
import Typography from "#material-ui/core/Typography";
import blue from "#material-ui/core/colors/blue";
import DialogContent from "#material-ui/core/DialogContent";
import Popper from "#material-ui/core/Popper";
const emails = ["username#gmail.com", "user02#gmail.com"];
const styles = {
avatar: {
backgroundColor: blue[100],
color: blue[600]
}
};
class SimpleDialog extends React.Component {
state = {
anchorEl: null,
openPopper: false
};
handleClose = () => {
this.props.onClose(this.props.selectedValue);
};
handleListItemClick = value => {
this.props.onClose(value);
};
copytoClipBoard = () => {
this.hello.select();
try {
return document.execCommand("copy");
} catch (err) {
console.log("Oops, unable to copy");
}
};
handleClick = event => {
const { currentTarget } = event;
this.setState(state => ({
anchorEl: currentTarget,
openPopper: !state.openPopper
}));
};
render() {
const { classes, onClose, selectedValue, ...other } = this.props;
const { anchorEl, openPopper } = this.state;
const id = openPopper ? "simple-popper" : null;
return (
<Dialog
onClose={this.handleClose}
aria-labelledby="simple-dialog-title"
{...other}
>
<DialogTitle id="simple-dialog-title">Set backup account</DialogTitle>
<DialogContent>
<Button
aria-describedby={id}
variant="contained"
onClick={this.handleClick}
>
Toggle Popper
</Button>
<Popper
id={id}
open={openPopper}
anchorEl={anchorEl}
style={{ zIndex: 10000 }}
>
<input
value="hello"
readOnly
type="text"
ref={node => (this.hello = node)}
/>
<Button onClick={this.copytoClipBoard}> Copy </Button>
</Popper>
<List>
{emails.map(email => (
<ListItem
button
onClick={() => this.handleListItemClick(email)}
key={email}
>
<ListItemAvatar>
<Avatar className={classes.avatar}>
<PersonIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary={email} />
</ListItem>
))}
<ListItem
button
onClick={() => this.handleListItemClick("addAccount")}
>
<ListItemAvatar>
<Avatar>
<AddIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary="add account" />
</ListItem>
</List>
</DialogContent>
</Dialog>
);
}
}
SimpleDialog.propTypes = {
classes: PropTypes.object.isRequired,
onClose: PropTypes.func,
selectedValue: PropTypes.string
};
const SimpleDialogWrapped = withStyles(styles)(SimpleDialog);
class SimpleDialogDemo extends React.Component {
state = {
open: false,
selectedValue: emails[1]
};
handleClickOpen = () => {
this.setState({
open: true
});
};
handleClose = value => {
this.setState({ selectedValue: value, open: false });
};
render() {
return (
<div>
<Typography variant="subtitle1">
Selected: {this.state.selectedValue}
</Typography>
<br />
<Button
variant="outlined"
color="primary"
onClick={this.handleClickOpen}
>
Open simple dialog
</Button>
<SimpleDialogWrapped
selectedValue={this.state.selectedValue}
open={this.state.open}
onClose={this.handleClose}
/>
</div>
);
}
}
export default SimpleDialogDemo;
#akhila-hegde You can add disablePortal to the Popper to solve this.
Note that problem is not with Copy to clipboard. Problem is that you are not able to select the text in the field (because it is in a Portal).
Here is Sandbox link with disablePortal set to true - https://codesandbox.io/s/lxjwj3p8m9

How to set the style a react.js component when creating it?

How to set the style a react.js component when creating it?
Below is some of my code (partially inherited from a stronger developer and then simplified for brevity).
I want to re-use my LogComponent to print several pages of a Log. However, in some cases I want to force a particular width on the returned List, rather than allowing it to flex as it sees fit.
I would prefer to not define a separate LogComponentFixed or to have an if (...) {return (...)} else {return(...)} in my LogComponent.
I have in mind to do something in Log.js like:
<LogComponent heading={"Page 1"}, lines={page_1}, style={styles.list_1} />
<LogComponent heading={"Page 1"}, lines={page_1}, style={styles.list_2} />
And to then, in LogComponent do something like:
<List style={style}> ... </List>
I also tried using something like
<List className={list_1}> ... </List>
But none of the things I've tried works...
Log.js
import React from 'react'
import Typography from '#material-ui/core/Typography'
import { withStyles } from '#material-ui/core/styles'
import LogComponent from './LogComponent'
const styles = theme => ({
title: {
padding: theme.spacing.unit*1.5,
},
list_1: {
},
list_2: {
width: "300px"
},
listContainer: {
flexGrow: 1,
minHeight: 0,
overflow: 'auto'
},
})
const Log = ({classes, log}) => {
const page_1 = log[0];
const page_2 = log[1];
return (
<div>
<Typography className={classes.title} color="textSecondary" key={1}>
Example Log
</Typography>
<div className={classes.listContainer} key={2}>
<LogComponent heading={'Page 1'} lines={page_1} />
<LogComponent heading={'Page 2'} lines={page_2} />
</div>
</div>
export default withStyles(styles)(Log)
LogComponent.js
import React from 'react'
import Typography from '#material-ui/core/Typography'
import { withStyles } from '#material-ui/core/styles'
import { List, ListItem, ListItemText } from '#material-ui/core';
const styles = theme => ({
title: {
padding: theme.spacing.unit*1.5,
},
}
const LogComponent = ({classes, list_class, heading, lines}) => {
return (
<div className={classes.root}>
<Typography className={classes.title} color="textSecondary" key={1}>
{heading}
</Typography>
<div>
<List dense>
{[...lines[0]].map(e =>
<ListItem><ListItemText primary={e} /></ListItem>
)}
</List>
</div>
</div>
)
}
export default withStyles(styles)(LogComponent)
Here you are sending the styles as a prop to LogComponent, that's why it will not be applied as styles to that component you have created. The style attribute is for HTML tags and in material-ui, you can pass styles to a wrapper component also.
In your case you can get the styles inside your LogComponent as below:
Send styles as a prop as you mentioned in the question
<LogComponent heading={"Page 1"}, lines={page_1}, style={styles.list_1} />
Now, you can access it from props,
// right below get the style
const LogComponent = ({classes, list_class, heading, lines, style}) => {
return (
<div className={classes.root} style={style}> // Look style attribute added, style(value) is from props
<Typography className={classes.title} color="textSecondary" key={1}>
{heading}
</Typography>
<div>
<List dense>
{[...lines[0]].map(e =>
<ListItem><ListItemText primary={e} /></ListItem>
)}
</List>
</div>
</div>
)
}

Categories