Material UI implementation in TypeScript - reactjs

I am new to typeScript and I am using this materialUI premium theme OnePirate and I am using the Footer component and it is working perfectly fine in the js but when I am moving the same Footer component to my project and rename the component to .tsx It throws errors saying "No Overload matches this call"
Here is the code:
import React from "react";
import { makeStyles } from "#material-ui/core/styles";
import Grid from "#material-ui/core/Grid";
import Link from "#material-ui/core/Link";
import Container from "#material-ui/core/Container";
import Typography from "./materialUi/Typography";
import TextField from "./materialUi/TextField";
import fbicon from "./static/themes/onepirate/appFooterFacebook.png";
import twicon from "./static/themes/onepirate/appFooterTwitter.png";
const useStyles = makeStyles((theme) => ({
root: {
display: "flex",
backgroundColor: theme.palette.secondary.light,
},
container: {
marginTop: theme.spacing(8),
marginBottom: theme.spacing(8),
display: "flex",
},
iconsWrapper: {
height: 120,
},
icons: {
display: "flex",
},
icon: {
width: 48,
height: 48,
display: "flex",
justifyContent: "center",
alignItems: "center",
backgroundColor: theme.palette.warning.main,
marginRight: theme.spacing(1),
"&:hover": {
backgroundColor: theme.palette.warning.dark,
},
},
list: {
margin: 0,
listStyle: "none",
paddingLeft: 0,
},
listItem: {
paddingTop: theme.spacing(0.5),
paddingBottom: theme.spacing(0.5),
},
language: {
marginTop: theme.spacing(1),
width: 150,
},
}));
const LANGUAGES = [
{
code: "en-US",
name: "English",
},
{
code: "fr-FR",
name: "Français",
},
];
function Footer() {
const classes = useStyles();
return (
<Typography component="footer" className={classes.root}>
<Container className={classes.container}>
<Grid container spacing={5}>
<Grid item xs={6} sm={4} md={3}>
<Grid
container
direction="column"
justify="flex-end"
className={classes.iconsWrapper}
spacing={2}
>
<Grid item className={classes.icons}>
<a href="https://material-ui.com/" className={classes.icon}>
<img src={fbicon} alt="Facebook" />
</a>
<a
href="https://twitter.com/MaterialUI"
className={classes.icon}
>
<img src={twicon} alt="Twitter" />
</a>
</Grid>
<Grid item>© 2019 Onepirate</Grid>
</Grid>
</Grid>
<Grid item xs={6} sm={4} md={2}>
<Typography variant="h6" marked="left" gutterBottom>
Legal
</Typography>
<ul className={classes.list}>
<li className={classes.listItem}>
<Link href="/premium-themes/onepirate/terms/">Terms</Link>
</li>
<li className={classes.listItem}>
<Link href="/premium-themes/onepirate/privacy/">Privacy</Link>
</li>
</ul>
</Grid>
<Grid item xs={6} sm={8} md={4}>
<Typography variant="h6" marked="left" gutterBottom>
Language
</Typography>
<TextField
select
SelectProps={{
native: true,
}}
className={classes.language}
>
{LANGUAGES.map((language) => (
<option value={language.code} key={language.code}>
{language.name}
</option>
))}
</TextField>
</Grid>
<Grid item>
<Typography variant="caption">
{"Icons made by "}
<Link
href="https://www.freepik.com"
rel="nofollow"
title="Freepik"
>
Freepik
</Link>
{" from "}
<Link
href="https://www.flaticon.com"
rel="nofollow"
title="Flaticon"
>
www.flaticon.com
</Link>
{" is licensed by "}
<Link
href="https://creativecommons.org/licenses/by/3.0/"
title="Creative Commons BY 3.0"
target="_blank"
rel="noopener noreferrer"
>
CC 3.0 BY
</Link>
</Typography>
</Grid>
</Grid>
</Container>
</Typography>
);
}
export default Footer;
This is the Typography code if I don't import from material UI
import React from "react";
import PropTypes from "prop-types";
import { withStyles } from "#material-ui/core/styles";
import { capitalize } from "#material-ui/core/utils";
import MuiTypography from "#material-ui/core/Typography";
const styles = (theme) => ({
markedH2Center: {
height: 4,
width: 73,
display: "block",
margin: `${theme.spacing(1)}px auto 0`,
backgroundColor: theme.palette.secondary.main,
},
markedH3Center: {
height: 4,
width: 55,
display: "block",
margin: `${theme.spacing(1)}px auto 0`,
backgroundColor: theme.palette.secondary.main,
},
markedH4Center: {
height: 4,
width: 55,
display: "block",
margin: `${theme.spacing(1)}px auto 0`,
backgroundColor: theme.palette.secondary.main,
},
markedH6Left: {
height: 2,
width: 28,
display: "block",
marginTop: theme.spacing(0.5),
background: "currentColor",
},
});
const variantMapping = {
h1: "h1",
h2: "h1",
h3: "h1",
h4: "h1",
h5: "h3",
h6: "h2",
subtitle1: "h3",
};
function Typography(props) {
const { children, classes, marked = false, variant, ...other } = props;
return (
<MuiTypography variantMapping={variantMapping} variant={variant} {...other}>
{children}
{marked ? (
<span
className={
classes[`marked${capitalize(variant) + capitalize(marked)}`]
}
/>
) : null}
</MuiTypography>
);
}
Typography.propTypes = {
children: PropTypes.node,
classes: PropTypes.object.isRequired,
marked: PropTypes.oneOf([false, "center", "left"]),
variant: PropTypes.string,
};
export default withStyles(styles)(Typography);
This is the error now:

