Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[MUI v6] Box component warns about TypeScript type #43955

Closed
mtr1990 opened this issue Oct 1, 2024 · 14 comments · Fixed by #44643
Closed

[MUI v6] Box component warns about TypeScript type #43955

mtr1990 opened this issue Oct 1, 2024 · 14 comments · Fixed by #44643
Assignees
Labels
bug 🐛 Something doesn't work component: Box The React component. typescript

Comments

@mtr1990
Copy link

mtr1990 commented Oct 1, 2024

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 mtr1990 added the status: waiting for maintainer These issues haven't been looked at yet by a maintainer label Oct 1, 2024
@zannager zannager added the component: Box The React component. label Oct 3, 2024
@sinhpn92
Copy link

I have the same issue. Waiting for maintainer. Thanks

@DiegoAndai DiegoAndai moved this to Backlog in Material UI Oct 14, 2024
@sjordan2010
Copy link

I have the same issue. Waiting for maintainer. Thanks

Same here!

@bartlangelaan
Copy link

bartlangelaan commented Oct 16, 2024

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.

@sjordan2010
Copy link

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!

@mtr1990
Copy link
Author

mtr1990 commented Oct 18, 2024

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

@bartlangelaan
Copy link

bartlangelaan commented Oct 18, 2024

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.
}));

@mtr1990
Copy link
Author

mtr1990 commented Oct 18, 2024

@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

@bartlangelaan
Copy link

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

@mtr1990
Copy link
Author

mtr1990 commented Oct 18, 2024

@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,
      },
    },
  ],
}));

@patik
Copy link

patik commented Oct 29, 2024

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
Copy link

patik commented Oct 30, 2024

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.

@DiegoAndai DiegoAndai assigned DiegoAndai and unassigned siriwatknp Nov 29, 2024
@DiegoAndai DiegoAndai moved this from Backlog to Selected in Material UI Nov 29, 2024
@DiegoAndai
Copy link
Member

Hey everyone! Thanks for the reports and the patience.

This is an oversight of #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: #44643
Sorry for the inconvenience.

@DiegoAndai DiegoAndai added bug 🐛 Something doesn't work typescript and removed status: waiting for maintainer These issues haven't been looked at yet by a maintainer labels Dec 3, 2024
@DiegoAndai DiegoAndai moved this from Selected to In progress in Material UI Dec 3, 2024
@github-project-automation github-project-automation bot moved this from In progress to Done in Material UI Dec 4, 2024
Copy link

github-actions bot commented Dec 4, 2024

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.

@DiegoAndai
Copy link
Member

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug 🐛 Something doesn't work component: Box The React component. typescript
Projects
Status: Done
Development

Successfully merging a pull request may close this issue.

8 participants