0

RELATED QUESTION OVER AT: Styles being overwritten by Material-UI style

I am create a component library on top of Material UI. Using JSS I'd like to be able to pass in styles to my custom components. However, I'm having issues with material-ui's root styles having higher specificity than what I'm passing in. I have tried overwriting the material-ui components default styles with the classes syntax but it simply creates another class with a similar name and higher specificity (makeStyles-root-51).

Chrome Dev Tools

Consuming Custom Component:

import React from 'react';
import {gSelect} from 'g-react-component-library/dist'
import {createUseStyles} from 'react-jss'

const useStyles = createUseStyles({
    gSelect: {margin: "15px"},
    example: {float: "left", display: "inline-block", whiteSpace: 'nowrap', verticalAlign: 'top'}
});

function App() {

    const classes = useStyles();
    return (
        <div className={classes.example}>
            <div className={classes.separator}>
                <div>Selects:</div>
                <gSelect default={1} classes={{gSelect: classes.gSelect}} callback={(e)=>{console.log(`${e} selected`)}} options={[1,2,3,4]}/>
                <gSelect default={'One'} classes={{gSelect: classes.gSelect}} callback={(e)=>{console.log(`${e} selected`)}} options={["One", "Two", "Three", "Four"]}/>
            </div>
        </div>
    );
}

export default App;

The Actual Custom Component:

import React, {useState} from 'react';
import {Button, Select, FormControl, MenuItem, InputLabel} from '@material-ui/core'
import {makeStyles} from '@material-ui/styles'
import PropTypes from 'prop-types'

const gSelect = (props) => {

    const [value, setValue] = useState();

    const handleChange = event => {
        props.callback(event.target.value);
        setValue(event.target.value);
    };

    const useStyles = makeStyles({
        select: {
            border: 'solid #33333366 1px',
            color: 'rgba(51, 51, 51, 0.66)',
            fontWeight: '700',
            backgroundColor: 'white',
            outline: 'none',
            borderRadius: '5px',
            textAlign: 'left',
            width: '300px',
            position: 'relative',
        },
        root: {

        }
    });

    const classes = useStyles(props);
    return (
        <FormControl classes={{root: classes.gSelect}}>
        <InputLabel id="demo-simple-select-label">{props.default}</InputLabel>
        <Select value={value} onChange={handleChange} className={classes.select}>
            {props.options.map((option, index) => {
                return <MenuItem key={`${option}_${index}`} value={option}>{option}</MenuItem>
            })}
        </Select>
        </FormControl>
    )
};

gSelect.propTypes = {
    callback: PropTypes.func.isRequired,
    options: PropTypes.array.isRequired,
    default: PropTypes.oneOfType([
        PropTypes.string,
        PropTypes.number
    ]).isRequired,
    disabled: PropTypes.bool,
    width: PropTypes.string
};

