What is the idiomatic way to do styling in Material-UI? - reactjs

There are a few ways to do styling in Material-UI.
Use makeStyles:
import Box from '#material-ui/core/Box'
const useStyles = makeStyles(theme => ({
testStyled: (props: isBoolean) => ({
width: props ? '1px' : '2px',
})
}))
<Box className={useStyles(true).root}></Box>
Use MuiBoxProps:
import Box, { MuiBoxProps } from '#material-ui/core/Box'
let isTrue = true
let props: MuiBoxProps = {
style: {
marginBottom: isTrue ? '1px' : '2px'
}
}
<Box {...props}></Box>
In the second way, I can put a special input for each individual component. For example:
let props: MuiBoxProps = {
mb: '1px'
}
Type inference can be used to see compilation errors.
However, this method is not applicable to makeStyles because its return type is always CSSProperties.
In the first case, however, I can style them internally by adding props.
So, in conclusion:
Type inference is impossible for a specific component. Can apply props in batch.
Type inference is possible for a specific component. Can't apply props in batch.
What is the official recommendation?

Actually, doesn't matter which way you are choosing because both the have in official documents. the matter is that in which method or way you are comfortable with.
But Yes, I think the best way is withSyles which is also I use in my professional development code.
Here is the Example:
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '#material-ui/core/styles';
import Button from '#material-ui/core/Button';
const styles = {
root: {
background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
border: 0,
borderRadius: 3,
boxShadow: '0 3px 5px 2px rgba(255, 105, 135, .3)',
color: 'white',
height: 48,
padding: '0 30px',
},
};
function HigherOrderComponent(props) {
const { classes } = props;
return <Button className={classes.root}>Higher-order component</Button>;
}
HigherOrderComponent.propTypes = {
classes: PropTypes.object.isRequired,
};
export default withStyles(styles)(HigherOrderComponent);

Related

How to style Input in material ui

I am new to material ui.
I use fifth version.
<InputBase
ref={params.InputProps.ref}
inputProps={params.inputProps}
autoFocus
sx={{
...InputCSS,
}}
/>
const InputCSS = {
width: '100%',
textIndent: '17px',
py: '12px',
fontSize: '20px',
borderRadius: '3px',
border: '1.5px solid rgba(0, 0, 0, 0.4)',
'MuiInputBase-root': { // does not work
borderColor: 'red !important',
color: 'red !important',
},
'&:focus' : { // does not work
...
}
}
I could have used styled('input') and it works, I can set &:focus and it works but I can't type anything in the input.
I want to get rid of the initial border and set focus property.
How can I change the border for this class?
I know that in v5 we can style our components using variants or sx or styled.
What is the best advice for styling mui components? Because almost all the info in the internet uses outdated useStyle or makeStyles which doesn't work with react 18v.
sometimes I just struggle with components styling in mui
You have several ways to customize a Mui component, but my three favorite approaches are:
Styled utility
Sx prop
Custom global theme
So when should I use each of these approaches?
Styled utility
If you want to make a component reusable across your app, go with styled, if you had ever used styled components then styled will be very familiar to you, here is an example of how to use it:
import * as React from 'react';
import Slider, { SliderProps } from '#mui/material/Slider';
import { alpha, styled } from '#mui/material/styles';
// if you are using typescript, don't forget to pass the component props on styled
const SuccessSlider = styled(Slider)<SliderProps>(({ theme }) => ({
width: 300,
color: theme.palette.success.main,
// to override the styles of inner elements,
// you have to use the & selector followed by the class name.
'&.MuiSlider-thumb': {
'&:hover, &.Mui-focusVisible': {
boxShadow: `0px 0px 0px 8px ${alpha(theme.palette.success.main, 0.16)}`,
},
'&.Mui-active': {
boxShadow: `0px 0px 0px 14px ${alpha(theme.palette.success.main, 0.16)}`,
},
},
}));
export default function StyledCustomization() {
return <SuccessSlider defaultValue={30} />;
}
To make the component even more dynamically, you can define custom props as well, like width, color, border and so on, read the styled docs to know more about it.
Sx prop
If you want to make an one time off style in a single component, the easiest way is to use the sx prop, but be careful with this approach because it can lead to a lot of repetition in your code. Following the code you gave:
<InputBase
ref={params.InputProps.ref}
inputProps={params.inputProps}
autoFocus
sx={{
...InputCSS,
}}
/>
const InputCSS = {
width: '100%',
textIndent: '17px',
py: '12px',
fontSize: '20px',
borderRadius: '3px',
border: '1.5px solid rgba(0, 0, 0, 0.4)',
// you should pass the & selector followed by the class that you want to override
'&.MuiInputBase-root': {
// avoid the use of !important
borderColor: 'red',
color: 'red',
},
'&.MuiInputBase-root:focus': {
...
}
}
check it out on codeSandbox. Just a suggestion, depending on your case the component TextField could fit better as it comes with some good styles and you don't need to style it from scratch.
Side note:
Every mui component has an api doc with a css section where you can find all available classes to override, for instance, see the InputBase api docs, also read the docs about sx prop.
Custom theme
At last but not least, if you want to customize your whole app with custom palette, components, typography, breakpoints and so on, no doubt you should use a custom theme, as mui provides a powerful tool called createTheme to do so, what I like the most in custom theme is the possibility to make custom variants of the component, like so:
import * as React from "react";
import { createTheme, ThemeProvider } from "#mui/material/styles";
import Button from "#mui/material/Button";
import { red } from "#mui/material/colors";
// if you are using typescript, you must declare the module with the
// custom properties in order to get access of this property when using the component
declare module "#mui/material/Button" {
// define custom variants
interface ButtonPropsVariantOverrides {
dashed: true;
redVariant: true;
}
}
const defaultTheme = createTheme();
const theme = createTheme({
components: {
MuiButton: {
variants: [
{
// use the variant name defined earlier
props: { variant: "dashed" },
// set the styles for this variant
style: {
textTransform: "none",
border: `2px dashed ${defaultTheme.palette.primary.main}`,
color: defaultTheme.palette.primary.main
}
},
{
props: { variant: "redVariant" },
style: {
border: `2px solid ${red[300]}`,
color: red[600]
}
}
]
}
}
});
export default function GlobalThemeVariants() {
return (
// use the theme provider to get access of custom theme
// and variants within any child component
<ThemeProvider theme={theme}>
<Button variant="dashed" sx={{ m: 1 }}>
Dashed
</Button>
<Button variant="redVariant" color="secondary">
custom red variant
</Button>
</ThemeProvider>
);
}
See the example, also take a look at the custom theme docs. Hope I clarified the things a bit!

