7
votes

Consider a component that renders a button and says this button should have a red background and a yellow text color. Also there exists a Parent component that uses this child but says, the yellow color is fine, but I want the background color to be green.

withStyles

No problem using the old withStyles.

import React from "react";
import { withStyles } from "@material-ui/core/styles";
import { Button } from "@material-ui/core";

const parentStyles = {
  root: {
    background: "green"
  }
};
const childStyles = {
  root: {
    background: "red"
  },
  label: {
    color: "yellow"
  }
};

const ChildWithStyles = withStyles(childStyles)(({ classes }) => {
  return <Button classes={classes}>Button in Child withStyles</Button>;
});

const ParentWithStyles = withStyles(parentStyles)(({ classes }) => {
  return <ChildWithStyles classes={classes} />;
});


export default ParentWithStyles;

Button in child using withStyles

https://codesandbox.io/s/passing-classes-using-withstyles-w17xs?file=/demo.tsx

makeStyles/useStyles

Let's try the makeStyles/useStyles instead and follow the guide Overriding styles - classes prop on material-ui.com.

import React from "react";
import { makeStyles } from "@material-ui/styles";
import { Button } from "@material-ui/core";

const parentStyles = {
  root: {
    background: "green"
  }
};
const childStyles = {
  root: {
    background: "red"
  },
  label: {
    color: "yellow"
  }
};

// useStyles variant does NOT let me override classes
const useParentStyles = makeStyles(parentStyles);
const useChildStyles = makeStyles(childStyles);

const ChildUseStyles = ({ classes: classesOverride }) => {
  const classes = useChildStyles({ classes: classesOverride });
  return (
    <>
      <Button classes={classes}>Button1 in Child useStyles</Button>
      <Button classes={classesOverride}>Button2 in Child useStyles</Button>
    </>
  );
};
const AnotherChildUseStyles = props => {
  const classes = useChildStyles(props);
  return (
    <>
      <Button classes={classes}>Button3 in Child useStyles</Button>
    </>
  );
};
const ParentUseStyles = () => {
  const classes = useParentStyles();
  return <>
    <ChildUseStyles classes={classes} />
    <AnotherChildUseStyles classes={classes} />
  </>
};

export default ParentUseStyles;

enter image description here

https://codesandbox.io/s/passing-classes-using-usestyles-6x5hf?file=/demo.tsx

There seems no way to get the desired effect that I got using withStyles. A few questions, considering I still want the same effect (green button yellow text) using some method of classes overriding (which seemed to make sense to me before).

  • How is my understanding wrong about how to pass classes as means to override parts of them using useStyles?
  • How should I approach it alternatively?
  • And if I'm using the wrong approach, why is material-ui still giving me a warning when the parent has something in the styles that the child doesn't have?

    the key something provided to the classes prop is not implemented in [Child]

  • Is the migration from the old approach (withStyles) vs the new approach documented somewhere?

Btw, I'm aware of this solution but that seems cumbersome when you have too much you want to override.

const useStyles = makeStyles({
  root: {
    backgroundColor: 'red',
    color: props => props.color, // <-- this
  },
});

function MyComponent(props) {
  const classes = useStyles(props);
  return <div className={classes.root} />;
}
2

2 Answers

5
votes

withStyles has very little functionality in it. It is almost solely a wrapper to provide an HOC interface to makeStyles / useStyles. So all of the functionality from withStyles is still available with makeStyles.

The reason you aren't getting the desired effect is simply because of order of execution.

Instead of:

const useParentStyles = makeStyles(parentStyles);
const useChildStyles = makeStyles(childStyles);

you should have:

const useChildStyles = makeStyles(childStyles);
const useParentStyles = makeStyles(parentStyles);

Edit Passing classes using useStyles

The order in which makeStyles is called determines the order of the corresponding style sheets in the <head> and when specificity is otherwise the same, that order determines which styles win (later styles win over earlier styles). It is harder to get that order wrong using withStyles since the wrapper that you are using to override something else will generally be defined after the thing it wraps. With multiple calls to makeStyles it is easier to do an arbitrary order that doesn't necessarily put the overrides after the base styles they should impact.

The key to understanding this is to recognize that you aren't really passing in overrides, but rather a set of classes to be merged with the new classes. If childClasses.root === 'child_root_1' and parentClasses.root === 'parent_root_1', then the merged result is mergedClasses.root === 'child_root_1 parent_root_1' meaning any elements that have their className set to mergedClasses.root are receiving both CSS classes. The end result (as far as what overrides what) is fully determined by CSS specificity of the styles in the two classes.

Related answers:

0
votes

In Material-ui 4.11.x while creating styles using makeStyles wrap the enclosing styles with createStyles, and this style will have highest priority than the default one.

const useStyles = makeStyles((theme: Theme) =>
  createStyles({
    backdrop: {
      zIndex: theme.zIndex.drawer + 1,
      color: '#fff',
    },
  }),
);

You could try removing the createStyles and see the difference. code source from https://material-ui.com/components/backdrop/