material-ui icon indicating copy to clipboard operation
material-ui copied to clipboard

[MUI v6] Box component warns about TypeScript type

Open mtr1990 opened this issue 1 year ago • 9 comments

Steps to reproduce

Based on the example provided from MUI https://github.com/mui/material-ui/tree/master/examples/material-ui-nextjs-ts

I have added the following code

import * as React from 'react';
import Container from '@mui/material/Container';
import Box, { BoxProps } from '@mui/material/Box';

function Li({ children, ...other }: BoxProps) {
  return (
    <Box
      component={'li'}
      {...other}
    >
      {children}
    </Box>
  );
}

function Ul({ children, ...other }: BoxProps) {
  return (
    <Box
      component={'ul'}
      {...other}
    >
      {children}
    </Box>
  );
}

function Svg({ children, ...other }: BoxProps) {
  return (
    <Box
      component={'svg'}
      width='24'
      height='25'
      viewBox='0 0 24 25'
      fill='none'
      xmlns='http://www.w3.org/2000/svg'
      {...other}
    >
      <path
        d='M4 22.5098L7.9997 15.5098H23.9998L19.9997 22.5098H4Z'
        fill='#3777E3'
      />
      <path
        d='M16.0003 15.5098H24.0001L16.0003 1.50977H8L16.0003 15.5098Z'
        fill='#FFCF63'
      />
      <path
        d='M0 15.5098L4.00024 22.5098L12 8.50977L7.99994 1.50977L0 15.5098Z'
        fill='#11A861'
      />
    </Box>
  );
}

export default function Home() {
  return (
    <Container maxWidth='lg'>
      <Svg />

      <Ul>
        <Li>Item 1</Li>
        <Li>Item 2</Li>
        <Li>Item 3</Li>
        <Li>Item 4</Li>
      </Ul>
    </Container>
  );
}

I use this method in MUI v5 without any problems. However, the error appears in MUI v6. I read in the documentation that components were removed in MUI v6 https://github.com/mui/material-ui/releases/tag/v6.0.0-rc.0

But I still see the document is still in use: https://mui.com/material-ui/react-box/#basics

So the component in Box is actually removed?

Current behavior

Error message appears

No overload matches this call.
  Overload 1 of 2, '(props: { component: "li"; } & BoxOwnProps<Theme> & Omit<Omit<DetailedHTMLProps<LiHTMLAttributes<HTMLLIElement>, HTMLLIElement>, "ref"> & { ...; }, keyof BoxOwnProps<...>>): Element | null', gave the following error.
    Type '{ children: ReactNode; ref?: Ref<unknown> | undefined; sx?: SxProps<Theme> | undefined; border?: ResponsiveStyleValue<number | (string & {}) | "inset" | "hidden" | "-moz-initial" | ... 194 more ... | undefined> | ((theme: Theme) => ResponsiveStyleValue<...>); ... 378 more ...; component: "li"; }' is not assignable to type 'Omit<Omit<DetailedHTMLProps<LiHTMLAttributes<HTMLLIElement>, HTMLLIElement>, "ref"> & { ref?: ((instance: HTMLLIElement | null) => void | (() => VoidOrUndefinedOnly)) | ... 2 more ... | undefined; }, keyof BoxOwnProps<...>>'.
      Types of property 'onToggle' are incompatible.
        Type 'ToggleEventHandler<HTMLDivElement> | undefined' is not assignable to type 'ToggleEventHandler<HTMLLIElement> | undefined'.
          Type 'ToggleEventHandler<HTMLDivElement>' is not assignable to type 'ToggleEventHandler<HTMLLIElement>'.
            Type 'HTMLDivElement' is missing the following properties from type 'HTMLLIElement': type, value
  Overload 2 of 2, '(props: DefaultComponentProps<BoxTypeMap<{}, "div", Theme>>): Element | null', gave the following error.
    Type '{ children: ReactNode; ref?: Ref<unknown> | undefined; sx?: SxProps<Theme> | undefined; border?: ResponsiveStyleValue<number | (string & {}) | "inset" | "hidden" | "-moz-initial" | ... 194 more ... | undefined> | ((theme: Theme) => ResponsiveStyleValue<...>); ... 378 more ...; component: string; }' is not assignable to type 'IntrinsicAttributes & BoxOwnProps<Theme> & Omit<Omit<DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & { ...; }, keyof BoxOwnProps<...>>'.
      Property 'component' does not exist on type 'IntrinsicAttributes & BoxOwnProps<Theme> & Omit<Omit<DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & { ...; }, keyof BoxOwnProps<...>>'.
