0
votes

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
};
2
You shouldn't be creating the useStyles hook inside the component, defeats the purpose.Adam
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
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
@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
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

2 Answers

1
votes

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

0
votes

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>