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)