Material UI button rendered with span that is not clickable - reactjs

I am building a site using material-UI and have run into a bit of a snag. It seems that the button is being rendered with a span element around the button's text. This makes it so the click event only fires when you click outside the span element.
As you can imagine it's not the greatest UI to have users click buttons that don't do anything. I am sure that I am not the first one to go through this, is there a way that I can propagate the event down to the child element programmatically? Below is my component snipit:
import withRoot from '../onepirate/modules/withRoot';
// --- Post bootstrap -----
import React from 'react';
import { useState, useStyles } from 'react';
import { makeStyles } from '#material-ui/core/styles';
import Grid from '#material-ui/core/Grid';
import Link from '#material-ui/core/Link';
import Typography from '../onepirate/modules/components/Typography';
import AppFooter from '../onepirate/modules/views/AppFooter';
import AppForm from '../onepirate/modules/views/AppForm';
import Button from '#material-ui/core/Button';
import MuiTextField from '#material-ui/core/TextField';
import List from '#material-ui/core/List';
import ListItem from '#material-ui/core/ListItem';
import ListItemText from '#material-ui/core/ListItemText';
import ListItemAvatar from '#material-ui/core/ListItemAvatar';
import Avatar from '#material-ui/core/Avatar';
import AddCircleIcon from '#material-ui/icons/AddCircle';
import WorkIcon from '#material-ui/icons/Work';
import BeachAccessIcon from '#material-ui/icons/BeachAccess';
import Divider from '#material-ui/core/Divider';
const Tickets = props => {
const useStyles = makeStyles(theme => ({
button: {
margin: theme.spacing(1),
backgroundColor: "#ff3366",
color: "#000000"
},
input: {
display: 'none',
},
ListItemText: {
marginLeft: 105,
}
}));
const classes = useStyles();
return (
<React.Fragment>
<AppForm>
<React.Fragment>
<Typography variant="h3" gutterBottom marked="center" align="center">
Order Tickets
</Typography>
<Typography variant="body2" align="center">
<Link href="/premium-themes/onepirate/sign-in/" underline="always">
Already have an account?
</Link>
</Typography>
</React.Fragment>
<Grid container spacing={2}>
<Grid item xs={12} sm={12}>
<List className={classes.root}>
<ListItem>
<Button
variant="contained"
className={classes.button}
onClick={props.quantity}
id="basic">
Add
</Button>
<ListItemText
primary="Basic"
secondary="$450"
className={classes.ListItemText}/>
{props.init.basic}
</ListItem>
<Divider variant="inset" component="li" />
<ListItem>
<Button
variant="contained"
className={classes.button}
onClick={props.quantity}
id="exec" >
Add
</Button>
<ListItemText
primary="Executive"
secondary="$550"
className={classes.ListItemText}/>
{props.init.exec}
</ListItem>
<Divider variant="inset" component="li" />
<ListItem>
<Button
variant="contained"
className={classes.button}
onClick={props.quantity}
id="vip">
Add
</Button>
<ListItemText
primary="VIP"
secondary="$750"
className={classes.ListItemText}/>
{props.init.vip}
</ListItem>
</List>
<Typography variant="h2">
{`Total: $ ${(props.init.basic * 450) + (props.init.exec * 550) + (props.init.vip * 750)}`}
</Typography>
<Button
className={classes.button}
size="large"
color="secondary"
fullWidth
onClick={props.price}
id=""
>
{'Proceed to Checkout'}
</Button>
</Grid>
</Grid>
</AppForm>
<AppFooter />
</React.Fragment>
);
}
export default withRoot(Tickets);
And the rendered HTML output of one of the buttons:
<button class="MuiButtonBase-root-307 MuiButton-root-290 makeStyles-button-87 MuiButton-contained-298"
tabindex="0"
type="button"
id="exec">
<span class="MuiButton-label-291">Add</span>
<span class="MuiTouchRipple-root-391"></span>
</button>

