How to pass custom props to Material-UI component using makeStyles/withStyles? - reactjs

How can I pass a custom props to MUI component? Let's say I want pass customProps='red' which changes the background color of a component?
In my case I need to add a different background color for a button and I want to add something like this:
<SubmitButton
variant={"outlined"}
customBackground={'red'} // this prop
>
Submit
</SubmitButton>
Should I use makeStyles?
Here's the codesandbox.

In your styles defined with withStyles
root: {
backgroundColor: (props) => props.customBackground
{...}
},
Then in the component created by withStyles HOC:
<SubmitButton customBackground="red"
If you want to set a group of style properties, you can pass a callback that returns a style object instead of a value (docs):
root: ({ mode }) => ({
...(mode === "dark" && {
backgroundColor: "black",
color: "lightgray",
"&:hover": {
backgroundColor: "darkblue"
}
}),
...(mode === "light" && {
backgroundColor: "white",
color: "black",
"&:hover": {
backgroundColor: "lightblue"
}
}),
})
Then pass the prop in your custom component to apply dynamic styles:
<SubmitButton mode="light">light</SubmitButton>
<SubmitButton mode="dark">dark</SubmitButton>
Should I use makeStyles?
makeStyles and withStyles are the same except that makeStyles returns a hook which cannot be used in a class component. If you use functional component, you should use makeStyles because it's more pluggable (easier to add or remove styles).

Related

MaterialUI v5 -> How to style Autocomplete options