No overload matches this call.
  Overload 1 of 2, '(props: { component: "ul"; } & BoxOwnProps<Theme> & Omit<Omit<DetailedHTMLProps<HTMLAttributes<HTMLUListElement>, HTMLUListElement>, "ref"> & { ...; }, keyof BoxOwnProps<...>>): Element | null', gave the following error.
    Type '{ children: ReactNode; ref?: Ref<unknown> | undefined; sx?: SxProps<Theme> | undefined; border?: ResponsiveStyleValue<number | (string & {}) | "inset" | "hidden" | "-moz-initial" | ... 194 more ... | undefined> | ((theme: Theme) => ResponsiveStyleValue<...>); ... 378 more ...; component: "ul"; }' is not assignable to type 'Omit<Omit<DetailedHTMLProps<HTMLAttributes<HTMLUListElement>, HTMLUListElement>, "ref"> & { ...; }, keyof BoxOwnProps<...>>'.
      Types of property 'onToggle' are incompatible.
        Type 'ToggleEventHandler<HTMLDivElement> | undefined' is not assignable to type 'ToggleEventHandler<HTMLUListElement> | undefined'.
          Type 'ToggleEventHandler<HTMLDivElement>' is not assignable to type 'ToggleEventHandler<HTMLUListElement>'.
            Type 'HTMLDivElement' is missing the following properties from type 'HTMLUListElement': compact, type
  Overload 2 of 2, '(props: DefaultComponentProps<BoxTypeMap<{}, "div", Theme>>): Element | null', gave the following error.
    Type '{ children: ReactNode; ref?: Ref<unknown> | undefined; sx?: SxProps<Theme> | undefined; border?: ResponsiveStyleValue<number | (string & {}) | "inset" | "hidden" | "-moz-initial" | ... 194 more ... | undefined> | ((theme: Theme) => ResponsiveStyleValue<...>); ... 378 more ...; component: string; }' is not assignable to type 'IntrinsicAttributes & BoxOwnProps<Theme> & Omit<Omit<DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & { ...; }, keyof BoxOwnProps<...>>'.
      Property 'component' does not exist on type 'IntrinsicAttributes & BoxOwnProps<Theme> & Omit<Omit<DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & { ...; }, keyof BoxOwnProps<...>>'.
No overload matches this call.
  Overload 1 of 2, '(props: { component: "svg"; } & BoxOwnProps<Theme> & Omit<Omit<SVGProps<SVGSVGElement>, "ref"> & { ref?: ((instance: SVGSVGElement | null) => void | (() => VoidOrUndefinedOnly)) | RefObject<...> | null | undefined; }, keyof BoxOwnProps<...>>): Element | null', gave the following error.
    Type '{ children: Element[]; ref?: Ref<unknown> | undefined; sx?: SxProps<Theme> | undefined; border?: ResponsiveStyleValue<number | (string & {}) | "inset" | "hidden" | "-moz-initial" | ... 194 more ... | undefined> | ((theme: Theme) => ResponsiveStyleValue<...>); ... 381 more ...; xmlns: string; }' is not assignable to type 'Omit<Omit<SVGProps<SVGSVGElement>, "ref"> & { ref?: ((instance: SVGSVGElement | null) => void | (() => VoidOrUndefinedOnly)) | RefObject<...> | null | undefined; }, keyof BoxOwnProps<...>>'.
      Types of property 'onCopy' are incompatible.
        Type 'ClipboardEventHandler<HTMLDivElement> | undefined' is not assignable to type 'ClipboardEventHandler<SVGSVGElement> | undefined'.
          Type 'ClipboardEventHandler<HTMLDivElement>' is not assignable to type 'ClipboardEventHandler<SVGSVGElement>'.
            Type 'HTMLDivElement' is missing the following properties from type 'SVGSVGElement': currentScale, currentTranslate, height, width, and 53 more.
  Overload 2 of 2, '(props: DefaultComponentProps<BoxTypeMap<{}, "div", Theme>>): Element | null', gave the following error.
    Type '{ children: Element[]; ref?: Ref<unknown> | undefined; sx?: SxProps<Theme> | undefined; border?: ResponsiveStyleValue<number | (string & {}) | "inset" | "hidden" | "-moz-initial" | ... 194 more ... | undefined> | ((theme: Theme) => ResponsiveStyleValue<...>); ... 381 more ...; xmlns: string; }' is not assignable to type 'IntrinsicAttributes & BoxOwnProps<Theme> & Omit<Omit<DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & { ...; }, keyof BoxOwnProps<...>>'.
      Property 'component' does not exist on type 'IntrinsicAttributes & BoxOwnProps<Theme> & Omit<Omit<DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & { ...; }, keyof BoxOwnProps<...>>'.

