DEV Community

suvo
suvo

Posted on • Edited on

4 3

Upgrade material ui 4 makeStyles and withStyles to material ui 5 using emotion JS with TS

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 {}
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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}>
  )
}
Enter fullscreen mode Exit fullscreen mode

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`
  })
})

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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} />
}
Enter fullscreen mode Exit fullscreen mode

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.

Postmark Image

Speedy emails, satisfied customers

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up

Top comments (0)

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

👋 Kindness is contagious

Dive into an ocean of knowledge with this thought-provoking post, revered deeply within the supportive DEV Community. Developers of all levels are welcome to join and enhance our collective intelligence.

Saying a simple "thank you" can brighten someone's day. Share your gratitude in the comments below!

On DEV, sharing ideas eases our path and fortifies our community connections. Found this helpful? Sending a quick thanks to the author can be profoundly valued.

Okay