React.js - Updating icon styles on component mount/unmount - reactjs

I have an app that I'm building where it has a navigation bar down the left hand side with icons that open a modal when clicked on:
I have successfully set this up so that a different modal is loaded whenever the user clicks on one of the icons in the navigation bar and I'm now try to update state to highlight which icon's modal is loaded like the below:
To achieve this I am getting to grips with parent/child relationships and manipulating state.
The parent element renders a <div> that captures state:
<div>
<ActiveDashboardIcon data={this.state.data} />
</div>
I then have a child function that populates the parent <div>:
const ActiveDashboardIcon = ({ data }) =>
<div>
{data ? data :
<ListItem button id="dashboardIcon" style={{ backgroundColor: 'black' }}>
<ListItemIcon>
<DashboardIcon style={{ color: 'white' }} />
</ListItemIcon>
</ListItem>
}
</div>;
Lastly I am updating on componentDidMount and componentWillUnmount so that it updates the button styles (icon color and background color):
componentDidMount() {
this.setState({
data:
<ListItem button id="dashboardIcon" style={{ backgroundColor: 'white' }}>
<ListItemIcon>
<DashboardIcon style={{ color: 'black' }} />
</ListItemIcon>
</ListItem>
})
}
componentWillUnmount() {
this.setState({
data:
<ListItem button id="dashboardIcon" style={{ backgroundColor: 'black' }}>
<ListItemIcon>
<DashboardIcon style={{ color: 'white' }} />
</ListItemIcon>
</ListItem>
})
}
This works mostly as intended but I am struggling to accomplish a few things:
This current approach works for one icon/modal combination but is difficult to scale as I am unable to define different componentDidMount functions for each icon/modal combination
My current approach doesn't currently 'unselect' the icon and revert it to it's original style on componentWillUnmount
I can't work out how to include an onClick property for the icon:
onClick={this.openModal("dashboard")}. When I try to include it in the child element the browser cannot read property 'openModal' as it is part of the parent.
I am unsure of the best approach to achieve what I'm after.

Related

text decoration not working for mui lit items

I am using mui lists to create a sidebar.
<List>
<StyledLink to="/">
<ListItem disablePadding>
<ListItemButton>
<ListItemIcon>
<GridView />
</ListItemIcon>
<ListItemText primary="Dashboard" />
</ListItemButton>
</ListItem>
</StyledLink>
</List>
I tried creating a custom Link like below,
import { Link } from "react-router-dom";
const StyledLink = ({ to, style = {}, children }) => (
<Link to={to} style={{ ...style, textDecoration: "none" }}>
{children}
</Link>
);
export default StyledLink;
I also tried this solution.
But still the blue color doesn't go away and it doesn't inherit mui list item text formatting.
text-decoration: "none" is only removing the underline style. You can get rid of the blue color by setting color attribute.
style={{ ...style, textDecoration: "none", color: "inherit" }}

ReactJS - getAttribute return null

I have a problem with getAttribute() in my React code.
const handleChange = (event) => {
let selectM = event.target.getAttribute("data-minister_name");
console.log(selectM);
};
<List dense sx={{ width: '100%', maxWidth: 360, bgcolor: 'background.paper' }}>
{listMiniters.map((c) => (
<ListItem
key={c.label}
disablePadding
onClick={handleChange}
data-minister_name="{c.label}"
>
<ListItemButton>
<ListItemAvatar>
<Avatar
alt={`Avatar n°${c.label + 1}`}
src={`https://flagcdn.com/w20/${c.code.toLowerCase()}.png`}
/>
</ListItemAvatar>
<ListItemText id={c.label} primary={`Line item ${c.label + 1}`} />
</ListItemButton>
</ListItem>
))}
</List>
It return to my
null
I don't understand where come from the problem...? Could you help my please ?
I'm using MUI.
The event.target will be the element that was actually clicked which isn't, necessarily, the same as the element to which the event handler was bound.
The ListItem has a number of descendant elements which will intercept the click and be the event.target.
Use event.currentTarget instead.
Please try without '_' in your attribute name.
Altrernatively try with e.target.attributes.getNamedItem("aatribute-name").value;

ForwardRef error in Chakra-ui when wrapping IconButton component