TL;DR: I've open sourced my implementation of onepirate in Typescript so you can just use that: https://github.com/rothbart/onepirate-typescript
This is a great question. Onepirate is a great resource but as you've noticed it isn't really built in a way that's Typescript friendly.
Your problem here is that the Typography implementation in onepirate isn't doing a great job of specifying which properties it expects, and is relying on the fact that vanilla JS will mostly just pass everything through to the underlying MuiTypography component.
Here's a much cleaner Typography implementation that preserves the underline effect in onepirate works with Typescript:
import React from "react";
import PropTypes, { InferProps } from "prop-types";
import {
withStyles,
WithStyles,
createStyles,
Theme,
} from "#material-ui/core/styles";
import MuiTypography, { TypographyProps } from "#material-ui/core/Typography";
const styles = (theme: Theme) =>
createStyles({
markedH2Center: {
height: 4,
width: 73,
display: "block",
margin: `${theme.spacing(1)}px auto 0`,
backgroundColor: theme.palette.secondary.main,
},
markedH3Center: {
height: 4,
width: 55,
display: "block",
margin: `${theme.spacing(1)}px auto 0`,
backgroundColor: theme.palette.secondary.main,
},
markedH4Center: {
height: 4,
width: 55,
display: "block",
margin: `${theme.spacing(1)}px auto 0`,
backgroundColor: theme.palette.secondary.main,
},
markedH6Left: {
height: 2,
width: 28,
display: "block",
marginTop: theme.spacing(0.5),
background: "currentColor",
},
});
function getMarkedClassName(variant: string) {
switch (variant) {
case "h2":
return "markedH2Center";
case "h3":
return "markedH3Center";
case "h4":
return "markedH4Center";
}
return "markedH6Left";
}
const variantMapping = {
h1: "h1",
h2: "h1",
h3: "h1",
h4: "h1",
h5: "h3",
h6: "h2",
subtitle1: "h3",
};
function Typography<C extends React.ElementType>(
props: TypographyProps<C, { component?: C }> &
WithStyles<typeof styles> &
InferProps<typeof Typography.propTypes>
) {
const { children, variant, classes, marked, ...other } = props;
return (
<MuiTypography variantMapping={variantMapping} variant={variant} {...other}>
{children}
{marked ? (
<span className={classes[getMarkedClassName(variant as string)]} />
) : null}
</MuiTypography>
);
}
Typography.propTypes = {
marked: PropTypes.oneOf([false, "center", "left"]),
};
Typography.defaultProps = {
marked: false,
};
export default withStyles(styles)(Typography);
Just a heads up that will run into this same problem with other onepirate components as well (I just finished re-implementing most of them in Typescript so I could use them in my own web app). If you're trying to reverse engineer how I did this I'd pay particular attention to how I took advantage of MuiTypography props and then just added the one new property I needed (marked).
TypographyProps<C, { component?: C }> &
WithStyles<typeof styles> &
InferProps<typeof Typography.propTypes>
I'd also highly recommend reading over https://material-ui.com/guides/typescript/ if you haven't already, it's a super helpful guide for understanding some of the Typescript gotchas in Material-UI.

Related

Getting an error titled "Uncaught TypeError: Cannot read properties of undefined (reading 'cartFoodItems')" when clicking on my cart button