It seems that Material UI v4 accepts both e.target.value and e.currentTarget.value.
However, if you use e.target.value to deliver buttons' value, and click on the span node (nested inside of button), the value won't be taken from the button node that you assigned it to, but the span HTML element that does not have the value property and so the value is undefined.
To remedy the above, use e.currentTarget.value as it seems to pick up the value property from the button, even if you click on the span node.
Below sandbox should show you the difference in using e.target.value and e.currentTarget.value with material UI buttons.
https://codesandbox.io/s/elated-glitter-wqtt3?file=/src/App.js
Open up the console and click interchangeably on span and button elements and observe logs when you do that.
I have not tested the latest v5 from Material UI, it could be that they changed (improved?) this behaviour.

try wrapping span and button inside a div or other wrapper component and call onClick on that

Related

Form inside Material UI dialog call submit of the form outside dialog

I have a form inside a form, The <form/> inside form is rendered as Material UI dialog and rendered to DOM in a different portal.
/* SPDX-FileCopyrightText: 2021 #koistya */
/* SPDX-License-Identifier: MIT */
import * as React from "react";
import {
Button,
Dialog,
DialogContent,
Stack,
TextField,
Typography,
CssBaseline,
Container,
} from "#mui/material";
import { Combobox } from "./Combobox";
import { AppToolbar, Toolbar } from "./AppToolbar";
import { ThemeProvider } from "./ThemeProvider";
/**
* The top-level (root) React component.
*
* #see https://reactjs.org/
* #see https://mui.com/core/
*/
export function App(): JSX.Element {
const [open, setOpen] = React.useState(false);
return (
<ThemeProvider>
<CssBaseline />
<Container sx={{ my: 2 }}>
<Button
variant="contained"
sx={{ mb: 3 }}
onClick={() => setOpen(true)}
>
Open Dialog
</Button>
<form
onSubmit={(evt) => {
evt.preventDefault();
alert("Form outside dialog");
}}
>
<Typography variant="h6" component="h1" sx={{ mb: 2 }}>
Form outside dialog
</Typography>
<Stack spacing={2}>
{/* #region DIALOG */}
<Dialog open={open} onClose={() => setOpen(false)}>
<DialogContent>
<form
onSubmit={(evt) => {
evt.preventDefault();
alert("Form inside dialog");
}}
>
<Typography variant="h6" component="h1" sx={{ mb: 2 }}>
Form inside dialog
</Typography>
<Stack spacing={2}>
<TextField placeholder="name inside dialog" />
<TextField placeholder="email inside dialog" />
<Button type="submit" variant="contained">
Submit
</Button>
</Stack>
</form>
</DialogContent>
</Dialog>
{/* #endregion DIALOG */}
<TextField placeholder="name outside dialog" />
<TextField placeholder="email outside dialog" />
<Button type="submit" variant="contained">
Submit
</Button>
</Stack>
</form>
</Container>
</ThemeProvider>
);
}
When I open the dialog and submit the form inside the dialog. The form outside dialog will triggered. Is there a way to prevent this?
Visit this link for code reproduction https://stackblitz.com/edit/react-material-ui-typescript-xfv3vu?file=components%2FApp.tsx
Steps to reproduce
Open dialog
Submit the form inside dialog
Two alert will be shown, first alert from form inside dialog, and later alert from form outside dialog
What I'm expecting is that the form outside dialog should not be called, when submitting form inside dialog.
The behavior is unexpected since the form inside dialog is rendered in dom in portal, not as child as form outside dialog
1 is root
2 is dialog portal
You can add the stopPropagation event in your submit of your modal
<form
onSubmit={(evt) => {
evt.preventDefault();
evt.stopPropagation();
alert("Form inside dialog");
}}
>
You can use event.stopPropagation inside the inner form submit (dialog submit):
onSubmit={(evt) => {
evt.preventDefault();
evt.stopPropagation(); // <----
alert("Form inside dialog");
}}
Another solution would be to put the dialog outside the outer form. Both solutions are valid.

Form validation and error messages not working