I created a reusable component that wraps the IconButton and pass as one of the props icons from react-icons.
I get the following error:
Warning: Function components cannot be given refs. Attempts to access
this ref will fail. Did you mean to use React.forwardRef()?`
Here's my component wrapper:
export function SidebarMenu({ icon, isActive, handleMenuClick, name }) {
return (
<IconButton
className={isActive ? 'active' : ''}
aria-label={name + ' menu'}
isActive={isActive}
bg="transparent"
px="0"
as={icon}
_hover={{
cursor: 'pointer',
backgroundColor: 'transparent',
}}
_active={{
backgroundColor: 'transparent',
}}
onClick={() => handleMenuClick(name)}
/>
Here's how i use the wrapper component
<SidebarMenu
icon={VscCode}
name="folders"
handleMenuClick={handleMenuClick}
isActive={menuList.folders}
/>
But if i change the content IconButton to a instead use a Icon wrapped in a Button the error disappears.
export function SidebarMenu({ icon, isActive, handleMenuClick, name }) {
return (
<Button
className={isActive ? 'active' : ''}
aria-label={name + ' menu'}
isActive={isActive}
bg="transparent"
px="0"
// as={icon}
_hover={{
cursor: 'pointer',
backgroundColor: 'transparent',
}}
_active={{
backgroundColor: 'transparent',
}}
onClick={() => handleMenuClick(name)}
>
<Icon as={icon} w={7} h={7} />
</Button>
);
}
Edit:
For anyone who may stumble on the same problem.
There are couple of things i did wrong here.
I was using as instead of icon prop in IconButton
Second, i was passing a reference instead of the component
Instead of doing <IconButton icon={<VsColorMode />} />, i was doing this <IconButton icon={VsCodeMode} />.
According to the doc, IconButton has its own prop icon, so instead of using as, in this case, use icon with Icon
function SidebarMenu({ icon }) {
return (
<IconButton
bg="transparent"
px="0"
icon={<Icon as={icon} w={7} h={7} />}
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_hover={{
cursor: "pointer",
backgroundColor: "transparent"
}}
_active={{
backgroundColor: "transparent"
}}
/>
);
}
Codesandbox demo

React MUI Drawer overlaying page content

I'd like to have a Drawer that doesn't overlay the page content. So far I have the following code
<ThemeProvider theme={oclockTheme}>
<main>
<Drawer
variant='temporary'
open={showMenu}
hideBackdrop
>
<List>
<ListItem sx={{ justifyContent: 'center' }}>
<img src={oclockLogo} alt="O'Clock logo" />
</ListItem>
</List>
<Divider light />
<List >
<ListItem>
Menu item
</ListItem>
</List>
</Drawer>
<Button onClick={() => setShowMenu(true)} variant="contained" color="primary">Teste</Button>
</main>
</ThemeProvider >
And the problem is that the Drawer overlays my button
And I'd like to push my bottom to the right when the Drawer is opened and pull it back to the left when it's closed...
I've seen a solution in the library docs, but I'd like to know if anyone else can help me doing it with a cleaner/smaller code. I also know that we have the SwipeableDrawer, but I couldn't find a way to change its styles...
Use persistent drawer instead.

React.js and Material ui - handleclick function not working

I am trying to use react.js with material-ui. However, my event handlers do not seem to work.
For example, I tried playing around with the boilerplate material js file: (https://github.com/mui-org/material-ui/blob/master/docs/src/pages/demos/lists/NestedList.js )
The handleclick function does not work in my application (i.e. clicking on it does not collapse the list).
Th one major difference is that I used the code in a jsx file instead of js (however, changing the file to js does not solve the problem).
my code:
import React, {Component} from 'react'
import { List, ListItem, ListItemText, Collapse, Typography } from '#material-ui/core'
import { ExpandLess, ExpandMore } from '#material-ui/icons'
import { withStyles } from '#material-ui/core/styles';
const styles = theme => ({
root: {
width: '100%',
maxWidth: 360,
backgroundColor: theme.palette.secondary,
},
nested: {
paddingLeft: theme.spacing.unit * 4,
},
});
class NestedList extends Component {
constructor(props){
super(props);
this.state = { open: true };
};
handleClick = () => {
this.setState(state => ({ open: !this.state.open }));
};
render() {
const { classes } = this.props;
return (
<div className={classes.root}>
<List
component="nav"
>
<ListItem>
<ListItemText
disableTypography
primary={<Typography type="subheadling" style={{ color: 'white' }}>Favourite</Typography>}
/>
</ListItem>
<ListItem button onClick={this.handleClick}>
<ListItemText
disableTypography
primary={<Typography type="subheadling" style={{ color: 'white' }}>Playlists</Typography>}
/>
{this.state.open ? <ExpandLess style={{ color: 'white' }} /> : <ExpandMore style={{ color: 'white' }} />}
</ListItem>
<Collapse in={this.state.open} timeout="auto" unmountOnExit>
<List component="div" disablePadding>
<ListItem button className={classes.nested}>
<ListItemText
disableTypography
primary={<Typography type="subheadling" style={{ color: 'white' }}>{`p1`}</Typography>}
/>
</ListItem>
<ListItem button className={classes.nested}>
<ListItemText
disableTypography
primary={<Typography type="subheadling" style={{ color: 'white' }}>{`p2`}</Typography>}
/>
</ListItem>
</List>
</Collapse>
</List>
</div>
);
}
}
export default withStyles(styles)(NestedList);
Update
I suspect there is something wrong with the onClick Event since click is not listed as an event listener on the browser.
Here is the working example of your code:
https://codesandbox.io/s/k2nkj8705r
Neither of the following solutions might be what you are looking for, but here are a couple things you might want to watch out for:
Using handleEvent = () => {} is part of the babel-plugin-transform-class-properties. This gives you the implicit binding so you dont have to bind it youself. Otherwise, typically you will have to write differently like the following.
handleClick {
this.setState(state => ({ open: !this.state.open }));
};
Then whenever you need to to use it, you have two options. Option 1 via bind. You can do that in your constructor.
this.handleClick = this.handleClick.bind(this);
Or(example onClick in a div). Another approach is to create a new function for every instance, not very efficient.
<div onClick={() => this.handleClick()}>Click me</div>
Although not a problem, setState is like this.
handleClick = () => {
this.setState({ open: !this.state.open });
};
When calling setState, you just need to pass in an object.
You forgot to bind your handleClick on your constructor, add this line to the bottom of your constructor this.handleClick = this.handleClick.bind(this);.
I just notice that you use handleClick = () => { ... }, if the babel was configured properly the binding won't be needed anymore, but let's try it and let me know if it's working or not
Thanks to everyone for the help. I really appreciate the help from #Leogoesger and #CaseyC. Made me realize what's working in my code and where to debug for errors.
So the reason that the event handler didn't work is because I only rendered my Material-ui files on the server side and renderToString() stripped out all the event handlers.
I followed the instruction here: https://material-ui.com/guides/server-rendering/ and managed to get my App working.

Resources