I am working on a food app that adds items to a cart then calculates the total amount. I am trying to implement a reducer as opposed to having multiple instances of state hooks and I am running into this error when trying to click open my cart modal.
I know the items are being added successfully to the cart because when I add them, then inspect in the console and remove the "display: none " property from my modal and see the added food items there. For some reason its specifically when I click my cart to see the modal is when I receive the error.
The error message is saying the error is located in my app.js file where I assign define the cartFoods prop to my Cartmodal component
App.js Component
import logo from "./logo.svg";
import "./App.css";
import NavHeader from "./NavHeader";
import { useState, useEffect, useReducer } from "react";
import FoodList from "./FoodList";
import CartModal from "./CartModal";
import "animate.css";
function App() {
const [foods, setFoods] = useState([
{
food: "sushi roll",
description: "fresh tuna topped with eel sauce and avocado",
price: "14.99",
quantity: 1,
},
{
food: "pizza",
description: "baked to a crisp with mozarrella, basil and tomato sauce",
price: "9.99",
quantity: 1,
},
{
food: "special fried rice",
description: "cooked with onions , shrimp , chicken",
price: "19.99",
quantity: 1,
},
{
food: "tacos",
description:
"choice of either ,shrimp , chicken, or steak, topped with pico de gallo",
price: "15.99",
quantity: 1,
},
]);
const [totalFoodAmount, setTotalFoodAmount] = useState(0);
// const [modalDisplay, setModalDisplay] = useState("none");
const [totalPrice, setTotalPrice] = useState(0);
const [cartFoods, setCartFoods] = useState([]);
const [cartItem, setCartItem] = useState({});
const [modalDisplay, setModalDisplay] = useState("none");
const [amountValue, setAmountValue] = useState(0);
function foodReducer(state, action) {
if (action.type === "ADD_CART_ITEMS") {
return { cartFoodItems: state.cartFoodItems.concat(action.val) };
}
}
const [foodState, dispatch] = useReducer(foodReducer, {
modalDisplay: "none",
totalFoodPrice: 0,
cartFoodItems: [],
});
function setDisplay() {
dispatch({ type: "ADD_MODAL_DISPLAY" });
setModalDisplay("flex");
}
function removeModal() {
dispatch({ type: "REMOVE_MODAL_DISPLAY" });
setModalDisplay("none");
}
function setQuantity(e) {
console.log(e.target.value);
setAmountValue(e.target.value);
}
function addFood(food) {
console.log(food);
let newFoodItem = { ...food, quantity: amountValue };
// setCartFoods((prevState) => cartFoods.concat(newFoodItem));
dispatch({ type: "ADD_CART_ITEMS", val: newFoodItem });
// ask slavo specific question on why state doesnt update right away when you console.log(cartFoods) inside the function
}
useEffect(() => {
let total = foodState.cartFoodItems.reduce(function (a, b) {
return a + Number(b.quantity);
}, 0);
setTotalFoodAmount(total);
foodState.cartFoodItems.map((food) =>
setTotalPrice(totalPrice + food.price * food.quantity)
);
}, [foodState]);
return (
<div className="App">
<NavHeader cartQuantity={totalFoodAmount} setDisplay={setDisplay} />
<FoodList foodList={foods} addFood={addFood} setAmount={setQuantity} />
<CartModal
cartFoods={foodState.cartFoodItems}
display={modalDisplay}
closeModal={removeModal}
totalPrice={totalPrice}
/>
</div>
);
}
export default App;
Cart.js component
import React from "react";
export default function Cart(props) {
return (
<div
style={{
display: "flex",
marginRight: "35px",
border: "1px solid lightgray",
padding: "5px",
borderRadius: "5px",
margin: "10px",
}}
className="cart-section"
onClick={props.setDisplay}
>
<div style={{ marginRight: "20px" }}>
<h4>Your Cart</h4>
</div>
<div style={{ position: "relative", top: "0vh" }}>
<h4>Quantity: {props.cartQuantity}</h4>
</div>
</div>
);
}
CartModal.js
import React from "react";
import "animate.css";
import { CSSTransition } from "react-transition-group";
export default function CartModal(props) {
return (
<div
style={{
background: "#000000a6",
width: "100vw",
height: "100vh",
position: "fixed",
top: "0",
left: "0",
overflow: "scroll",
display: props.display,
justifyContent: "center",
alignItems: "center",
}}
>
<div
style={{
background: "white",
padding: "30px",
minHeight: "60vh",
minWidth: "75vw",
borderRadius: "10px",
}}
className="cart-modal"
>
<div style={{ display: "flex", justifyContent: "flex-end" }}>
<h3 onClick={props.closeModal}>x</h3>
</div>
<h4 style={{ textAlign: "center" }}>Your Cart</h4>
{props.cartFoods.length === 0 ? (
<h1>There are no Items in your cart</h1>
) : (
props.cartFoods.map((food, index) => (
<div
style={{
display: "flex",
justifyContent: "space-between",
borderBottom: "2px solid lightgray",
}}
key={index}
>
<div style={{ width: "10vh" }}>
<h3>{food.food}</h3>
<p>{food.price}</p>
<p>Quantity: {food.quantity}</p>
</div>
<div style={{ display: "flex" }}>
<button style={{ margin: "10px", maxHeight: "20px" }}>-</button>
<button style={{ margin: "10px", maxHeight: "20px" }}>+</button>
</div>
</div>
))
)}
<h2>Price: ${props.totalPrice}</h2>
</div>
</div>
);
}