Material UI - Apply props to pseudo-element

import React from "react";
import { Box } from "#material-ui/core";
import { makeStyles } from '#material-ui/core/styles';
const useStyles = makeStyles({
box: {
'&::before': {
content: props => `${props.text}` // should be props.text from <Child /> (i.e. '111')
}
}
});
function Parent() {
return <Child text="111" />
}
function Child(props) {
const { text } = props;
const classes = useStyles({ text });
return <Box className={classes.box} />
}
How can I use props outside of the <Child />? I've tried data attribute but not working either. It seems content in the pseudo element is alittle bit different from other properties
You can pass the props like this:
import React from "react";
import { Box ,Button} from "#material-ui/core";
import { makeStyles } from '#material-ui/core/styles';
const useStyles = makeStyles({
box: {
background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
border: 0,
borderRadius: 3,
boxShadow: '0 3px 5px 2px rgba(255, 105, 135, .3)',
color: 'black',
height: 48,
padding: '0 30px',
'&:hover':{
background:props=>props.hover
}
}
});
function Parent() {
return <Child hover='black' />
}
function Child(props) {
//const props = { hover:'black' };
const classes = useStyles(props);
return <Box className={classes.box}/>
}
In case of the pseudo element ::before the syntax for the content property is a little different. We use additional inverted commas with the single commas like this:
'&::before':{
content:'"Some text"'
}
In case we are passing props it will look like this:
'&::before':{
content:props=>`"${props.content}"`
}
For more info check out material-ui docs
You can pass props through in your useStyles call.
const classes = useStyles({text: 'asdf'})
Usage in your style definition would be something like this
const useStyles = makeStyles({
box: {
'&::before': {
content: (props) => `${props.text}`
}
}
});

Typescript/React/MaterialUI/JSS project: override CSS for a single React component

I am trying to understand how to override the CSS/formatting/theme for just a single React component in a Typescript/React/MaterialUI/JSS project. Below is the code, however the {classes.gridStyle} within the JSX doesn't get processed (see screenshot below the code snippet). I can see it show up in the browser debugger as unmodified and the typescript editor is also giving me a warning that variable "classes" is never used.
import React from 'react';
import { makeStyles } from '#material-ui/core/styles';
import { Grid, createStyles } from '#material-ui/core';
const useStyles = makeStyles(() =>
createStyles({
gridStyle: {
display: 'inline-block',
padding: '0 20px',
height: '40px',
},
'& div': {
display: 'inline-block',
fontSize: '10px',
padding: '0 20px',
height: '40px',
},
})
);
export default function HomeHeader(): JSX.Element {
const classes = useStyles();
return (
<Grid item className="{classes.gridStyle}">
Row 1 completely override css experiment...
</Grid>
);
}
Issue inside browser debugger
className="{classes.gridStyle}">
should be
className={classes.gridStyle}>

