mui / material-ui

Material UI: Comprehensive React component library that implements Google's Material Design. Free forever.
https://mui.com/material-ui/
MIT License
93.9k stars 32.26k forks source link

Components rendered by React 18 ReactDOMServer don't have CSS classes reflecting their `sx` prop #32893

Open nsilvestri opened 2 years ago

nsilvestri commented 2 years ago

Duplicates

Latest version

Current behavior 😯

Components rendered with ReactDOMServer are given a unique CSS class for the style changes in their sx prop, but this CSS class does not actually get created, so the rendered component is unstyled. Excecption: if the component shares an sx prop with another component that is rendered normally, the CSS is added and rendered correctly.

Expected behavior 🤔

Components rendered with ReactDOMServer should have styles that match the sx prop applied.

Steps to reproduce 🕹

I have created a minimal reproduction on CodeSandbox: https://codesandbox.io/s/nervous-benz-354utb?file=/src/Demo.tsx

Context 🔦

I am working with some non-React JS libraries (in this case, Leaflet) that renders raw HTML strings. If I want to render a component within the context of this library, I must first render the component as a string on the client side (with ReactDOMServer.renderToString() and pass this string to the library. Unfortunately, styling via the sx prop is not possible with this method.

I believe this issue is a duplicate of #30865. This issue does not happen on React 17.

Your environment 🌎

No response

jaballogian commented 2 years ago

Hi @nsilvestri

Case

I also experience the same thing. I have to use the ReactDOMServer.renderToString() function to render custom react-leaflet marker icon with React component.

Here is the result when I use react and react-dom v17 (the custom markers are fine) image and here is the sample code of the marker (using sx prop to control the value of the dynamic background color):

<Marker
  position={[ 
    parseFloat(location.lat), 
    parseFloat(location.lng) 
  ]}
  icon={L.divIcon({
    className: 'custom-icon',
    html: ReactDOMServer.renderToString(
      <Box className={classes.root}>
        {/* ICON */}
        <Avatar 
          className={classes.marker}
          sx={{ backgroundColor: backgroundColor }} // using the sx prop
        >
          {icon}
        </Avatar>

        {/* TEXT */}
        <Typography 
          variant='caption'
          className={classes.text}
          sx={{ backgroundColor: backgroundColor }}  // using the sx prop
        >
          {objectData.deviceVehicleLable}
        </Typography>
      </Box>
    ),
  })}
/>

Here is the result after upgrading to react and react-dom v18 (using the same custom marker code): image

Temporary Solution

Adding style prop or passing props to the makeStyles function may be the temporary solution (but still couldn't fully replace the sx prop). Some components support these solutions but some don't.

Here is the code of implementing style prop:

<Box className={classes.root}>
  {/* ICON */}
  <Avatar 
    className={classes.marker}
    // sx={{ backgroundColor: backgroundColor }}
    style={{ backgroundColor: backgroundColor }}
  >
    {icon}
  </Avatar>

  {/* TEXT */}
  <Typography 
    variant='caption'
    className={classes.text}
    // sx={{ backgroundColor: backgroundColor }}
    style={{ backgroundColor: backgroundColor }}
  >
    {objectData.deviceVehicleLable}
  </Typography>
</Box>

Here is the code of passing props to the makeStyles function:

const backgroundColor = group.groupColor ? objectData.group.groupColor : 'orange'

const classes = useStyles({ backgroundColor })

const useStyles = makeStyles((theme) => ({
  marker: {
    '&.MuiAvatar-root': {
      height: 24,
      width: 24,
      color: 'white',
      backgroundColor: (props) => props.backgroundColor,
    },
  },
  icon: {
    height: '16px !important',
    width: '16px !important',
  },
  text: {
    color: 'white',
    whiteSpace: 'nowrap',
    marginLeft: '8px !important',
    padding: '2px 8px',
    textAlign: 'center',
    opacity: 0.85,
    filter: 'drop-shadow(0px 0px 3px rgba(0,0,0,0.2))',
    fontWeight: 600,
    backgroundColor: (props) => props.backgroundColor,
  },
}))

Here is the result of implementing style prop or passing props to the makeStyles function to replace the sx prop: image As we can see that the Typography component is still fine but the Avatar component doesn't.

siriwatknp commented 2 years ago

I guess this issue is either related to react18 or emotion.

It works in react 17: https://codesandbox.io/s/nameless-hooks-nc89ru?file=/src/Demo.tsx.

@Andarist Sorry for tagging you but I think you can shed some light for us on this.

Andarist commented 2 years ago

It's basically a "duplicate" of this issue: https://github.com/emotion-js/emotion/issues/2691