Is there a performance difference between the sx prop and the makeStyles function in Material UI?
JSS is slightly faster than Emotion for static styles (i.e. styles that aren't dynamic based on props). JSS is much slower than Emotion for dynamic styles -- Emotion has similar performance for both static and dynamic styles.
You can find information about the performance difference for static styles between JSS and Emotion in the following issues:
- https://github.com/mui-org/material-ui/issues/22342#issuecomment-696553225
- https://github.com/mui-org/material-ui/pull/22173#issuecomment-673486269
JSS was about 10% faster than Emotion for static styles. For dynamic styles, JSS was 6 times slower than Emotion in one test the Material-UI team performed, and this is why JSS was eliminated from the list of possible styling engines for v5.
The documentation at https://next.material-ui.com/system/basics/#the-sx-prop contains the following performance information:
Benchmark case | Code snippet | Time normalized |
---|---|---|
a. Render 1,000 primitives | <div className="…"> | 100ms |
b. Render 1,000 components | <Div> | 120ms |
c. Render 1,000 styled components | <StyledDiv> | 160ms |
d. Render 1,000 Box | <Box sx={…}> | 370ms |
Why is the `sx` prop so much slower?
As I started to dig into this, I realized that I needed to measure the performance of different scenarios in order to have any confidence in my understanding of the performance aspects of the sx
prop.
I believe that the performance information in the MUI documentation was gathered using some variation of this repository: https://github.com/mnajdova/react-native-web. The react-native-web repo was used as a starting point because of its "benchmarks" package which contains a useful framework for measuring the performance of different React element rendering/styling approaches.
I created my own version here: https://github.com/ryancogswell/mui-style-benchmarks. You can use this as a starting point to dig into this further. Below are the measurements I made and my conclusions.
My Results for the "Mount deep tree" Benchmark
This test renders 639 elements with approximately 17 CSS properties each except for the cases ("..._minimal", "..._medium")
which reduce the number of CSS properties to show the performance impact.
Styling Implementation | Time in ms | Implementation Desc |
---|---|---|
inline-styles | 22.78 | No styling engine, just use style prop |
mui_sx_full | 36.89 | MUI Box sx prop with 17 CSS properties |
mui_sx_medium | 24.09 | MUI Box sx prop with 9 CSS properties |
mui_sx_minimal | 18.15 | MUI Box sx prop with 4 CSS properties |
mui_styled_box | 22.38 | MUI styled MUI Box with 17 CSS properties |
mui_styled_box_minimal | 17.90 | MUI styled MUI Box with 4 CSS properties |
tss_react_makestyles | 17.10 | makeStyles from tss-react with 17 CSS properties |
mui_styled | 16.93 | MUI styled div with 17 CSS properties |
mui_styled_minimal | 13.77 | MUI styled div with 4 CSS properties |
emotion_styled | 16.69 | Emotion styled div with 17 CSS properties |
emotion_styled_minimal | 12.76 | Emotion styled div with 4 CSS properties |
emotion_css | 12.58 | Emotion css div with 17 CSS properties |
Using conditional styles in Material-UI with styled vs JSS
Material-UI v5 uses Emotion for the default style engine and consistently uses styled
internally in order to make it easier for people who want to use styled-components instead of Emotion to not have to include both in the bundle.
Though the styled
API works fine for a lot of use cases, it seems like a clumsy fit for this particular use case. There are two main options that provide a considerably better DX.
One option is to use the new sx prop available on all Material-UI components (and the Box component can be used to wrap non-MUI components to access the sx
features). Below is a modification of one of the List demos demonstrating this approach (with the custom ListItemButton
simulating the role of your ListItemLink
):
import * as React from "react";
import Box from "@material-ui/core/Box";
import List from "@material-ui/core/List";
import MuiListItemButton, {
ListItemButtonProps
} from "@material-ui/core/ListItemButton";
import ListItemIcon from "@material-ui/core/ListItemIcon";
import ListItemText from "@material-ui/core/ListItemText";
import Divider from "@material-ui/core/Divider";
import InboxIcon from "@material-ui/icons/Inbox";
import DraftsIcon from "@material-ui/icons/Drafts";
const ListItemButton = ({
selected = false,
...other
}: ListItemButtonProps) => {
const match = selected;
return (
<MuiListItemButton
{...other}
sx={{ color: match ? "primary.main" : undefined }}
/>
);
};
export default function SelectedListItem() {
const [selectedIndex, setSelectedIndex] = React.useState(1);
const handleListItemClick = (
event: React.MouseEvent<HTMLDivElement, MouseEvent>,
index: number
) => {
setSelectedIndex(index);
};
return (
<Box sx={{ width: "100%", maxWidth: 360, bgcolor: "background.paper" }}>
<List component="nav" aria-label="main mailbox folders">
<ListItemButton
selected={selectedIndex === 0}
onClick={(event) => handleListItemClick(event, 0)}
>
<ListItemIcon>
<InboxIcon />
</ListItemIcon>
<ListItemText primary="Inbox" />
</ListItemButton>
<ListItemButton
selected={selectedIndex === 1}
onClick={(event) => handleListItemClick(event, 1)}
>
<ListItemIcon>
<DraftsIcon />
</ListItemIcon>
<ListItemText primary="Drafts" />
</ListItemButton>
</List>
<Divider />
<List component="nav" aria-label="secondary mailbox folder">
<ListItemButton
selected={selectedIndex === 2}
onClick={(event) => handleListItemClick(event, 2)}
>
<ListItemText primary="Trash" />
</ListItemButton>
<ListItemButton
selected={selectedIndex === 3}
onClick={(event) => handleListItemClick(event, 3)}
>
<ListItemText primary="Spam" />
</ListItemButton>
</List>
</Box>
);
}
The only downside of this approach is that it is currently notably slower than using styled
, but it is still fast enough to be fine for most use cases.
The other option is to use Emotion directly via its css prop. This allows a similar DX (though not quite as convenient use of the theme), but without any performance penalty.
/** @jsxImportSource @emotion/react */
import * as React from "react";
import Box from "@material-ui/core/Box";
import List from "@material-ui/core/List";
import MuiListItemButton, {
ListItemButtonProps
} from "@material-ui/core/ListItemButton";
import ListItemIcon from "@material-ui/core/ListItemIcon";
import ListItemText from "@material-ui/core/ListItemText";
import Divider from "@material-ui/core/Divider";
import InboxIcon from "@material-ui/icons/Inbox";
import DraftsIcon from "@material-ui/icons/Drafts";
import { css } from "@emotion/react";
import { useTheme } from "@material-ui/core/styles";
const ListItemButton = ({
selected = false,
...other
}: ListItemButtonProps) => {
const match = selected;
const theme = useTheme();
return (
<MuiListItemButton
{...other}
css={css({ color: match ? theme.palette.primary.main : undefined })}
/>
);
};
export default function SelectedListItem() {
const [selectedIndex, setSelectedIndex] = React.useState(1);
const handleListItemClick = (
event: React.MouseEvent<HTMLDivElement, MouseEvent>,
index: number
) => {
setSelectedIndex(index);
};
return (
<Box sx={{ width: "100%", maxWidth: 360, bgcolor: "background.paper" }}>
<List component="nav" aria-label="main mailbox folders">
<ListItemButton
selected={selectedIndex === 0}
onClick={(event) => handleListItemClick(event, 0)}
>
<ListItemIcon>
<InboxIcon />
</ListItemIcon>
<ListItemText primary="Inbox" />
</ListItemButton>
<ListItemButton
selected={selectedIndex === 1}
onClick={(event) => handleListItemClick(event, 1)}
>
<ListItemIcon>
<DraftsIcon />
</ListItemIcon>
<ListItemText primary="Drafts" />
</ListItemButton>
</List>
<Divider />
<List component="nav" aria-label="secondary mailbox folder">
<ListItemButton
selected={selectedIndex === 2}
onClick={(event) => handleListItemClick(event, 2)}
>
<ListItemText primary="Trash" />
</ListItemButton>
<ListItemButton
selected={selectedIndex === 3}
onClick={(event) => handleListItemClick(event, 3)}
>
<ListItemText primary="Spam" />
</ListItemButton>
</List>
</Box>
);
}
In the app I work on (which I haven't yet started to migrate to v5), I expect to use a combination of styled
and Emotion's css
function/prop. I'm hesitant to use the sx
prop heavily until its performance improves a bit (which I think will happen eventually). Even though it performs "fast enough" for many cases, when I have two options with similar DX available and one is twice as fast as the other, I find it difficult to opt for the slower one. The main cases where I would opt for the sx
prop are for components where I want to set CSS properties differently for different breakpoints or similar areas where the sx
prop provides much nicer DX than other options.
Related answers:
- Is there a performance difference between the sx prop and the makeStyles function in Material UI?
- When migrating to Material-UI v5, how to deal with conditional classes?
Related Topics
Bug with Chrome's Localstorage Implementation
Use a New CSS File to Override Current Website'S
Playing HTML5 Audio in Android Browser
Why Aren't Safari or Firefox Able to Process Audio Data from Mediaelementsource
Googlemaps Does Not Load on Page Load
Running JavaScript in New Window.Open
Redirect Automatically When Selecting an Item from a Select Drop-Down List
Download Attribute Not Working in Firefox
How to Remove the Parent Element Using Plain JavaScript
Handling "Onclick" Event with Pure JavaScript
Can Multiple HTML Elements Receive Focus at the Same Time
How to Access the Content of the "Embed" Tag in HTML
Is There a Flexible Way to Modify the Contents of an Editable Element
Detect Browser Character Support in JavaScript
Fullcalendar.Io: How to Display One Event Per Line in Agendaweek Then Mix All in One