I'm currently working on several forms for an app and chose to use Material UI and React Hook Forms to build them. The basic functions are working, which means I can only proceed when all required inputs are filled and I'm getting the desired data.
Unfortunately I'm not able to use the form validation or display of error messages that comes with React Hook Form. It is still using the Material UI validation, even though I followed along to the documentation as close as possible.
Here's what I want to be able to do:
define the min and max length of an input
enter RegEx patterns for password inputs
show the neat looking error messages of React Hook Form
Some of the logic is working, some is not. Can you help me figure out why? Thank you in advance!
import React from 'react';
import { useForm, Controller } from 'react-hook-form';
import { Link } from 'react-router-dom';
// COMPONENTS
import Button from '../01-atoms/inputs/Button';
import Textfield from '../01-atoms/inputs/Textfield';
// MATERIAL UI - CORE
import Fade from '#material-ui/core/Fade';
import Grid from '#material-ui/core/Grid';
import InputAdornment from '#material-ui/core/InputAdornment';
import Typography from '#material-ui/core/Typography';
import Paper from '#material-ui/core/Paper';
// MATERIAL UI - ICONS
import LockSharpIcon from '#material-ui/icons/LockSharp';
import PersonAddSharpIcon from '#material-ui/icons/PersonAddSharp';
export default function SignUp({ i18n, submitSignUpData }) {
const { register, handleSubmit, control, errors } = useForm();
return (
<Grid item xs={12} sm={6} md={3}>
<Fade in>
<Paper elevation={3}>
<Typography align='center' gutterBottom variant='h5'>
{i18n.sign_up.page_title}
</Typography>
<form onSubmit={handleSubmit(submitSignUpData)}>
<Grid container spacing={1}>
<Grid item xs={12}>
<Controller
// This is not working:
rules={register({
required: true,
minLength: 8,
})}
// But this is:
required
as={Textfield}
name='newPassword'
control={control}
defaultValue=''
fullWidth
label={i18n.login.password_placeholder}
variant='outlined'
type='password'
InputProps={{
endAdornment: (
<InputAdornment position='end'>
<LockSharpIcon />
</InputAdornment>
),
}}
/>
{errors.newPassword && 'Your input is required!'}
</Grid>
<Grid item xs={12}>
<Button
fullWidth
content={i18n.sign_up.get_started_button}
variant='contained'
color='secondary'
type='submit'
endIcon={<PersonAddSharpIcon />}
/>
</Grid>
</Grid>
</form>
<Link to='/log-in'>
<Typography>{i18n.login.login_button}</Typography>
</Link>
</Paper>
</Fade>
</Grid>
);
}
Instead of using controller why don't you use TextField of Material UI. I have something like this in my code.
<TextField
name="newPassword"
label="Password"
inputRef={register({ required: true, minLength: 8 })}
defaultValue=''
/>
{
errors.newPassword &&
<ErrorText>
{errors.newPassword.type === "required" ?
'Password is required.' :
`Min character limit for Password is 8.`}
</ErrorText>
}

Material UI Button contained style not working with Transition

Current Behavior 😯
When having a Button with a "contained" variant in a Transition (Slide, Grow or Fade) the Button's variant is no longer recognized.
Expected Behavior 🤔
The Button should have a background color.
Steps to Reproduce 🕹
`import { ButtonGroup, Button, Slide } from '#material-ui/core';`
<ButtonGroup variant="text" color="primary" aria-label="menu" id="buttongroup">
{sections.map(x => <Button onClick={() => window.location.href = `#${x.name.toLowerCase()}`} key={x.name}>{x.name}</Button>)}
<Button onClick={() => window.open('tel:00918779839201')}>(+91) 8779839201</Button>
<Slide
direction="left"
in={useScrollTrigger({threshold: document.documentElement.clientHeight/1.5})}
mountOnEnter
unmountOnExit
>
<Button variant="contained">Schedule Site Visit</Button>
</Slide>
</ButtonGroup>
You can see an example here.
Just scroll down and you'll see the Button sliding in on the top right of the Appbar.
ButtonGroup assumes that its direct children are Button elements. Unless the child has the variant prop specified, ButtonGroup will give the child its own variant. In your case, you have a child that is a Slide element and that Slide element does not have a variant specified, so ButtonGroup gives it one (text in your case). Slide passes this property on to the element it wraps and thus overrides the variant of your contained Button.
You can overcome this by specifying the variant on the Slide element:
import React from "react";
import Button from "#material-ui/core/Button";
import ButtonGroup from "#material-ui/core/ButtonGroup";
import Slide from "#material-ui/core/Slide";
export default function App() {
const [slideIn, setSlideIn] = React.useState(false);
return (
<ButtonGroup variant="text">
<Button onClick={e => setSlideIn(true)}>Slide In</Button>
<Button onClick={e => setSlideIn(false)}>Slide Out</Button>
<Slide
variant="contained"
direction="left"
in={slideIn}
mountOnEnter
unmountOnExit
>
<Button variant="contained">Schedule Site Visit</Button>
</Slide>
</ButtonGroup>
);
}

