React.js and Material ui - handleclick function not working - reactjs

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.

Related

Use child component props value in event handler in react

Here is my code
import React from 'react';
import './style.scss';
const CalcButton = (props) => {
return (
<button id="calcBtn" style={props.style} onClick={props.onClick}>
{props.btnText}
</button>
);
};
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
currentNum: '0',
log: ' ',
};
this.handleClick = this.handleClick.bind(this);
}
handleClick(e) {
console.log('a', e.target.value);
this.setState((state) => ({
currentNum: state.currentNum + e.target.getAttribute(this.props.btnText),
}));
}
render() {
return (
<div id="container">
<div id="display">
<h3 id="log">{this.state.log}</h3>
<h3 id="currentNum">{this.state.currentNum}</h3>
</div>
<CalcButton
style={{
width: 150,
backgroundColor: 'rgb(173, 0, 0)',
}}
btnText="AC"
onClick={() => {
return this.setState({
currentNum: '',
log: '',
});
}}
/>
<CalcButton
btnText="/"
style={{
backgroundColor: 'gray',
}}
/>
<CalcButton
btnText="X"
style={{
backgroundColor: 'gray',
}}
/>
<CalcButton btnText="7" onClick={this.handleClick}/>
<CalcButton btnText="8" />
<CalcButton btnText="9" />
<CalcButton
btnText="-"
style={{
backgroundColor: 'gray',
}}
/>
<CalcButton btnText="4" />
<CalcButton btnText="5" />
<CalcButton btnText="6" />
<CalcButton
btnText="+"
style={{
backgroundColor: 'gray',
}}
/>
<CalcButton btnText="1" />
<CalcButton btnText="2" />
<CalcButton btnText="3" />
<CalcButton
btnText="="
style={{
float: 'right',
height: 150,
backgroundColor: 'rgb(34, 86, 134)',
}}
/>
<CalcButton
btnText="0"
style={{
width: 150,
}}
/>
<CalcButton btnText="." />
</div>
);
}
}
export default App;
As you can see I am trying to build a calculator, but the problem I am facing is that I want to use btnText prop value of CalcButton in handleClick event handler, but I am unable to figure out how to access it in the said event handler. I know it is a very basic problem but trust me I have searched and unable to find any reference regarding my problem, kindly help.
add data-btntext={props.btnText} to the button element in the CalcButton function. Then access it with e.target.dataset.btntext. –
Emil Karlsson
The above mentioned comment solves the problem,although I do agree with #maxagno3's answer, however, since I am still learning, I really wanted to learn that concept.
Thanks for all the answers.
I do not think you need to create CalcButton component. Since your button text is going to be the same, you can do it the following way -
Create a new state which stores your button texts in an array.
Map over that state and create buttons for the calculator.
When using the handle click function, pass in the button text value.
In that function check if the button text is AC then execute another function or set the state value. Better if you create a separate function for the AC button and call it in the if-else condition you'd be doing inside the handleClick function.
If you really need to create the component then you'd need to add the handle click function to the multiple components you are reusing and pass in the value of button text into it as an argument.
I hope this helps.
in child component :
const CalcButton = (props) => {
const handleClick = () => {
if (props.onClick) {
props.onClick(props.btnText);
}
};
return (
<button id="calcBtn" style={props.style} onClick={handleClick}>
{props.btnText}
</button>
);
};
In Parent component :
class App extends React.Component { ...
handleClick(val) {
console.log('a', val);
this.setState((state) => ({
currentNum: state.currentNum + val,
}));
}
......
<CalcButton btnText="7" onClick={this.handleClick}/>

React Hooks useRef initialization issue, useRef only works on subsequent calls

I am implementing useRef into my project. I have a form that has clickable sections. Once clicked it opens the form. I'm using Reactstrap Collapse to show/hide the form. I need to be able to open the form and show the section that needs to be filled out, however the scrollIntoView once I click the section doesn't work until I open and close the form again. I'm stumped. I console.log(formRef), the ref returns as expected of the component that I want to be scrolled to the top of viewport on subsequent calls. My guess would be that the formRef is being initialized as null to begin with so initial calls to the ref do not work. However, once it knows the ref the subsequent calls work. I'm not sure how to go about this..
If I need to provide an example that is stripped please let me know. I am expecting this to be just an initialization issue.
Form
import React, { useRef, useContext, useEffect } from "react";
import {
FormQuestionsContext,
FormAnswersContext,
ExpandedSectionContext,
} from "../../Store";
import SectionHeader from "../SectionHeader";
import ImageUploader from "../CommentsSection";
import Ratings from "../Ratings";
import { Collapse, Button, CardBody, Card } from "reactstrap";
import FontAwesome from "react-fontawesome";
import styles from "./bedthreeform.module.css";
function BedThreeForm({ Name }) {
const formRef = useRef(null); //useRef Initialization
const [expandedSection, setExpandedSection] = useContext(
ExpandedSectionContext
);
const [formQuestions, setFormQuestions] = useContext(FormQuestionsContext);
const [formAnswers, setFormAnswers] = useContext(FormAnswersContext);
const array = formQuestions.bedthree;
const onChange = (e, name) => {
const { value } = e.target;
setFormAnswers((state) => ({
...state,
[Name]: { ...state[Name], [name]: value },
}));
};
//! The function I use when I want to tell useRef to scrollIntoView
const handleOpen = () => {
expandedSection === Name
? setExpandedSection("")
: setExpandedSection(Name);
formRef.current.scrollIntoView();
};
const answeredQuestions = formAnswers.bedthree
? Object.keys(formAnswers.bedthree)
: null;
console.log(formRef);
return (
<div>
<Button
className={styles["CollapseBtn"]}
onClick={handleOpen} //Calling the function here
style={
answeredQuestions &&
answeredQuestions.length === formQuestions.bedthree.length
? {
color: "white",
":focus": {
backgroundColor: "#02BD43",
},
backgroundColor: "#02BD43",
marginBottom: "1rem",
width: "100%",
}
: answeredQuestions &&
answeredQuestions.length !== formQuestions.bedthree.length
? {
color: "white",
":focus": {
backgroundColor: "#bd0202",
},
backgroundColor: "#bd0202",
marginBottom: "1rem",
width: "100%",
}
: {
":focus": {
backgroundColor: "#fafafa",
},
marginBottom: "1rem",
width: "100%",
}
}
>
<p>BEDROOM #3 INSPECTION</p>
<FontAwesome
className="super-crazy-colors"
name="angle-up"
rotate={expandedSection === Name ? null : 180}
size="lg"
style={{
marginTop: "5px",
textShadow: "0 1px 0 rgba(0, 0, 0, 0.1)",
}}
/>
</Button>
<Collapse
className={styles["Collapse"]}
isOpen={expandedSection === Name}
>
<Card>
<CardBody>
{array ? (
<div>
<SectionHeader title="Bedroom #3 Inspection" name={Name} />
<div
ref={formRef}
className={styles["BedroomThreeFormWrapper"]}
id="bedroom-three-form"
>
{array.map((question, index) => {
const selected =
formAnswers[Name] && formAnswers[Name][question]
? formAnswers[Name][question]
: "";
return (
<div className={styles["CheckboxWrapper"]} key={index}>
<h5>{question}</h5>
<Ratings
section={Name}
question={question}
onChange={onChange}
selected={selected}
/>
</div>
);
})}
</div>
{!answeredQuestions ? (
""
) : (
<Button
onClick={(e) => e.preventDefault()}
style={
!answeredQuestions ||
(answeredQuestions &&
answeredQuestions.length !==
formQuestions.bedthree.length)
? {
backgroundColor: "#bd0202",
color: "white",
pointerEvents: "none",
}
: {
backgroundColor: "#02BD43",
color: "white",
pointerEvents: "none",
}
}
>
{!answeredQuestions ||
(answeredQuestions &&
answeredQuestions.length !==
formQuestions.bedthree.length)
? "Incomplete"
: "Complete"}
</Button>
)}
<br />
<ImageUploader name="bedthree" title={"Bedroom #3"} />
</div>
) : (
<div></div>
)}
</CardBody>
</Card>
</Collapse>
</div>
);
}
export default BedThreeForm;
CodeSandbox Stripped Form Doesn't work as expected, however that is the stripped code.
Update I'm open to suggestions to bypass this, or an alternative way to do this. I'm not sure why it only does it on subsequent calls.
Look at these lines:
<CardBody>
{array ? (
...
<div
ref={formRef}
...
This (virtual) dom will be evaluated only if array is defined. In case you would like to have your formRef always to point to the dom, then You'll have to strip it out from your condition.
I've figured out the issue, the issue is calling it when the content in the collapse hasn't been loaded yet, Reactstrap has an attribute onEntered which basically when set, will run the function as soon as the collapse has fully opened. The example that I found is here. Also, by setting the attribute innerRef on a Reactstrap component I can manipulate it just like I could a regular component using ref.

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

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.

MUI - Remove overlay in Drawer?

I am trying to use the Drawer component and I have noticed that when the drawer is fed the prop open={true}, there is a default dimmed overlay on the underlying page / div.
Is there a best-practice Material-approved way to remove the dimming? I have had some partial success with setting the prop variant={"persistent"}. This stops the dimming, but it also forces me to add a close button just to close the drawer.
What I am looking for is the drawer to be closable when clicking outside its boundary, and while it is open I would like to have the dimming go away (without resorting to a button to close the drawer).
I have looked at the docs and tried passing the prop
variant={"persistent"}
Which gets rid of the overlay, but now when I click outside the drawer it doesn't auto-close.
<Drawer
open={open}
anchor="top"
onClose={toggleDrawer}
variant={"persistent"}
modal={true}
>
I would like to have the dimming go away (without resorting to a button).
Are there any options that are Material - approved? I can try CSS hacks but I don't want to break Material's CSS or have glitchy flashes of overlay.
You can add BackdropProps={{ invisible: true }}.
Working Example:
import React from "react";
import { makeStyles } from "#material-ui/core/styles";
import Drawer from "#material-ui/core/Drawer";
import Button from "#material-ui/core/Button";
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 InboxIcon from "#material-ui/icons/MoveToInbox";
import MailIcon from "#material-ui/icons/Mail";
const useStyles = makeStyles({
list: {
width: 250
}
});
export default function TemporaryDrawer() {
const classes = useStyles();
const [state, setState] = React.useState({
top: false,
left: false,
bottom: false,
right: false
});
const toggleDrawer = (side, open) => event => {
if (
event.type === "keydown" &&
(event.key === "Tab" || event.key === "Shift")
) {
return;
}
setState({ ...state, [side]: open });
};
const sideList = side => (
<div
className={classes.list}
role="presentation"
onClick={toggleDrawer(side, false)}
onKeyDown={toggleDrawer(side, false)}
>
<List>
{["Inbox", "Starred", "Send email", "Drafts"].map((text, index) => (
<ListItem button key={text}>
<ListItemIcon>
{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}
</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
</div>
);
return (
<div>
<Button onClick={toggleDrawer("left", true)}>Open Left</Button>
<Drawer
BackdropProps={{ invisible: true }}
open={state.left}
onClose={toggleDrawer("left", false)}
>
{sideList("left")}
</Drawer>
</div>
);
}
Relevant documentation links:
https://material-ui.com/api/backdrop/#props
Documents the invisible prop
https://material-ui.com/api/modal/#props
Documents the BackdropProps prop of Modal
https://material-ui.com/api/drawer/#import
The props of the Modal component are available when variant="temporary" is set.
ModalProps={{
hideBackdrop: true,
}}
That solved the problem for me :)
It removes the whole backdrop thing and you are free to press whatever you want or the same button you opened it to close it :)
In later versions of Material UI you can just use the following:
<Drawer
hideBackdrop
>
For MUI 5+ Drawer (temporary):
<Drawer
PaperProps={{
style: {
//style props for your drawer menu here
},
}}
ModalProps={{
slots: { backdrop: "div" //override the backdrop component },
slotProps: {
root: { //override the fixed position + the size of backdrop
style: {
position: "absolute",
top: "unset",
bottom: "unset",
left: "unset",
right: "unset",
},
},
},
}}
>

MUI - Styling text inside ListItemText

I'm trying to apply style to the text inside a ListItemText (MUI):
const text = {
color: 'red'
}
<ListItem button><ListItemText style={text} primary="MyText" /></ListItem>
But the rendered Typograhy element inside is not styled at all ("MyText" is not red).
Looking at the generated code, it seems that the default CSS rules for Typography > subheading is overriding my CSS.
Thanks for your help
Edit: In the first version of the question, there was a misake ("className" instead of "style" prop on ListItemText, sorry about that).
I beleive the only way to achieve this right now is to use the 'disableTypography' prop of the ListItemText element.
<ListItemText
disableTypography
primary={<Typography variant="body2" style={{ color: '#FFFFFF' }}>MyTitle</Typography>}
/>
This lets you embed your own text element with whatever styling you want on it.
this is good one, you can implement without disabling typography
<ListItemText
classes={{ primary: this.props.classes.whiteColor }}
primary="MyTitle"
/>
Per the documentation, the <ListItemText /> component exposes the prop primaryTypographyProps, which we can use to accomplish what you're attempting in your question:
const text = {
color: "red"
};
<ListItem button>
<ListItemText primaryTypographyProps={{ style: text }} primary="MyText" />
</ListItem>
Hope that helps!
<ListItem >
<Avatar style={{ backgroundColor: "#ff6f00" }}>
<LabIcon />
</Avatar>
<ListItemText
primary={<Typography variant="h6" style={{ color: '#ff6f00' }}>Lab</Typography>}
secondary="Experiments" />
</ListItem>
Turns out there's an even better way to do this as such:
const styles = {
selected: {
color: 'green',
background: 'red',
},
}
const DashboardNagivationItems = props => (
<ListItemText
classes={{ text: props.classes.selected }}
primary="Scheduled Calls"
/>
)
export default withStyles(styles)(DashboardNagivationItems)
You can read more about how this is done here: https://material-ui-next.com/customization/overrides/#overriding-with-classes
Method 1
const textColor = {
color: "red"
};
<ListItemText primaryTypographyProps={{ style: textColor }} primary="BlodyLogic" />
For the Secondary Text
const secondaryColor = {
color: 'green'
}
<ListItemText secondaryTypographyProps={{ style: secondaryColor }}
secondary="If you say that you" />
Method 2
<ListItemText
primary={
<Typography variant="caption" display="block" gutterBottom>
caption text
</Typography>
}
/>
Custom design:
const useStyles = makeStyles({
text: {
color: 'red',
fontSize: 49
},
});
/.....// all make classes
<ListItemText
primary={
<Typography className={classes.text}>
caption text
</Typography>
}
/>
Offical Docs
MUI v5 update
You can leverage system properties in Typography to directly add styling props in the primary and secondary components inside the ListItemText:
<ListItemText
primary="Photos"
secondary="Jan 9, 2014"
primaryTypographyProps={{
fontSize: 22,
color: 'primary.main',
}}
secondaryTypographyProps={{
fontSize: 15,
color: 'green',
}}
/>
You can also use styled if you want to reuse ListItemText in multiple places:
import MuiListItemText from '#mui/material/ListItemText';
import { styled } from '#mui/material/styles';
const ListItemText = styled(MuiListItemText)({
'& .MuiListItemText-primary': {
color: 'orange',
},
'& .MuiListItemText-secondary': {
color: 'gray',
},
});
Live Demo
If you are using material-ui 3.x, this is how it is done:
import { withStyles } from '#material-ui/core/styles';
const styles = {
listItemText: {
color: 'white',
},
}
class YourComponent extends Component {
...
render() {
const { classes } = this.props; // this is magically provided by withStyles HOC.
return (
<ListItem button>
<ListItemIcon>
<DashboardIcon />
</ListItemIcon>
<ListItemText classes={{ primary: classes.listItemText }} primary="My Bookings" />
</ListItem>
);
...
}
export default withStyles(styles)(YourComponent);
set all your text related styles on primary property. Sad that it's hidden so deep in the documentation. https://material-ui.com/api/list-item/
Material v1.0
I would add something to #SundaramRavi in regards to:
the way he is using style element which is not great as for Material v1.0 (read the very important difference between v0.16+ and v1.0.
the way files can be structured for better separation of concern.
Whatever.styles.js
const styles = theme => ({
white: {
color: theme.palette.common.white
}
});
exports.styles = styles;
Whatever.js
const React = require('react');
const PropTypes = require('prop-types');
const {styles} = require('./Whatever.styles');
class Whatever extends React.Component {
constructor(props) {
super(props);
}
render() {
const {classes} = this.props;
return (
<div>
<ListItemText
disableTypography
primary={
<Typography type="body2" style={{body2: classes.white}}>
MyTitle
</Typography>
}
/>
</div>
);
}
}
Whatever.propTypes = {
classes: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired
};
exports.Whatever = withStyles(styles, {withTheme: true})(Whatever);
you can easily style text by using & span
const useStyles = makeStyles(theme => ({
listItem: {
"& span": {
color: "red"
}
}
}));
..
..
..
<ListItem button>
<ListItemIcon>
<SendIcon />
</ListItemIcon>
<ListItemText className={classes.listItem} primary="Sent mail"/>
</ListItem>
If your are using "#material-ui/core": "^4.11.4" (4.X.X or newer version) then it's simple:
#1st step: Define your styles like this:
const useStyles = makeStyles((theme: Theme) =>
createStyles({
// Other Styling if you wish to use it
root: {
width: '100%',
maxWidth: '36ch',
backgroundColor: theme.palette.background.paper
},
// Other Styling if you wish to use it
inline: {
display: 'inline'
},
// Styling that you asked for
textColor: {
color: 'red'
}
}),
);
#2nd step: Define a constant to use the specific Styles like this:
const AlignItemsList = (props: any) => {
const classes = useStyles(); // Like this one
......
}
#3rd step: In your ListItemText component do like this:
const AlignItemsList = (props: any) => {
const classes = useStyles();
......
<ListItemText
primary="Your Text Goes Here"
classes={{ primary: classes.textColor }} // Like this one
...
/>
};
#4th & final step: Just export your component normally without any other stuff, like this:
export default AlignItemsList;
MUI v5
I recommend to you use global styles for all components. For example you can override styles when you use createTheme.
Here is small example:
export default createTheme({
components: {
MuiListItemText: {
styleOverrides: {
root: {
marginTop: 0,
marginBottom: 0,
},
primary: {
fontSize: '1rem',
},
secondary: {
fontSize: '0.8rem',
},
},
},
},
});
More details on official page

Resources