How to customize material-ui TextField Input underline:after?

I am using Material-ui for React.
I am trying to customize the color of the underline that transitions into place when the user clicks the Mui <TextField> component, which is a result of jss injecting the following CSS:
.MuiInput-underline:after {
border-bottom: 2px solid #303f9f;
}
I am already invested into styled-components theme provider and do not want to bring in the MuiTheme provider in order to use createMuiTheme and override.
I have used styled-components to override styling for many other Mui components, but have been unable to override .MuiInput-underline:after using styled-components.
I am now trying to use Mui's withStyles, but am unsure of the exact style semantics. I've been unsuccessful using InputProps and using classes.
const styles = theme => ({
inputProps: {
underline: {
'&:after': {
border: '2px solid red'
}
}
}
});
const MyTextField = props => {
const { classes, ...rest } = props;
return (
<TextField InputProps={{ inputProps: classes.inputProps }} {...rest} />
);
};
export default withStyles(styles)(MyTextField);
Any thoughts? Thanks.
Here's an example of how to customize the underline using styled-components:
import TextField from "#material-ui/core/TextField";
import styled from "styled-components";
const StyledTextField = styled(TextField)`
/* default */
.MuiInput-underline:before {
border-bottom: 2px solid green;
}
/* hover (double-ampersand needed for specificity reasons. */
&& .MuiInput-underline:hover:before {
border-bottom: 2px solid lightblue;
}
/* focused */
.MuiInput-underline:after {
border-bottom: 2px solid red;
}
`;
export default StyledTextField;
Related answers:
How can I remove the underline of TextField from Material-UI?
How do I custom style the underline of Material-UI without using theme?
You need to omit the inputProps key in styles object.
You also need to provide classses Prop to InputProps:
const styles = theme => ({
underline: {
color: 'red' ,
'&::after': {
border: '2px solid red'
}
}
});
const MyTextField = props => {
const { classes, ...rest } = props;
return (
<TextField InputProps={{ classes: {underline: classes.underline} }} {...rest} />
);
};
You can check this working code sandbox example: https://codesandbox.io/s/material-demo-75w7p?fontsize=14
Here is an example of using withStyles.
const MyTextField = withStyles((theme: Theme) => ({
root: {
"& label.Mui-focused": {
color: theme.palette.primary.dark
},
"& .MuiInput-underline:before": {
borderBottomColor: "green"
},
"& .MuiInput-underline:after": {
borderBottomColor: "green"
},
"& .MuiInputBase-root": {
"& fieldset": {
borderColor: "red"
},
"&:hover fieldset": {
borderColor: "yellow"
},
"&.Mui-focused fieldset": {
borderColor: theme.palette.primary.main
}
},
"& .MuiOutlinedInput-root": {
"& fieldset": {
borderColor: "red"
},
"&:hover fieldset": {
borderColor: "yellow"
},
"&.Mui-focused fieldset": {
borderColor: theme.palette.primary.main
}
}
}
}))(TextField);
https://codesandbox.io/s/material-demo-forked-c2opb?file=/demo.tsx

How to style components using makeStyles and still have lifecycle methods in Material UI?

