I just came across a very nice blog when the upgrading to mui5 using emotion, it is nicely demonstrated here
But there are a few things which are lacking in this implementation, i.e. TS support, how to handle withStyles Styled components.
In this blog post I would mention those missing items.
Styles Root part is same as mentioned in the above mentioned blog.
The emotion theme declaration
import { Theme as MuiTheme } from '@mui/material/styles'
import '@emotion/react'
declare module '@emotion/react' {
  // eslint-disable-next-line @typescript-eslint/no-empty-interface
  export interface Theme extends MuiTheme {}
}
Custom hook with TS support
import { useMemo } from 'react'
import { css, CSSInterpolation } from '@emotion/css'
import { useTheme } from '@emotion/react'
import { Theme as MuiTheme } from '@mui/material/styles'
function useEmotionStyles(
  styles: () => Record<string, CSSInterpolation>
): Record<string, ReturnType<typeof css>>
function useEmotionStyles(
  styles: (theme: MuiTheme) => Record<string, CSSInterpolation>
): Record<string, ReturnType<typeof css>>
function useEmotionStyles<T>(
  styles: (theme: MuiTheme, props: T) => Record<string, CSSInterpolation>,
  props: T
): Record<string, ReturnType<typeof css>>
function useEmotionStyles<T>(
  styles: (theme: MuiTheme, props?: T) => Record<string, CSSInterpolation>,
  props?: T
): Record<string, ReturnType<typeof css>> {
  const theme = useTheme()
  return useMemo(() => {
    const classes = styles(theme, props)
    const classNameMap = {}
    Object.entries(classes).forEach(([key, value]) => {
      classNameMap[key] = css(value)
    })
    return classNameMap
  }, [props, styles, theme])
}
export default useEmotionStyles
Here we have a overloaded hook for possible calls to the hook.
A simple example would be
type GridProps = { itemMargin: number | string }
const gridStyles = (theme: Theme, { itemMargin }: GridProps) => ({
  container: {
    display: 'flex',
    justifyContent: 'center',
    alignItems: 'center',
    flexWrap: 'wrap' as CSSTypes.Property.FlexWrap,
    maxWidth: theme.breakpoints.values.md,
    [theme.breakpoints.down('sm')]: {
      maxWidth: 420
    },
    '&>*': {
      margin: itemMargin
    }
  }
})
const Component = () => {
  const { container } = useEmotionStyles<GridProps>(gridStyles, { itemMargin })
  return (
    <Container className={container}>
  )
}
If you want to implement keyframes animations using emotion you can use this way.
import { css, keyframes } from '@emotion/react'
const fadeIn = keyframes({
  '0%': {
    opacity: 0
  },
  '100%': {
    opacity: 1
  }
})
const styles = () => ({
  text: css({
    display: 'flex',
    alignItems: 'center',
    animation: `${fadeIn} 2s`
  })
})
Custom Hook for Styled Component (replacement for withStyles)
import React, { useMemo } from 'react'
import { Theme, useTheme } from '@emotion/react'
import { Theme as MuiTheme } from '@mui/material/styles'
import styled, { StyledComponent } from '@emotion/styled/macro'
import { CSSInterpolation } from '@emotion/css'
import {
  OverridableComponent,
  OverridableTypeMap
} from '@mui/material/OverridableComponent'
type ReturnedType<T extends ComponentType> = StyledComponent<
  JSX.LibraryManagedAttributes<T, React.ComponentProps<T>> & {
    theme?: Theme
  }
>
type ComponentType =
  | OverridableComponent<OverridableTypeMap>
  | React.JSXElementConstructor<JSX.Element>
  | ((props?: React.ComponentProps<any>) => JSX.Element)
function useEmotionStyledComponent<T extends ComponentType>(
  styles: () => Record<string, CSSInterpolation>,
  WrappedComponent: T
): ReturnedType<T>
function useEmotionStyledComponent<T extends ComponentType>(
  styles: (theme: MuiTheme) => Record<string, CSSInterpolation>,
  WrappedComponent: T
): ReturnedType<T>
function useEmotionStyledComponent<T extends ComponentType, R>(
  styles: (theme: MuiTheme, props: R) => Record<string, CSSInterpolation>,
  WrappedComponent: T,
  props: R
): ReturnedType<T>
function useEmotionStyledComponent<T extends ComponentType, R>(
  styles: (theme: MuiTheme, props?: R) => Record<string, CSSInterpolation>,
  WrappedComponent: T,
  props?: R
): ReturnedType<T> {
  const theme = useTheme()
  return useMemo(() => {
    const strings = styles(theme, props)
    return styled(WrappedComponent)(strings?.root)
  }, [WrappedComponent, props, styles, theme])
}
export default useEmotionStyledComponent
To use this hook there must be only one root element in styles and all styles must be inside it.
const StyledDialog = (props: DialogProps) => {
  const Component = useEmotionStyledComponent<typeof Dialog>(
    (theme: Theme) => ({
      root: {
        '& div.MuiDialog-container': {
          height: 'auto'
        },
        '& div.MuiDialog-paper': {
          alignItems: 'center',
          padding: theme.spacing(0, 2, 2, 2),
          minWidth: 240
        }
      }
    }),
    Dialog
  )
  return <Component {...props} />
}
const MenuButton = (props: FabProps) => {
  const StyledMenuButton = useEmotionStyledComponent<typeof Fab, FabProps>(
    (theme: Theme) => ({
      root: {
        position: 'fixed',
        top: theme.spacing(2),
        left: theme.spacing(4)
      }
    }),
    Fab,
    props
  )
  return <StyledMenuButton {...props} />
}
and use this component as a Styled component.
With this two custom hooks you can replace the makeStyles and withStyles, if you have any questions, let me know.
 

 
    
Top comments (0)