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>
Related
I am trying to build a hamburger menu using Material UI List on Next.js similar to Nested List example on Material UI docs. However, I am keep getting error if I use "collapse" on my code. If I remove collapse then the other parts of the code works fine.
Can anyone tell me what am I doing wrong?
screenshot of an error message
I am trying to build similar to this
import * as React from "react";
import { useState } from "react";
import IconButton from "#mui/material/IconButton";
import ListSubheader from "#mui/material/ListSubheader";
import { Drawer } from "#mui/material";
import List from "#mui/material/List";
import ListItem from "#mui/material/ListItem";
import ListItemButton from "#mui/material/ListItemButton";
import ListItemIcon from "#mui/material/ListItemIcon";
import ListItemText from "#mui/material/ListItemText";
import MenuIcon from "#mui/icons-material/Menu";
import { ExpandLess, ExpandMore } from "#mui/icons-material";
import Collapse from "#mui/material";
const myMenu = [
{
title: "about",
},
{
title: "services",
submenu: [{ title: "Digital Marketing" }, { title: "Promotion" }],
},
{
title: "blog",
},
{
title: "contact",
},
];
const ResponsiveAppBar = () => {
const [openDrawer, setOpenDrawer] = useState(false);
const [open, setOpen] = React.useState(false);
const handleClick = () => {
setOpen(!open);
};
return (
<React.Fragment>
<Drawer
anchor="right"
open={openDrawer}
onClose={() => setOpenDrawer(false)}
>
<List
sx={{ width: "100%", maxWidth: 360, bgcolor: "background.paper" }}
component="nav"
aria-labelledby="nested-list-subheader"
subheader={
<ListSubheader component="div" id="nested-list-subheader">
Navigation
</ListSubheader>
}
>
{myMenu.map((page, index) =>
page.submenu ? (
<div>
<ListItemButton key={index} onClick={handleClick}>
<ListItemText>{page.title}</ListItemText>
{open ? <ExpandLess /> : <ExpandMore />}
</ListItemButton>
<Collapse in={open} timeout="auto" unmountOnExit>
<List component="div" disablePadding>
{page.submenu.map((sub, index) => {
return (
<ListItemButton key={index}>
<ListItem key={index}>
<ListItemText>{sub.title}</ListItemText>
</ListItem>
</ListItemButton>
);
})}
</List>
</Collapse>
</div>
) : (
<ListItemButton key={index}>
<ListItemIcon>
<ListItemText>{page.title}</ListItemText>
</ListItemIcon>
</ListItemButton>
)
)}
</List>
</Drawer>
<IconButton
sx={{ color: "black", marginLeft: "auto" }}
onClick={() => setOpenDrawer(!openDrawer)}
>
<MenuIcon color="white" />
</IconButton>
</React.Fragment>
);
};
export default ResponsiveAppBar;
The culprit is an incorrect import of Collapse:
import Collapse from "#mui/material";
Since Collapse is a named import, you must import it in one of these two ways:
import { Collapse } from "#mui/material";
// Alternatively
import Collapse from "#mui/material/Collapse";
In my application, the home page contains a form that creates workout objects. I am trying to do the same thing with meal objects, but on another page, and after clicking the "meals" button from my navbar, I get an error.
This is my navbar component where I click on the meals button.
import React,{useState, useEffect} from 'react';
import {Link, useLocation} from 'react-router-dom';
import { AppBar, Avatar, Toolbar, Typography, Button} from '#material-ui/core';
import { useDispatch } from 'react-redux';
import decode from 'jwt-decode';
import {useNavigate} from 'react-router-dom';
import useStyles from './styles';
const Navbar = () => {
const classes = useStyles();
const [user, setUser]=useState(JSON.parse(localStorage.getItem('profile')));
const dispatch=useDispatch();
const navigate=useNavigate();
const location=useLocation();
const logout=()=>{
dispatch({type:'LOGOUT'});
navigate('/');
setUser(null);
};
const meals=()=>{
dispatch({type:'MEALS'});
navigate('/meals');
};
useEffect(()=>{
const token=user?.token;
if(token){
const decodedToken=decode(token);
if (decodedToken.exp * 1000 < new Date().getTime()) logout();
}
setUser(JSON.parse(localStorage.getItem('profile')));
},[location])
return (
<AppBar className={classes.AppBar} position="static" color="inherit">
<div className={classes.brandContainer}>
<Typography component={Link} to="/" className={classes.heading} variant="h2" align="center">Workouts</Typography>
</div>
<Toolbar className={classes.toolbar}>
{user ? (
<div className={classes.profile}>
<Avatar className={classes.purple} referrerPolicy="no-referrer" alt={user.result.name} src={user.result.imageUrl}>{user.result.name.charAt(0)}</Avatar>
<Typography className={classes.userName} variant="h6">{user.result.name}</Typography>
<Button variant="contained" className={classes.logout} color="secondary" onClick={meals}>Meals</Button>
<Button variant="contained" className={classes.logout} color="secondary" onClick={logout}>Logout</Button>
</div>
) : (
<Button component={Link} to="/auth" variant="contained" color="primary">Sign In</Button>
)}
</Toolbar>
</AppBar>
)
}
export default Navbar
This is my single Meal component
import React from 'react';
import { Card, CardActions, CardContent, CardMedia, Button, Typography } from '#material-ui/core/';
import ThumbUpAltIcon from '#material-ui/icons/ThumbUpAlt';
import DeleteIcon from '#material-ui/icons/Delete';
import MoreHorizIcon from '#material-ui/icons/MoreHoriz';
import ThumbUpAltOutlined from '#material-ui/icons/ThumbUpAltOutlined';
import moment from 'moment';
import {useDispatch} from 'react-redux';
import useStyles from './styles';
import {deleteMeal, likeMeal} from '../../../actions/meals';
const Meal=({meal, setCurrentId})=> {
const classes=useStyles();
const dispatch=useDispatch();
const user = JSON.parse(localStorage.getItem('profile'));
const Likes = () => {
if (meal.likes.length > 0) {
return meal.likes.find((like) => like === (user?.result?.googleId || user?.result?._id))
? (
<><ThumbUpAltIcon fontSize="small" /> {meal.likes.length > 2 ? `You and ${meal.likes.length - 1} others` : `${meal.likes.length} like${meal.likes.length > 1 ? 's' : ''}` }</>
) : (
<><ThumbUpAltOutlined fontSize="small" /> {meal.likes.length} {meal.likes.length === 1 ? 'Like' : 'Likes'}</>
);
}
return <><ThumbUpAltOutlined fontSize="small" /> Like</>;
};
return (
<Card className={classes.card}>
<CardMedia className={classes.media} image={meal.selectedFile|| 'https://user-images.githubusercontent.com/194400/49531010-48dad180-f8b1-11e8-8d89-1e61320e1d82.png'} title={meal.category}/>
<div className={classes.overlay}>
<Typography variant="h6">{meal.calories}</Typography>
<Typography variant="body2">{moment(meal.createdAt).fromNow()}</Typography>
</div>
{(user?.result?.googleId===meal?.creator || user?.result?._id===meal?.creator) && (
<div className={classes.overlay2}>
<Button style={{color:'white'}} size="small" onClick={()=>setCurrentId(meal._id)}>
<MoreHorizIcon fontSize="medium"/>
</Button>
</div>
)}
<div className={classes.details}>
<Typography className={classes.title} gutterBottom variant="h5" component="h2">{meal.name}</Typography>
</div>
<Typography className={classes.difficulty} variant="body2" color="textSecondary" component="p">{meal.category}</Typography>
<CardContent>
<Typography variant="body2" color="textSecondary" component="p">{meal.calories}</Typography>
</CardContent>
<CardActions className={classes.cardActions}>
<Button size='small' color="primary" disabled={!user?.result} onClick={()=>{dispatch(likeMeal(meal._id))}}>
<Likes/>
</Button>
{(user?.result?.googleId===meal?.creator || user?.result?._id===meal?.creator) && (
<Button size='small' color="primary" onClick={()=>{dispatch(deleteMeal(meal._id))}}>
<DeleteIcon fontSize="small"/>
Delete
</Button>
)}
</CardActions>
</Card>
);
}
export default Meal;
This is my Meals components that stands for all the Meal objects.
import React from 'react';
import {Grid, CircularProgress} from '#material-ui/core';
import { useSelector } from 'react-redux';
import Meal from './Meal/Meal';
import useStyles from './styles';
const Meals=({setCurrentId})=> {
const meals=useSelector((state)=>state.meals);
const classes =useStyles();
return (
!meals.length ? <CircularProgress/>:(
<Grid className={classes.container} container alignItems="stretch" spacing={3}>
{
meals.map((meal)=>(
<Grid key={meal._id} item xs={12} sm={6} md={6}>
<Meal meal={meal} setCurrentId={setCurrentId}/>
</Grid>
))}
</Grid>
)
);
}
export default Meals;
The error says "cannot read properties of undefined (reading 'length')" and length is in this component.
And, below I have my MealsForm which is the form used for creating the objects.
import React, { useState, useEffect } from 'react';
import { TextField, Button, Typography, Paper } from '#material-ui/core';
import { useDispatch, useSelector } from 'react-redux';
import FileBase from 'react-file-base64';
import useStyles from './styles';
import { createMeal, updateMeal } from '../../actions/meals';
const MealForm=({ currentId, setCurrentId=()=>{} })=>{
const [mealData, setMealData]=useState({
category:'',name:'', calories:'',selectedFile:''
});
const meal = useSelector((state) => (currentId ? state.meals.find((meal) => meal._id === currentId) : null));
const dispatch=useDispatch();
const classes=useStyles();
const user=JSON.parse(localStorage.getItem('profile'));
useEffect(()=>{
if(meal) setMealData(meal);
},[meal]);
const clear = () => {
setCurrentId(0);
setMealData({ category:'',name:'', calories:'',selectedFile:'' });
};
const handleSubmit=async (e)=>{
e.preventDefault();
if(!currentId) {
dispatch(createMeal(mealData));
clear();
} else{
dispatch(updateMeal(currentId, mealData));
clear();
}
};
if (!user?.result?.name) {
return (
<Paper className={classes.paper}>
<Typography variant="h6" align="center">
Please Sign In to add meals
</Typography>
</Paper>
);
}
return(
<Paper className={classes.paper}>
<form autoComplete="off" noValidate className={`${classes.root} ${classes.form}`} onSubmit={handleSubmit}>
<Typography variant="h6">{currentId ? `Editing "${meal.calories}"` : 'Creating a meal'}</Typography>
<TextField name="category" variant="outlined" label="Category" fullWidth value={mealData.category} onChange={(e)=>setMealData({...mealData, category:e.target.value})}/>
<TextField name="name" variant="outlined" label="Name" fullWidth value={mealData.name} onChange={(e)=>setMealData({...mealData, name:e.target.value})}/>
<TextField name="calories" variant="outlined" label="Calories" fullWidth value={mealData.calories} onChange={(e)=>setMealData({...mealData, calories:e.target.value})}/>
<div className={classes.fileInput}>
<FileBase
type="file"
multiple={false}
onDone={({base64})=>setMealData({...mealData,selectedFile:base64})}
/>
</div>
<Button className={classes.buttonSubmit} variant="contained" color="primary" size="large" type="submit" fullWidth>Submit</Button>
<Button variant="contained" color="secondary" size="small" onClick={clear} fullWidth>Clear</Button>
</form>
</Paper>
);
}
export default MealForm;
Here I have the code for the actions on the meals object
import { FETCH_ALL, CREATE, UPDATE, DELETE, LIKE } from '../constants/actionTypes';
import * as api from '../api';
export const getMeals=()=> async (dispatch)=>{
try{
const {data}=await api.fetchMeals();
dispatch({type:FETCH_ALL,payload:data});
}catch(error){
console.log(error);
}
}
export const createMeal=(meal)=>async(dispatch)=>{
try{
const {data}=await api.createMeal(meal);
dispatch({type:CREATE,payload:data});
console.log(data);
}catch(error){
console.log(error.message);
}
}
export const updateMeal=(id,meal)=>async(dispatch)=>{
try{
const {data}=await api.updateMeal(id,meal);
dispatch({type:UPDATE,payload:data})
}catch(error){
console.log(error);
}
}
export const deleteMeal=(id)=>async (dispatch)=>{
try{
await api.deleteMeal(id);
dispatch({type:DELETE,payload:id});
}catch(error){
console.log(error);
}
}
export const likeMeal=(id)=>async(dispatch)=>{
try{
const {data}=await api.likeMeal(id);
dispatch({type:LIKE,payload:data})
}catch(error){
console.log(error);
}
}
Below I have the MealsPage component. The flow goes like this: Navbar, MealsPage, Meals, Meal
import React, {useState, useEffect} from 'react'
import {Container, Grow, Grid} from '#material-ui/core';
import {useDispatch} from 'react-redux';
import {getMeals} from '../../actions/meals';
import Meals from "../Meals/Meals";
import MealForm from "../MealForm/MealForm";
import useStyles from './styles';
function MealsPage() {
const [currentId, setCurrentId]=useState(null);
const classes=useStyles();
const dispatch=useDispatch();
useEffect(()=>{
dispatch(getMeals());
},[currentId, dispatch]);
const meals=getMeals();
console.log(meals);
return (
<Grow in>
<Container>
<Grid className={classes.mainContainer} container justifyContent="space-between" alignItems='stretch' spacing={3}>
<Grid item xs={12} sm={7}>
<Meals setCurrentId={setCurrentId}/>
</Grid>
<Grid item xs={12} sm={4}>
<MealForm currentId={currentId} setCurrentId={setCurrentId}/>
</Grid>
</Grid>
</Container>
</Grow>
)
}
export default MealsPage
I cannot see the problem. Could you help me solve this?
I am getting this error on my React material UI project
By looking at the error I guess it comes somewhere inside the Drawer.js component.
This is my full Drawer.js component
import React, { Fragment, useState } from "react";
import clsx from "clsx";
import { makeStyles, useTheme } from "#material-ui/core/styles";
import Drawer from "#material-ui/core/Drawer";
import { List, Collapse } from "#material-ui/core";
import Divider from "#material-ui/core/Divider";
import ListItem from "#material-ui/core/ListItem";
import IconButton from "#material-ui/core/IconButton";
import ListItemText from "#material-ui/core/ListItemText";
import { toggleDrawer } from "../../store/actions/authActions";
import ExpandLess from "#material-ui/icons/ExpandLess";
import ExpandMore from "#material-ui/icons/ExpandMore";
import ChevronLeftIcon from "#material-ui/icons/ChevronLeft";
import ChevronRightIcon from "#material-ui/icons/ChevronRight";
// import ClickAwayListener from '#material-ui/core/ClickAwayListener'
import { useHistory } from 'react-router-dom';
import { connect } from "react-redux";
import { withRouter } from "react-router";
const useStyles = makeStyles({
list: {
width: 250,
},
fullList: {
width: "auto",
},
});
function TemporaryDrawer(props) {
const classes = useStyles();
const theme = useTheme();
// const [open, setOpen] = React.useState(false);
const [openIndex, setOpenIndex] = useState(0);
let history = useHistory();
// const handleDrawerOpen = () => {
// setOpen(true);
// };
const handleDrawerClose = () => {
props.toggleDrawer();
};
const handleClick = (index) => {
if (openIndex === index) {
setOpenIndex(-1);
} else {
setOpenIndex(index);
}
};
const onToggle = () => (event) => {
if (
event.type === "keydown" &&
(event.key === "Tab" || event.key === "Shift")
) {
return;
}
props.toggleDrawer();
};
const onRoute = (path) => {
// props.history.push(path);
history.push(path);
props.toggleDrawer();
};
const list = (anchor) => (
<div
className={clsx(classes.list, {
[classes.fullList]: anchor === "top" || anchor === "bottom",
})}
role="presentation"
>
<div className={classes.drawerHeader}>
<IconButton onClick={handleDrawerClose}>
{theme.direction === "ltr" ? (
<ChevronLeftIcon />
) : (
<ChevronRightIcon />
)}
</IconButton>
</div>
<Divider />
<List>
{props.permissions.map((route, index) => (
<Fragment key={index}>
<ListItem button onClick={(e) => handleClick(index)}>
<ListItemText primary={route.name} />
{index === openIndex ? <ExpandLess /> : <ExpandMore />}
</ListItem>
{route.children.length && (
<Collapse
in={openIndex === index ? true : false}
timeout="auto"
unmountOnExit
>
<List component="div" disablePadding>
{route.children.map((child, idx) => (
<ListItem
button
className={classes.nested}
key={idx}
onClick={() => onRoute(child.path)}
>
<ListItemText primary={child.name} />
</ListItem>
))}
</List>
<Divider />
</Collapse>
)}
</Fragment>
))}
</List>
{/* <Divider /> */}
</div>
);
return (
<div>
{props.token && (
<Drawer
anchor="left"
open={props.isDrawerOpen}
onClose={onToggle("left", false)}
variant="persistent"
>
{list("left")}
</Drawer>
)}
</div>
);
}
const mapStateToProps = (state) => {
return {
isDrawerOpen: state.auth.isDrawerOpen,
token: state.auth.token,
permissions: state.auth.routes,
};
};
export default withRouter(
connect(mapStateToProps, { toggleDrawer })(TemporaryDrawer)
);
I go through this error and some say this is a problem in MUI library and no way to fix this. But I believe there must be a workaround for this. This causes serious problems for UI.
Where this error comes from and what can I do to fix this?
Any help!
Thanks in advance. =)
I use material UI 4.11.0
and react 16
You only need to create a new childComponent with react.forwardRef passing respective props and refs:
const ChildComponent = React.forwardRef((props, ref) =>
<div ref={ref}>
<yourChildcomponent {...props} />
</div>
);
In your render change the name of the original Child for the new:
<ChildComponent... />
I have built a Trello clone using ReactJS, where I have 4 columns called TODO, DOING, DONE and REJECTED, where I can add a card to any column.
In a file I am trying to map over card component and rendering properties from defined dummy data.
What I want to do?
I want to delete a specific card when the button is clicked.
What I tried?
I have added the functionality in my Redux store, but when adding the onclick event to my button, I cannot access the dispatch method which will trigger the deleteCard function.
How do I do that?
My TaskboardList.js component :
import React from "react";
import TaskboardCard from "./TaskboardCard";
import TaskboardActionButton from "./TaskboardActionButton";
import { Droppable } from "react-beautiful-dnd";
const TaskboardList = ({ title, cards, listID }) => {
return (
<Droppable droppableId={String(listID)}>
{provided => (
<div
className="taskboardlist_container"
{...provided.droppableProps}
ref={provided.innerRef}
style={styles.container}
>
<div className="sub-heading">{title}</div>
{cards.map((card, index) => (
<TaskboardCard
key={card.id}
index={index}
text={card.text}
id={card.id}
/>
))}
<TaskboardActionButton listID={listID} />
{provided.placeholder}
</div>
)}
</Droppable>
);
};
const styles = {
container: {
backgroundColor: "#eee",
width: 300,
padding: "0.5rem",
marginRight: "1rem",
height: "100%"
}
};
export default TaskboardList;
My TaskboardCard.js component
import React from "react";
import Card from "#material-ui/core/Card";
import Typography from "#material-ui/core/Typography";
import CardContent from "#material-ui/core/CardContent";
import { Draggable } from "react-beautiful-dnd";
import { connect } from "react-redux";
import { deleteCard } from "../actions";
const TaskboardCard = ({ text, id, index, sample, cardId }) => {
// handleClickDelete = () => {
// // const { dispatch } = this.props;
// // dispatch(deleteCard(cardId));
// console.log("clicked");
// };
return (
<Draggable draggableId={String(id)} index={index}>
{provided => (
<div
className="taskboard_container"
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
<Card>
<CardContent>
<Typography style={{ fontSize: "1.5rem" }} gutterBottom>
{text}
</Typography>
</CardContent>
</Card>
{/* //delete added */}
<button
onClick={(cardId, props, sample, dispatch) => {
//const { dispatch } = this.props;
dispatch(deleteCard(cardId));
}}
>
DELETE
</button>
{/* ////////////////////// */}
</div>
)}
</Draggable>
);
};
export default connect()(TaskboardCard);
In the above component delete button is not working because somehow i cannot access the dispatch.
Here is my codesandbox link for further reference to files https://codesandbox.io/s/github/abhinav-anshul/consensolabs
Remove the props and dispatch from onclick event and add dispatch in component parameter list.
If you don't specify the second argument to connect(), your component will receive dispatch by default in porps.
import React from "react";
import Card from "#material-ui/core/Card";
import Typography from "#material-ui/core/Typography";
import CardContent from "#material-ui/core/CardContent";
import { Draggable } from "react-beautiful-dnd";
import { connect } from "react-redux";
import { deleteCard } from "../actions";
const TaskboardCard = ({ text, id, index, sample, cardId, dispatch }) => {
// handleClickDelete = () => {
// // const { dispatch } = this.props;
// // dispatch(deleteCard(cardId));
// console.log("clicked");
// };
return (
<Draggable draggableId={String(id)} index={index}>
{provided => (
<div
className="taskboard_container"
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
<Card>
<CardContent>
<Typography style={{ fontSize: "1.5rem" }} gutterBottom>
{text}
</Typography>
</CardContent>
</Card>
{/* //delete added */}
<button
onClick={(cardId, sample) => {
//const { dispatch } = this.props;
dispatch(deleteCard(cardId));
}}
>
DELETE
</button>
{/* ////////////////////// */}
</div>
)}
</Draggable>
);
};
export default connect()(TaskboardCard);
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.