I'm trying to apply some basic styles to the options inside the Autocomplete component from MUI v5. I'm just trying to change the background color on hover, and based on whether or not it is selected. I've tried 2 approaches based on the documentation, using a theme, and applying the sx prop to Autocomplete.
Using Theme almost has me there, code below:
import { createTheme, ThemeProvider } from '#mui/material/styles';
const theme = createTheme({
components: {
MuiAutocomplete: {
styleOverrides: {
option: {
'&[aria-selected="true"]': {
backgroundColor: '#e3abed',
},
'&:hover': {
backgroundColor: '#9c27b0',
},
backgroundColor: '#fff',
},
},
},
},
})
I have the ThemeProvider wrapped around my entire app
and the component:
<Autocomplete
options={['1', '2', '3']}
renderInput={(params) => <TextField {...params} label="Priority" />}
onChange={(_e, val) => setPriority(val)}
/>
So, this almost works. However the [aria-selected="true"] styling is only being applied when I am also hovering over another option in the dropdown. Otherwise it's the default grey that comes with the component and I don't understand why.
The second path I have tried is to use the sx prop on the Autocomplete component. In the docs it says you can target child components by their class name: https://mui.com/material-ui/customization/how-to-customize/#overriding-styles-with-class-names
Here is my component:
<Autocomplete
options={['1', '2', '3']}
renderInput={(params) => <TextField {...params} label="Priority" />}
onChange={(_e, val) => setPriority(val)}
sx={{
'& .MuiAutocomplete-option': {
backgroundColor: '#000',
},
'& .Mui-focused': {
backgroundColor: 'red',
},
}}
open
/>
That doesn't appear to be having any effect. I've been working on this for almost 2 hours and can't seem to get to the finish line here. Any help would be greatly appreciated.
I had the same problem; turns out I just needed to explore the Autocomplete API docs' Props section a bit further. There are several customization possibilities if you don't want to deal with global theme customization:
The PaperComponent prop allows customization of "[t]he component used to render the body of the popup." Note that simply using sx on Autocomplete does not work here because (as #bnays-mhz pointed out) the PopperComponent that the PaperComponent is nested in lives outside the main DOM tree (for z-index/modal UX purposes).
The renderGroup prop allows overriding the rendering of option group headers.
The renderOption prop allows overriding the rendering of individual options.
function CustomPaper({ children }) {
return (
<Paper
sx={{
"& .MuiAutocomplete-listbox": {
"& .MuiAutocomplete-option[aria-selected='true']": {
bgcolor: "purple",
"&.Mui-focused": {
bgcolor: "purple",
}
}
},
"& .MuiAutocomplete-listbox .MuiAutocomplete-option.Mui-focused": {
bgcolor: "red",
}
}}
>
{children}
</Paper>
);
}
Following up on Lars' answer, here's an example using a custom Paper component. Just pass in the custom Paper component name to the PaperComponent prop on Autocomplete <Autocomplete PaperComponent={CustomPaper} {...blahBlahOtherProps} />. This way is nice if you don't want to override the theme for all Autocomplete components.
You can override Autocomplete options css by targeting class
'.MuiAutocomplete-popper'
You will have to apply css globally because this node is created outside the root element in DOM.
I too faced this issue and found a solution.
You Can try this to set the css for options, single option and while hovered (focused) in the Autocomplete using 'sx' prop
Easy Way to customize your AutoComplete component which can be used in Mui V5
<Autocomplete
limitTags={1}
disablePortal
id="simple-search"
value={select.region}
onChange={handleChange("region")}
options={region}
sx={{
width: { sm: "100%", md: 340 },
"& + .MuiAutocomplete-popper .MuiAutocomplete-option":
{
backgroundColor: "#363636",
},
"& + .MuiAutocomplete-popper .MuiAutocomplete-option[aria-selected='true']":
{
backgroundColor: "#4396e6",
},
"& + .MuiAutocomplete-popper .MuiAutocomplete-option[aria-selected ='true'] .Mui-focused":
{
backgroundColor: "#3878b4",
},
}}
disableCloseOnSelect
multiple
renderInput={(params) => (
<TextField {...params} label="Region" color="info" />
)}
/>

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 - Why should I use makeStyles instead of inline styles?

In Material-UI, there is the makeStyles function which can be used to get custom CSS-Styling.
Should I use it if I am not using a theme in that specific CSS?
For example:
import React from "react";
import { TextField, Paper, Button, Box } from "#material-ui/core";
const classes = {
paper: {
backgroundColor: "#eee",
marginLeft: "30%",
marginRight: "30%"
},
textField: {
backgroundColor: "#fff"
},
button: {
backgroundColor: "green",
marginLeft: 20
}
};
const Ohne = () => {
console.log(classes);
return (
<Paper style={classes.paper}>
<Box>
<TextField style={classes.textField} />
<Button style={classes.button}>abc</Button>
</Box>
</Paper>
);
};
export default Ohne;
The object was:
{
"paper": {
"backgroundColor": "#eee",
"marginLeft": "30%",
"marginRight": "30%"
},
"textField": {
"backgroundColor": "#fff"
},
"button": {
"backgroundColor": "green",
"marginLeft": 20
}
}
vs
import React from "react";
import { makeStyles } from "#material-ui/styles";
import { TextField, Paper, Button, Box } from "#material-ui/core";
const useStyles = makeStyles(theme => ({
paper: {
backgroundColor: "#eee",
marginLeft: "30%",
marginRight: "30%"
},
textField: {
backgroundColor: "#fff"
},
button: {
backgroundColor: "green",
marginLeft: 20
}
}));
const Mit = () => {
const classes = useStyles();
console.log(classes);
return (
<Paper className={classes.paper}>
<Box>
<TextField className={classes.textField} />
<Button className={classes.button}>abc</Button>
</Box>
</Paper>
);
};
export default Mit;
The object was:
{
"paper": "makeStyles-paper-85",
"textField": "makeStyles-textField-86",
"button": "makeStyles-button-87"
}
So there are 3 main points (that I see):
One way creates classes and references them, the other just uses the object as is.
In the first case an object is assigned to the style property which is inline and has a higher priority.
In the first example it is important to keep the const outside of the class, so the object is still created only once and won't trigger a rerender.
But the resulting component looks identical (in my Firefox).
My questions:
Can an example be constructed where these two approaches result in different controls?
Is there any performance aspect to it?
Any other differences?
There are a few scenarios where using CSS classes (e.g. via makeStyles or withStyles) is necessary:
If you want to use media queries in your CSS
If you want to target pseudo-classes (e.g. :hover)
If you are creating a reusable customization of one of the Material-UI components and want to customize some of the classes that are conditionally applied to the element based on props or some other context (e.g. if you want to customize the "error" look of an Input while leaving it up to the places where the custom component is used to specify the error prop either directly or via the FormControl context)
As far as performance concerns, I would expect inline styles to be faster for most use cases. Whether the difference is enough to matter would depend on a lot of specifics regarding your particular application. The team I work with uses makeStyles or withStyles for most of our styling.
Inline styles can result in a lot of duplicated CSS in the DOM if a particular component is rendered many times within the document (e.g. list items, table cells, etc.). One nice thing about always using classes is that you can change the CSS for the class in the browser's developer tools and see that change applied throughout all its usages in the document.

Material-UI Theme Overrides: How To Override Children Styles Globally?

I'm building an app with the Material-UI library for ReactJS. Using the Theme Overrides API, I'm trying to figure out how I can globally style a component, but only when it is a child of another specific component.
For example, I'm trying to set the background/text coloring of MenuItems inside a <Select> menu, where each <MenuItem> contains a <listItemText>. Here's my component:
import { MenuItem, Select, ListItemText } from '#material-ui/core';
import { MuiThemeProvider } from '#material-ui/core/styles';
import * as React from 'react';
import theme from './theme';
const MySelect = props => {
return (
<MuiThemeProvider theme={theme}>
<Select variant="standard" value="2" open>
<MenuItem value="1">
<ListItemText>One</ListItemText>
</MenuItem>
<MenuItem value="2">
<ListItemText>Two</ListItemText>
</MenuItem>
<MenuItem value="3">
<ListItemText>Three</ListItemText>
</MenuItem>
<MenuItem value="4">
<ListItemText>Four</ListItemText>
</MenuItem>
</Select>
</MuiThemeProvider>
);
};
export default MySelect;
Unfortunately, applying a color to the <MenuItem> directly doesn't work because the <ListItemText> overrides it with a <Typography> that has its own coloring set. This is fine for a non-hovered, non-selected state, but if I style the "selected" MenuItem to have a darker background, I need it to have a lighter text.
Here is my theme file:
import { createMuiTheme, createStyles } from '#material-ui/core';
const myTheme = createMuiTheme({
overrides: {
MuiMenuItem: createStyles({
root: {
'&&:hover': {
backgroundColor: 'pink',
color: 'white'
}
},
selected: {
'&&': {
backgroundColor: 'blue',
color: 'white'
},
'&&:hover': {
backgroundColor: 'darkblue',
color: 'white'
}
}
}),
// How do I enforce this ONLY inside of MuiMenuItem and only for
// the selected variant of that?
MuiTypography: {
subheading: {
color: 'white'
}
}
}
});
export default myTheme;
So, my question is: is there a way to do this using just Theme Overrides? Or do I need to conditionally pass this styling into the <ListItemText> component using props? Since most of the styling here fits nicely into Theme Overrides, that seems like a nicer way to do it, but maybe I'm misusing the API.
For a working demo of my above code, see: https://codesandbox.io/s/3r9mkxq231
Any insight is welcome! Thank you!
One way to accomplish that is to target the descendant html element (e.g. the span for the ListItemText) from the ancestor styles (MenuItem in this case).
Here's an example of how the MenuItem.selected style could be specified:
selected: {
"&&": {
backgroundColor: "blue",
color: "white",
"&& span": {
color: "white"
}
},
"&&:hover": {
backgroundColor: "darkblue",
color: "white"
}
}
The full code (forked from your CodeSandbox) is here:
First of all, I don't think we can do that in the theme overrides. Theme overrides is a way to override a default style configuration of an existing material-ui component.
Second, I don't think you need to make it too complex with conditional statements. This can be solved without that also. I didn't understand why did you need to use <ListItemText> when <MenuItem> itself has functionality to display text.
Just simply remove <ListItemText> from you code and then you can use theme overrides to modify your <MenuItem> as you like.
Find the modified code here : https://codesandbox.io/s/30p3o4jjz5
Let me know if this clarifies your doubt.
Yes you can do this in theme overrides using jss-nested syntax:
const myTheme = createMuiTheme({
overrides: {
MuiMenuItem: createStyles({
root: {
"&&:hover": {
backgroundColor: "pink",
"& *": {
color: "white"
}
}
},
selected: {
"&&": {
backgroundColor: "blue",
"& *": {
color: "white"
}
},
"&&:hover": {
backgroundColor: "darkblue",
"& *": {
color: "white"
}
}
}
})
}
});
export default myTheme;
See working codepen here: https://codesandbox.io/embed/308mk7k5x6?fontsize=14

Set the background color of a Snackbar in MUI

I am using a Snackbar component from MUI. At the moment the Snackbar appears in black. Do you know how I can change the color? Setting background-color only changes the color of the whole div in which the Snackbar is presented. It does not change the color of the Snackbar.
With material-ui 1.0 (or higher) you should override the root CSS class from the SnackbarContent component with the prop ContentProps.
Here is an example:
const styles = {
root: {
background: 'red'
}
};
class MySnackbar extends Component {
render() {
const { classes } = this.props;
return (
<Snackbar
ContentProps={{
classes: {
root: classes.root
}
}}
message={<span>Some message</span>}
/>
);
}
}
export default withStyles(styles)(MySnackbar);
If someone do not want to pass style as props, we can also write a class in a css file and assign it to the ContentProps, like this:
ContentProps={{
classes: {
root: 'errorClass'
}
}}
and in index.css file:
.errorClass { color: 'red' }
With the current material-ui version (4.3.0) there is a even simpler approach than the ContentProps way. Instead of the message attribute you can use a SnackbarContent as child and simply call style={} on it
<Snackbar
open={this.state.showSnackbar}
autoHideDuration={3000}
onClose={() => {this.setState({showSnackbar: false})}}
>
<SnackbarContent style={{
backgroundColor:'teal',
}}
message={<span id="client-snackbar">Hello World</span>}
/>
</Snackbar>
You have to set the bodyStyle property:
<Snackbar bodyStyle={{ backgroundColor: 'teal', color: 'coral' }} />
You can find more info about how you customize the snackbars in the documentation
The root component of the Snackbar only concerns about positioning itself correctly, if you want to change the appearance of the physical Snackbar, you need to target the SnackbarContent via ContentProps prop. In v5, you can use the sx to do that easily:
<Snackbar
ContentProps={{
sx: {
background: "red"
}
}}
Another way is to create a custom variant for your Snackbar:
import { createTheme, ThemeProvider } from "#mui/material/styles";
const theme = createTheme({
components: {
MuiSnackbar: {
variants: [
{
props: { variant: "trouble" },
style: {
"& .MuiSnackbarContent-root": {
background: "orange"
}
}
},
{
props: { variant: "bigTrouble" },
style: {
"& .MuiSnackbarContent-root": {
background: "red"
}
}
}
]
}
}
});
<Snackbar variant="bigTrouble"
To use with typescript, you also need to update the prop type of Snackbar:
declare module "#mui/material/Snackbar" {
interface SnackbarProps {
variant: "trouble" | "bigTrouble";
}
}
With MUI v5 the optimal option to customize Snackbar (background, text color or any other styles) is to use sx prop and specific classNames for variants:
<Snackbar
sx={{
'& .SnackbarItem-variantSuccess': {
backgroundColor: colors.primary, //your custom color here
},
'& .SnackbarItem-variantError': {
backgroundColor: colors.alert, //your custom color here
},
'& .SnackbarItem-variantWarning': {
backgroundColor: colors.attention, //your custom color here
},
'& .SnackbarItem-variantInfo': {
backgroundColor: colors.secondary, //your custom color here
},
}}
autoHideDuration={10000}
//...other props here>
</Snackbar>
The same approach can be applied to notistack library to customize their snackbar.

Resources