Andrei
Andrei

Reputation: 1863

When migrating to Material-UI v5, how to deal with conditional classes?

In the official migration guide, they give the following example of changing the code from JSS (makeStyles) to the new styled mode.

Before:

const useStyles = makeStyles((theme) => ({
    background: theme.palette.primary.main,
}));

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

After:

const MyComponent = styled('div')(({ theme }) => 
    ({ background: theme.palette.primary.main }));

function App(props) {
    return (
        <ThemeProvider theme={theme}>
            <MyComponent {...props} />
        </ThemeProvider>
    );
}

This is fine for a single class, but what to do when the code has conditional classes?

e.g.

<main className={classnames(content, open ? contentOpen : contentClosed)}>
    {/* content goes here */}
</main>

Here, content, contentOpen, and contentClosed are classes returned from useStyles, but contentOpen and contentClosed are rendered conditionally based on the value of open.

With the new styled method, instead of generating class names we're generating components. Is there a way to elegantly replicate the rendering without resorting to content duplication in the ternary expression?

e.g. we don't want to do something like:

function App(props) {
    return (
        <ThemeProvider theme={theme}>
            {open
            ? <MyOpenComponent {...props}>{/* content */}</MyOpenComponent>
            : <MyClosedComponent {...props}>{/* content */}</MyClosedComponent>
        </ThemeProvider>
    );
}

Upvotes: 2

Views: 1951

Answers (1)

Ryan Cogswell
Ryan Cogswell

Reputation: 81026

There are quite a few possible ways to deal with this. One approach using styled is to leverage props to do dynamic styles rather than trying to use multiple classes.

Here's an example:

import React from "react";
import Button from "@mui/material/Button";
import { styled } from "@mui/material/styles";

const StyledDiv = styled("div")(({ open, theme }) => {
  const color = open
    ? theme.palette.primary.contrastText
    : theme.palette.secondary.contrastText;
  return {
    backgroundColor: open
      ? theme.palette.primary.main
      : theme.palette.secondary.main,
    color,
    padding: theme.spacing(0, 1),
    "& button": {
      color
    }
  };
});

export default function App() {
  const [open, setOpen] = React.useState(false);
  return (
    <StyledDiv open={open}>
      <h1>{open ? "Open" : "Closed"}</h1>
      <Button onClick={() => setOpen(!open)}>Toggle</Button>
    </StyledDiv>
  );
}

Edit replacing multiple makeStyles classes in v5

Here's an equivalent example using TypeScript:

import * as React from "react";
import Button from "@mui/material/Button";
import { styled } from "@mui/material/styles";

const StyledDiv: React.ComponentType<{ open: boolean }> = styled("div")(
  ({ open, theme }) => {
    const color = open
      ? theme.palette.primary.contrastText
      : theme.palette.secondary.contrastText;
    return {
      backgroundColor: open
        ? theme.palette.primary.main
        : theme.palette.secondary.main,
      color,
      padding: theme.spacing(0, 1),
      "& button": {
        color
      }
    };
  }
);

export default function App() {
  const [open, setOpen] = React.useState(false);
  return (
    <StyledDiv open={open}>
      <h1>{open ? "Open" : "Closed"}</h1>
      <Button onClick={() => setOpen(!open)}>Toggle</Button>
    </StyledDiv>
  );
}

Edit replacing multiple makeStyles classes in v5

Some other possible approaches:

  • Use Emotion's css prop and Emotion's capabilities for composing styles
  • Use tss-react to retain similar syntax to makeStyles but backed by Emotion (so you wouldn't be including both Emotion and JSS in your bundle as would be the case if you leverage makeStyles from @material-ui/styles). This is the route I took when migrating to v5 and as part of my migration I created a codemod for migrating JSS makeStyles to tss-react's makeStyles.

Upvotes: 7

Related Questions