Expected behavior

No TypeScript errors appeared.

I would like to understand clearly how to use Box in MUI v6.

Because in the project I use a lot of places like:

  • <Box component={'li'}
  • <Box component={'ul'}
  • <Box component={'svg'} ...

So it will take a lot of time to restructure the whole thing.

Looking forward to suitable suggestions for this situation. Thanks!

mtr1990 avatar Oct 01 '24 12:10 mtr1990

I have the same issue. Waiting for maintainer. Thanks

sinhpn92 avatar Oct 11 '24 16:10 sinhpn92

I have the same issue. Waiting for maintainer. Thanks

Same here!

sjordan2010 avatar Oct 15 '24 16:10 sjordan2010

The trick here is to use a type generic:

function Li({ children, ...other }: BoxProps<'li'>) {

function Ul({ children, ...other }: BoxProps<'ul'>) {

function Svg({ children, ...other }: BoxProps<'svg'>) {

See demo: typescript playground

This ensures that TypeScript understands what's going on, and also makes sure that you can, for example, pass the viewBox prop to the Svg component but not to the Li and Ul components.

bartlangelaan avatar Oct 16 '24 19:10 bartlangelaan

The trick here is to use a type generic:

function Li({ children, ...other }: BoxProps<'li'>) {

function Ul({ children, ...other }: BoxProps<'ul'>) {

function Svg({ children, ...other }: BoxProps<'svg'>) {

See demo: typescript playground

This ensures that TypeScript understands what's going on, and also makes sure that you can, for example, pass the viewBox prop to the Svg component but not to the Li and Ul components.

This works perfectly for me, thank you!

sjordan2010 avatar Oct 17 '24 16:10 sjordan2010

The trick here is to use a type generic:

function Li({ children, ...other }: BoxProps<'li'>) {

function Ul({ children, ...other }: BoxProps<'ul'>) {

function Svg({ children, ...other }: BoxProps<'svg'>) {

See demo: typescript playground

This ensures that TypeScript understands what's going on, and also makes sure that you can, for example, pass the viewBox prop to the Svg component but not to the Li and Ul components.

@bartlangelaan Thank you it works well. But is there any solution when we use with styled() ?


<WithStyled3 active component="nav" sx={{ color: 'white' }} />
      
const WithStyled3 = styled(Box, {
  shouldForwardProp: (prop) => !['active'].includes(prop as string),
})<{ active?: boolean }>(({ theme }) => ({
  width: 100,
  height: 50,
  marginBottom: 24,
  backgroundColor: theme.palette.info.main,
  variants: [
    {
      props: { active: true },
      style: {
        backgroundColor: theme.palette.success.main,
      },
    },
  ],
})) as typeof Box;

https://stackblitz.com/edit/github-p7amox?file=src%2Fapp%2Fpage.tsx,package.json

mtr1990 avatar Oct 18 '24 07:10 mtr1990

In that case, you can just style a div instead of a Box. The styled utility always returns a component that has the sx prop available - you don't need a Box for that.

Instead of passing a component prop, you can pass the as prop.

// Instead of the 'component' prop, use the 'as' prop.
<WithStyled3 active as="nav" sx={{ color: 'white' }} />

// Style a 'div' instead of 'Box'.
const WithStyled3 = styled('div', {
  shouldForwardProp: (prop) => !['active'].includes(prop as string),
})<{ active?: boolean }>(({ theme }) => ({
  // styling.
  // You don't need the 'as typeof Box' anymore.
}));

bartlangelaan avatar Oct 18 '24 08:10 bartlangelaan

@bartlangelaan Thank you very much as= solved in this case.

Can you provide documentation with as= ?

Previously I only used component= without knowing about as= . I also can't find the official documentation page for this props (as=) on mui.com

The reason I want to use Box is because I want to inherit the default properties from Box to make the code more concise

In MUI v5 I just do this


<NavItemv5 active sx={{ color: 'white' }} />
      
const NavItemv5 = React.forwardRef<
  HTMLUListElement,
  BoxProps & { active?: boolean }
>(({ active, sx, ...other }, ref) => {
  return (
    <WithMuiv5 ref={ref} component="nav" active={active} sx={sx} {...other}>
      <span>Icon</span>
      <span>Text</span>
      <span>Arrow</span>
    </WithMuiv5>
  );
});

const WithMuiv5 = styled(Box, {
  shouldForwardProp: (prop) => !['active'].includes(prop as string),
})<{ active?: boolean }>(({ theme }) => ({
  width: 100,
  height: 50,
  marginBottom: 24,
  backgroundColor: theme.palette.info.main,
  variants: [
    {
      props: { active: true },
      style: {
        backgroundColor: theme.palette.success.main,
      },
    },
  ],
}));

But now v6 it becomes like this

<NavItemv6 active sx={{ color: 'white' }} />

const NavItemv6 = React.forwardRef<
  HTMLUListElement,
  React.HTMLAttributes<HTMLUListElement> & {
    active?: boolean;
    sx?: SxProps<Theme>;
  }
>(({ active, sx, ...other }, ref) => {
  return (
    <WithMuiv6 ref={ref} active={active} sx={sx} {...other}>
      <span>Icon</span>
      <span>Text</span>
      <span>Arrow</span>
    </WithMuiv6>
  );
});

const WithMuiv6 = styled('ul', {
  shouldForwardProp: (prop) => !['active', 'sx'].includes(prop as string),
})<{ active?: boolean }>(({ theme }) => ({
  width: 100,
  height: 50,
  marginBottom: 24,
  backgroundColor: theme.palette.info.main,
  variants: [
    {
      props: { active: true },
      style: {
        backgroundColor: theme.palette.success.main,
      },
    },
  ],
}));

It is much more cumbersome to define types

  • React.HTMLAttributes<HTMLUListElement> & { sx?: SxProps<Theme>}
  • React.HTMLAttributes<HTMLSpanElement> & { sx?: SxProps<Theme>}
  • React.LiHTMLAttributes<HTMLLIElement> & { sx?: SxProps<Theme>} ...

instead of BoxProps

https://stackblitz.com/edit/github-p7amox?file=src%2Fapp%2Fpage.tsx,src%2Fapp%2Flayout.tsx

mtr1990 avatar Oct 18 '24 09:10 mtr1990

If you use React.ComponentProps, it automatically takes the sx prop into account:

const NavItemv6 = React.forwardRef<
  HTMLUListElement,
  React.ComponentProps<typeof WithMuiv6>
>(({ active, sx, ...other }, ref) => {
  return (
    <WithMuiv6 ref={ref} active={active} sx={sx} {...other}>
      <span>Icon</span>
      <span>Text</span>
      <span>Arrow</span>,<li></li>
    </WithMuiv6>
  );
});

And because the active and sx prop are passed as-is, you can keep them in the other props if you want:

const NavItemv6 = React.forwardRef<
  HTMLUListElement,
  React.ComponentProps<typeof WithMuiv6>
>(({ ...other }, ref) => {
  return (
    <WithMuiv6 ref={ref} {...other}>
      <span>Icon</span>
      <span>Text</span>
      <span>Arrow</span>,<li></li>
    </WithMuiv6>
  );
});

bartlangelaan avatar Oct 18 '24 12:10 bartlangelaan

@bartlangelaan

Thanks for your efforts to help so far but in my case React.ComponentProps<typeof WithMuiv6> is shared in many places and this doesn't seem possible to me.

Maybe I will use this method. Although it is more cumbersome than the old method (MUI v5), it is more flexible

// file.types.ts
export  type NavItemProps = BoxProps<'ul'> & {
  open?: boolean;
  active?: boolean;
};
...

// file.item.ts
const NavItemv6 = forwardRef<HTMLUListElement, NavItemProps>(
  ({ active,  children, open, ...other }, ref) => (
    <WithMuiv6 ref={ref} open={open} active={active} {...other}>
      {children}
      <span> Icon </span>
       <span> Text </span>
    </WithMuiv6>
  )
);

const WithMuiv6 = styled(
  forwardRef((props: NavItemProps, ref) => <Box {...props} ref={ref} component="ul" />),
  {
    shouldForwardProp: (prop) => !['open', 'active'].includes(prop as string),
  }
)(({ theme }) => ({
  width: 100,
  height: 50,
  display: 'flex',
  marginBottom: 24,
  alignItems: 'center',
  justifyContent: 'center',
  backgroundColor: theme.palette.secondary.main,
  variants: [
    {
      props: { active: true },
      style: {
        backgroundColor: theme.palette.success.main,
      },
    },
    {
      props: { open: true },
      style: {
        backgroundColor: theme.palette.common.black,
      },
    },
  ],
}));



mtr1990 avatar Oct 18 '24 18:10 mtr1990

Building on earlier comments here, I settled on this:

// Before
function MyCustomComponent({ component, ...props }: BoxProps) {
    return <Box component={component} {...props} />
}

// After
const Bachs = styled('div')()
function MyCustomComponent({ as, ...props }: ComponentProps<typeof Bachs>) {
    return <Bachs as={as} {...props} />
}

This means I have to refactor my other code to send as instead of component, but otherwise it seems to work fine.

patik avatar Oct 29 '24 17:10 patik

Just stumbled upon this section of the docs. Based on that, I ended up with this:

import { ElementType } from 'react'

const MyComponent = (boxProps: BoxProps<ElementType, { component?: ElementType }>) => {
    return <Box {...boxProps} />
}

Or with ref forwarding:

import { ElementType, ForwardedRef } from 'react'

const MyComponent = (
    boxProps: BoxProps<ElementType, { component?: ElementType }>,
    ref: ForwardedRef<ElementType | null>
) => {
    return <Box {...boxProps} ref={ref} />
}

const MyComponentWithRef = forwardRef<ElementType | null, BoxProps<ElementType>>(MyComponent)

This avoids involved styled() and it means I can keep using the component prop instead of switching to as.

Edit: just noticed my ref solution accepts garbage, e.g. <MyComponentWithRef component="foo">. The first solution works, though.

patik avatar Oct 30 '24 09:10 patik

Hey everyone! Thanks for the reports and the patience.

This is an oversight of https://github.com/mui/material-ui/pull/43384, as the Material UI and System BoxProps types should both have had the component, but Material UI's didn't.

I opened a PR to fix this: https://github.com/mui/material-ui/pull/44643 Sorry for the inconvenience.

DiegoAndai avatar Dec 03 '24 13:12 DiegoAndai

This issue has been closed. If you have a similar problem but not exactly the same, please open a new issue. Now, if you have additional information related to this issue or things that could help future readers, feel free to leave a comment.

[!NOTE] @mtr1990 How did we do? Your experience with our support team matters to us. If you have a moment, please share your thoughts in this short Support Satisfaction survey.

github-actions[bot] avatar Dec 04 '24 13:12 github-actions[bot]

The fix has been merged, and it will be available starting from the next release (>6.1.10)

DiegoAndai avatar Dec 04 '24 13:12 DiegoAndai