Appbar not visible with Table on the same page - ReactJS

I'm new to ReactJS and I'm trying to put a table on a page with an AppBar. Both of these components are from Material UI, but only the Table is displayed, the AppBar seems to be just hidden.
I don't really get what is happening.
Here is the code (full):
import React from 'react';
import {Grid, Typography, Paper, Box, Toolbar, AppBar, ThemeProvider, createMuiTheme} from '#material-ui/core';
import Button from '#material-ui/core/Button';
import Table from '#material-ui/core/Table';
import TableBody from '#material-ui/core/TableBody';
import TableCell from '#material-ui/core/TableCell';
import TableContainer from '#material-ui/core/TableContainer';
import TableHead from '#material-ui/core/TableHead';
import TableRow from '#material-ui/core/TableRow';
import { withStyles, makeStyles } from '#material-ui/core/styles';
const CustomCell = withStyles((theme) => ({
head: {
backgroundColor: '#3f51b5',
color: theme.palette.common.white,
},
body: {
fontSize: 14,
},
}))(TableCell);
const CustomRow = withStyles((theme) => ({
root: {
'&:nth-of-type(odd)': {
backgroundColor: theme.palette.action.hover,
},
},
}))(TableRow);
function createData(registration_nr, name, surname, e_mail) {
return {registration_nr, name, surname, e_mail};
}
const rows = [
createData('IE0001', 'Johnson', 'John', 'interesting_mail#domain.com'),
createData('IR0001', 'Dan', 'Thomas', 'interesting_mail#domain.com'),
createData('IA0011', 'Thompson', 'Bob', 'interesting_mail#domain.com'),
createData('MI0021', 'Martinez', 'Nancy', 'interesting_mail#domain.com'),
createData('MA0021', 'Gomez', 'Freddy', 'interesting_mail#domain.com'),
createData('ME0201', 'Charles', 'Diane', 'interesting_mail#domain.com'),
];
const useStyles = makeStyles({
table: {
minWidth: 300,
},
});
export default function BookView() {
const paperStyle = { padding: '0px 0px', width: 'auto', margin: '4px auto', textAlign: 'top-center', background: 'transparent', display: 'flex' }
const btnStyle = { width: '12vw', background: '#3f51b5', color: '#FFFFFF', height: '2.4vw', marginLeft: '40px', marginRight: '40px'}
const boxStyle = { background:'#FFFFFF', textAlign:'center', padding:'2px 2px', marginTop:9, justifyContent:'center', height:500 }
const narrowBox = { background:'#FFFFFF', textAlign:'center', padding:'0px 10px', width:'15%', margin:0, height:'100%'};
const container = { display: 'flex', justifyContent: 'center', fontSize:'1.12vw' }
// let day = getCurrentDate('-');
// const datas = [{ startDate: '2018-11-01T09:45', endDate: '2018-11-01T11:00', title: 'Just for the sake of working' }];
const classes = useStyles();
return (
<Grid>
<AppBar position='static' style={{background: '#757de8', marginTop: 'auto'}}>
<Toolbar gutterBottom>
<Paper style={paperStyle} elevation={0}>
<Button href="/signin" style={btnStyle}>Sign in</Button>
<Typography variant='h6' style={container}>The Library of 'Murica</Typography>
<Button href="/register" style={btnStyle}>Register</Button>
<Button href="/" style={btnStyle}>Home</Button>
</Paper>
</Toolbar>
</AppBar>
<Paper style={paperStyle} elevation={0}>
<TableContainer component={Grid}>
<Table className={classes.table} aria-label="customized table">
<TableHead>
<TableRow>
<CustomCell>Numar matricol</CustomCell>
<CustomCell align="right">Nume</CustomCell>
<CustomCell align="right">Prenume</CustomCell>
<CustomCell align="right">E-mail</CustomCell>
</TableRow>
</TableHead>
<TableBody>
{rows.map((row) => (
<CustomRow key={row.registration_nr}>
<CustomCell component="th" scope="row">{row.registration_nr}</CustomCell>
<CustomCell align="right">{row.name}</CustomCell>
<CustomCell align="right">{row.surname}</CustomCell>
<CustomCell align="right">{row.e_mail}</CustomCell>
</CustomRow>
))}
</TableBody>
</Table>
</TableContainer>
</Paper>
</Grid>
);
}
I tried with Paper, Box, but worthless. The table seems to be the only thing that is displayed on that page, no matter what I try.
Your code seems to be working fine on codesandbox, unless I'm missing something...