How to make material ui icon clickable and redirect to url

I want to make GitHub icon <GitHubIcon /> clickable and make it redirect to external url, looking in the api it doesn't seem to have link prop ... https://material-ui.com/api/svg-icon/
<div className={classes.root}>
<AppBar position="static">
<Toolbar>
<Typography variant="h6" className={classes.title}>
Made with <FavoriteIcon /> by
<GitHubIcon /> //Not working
</Typography>
</Toolbar>
</AppBar>
</div>
How is it done usually ??
Add this:
<GitHubIcon onClick={handlePageChange} />
and on the function definition:
const handlePageChange() {
window.location.href="pagelink"
}
or directly using an arrow function:
<GitHubIcon onClick={event => window.location.href='pagelink'} />
If looking to add effects on hover with material-ui styles:
How to add stlyes on Material-UI:
Import makeStyles:
import { makeStyles } from '#material-ui/core/styles';
Create the class:
const useStyles = makeStyles(theme => ({
clickableIcon: {
color: 'green',
'&:hover': {
color: 'yellow',
},
},
}));
Add the API call on your function declaration:
const classes = useStyles();
And add this class to your element:
<GitHubIcon
onClick={event => window.location.href='pagelink'}
className={classes.clickableIcon}
/>
You could also use directly a css file.
I'm not really happy with css styling over material-ui api, specially because this horrible CSS-in-JS syntaxi, but it works well, here you'll find some documentation: Material-ui Styles

React Material UI Button component with hookrouter

I am trying to migrate a class component React app to functional components with hooks.
When using class components I use to pass a Link component to a Button component since I was using react-router-dom library.
But now I am trying to use Paratron/hookrouter library for routing but I get an error when passing an A component to the Button:
TypeError: props.href is undefined
My code now looks like this:
import React, { Fragment } from 'react';
import { Grid, Button } from '#material-ui/core';
import { A } from 'hookrouter';
import './styles.css';
const Home = props => {
return (
<Fragment>
<Grid container spacing={24} className="landing">
<Grid item xs={6}>
<Button
variant="contained"
color="primary"
size="large"
component={A}
to="/login"
>Login</Button>
</Grid>
<Grid item xs={6}>
<Button
variant="contained"
color="secondary"
size="large"
component={A}
to="/contact"
>Contact us</Button>
</Grid>
</Grid>
</Fragment>
);
}
export default Home;
I guess this A component does not contain an href property. Not sure how to proceed. Any comments will be appreciated.
You need to provide 'href' prop, not 'to' prop.
<Fragment>
<Grid container spacing={24} className="landing">
<Grid item xs={6}>
<Button
variant="contained"
color="primary"
size="large"
component={A}
href="/login" //href
>Login</Button>
</Grid>
<Grid item xs={6}>
<Button
variant="contained"
color="secondary"
size="large"
component={A}
href="/contact" //href
>Contact us</Button>
</Grid>
</Grid>
</Fragment>
I also had to wrap A with class component, since A is probably function component, and they can't hold a ref (which is required by button component prop)
You can refer to this working CodeSandbox demo

Resources