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)