How do you change a style of a child when hovering over a parent using MUI styles?

I'm using MUI in react. Let's say I have this component with these styles:
const useStyles = makeStyles(theme => ({
outerDiv: {
backgroundColor: theme.palette.grey[200],
padding: theme.spacing(4),
'&:hover': {
cursor: 'pointer',
backgroundColor: theme.palette.grey[100]
}
},
addIcon: (props: { dragActive: boolean }) => ({
height: 50,
width: 50,
color: theme.palette.grey[400],
marginBottom: theme.spacing(2)
})
}));
function App() {
const classes = useStyles();
return (
<Grid container>
<Grid item className={classes.outerDiv}>
<AddIcon className={classes.addIcon} />
</Grid>
</Grid>
);
}
I want to change the style of addIcon when hovering over outerDiv using the styles above.
Here's my example.
Below is an example of the correct syntax for v4 ("& $addIcon" nested within &:hover). Further down are some v5 examples.
import * as React from "react";
import { render } from "react-dom";
import { Grid, makeStyles } from "#material-ui/core";
import AddIcon from "#material-ui/icons/Add";
const useStyles = makeStyles(theme => ({
outerDiv: {
backgroundColor: theme.palette.grey[200],
padding: theme.spacing(4),
'&:hover': {
cursor: 'pointer',
backgroundColor: theme.palette.grey[100],
"& $addIcon": {
color: "purple"
}
}
},
addIcon: (props: { dragActive: boolean }) => ({
height: 50,
width: 50,
color: theme.palette.grey[400],
marginBottom: theme.spacing(2)
})
}));
function App() {
const classes = useStyles();
return (
<Grid container>
<Grid item className={classes.outerDiv}>
<AddIcon className={classes.addIcon} />
</Grid>
</Grid>
);
}
const rootElement = document.getElementById("root");
render(<App />, rootElement);
Related documentation and answers:
https://cssinjs.org/jss-plugin-nested?v=v10.0.0#use-rulename-to-reference-a-local-rule-within-the-same-style-sheet
how to use css in JS for nested hover styles, Material UI
Material UI: affect children based on class
Advanced styling in material-ui
For those who have started using Material-UI v5, the example below implements the same styles but leveraging the new sx prop.
import Grid from "#mui/material/Grid";
import { useTheme } from "#mui/material/styles";
import AddIcon from "#mui/icons-material/Add";
export default function App() {
const theme = useTheme();
return (
<Grid container>
<Grid
item
sx={{
p: 4,
backgroundColor: theme.palette.grey[200],
"&:hover": {
backgroundColor: theme.palette.grey[100],
cursor: "pointer",
"& .addIcon": {
color: "purple"
}
}
}}
>
<AddIcon
className="addIcon"
sx={{
height: "50px",
width: "50px",
color: theme.palette.grey[400],
mb: 2
}}
/>
</Grid>
</Grid>
);
}
Here's another v5 example, but using Emotion's styled function rather than Material-UI's sx prop:
import Grid from "#mui/material/Grid";
import { createTheme, ThemeProvider } from "#mui/material/styles";
import AddIcon from "#mui/icons-material/Add";
import styled from "#emotion/styled/macro";
const StyledAddIcon = styled(AddIcon)(({ theme }) => ({
height: "50px",
width: "50px",
color: theme.palette.grey[400],
marginBottom: theme.spacing(2)
}));
const StyledGrid = styled(Grid)(({ theme }) => ({
padding: theme.spacing(4),
backgroundColor: theme.palette.grey[200],
"&:hover": {
backgroundColor: theme.palette.grey[100],
cursor: "pointer",
[`${StyledAddIcon}`]: {
color: "purple"
}
}
}));
const theme = createTheme();
export default function App() {
return (
<ThemeProvider theme={theme}>
<Grid container>
<StyledGrid item>
<StyledAddIcon />
</StyledGrid>
</Grid>
</ThemeProvider>
);
}
And one more v5 example using Emotion's css prop:
/** #jsxImportSource #emotion/react */
import Grid from "#mui/material/Grid";
import { createTheme, ThemeProvider } from "#mui/material/styles";
import AddIcon from "#mui/icons-material/Add";
const theme = createTheme();
export default function App() {
return (
<ThemeProvider theme={theme}>
<Grid container>
<Grid
item
css={(theme) => ({
padding: theme.spacing(4),
backgroundColor: theme.palette.grey[200],
"&:hover": {
backgroundColor: theme.palette.grey[100],
cursor: "pointer",
"& .addIcon": {
color: "purple"
}
}
})}
>
<AddIcon
className="addIcon"
css={(theme) => ({
height: "50px",
width: "50px",
color: theme.palette.grey[400],
marginBottom: theme.spacing(2)
})}
/>
</Grid>
</Grid>
</ThemeProvider>
);
}
This denotes the current selector which is the parent component:
'&': { /* styles */ }
This means the parent component in hover state:
'&:hover': { /* styles */ }
This means the child component inside the parent that is in hover state:
'&:hover .child': { /* styles */ }
You can also omit the ampersand & if you're using a pseudo-class:
':hover .child': { /* styles */ }
Complete code using sx prop (The same style object can also be used in styled()):
<Box
sx={{
width: 300,
height: 300,
backgroundColor: "darkblue",
":hover .child": {
backgroundColor: "orange"
}
}}
>
<Box className="child" sx={{ width: 200, height: 200 }} />
</Box>
Possibly an obvious point, but just to add to the answer above: if you are referencing a separate className, don't forget that you also need to create it in the makeStyles hook or else it won't work. For instance:
const useStyles = makeStyles({
parent: {
color: "red",
"&:hover": {
"& $child": {
color: "blue" // will only apply if the class below is declared (can be declared empty)
}
}
},
// child: {} // THIS must be created / uncommented in order for the code above to work; assigning the className to the component alone won't work.
})
const Example = () => {
const classes = useStyles()
return (
<Box className={classes.parent}>
<Box className={classes.child}>
I am red unless you create the child class in the hook
</Box>
</Box>
)
}
If you were using makeStyles in MUI v4 and have migrated to MUI v5 then you are likely now importing makeStyles from tss-react. If that is the case, then you achieve the same by the following:
import { makeStyles } from 'tss-react';
const useStyles = makeStyles((theme, props, classes) => ({
outerDiv: {
backgroundColor: theme.palette.grey[200],
padding: theme.spacing(4),
'&:hover': {
cursor: 'pointer',
backgroundColor: theme.palette.grey[100],
[`& .${classes.addIcon}`]: {
color: "purple"
}
}
},
addIcon: (props: { dragActive: boolean }) => ({
height: 50,
width: 50,
color: theme.palette.grey[400],
marginBottom: theme.spacing(2)
})
}));
The third argument passed to the makeStyles callback is the classes object.