module.exports = {
    gSelect
};
Jonny B
  • 680
  • 2
  • 6
  • 29
  • You shouldn't be creating the `useStyles` hook inside the component, defeats the purpose. – Adam Jenkins Feb 07 '20 at 19:14
  • How so? If I want to create a custom component library on top of material-UI you have to give it custom styles somehow. – Jonny B Feb 07 '20 at 19:18
  • I've explained in my answer below - the crux of the matter is that `classes` should come through props and you should pass those props to your hook - material UI does all the magic of concatenating passed in classes to your own. I personally prefer the `withStyles` hoc because all of this logic is handled automagically for you and you don't have to worry about forgetting to pass your props to your useStyles hook. – Adam Jenkins Feb 07 '20 at 19:21
  • @Adam, I've made the changes you suggested. However, I'm still running into the issue where MUI's root class has higher specificity and is overwriting my margin. Also, I have cut down the code a lot from it's source for this question and so if there are some glaring issues that probably is what you are seeing. In my original question I realized that the style I was passing into the component was the same as the style it was using internally to customize the select. This was just a mistake in me editing things down for the question. I should have fixed it in the updated question. – Jonny B Feb 07 '20 at 19:59
  • If you could post a codepen/stackblitz demonstrating an issue (doesn't have to be this exact code, just something similar) it'd go a long way. – Adam Jenkins Feb 07 '20 at 20:15
  • Don’t try and use your existing code, make a small repro that uses a material ui and a couple of dummy components, while you’re doing it you might even solve your issue! – Adam Jenkins Feb 07 '20 at 20:36
  • It's tough to explain, check the diff between yours and what I did here: https://codesandbox.io/s/material-demo-so5yk. Ask whatever questions you like. I spent some time learning material-ui's styling solution a few months ago, it's amazing, but until you really grok it, you're going to struggle with it. (EDIT: added extensive commenting, hopefully to make it clear what's going on). – Adam Jenkins Feb 07 '20 at 22:40
  • Those aren't errors, those are warnings displayed with a `console.error`. As a matter of fact, they are [this warning](https://github.com/mui-org/material-ui/blob/master/packages/material-ui-styles/src/mergeClasses/mergeClasses.js#L31). Material UI is providing those warnings because they think that it's caused by devs not knowing what they are doing (most of the time they are correct). But if you know how the merging of classes works, they are honestly safe to ignore. By the way, this sandbox should show you how to "fix" it: https://codesandbox.io/s/material-demo-udlfh – Adam Jenkins Feb 10 '20 at 22:14
  • @Adam, I've updated my code sandbox example to use `className` and that worked. I tried this originally but I think because I was using the `createUseStyles` instead of `makeStyles` it was not working. However, your help was really invaluable to getting to that solution, thanks! Also, I've run into a very similar but different issue and created another question. By no means do I expect your help but if you wanted to take a crack at it be my guest. Thanks again! https://stackoverflow.com/questions/60179584/styles-being-overwritten-by-material-ui-style – Jonny B Feb 12 '20 at 00:59

2 Answers2

1

You're doing it wrong. GSelect should receive classes like this:

In App:

<GSelect default={1} classes={{gSelect:classes.gSelect}} callback={(e)=>{console.log(`${e} selected`)}} options={[1,2,3,4]}/>

Then in GSelect:

const useStyles = createStyles(...your styles); // call this hook factory outside your render

const GSelect = props => {
   const classes = useStyles(props) <- props contains an object called classes with a property gselect that gets merged into yours

   <Select value={value} onChange={handleChange} classes={{root:classes.gSelect}}>
}

EDIT: As for my original comment about creating your hook outside of your component, I meant do this:


// move this outside of your render
    const useStyles = createUseStyles({
        gSelect: {margin: "15px"},
        separator: {marginTop: "15px"},
        example: {float: "left", display: "inline-block", whiteSpace: 'nowrap', verticalAlign: 'top'}
    });

function App() {
    // use it inside of your render
    const classes = useStyles();
    ...
}

Read through this section and the following two, after a while, it'll click: https://material-ui.com/styles/advanced/#overriding-styles-classes-prop

Adam Jenkins
  • 51,445
  • 11
  • 72
  • 100
0

The solution to passing in styles to a custom component in the end was very simple. While @Adam answered my question the way it was worded, the solution I ended up with is as follows:

Consuming Custom Component:

import React from 'react';
import {gSelect} from 'g-react-component-library/dist'
import { makeStyles } from "@material-ui/core/styles";

const useStyles = makeStyles ({
    gSelect: {margin: "15px"},
    example: {float: "left", display: "inline-block", whiteSpace: 'nowrap', verticalAlign: 'top'}
});

function App() {

    const classes = useStyles();
    return (
        <div className={classes.example}>
            <div className={classes.separator}>
                <div>Selects:</div>
                <gSelect default={1} className={classes.gSelect} callback={(e)=>{console.log(`${e} selected`)}} options={[1,2,3,4]}/>
                <gSelect default={'One'} className={classes.gSelect} callback={(e)=>{console.log(`${e} selected`)}} options={["One", "Two", "Three", "Four"]}/>
            </div>
        </div>
    );
}

export default App;

Fixes Above Include :

  • switched createUseStyles to makeStyles from @material-ui/core/styles
  • used className={classes.gSelect} instead of classes={{root: ...}}

The Actual Custom Component:

import React, {useState} from 'react';
import {Button, Select, FormControl, MenuItem, InputLabel} from '@material-ui/core'
import {makeStyles} from '@material-ui/styles'
import PropTypes from 'prop-types'

const gSelect = (props) => {

const [value, setValue] = useState();

const handleChange = event => {
    props.callback(event.target.value);
    setValue(event.target.value);
};

const useStyles = makeStyles({
    select: {
        border: 'solid #33333366 1px',
        color: 'rgba(51, 51, 51, 0.66)',
        fontWeight: '700',
        backgroundColor: 'white',
        outline: 'none',
        borderRadius: '5px',
        textAlign: 'left',
        width: '300px',
        position: 'relative',
    }
});

const classes = useStyles();
return (
    <FormControl className={props.className}>
    <InputLabel id="demo-simple-select-label">{props.default}</InputLabel>
    <Select value={value} onChange={handleChange} className={classes.select}>
        {props.options.map((option, index) => {
            return <MenuItem key={`${option}_${index}`} value={option}>{option}</MenuItem>
        })}
    </Select>
    </FormControl>
)
};

gSelect.propTypes = {
    callback: PropTypes.func.isRequired,
    options: PropTypes.array.isRequired,
    default: PropTypes.oneOfType([
        PropTypes.string,
        PropTypes.number
    ]).isRequired,
    disabled: PropTypes.bool,
    width: PropTypes.string
};

module.exports = {
    gSelect
};

Fixes Above Include :

  • used className={props.className} on <FormControl>
Jonny B
  • 680
  • 2
  • 6
  • 29