I get the below error whenever I try to use makeStyles() with a component with lifecycle methods:
Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app
Below is a small example of code that produces this error. Other examples assign classes to child items as well. I can't find anything in MUI's documentation that shows other ways to use makeStyles and have the ability to use lifecycle methods.
import React, { Component } from 'react';
import { Redirect } from 'react-router-dom';
import { Container, makeStyles } from '#material-ui/core';
import LogoButtonCard from '../molecules/Cards/LogoButtonCard';
const useStyles = makeStyles(theme => ({
root: {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
},
}));
const classes = useStyles();
class Welcome extends Component {
render() {
if (this.props.auth.isAuthenticated()) {
return <Redirect to="/" />;
}
return (
<Container maxWidth={false} className={classes.root}>
<LogoButtonCard
buttonText="Enter"
headerText="Welcome to PlatformX"
buttonAction={this.props.auth.login}
/>
</Container>
);
}
}
export default Welcome;
Hi instead of using hook API, you should use Higher-order component API as mentioned here
I'll modify the example in the documentation to suit your need for class component
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '#material-ui/core/styles';
import Button from '#material-ui/core/Button';
const styles = theme => ({
root: {
background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
border: 0,
borderRadius: 3,
boxShadow: '0 3px 5px 2px rgba(255, 105, 135, .3)',
color: 'white',
height: 48,
padding: '0 30px',
},
});
class HigherOrderComponentUsageExample extends React.Component {
render(){
const { classes } = this.props;
return (
<Button className={classes.root}>This component is passed to an HOC</Button>
);
}
}
HigherOrderComponentUsageExample.propTypes = {
classes: PropTypes.object.isRequired,
};
export default withStyles(styles)(HigherOrderComponentUsageExample);
I used withStyles instead of makeStyle
EX :
import { withStyles } from '#material-ui/core/styles';
import React, {Component} from "react";
const useStyles = theme => ({
root: {
flexGrow: 1,
},
});
class App extends Component {
render() {
const { classes } = this.props;
return(
<div className={classes.root}>
Test
</div>
)
}
}
export default withStyles(useStyles)(App)
What we ended up doing is stopped using the class components and created Functional Components, using useEffect() from the Hooks API for lifecycle methods. This allows you to still use makeStyles() with Lifecycle Methods without adding the complication of making Higher-Order Components. Which is much simpler.
Example:
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import { Redirect } from 'react-router-dom';
import { Container, makeStyles } from '#material-ui/core';
import LogoButtonCard from '../molecules/Cards/LogoButtonCard';
const useStyles = makeStyles(theme => ({
root: {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
margin: theme.spacing(1)
},
highlight: {
backgroundColor: 'red',
}
}));
// Highlight is a bool
const Welcome = ({highlight}) => {
const [userName, setUserName] = useState('');
const [isAuthenticated, setIsAuthenticated] = useState(true);
const classes = useStyles();
useEffect(() => {
axios.get('example.com/api/username/12')
.then(res => setUserName(res.userName));
}, []);
if (!isAuthenticated()) {
return <Redirect to="/" />;
}
return (
<Container maxWidth={false} className={highlight ? classes.highlight : classes.root}>
<LogoButtonCard
buttonText="Enter"
headerText={isAuthenticated && `Welcome, ${userName}`}
buttonAction={login}
/>
</Container>
);
}
}
export default Welcome;
useStyles is a React hook which are meant to be used in functional components and can not be used in class components.
From React:
Hooks let you use state and other React features without writing a
class.
Also you should call useStyles hook inside your function like;
function Welcome() {
const classes = useStyles();
...
If you want to use hooks, here is your brief class component changed into functional component;
import React from "react";
import { Container, makeStyles } from "#material-ui/core";
const useStyles = makeStyles({
root: {
background: "linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)",
border: 0,
borderRadius: 3,
boxShadow: "0 3px 5px 2px rgba(255, 105, 135, .3)",
color: "white",
height: 48,
padding: "0 30px"
}
});
function Welcome() {
const classes = useStyles();
return (
<Container className={classes.root}>
<h1>Welcome</h1>
</Container>
);
}
export default Welcome;
🏓 on ↓ CodeSandBox ↓
Instead of converting the class to a function, an easy step would be to create a function to include the jsx for the component which uses the 'classes', in your case the <container></container> and then call this function inside the return of the class render() as a tag. This way you are moving out the hook to a function from the class. It worked perfectly for me. In my case it was a <table> which i moved to a function- TableStmt outside and called this function inside the render as <TableStmt/>
Another one solution can be used for class components -
just override default MUI Theme properties with MuiThemeProvider.
This will give more flexibility in comparison with other methods - you can use more than one MuiThemeProvider inside your parent component.
simple steps:
import MuiThemeProvider to your class component
import createMuiTheme to your class component
create new theme
wrap target MUI component you want to style with MuiThemeProvider and your custom theme
please, check this doc for more details:
https://material-ui.com/customization/theming/
import React from 'react';
import PropTypes from 'prop-types';
import Button from '#material-ui/core/Button';
import { MuiThemeProvider } from '#material-ui/core/styles';
import { createMuiTheme } from '#material-ui/core/styles';
const InputTheme = createMuiTheme({
overrides: {
root: {
background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
border: 0,
borderRadius: 3,
boxShadow: '0 3px 5px 2px rgba(255, 105, 135, .3)',
color: 'white',
height: 48,
padding: '0 30px',
},
}
});
class HigherOrderComponent extends React.Component {
render(){
const { classes } = this.props;
return (
<MuiThemeProvider theme={InputTheme}>
<Button className={classes.root}>Higher-order component</Button>
</MuiThemeProvider>
);
}
}
HigherOrderComponent.propTypes = {
classes: PropTypes.object.isRequired,
};
export default HigherOrderComponent;
Further to the answer provided by #vikas-kumar, it's also possible to make use of the props that are being set on the component being styled, e.g.
const styles = theme => ({
root: {
background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
border: 0,
borderRadius: 3,
boxShadow: '0 3px 5px 2px rgba(255, 105, 135, .3)',
color: 'white',
height: props => props.height,
padding: '0 30px',
},
});
So the height for the style applied can be governed by
<HigherOrderComponentUsageExample height={48}/>
Further details on dynamic styling are available here: https://material-ui.com/styles/basics/#adapting-the-higher-order-component-api
You are calling useStyles hook outside of a function.Thats why

Resources