Trying to change the colour of SVG Image Icon upon selection Material UI

I've created a vertical tab where when clicked the text changes color. I'm trying to change the icon color as well with a separate color. Can anyone help, please
I've tried naming it on the tab style sheet and individually but no result. The code below will show my attempt where I tried creating a class, but an error of Cannot read property 'icon' of undefined appears. If the solution to color the icon and separately can be done with classes then I guess we just have to resolve this error. If not please ignore the error and if there are any other solutions please suggest. thank you
imported files:
import React from "react";
import PropTypes from "prop-types";
import ReactDOM from "react-dom";
import Tabs from "#material-ui/core/Tabs";
import Tab from "#material-ui/core/Tab";
import Typography from "#material-ui/core/Typography";
import { withStyles } from "#material-ui/core/styles";
import Home from './Screens/Home'
import home from './home.svg';
Class content:
export default class ProfileTabs extends React.PureComponent {
state = { activeIndex: 0 };
handleChange = (_, activeIndex) => this.setState({ activeIndex });
render() {
const { classes } = this.props;
const { activeIndex } = this.state;
return (
<nav className= "side-drawer">
<div style={{letterSpacing: 0.7, left: 70, position: "absolute", marginTop: 40 ,}}>
<VerticalTabs className={classes.icon} variant="fullWidth" value={activeIndex} onChange={this.handleChange}>
<MyTab icon ={<img className= "home" src={home} alt="home" style={{height: 45, left:20, top:20, position: "absolute"}}/*Pay FlatIcon or replace by design *//>}
label={<p style={{ textTransform:"capitalize", position:"absolute", left:120, top:27.5,}}>
Home
</p>}
</VerticalTabs>
{activeIndex === 0 && <TabContainer><Home/></TabContainer>}
</div>
</nav>
);
}
}
Styles and other:
const VerticalTabs = withStyles(theme => ({
flexContainer: {
flexDirection: "column"
},
indicator: {
display: "none"
},
root:{
position:"absolute",
left:-70,
top:-40,
}
}))(Tabs);
const MyTab = withStyles(theme => ({
selected: {
color: "White",
borderRight: "none",
},
root: {
minWidth: 221,
margin:0,
paddingBottom:99
},
}))(Tab);
const styles = theme => ({
icon: {
color:"red"
}
});
function TabContainer(props) {
return (
<Typography component="div" style={{ padding: 9 * 3 }}>
{props.children}
</Typography>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<ProfileTabs />, rootElement);
ProfileTabs.propTypes = {
classes: PropTypes.object.isRequired
};
Current Error:
TypeError: Cannot read property 'icon' of undefined
28 | return (
29 | <nav className= "side-drawer">
30 | <div style={{letterSpacing: 0.7, left: 70, position: "absolute", marginTop: 40 ,}}>
> 31 | <VerticalTabs className={classes.icon} variant="fullWidth" value={activeIndex} onChange={this.handleChange}>
| ^ 32 | <MyTab icon ={<img className= "home" src={home} alt="home" style={{height: 45, left:20, top:20, position: "absolute"}}/*Pay FlatIcon or replace by design *//>}
33 | label={<p style={{ textTransform:"capitalize", position:"absolute", left:120, top:27.5,}}>
34 | Home
I hope this helps. I've refactored your code to separate all the styles, and then used the MUI classes objects to pass your specific style overrides down to their elements. The styles and classes may still need some tweaking - this is a best-bet given the supplied code:
import Icon from "#material-ui/core/Icon"; // <== only if you want to use the MUI 'home'
const styles = {
iconOnlyStyle: {
color: 'blue'
},
myDivStyles: {
letterSpacing: 0.7,
left: 70,
position: "absolute",
marginTop: 40
},
myIconStyles: {
color:"red", // <== this will change both label and icon
height: 45,
left:20,
top:20,
position: "absolute"
},
myLabelStyles: {
textTransform: "capitalize",
position: "absolute",
left: 120,
top: 27.5
},
sideDrawer: {
// ...styles
},
tabRoot: {
minWidth: 221,
margin:0,
paddingBottom:99
},
tabSelected: {
color: "White",
borderRight: "none"
},
tabsRoot: {
position:"absolute",
left:-70,
top:-40
},
tabsFlexContainer: {
flexDirection: "column"
},
tabsIndicator: {
display: "none"
}
}
class ProfileTabs extends React.PureComponent {
state = { activeIndex: 0 };
handleChange = (_, activeIndex) => this.setState({ activeIndex });
render() {
const { classes } = this.props;
return (
<nav className={classes.sideDrawer}>
<div className={classes.myDivStyles}>
<Tabs
variant="fullWidth"
value={activeIndex}
onChange={this.handleChange}
classes={{
root: classes.tabsRoot,
flexContainer: classes.tabsFlexContainer,
indicator: classes.tabsIndicator
}}
>
<Tab
label="Home"
icon={home}
icon={<Icon classes={{ root: classes.iconOnlyStyle }}>home</Icon>}
classes={{
root: classes.tabRoot,
labelIcon: classes.myIconStyles,
label: classes.myLabelStyles,
selected: classes.tabSelected
}}
/>
</Tabs>
</div>
</nav>
)
}
}
export default withStyles(styles)(ProfileTabs)
Let me know if this doesn't make sense or if you need any explanations. The classes prop in the MUI library took me a while to get the gist of when I first used it, but hopefully this helps clear-up how it can work.

multilevel nested list in material-ui next

I'd like to create multilevel nested list to display the menu on the left side - - very similar as on oficial site: https://material-ui-next.com/.
data source is in JSON where for each item there's also information about parent item and some other data - - this is the example:
{
"task_number": 1201092,
"task": "Monthly Cash Flow from Deliveries",
"task_parent_number": 1201090,
"task_parent": "Wholesale Cash Flow"
},
{
"task_number": 1201095,
"task": "Monthly Cash Flow from Fix Amounts",
"task_parent_number": 1201090,
"task_parent": "Wholesale Cash Flow"
},
{
"task_number": 1201100,
"task": "Wholesale Positions",
"task_parent_number": 1200007,
"task_parent": "Wholesale Contract Portfolio"
},
{
"task_number": 1201200,
"task": "All Wholesale Positions",
"task_parent_number": 1201100,
"task_parent": "Wholesale Positions"
}
I'am able to create an object with various nested elements - children - if they exist with followin function:
function getNestedChildren(arr, parent) {
var out = [];
for (var i in arr) {
if (arr[i].task_parent_number == parent) {
//console.log(i, arr[i].task_parent_number, arr[i].task_number);
var children = getNestedChildren(arr, arr[i].task_number);
if (children.length) {
arr[i].children = children;
}
out.push(arr[i]);
}
}
return out;
}
I've been following instructions for creating a nested list and importing it here: https://material-ui-next.com/demos/lists/#nested-list
..but I am not able to create a menu with nested elements as desired . . if someone can point me in the right direction would be great..
OK, i got this to work combining these 2 parts:
React example recursive render: https://gist.github.com/nkvenom/bf7b1adfe982cb47dee3
Material List guide here - https://medium.com/#ali.atwa/getting-started-with-material-ui-for-react-59c82d9ffd93
There is mui component.
I add recursion to the last example.
import type { SvgIconProps } from '#mui/material/SvgIcon';
import * as React from 'react';
import { styled } from '#mui/material/styles';
import Box from '#mui/material/Box';
import TreeView, { TreeViewProps } from '#mui/lab/TreeView';
import TreeItem, { TreeItemProps, treeItemClasses } from '#mui/lab/TreeItem';
import Typography from '#mui/material/Typography';
// import DeleteIcon from '#mui/icons-material/Delete';
// import Label from '#mui/icons-material/Label';
// import SupervisorAccountIcon from '#mui/icons-material/SupervisorAccount';
// import InfoIcon from '#mui/icons-material/Info';
// import ForumIcon from '#mui/icons-material/Forum';
// import LocalOfferIcon from '#mui/icons-material/LocalOffer';
import ArrowDropDownIcon from '#mui/icons-material/ArrowDropDown';
import ArrowRightIcon from '#mui/icons-material/ArrowRight';
declare module 'react' {
interface CSSProperties {
'--tree-view-color'?: string;
'--tree-view-bg-color'?: string;
}
}
type StyledTreeItemProps = TreeItemProps & {
bgColor?: string;
color?: string;
labelIcon?: React.ElementType<SvgIconProps>;
labelInfo?: React.ReactNode;
labelText: string;
};
type RecursiveStyledTreeItemProps = StyledTreeItemProps & {
childrenProps?: RecursiveStyledTreeItemProps[]
}
const StyledTreeItemRoot = styled(TreeItem)(({ theme }) => ({
color: theme.palette.text.secondary,
[`& .${treeItemClasses.content}`]: {
color: theme.palette.text.secondary,
borderTopRightRadius: theme.spacing(2),
borderBottomRightRadius: theme.spacing(2),
paddingRight: theme.spacing(1),
fontWeight: theme.typography.fontWeightMedium,
'&.Mui-expanded': {
fontWeight: theme.typography.fontWeightRegular,
},
'&:hover': {
backgroundColor: theme.palette.action.hover,
},
'&.Mui-focused, &.Mui-selected, &.Mui-selected.Mui-focused': {
backgroundColor: `var(--tree-view-bg-color, ${theme.palette.action.selected})`,
color: 'var(--tree-view-color)',
},
[`& .${treeItemClasses.label}`]: {
fontWeight: 'inherit',
color: 'inherit',
},
},
[`& .${treeItemClasses.group}`]: {
marginLeft: 0,
[`& .${treeItemClasses.content}`]: {
paddingLeft: theme.spacing(2),
},
},
}));
const RecursiveStyledTreeItem = ({ childrenProps, ...p }: RecursiveStyledTreeItemProps) => childrenProps
? <RecursiveStyledTreeItem {...p}>
{
childrenProps.map(cP => <RecursiveStyledTreeItem
key={cP.nodeId}
{...cP}
/>)
}
</RecursiveStyledTreeItem>
: < StyledTreeItem {...p} />
function StyledTreeItem(props: StyledTreeItemProps) {
const {
bgColor,
color,
labelIcon: LabelIcon = null,
labelInfo = null,
labelText,
...other
} = props;
return (
<StyledTreeItemRoot
label={
<Box sx={{ display: 'flex', alignItems: 'center', p: 0.5, pr: 0 }}>
{
LabelIcon && <Box component={LabelIcon} color="inherit" sx={{ mr: 1 }} />
}
<Typography variant="body2" sx={{ fontWeight: 'inherit', flexGrow: 1 }}>
{labelText}
</Typography>
{
labelInfo && <Typography variant="caption" color="inherit">
{labelInfo}
</Typography>
}
</Box>
}
style={{
'--tree-view-color': color,
'--tree-view-bg-color': bgColor,
}}
{...other}
/>
);
}
export default function CustomTreeView({ treeViewProps, items }: {
treeViewProps: Partial<TreeViewProps>
items: RecursiveStyledTreeItemProps[]
}) {
return (
<TreeView
// defaultExpanded={['3']}
defaultCollapseIcon={<ArrowDropDownIcon />}
defaultExpandIcon={<ArrowRightIcon />}
defaultEndIcon={<div style={{ width: 24 }} />}
sx={{ height: 264, flexGrow: 1, maxWidth: 400, overflowY: 'auto' }}
{...treeViewProps}
>
{items.map(item => <RecursiveStyledTreeItem
key={item.nodeId}
{...item}
/>)}
</TreeView>
);
}
There is mui-tree-select

Resources