2

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>
    );
}
Andrei
  • 1,723
  • 1
  • 16
  • 27

1 Answers1

7

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.
Ryan Cogswell
  • 75,046
  • 9
  • 218
  • 198
  • Thanks, that worked! I'll add another remark that someone may find useful: since the function passed to `styled(Component)(() => { /*...*/ })` runs at render time, we can call hooks inside. So we can access the Context API in this function. – Andrei Aug 06 '21 at 07:54
  • Great answer! I wonder if there is also a TypeScript example. I can't figure out the typing to allow passing things like `open` to the styled component. – Jaanus Varus Sep 25 '21 at 19:58
  • 1
    @JaanusVarus I've added a TypeScript example – Ryan Cogswell Sep 25 '21 at 20:20
  • 1
    This is shorter in typescript: `styled("div")(...)`. There is no need to declare `React.ComponentType` here. – NearHuscarl Oct 31 '21 at 08:04