The following code in not compiling anymore in mui v4.0:
<ListItem button={editable} >
<ListItemText primary="text" />
</ListItem>
where editable
is a boolean value.
Should compile without error.
Type 'boolean' is not assignable to type 'true'.
button={editable}
../../../node_modules/@material-ui/core/ListItem/ListItem.d.ts:23:38
23 ExtendButtonBase
The expected type comes from property 'button' which is declared here on type 'IntrinsicAttributes & { action?: ((actions: ButtonBaseActions) => void) | undefined; buttonRef?: ((instance: any) => void) | RefObject
| Tech | Version |
|--------------|---------|
| Material-UI | 4.0.0-alpha.4 |
| React | 16.8.4 |
| @types/react | 16.8.8 |
| TypeScript | 3.3.3333 |
I fail to reproduce the problem. Do you have a reproduction?
Thank you @oliviertassinari for looking at it.
I cannot reproduce on CodeSandbox, even if I use the same configuration as home. :disappointed_relieved:
I made a minimal project where the compilation fails because of this issue (see its README in order to see how to use it):
https://github.com/sveyret/issue-mui-14971
@sveyret Please add a lockfile to your repository. Otherwise changed dependencies over time might change to result.
@eps1lon Done.
@sveyret Looks like a limitation of TypeScript. For more details see #15049. Will keep an eye on this one but you might have to use an any
cast
Thank you @eps1lon, I hadn't notice it became a discriminent. I'll update my code for that.
We have added a test which includes a workaround (with runtime overhead) for this issue. If anybody can come up with a solution that breaks the test in #15049 but passes all other tests we would be happy to accept PRs with that patch.
I have the same issue with MeunItem
component...
Just wanted to add that if you provide ref
prop to ListItem
then typescript requires that you specify button={true}
. It doesn't accept false
and it doesn't allow you to omit button
prop. So the workaround from this test https://github.com/mui-org/material-ui/pull/15049 doesn't work here.
The workaround I'm using is basically hiding ref
prop from the compiler:
// Only store `ref` here
const restProps: any = {
ref: myRef,
};
return (
<ListItem button={false} {...restProps}>
{content}
</ListItem>
);
Another way would be to drop typechecking for ListItem
entirely, but it's a bit worse imo.
const ListItemComponent = ListItem as any;
<ListItemComponent button={false} ref={myRef}>
{content}
</ListItemComponent>
Would be happy to know if there is a better workaround.
Just wanted to add that if you provide ref prop to ListItem then typescript requires that you specify button={true}
What type is that ref?
Just wanted to add that if you provide ref prop to ListItem then typescript requires that you specify button={true}
What type is that ref?
React.RefObject<HTMLDivElement>
That you encounter an error is expected though. It's just not a helpful error message:
The component used for the root node. Either a string to use a DOM element or a component. By default, it's a li when button is false and a div when button is true.
-- https://material-ui.com/api/list-item/#listitem-api
For button={false}
you will get a ref to an HTMLLIElement
. With button={true}
you will get HTMLDivElement
.
However, it might not be possible by falling back to a more abstract type. If you use more abstract refs I recommend casting to any
. For specific ref types our types should catch errors (without a helpful message though).
@eps1lon got it, no error after specifying correct ref type. Thank you! My apology for hijacking this thread, my problem clearly had nothing to do with this issue.
Is there a fix or for the initial issue from @sveyret ? :)
Thanks!
/related #16245
Reproduction/broken test pr #16315
Just wanted to add that if you provide
ref
prop toListItem
then typescript requires that you specifybutton={true}
. It doesn't acceptfalse
and it doesn't allow you to omitbutton
prop. So the workaround from this test #15049 doesn't work here.The workaround I'm using is basically hiding
ref
prop from the compiler:// Only store `ref` here const restProps: any = { ref: myRef, }; return ( <ListItem button={false} {...restProps}> {content} </ListItem> );
Another way would be to drop typechecking for
ListItem
entirely, but it's a bit worse imo.const ListItemComponent = ListItem as any; <ListItemComponent button={false} ref={myRef}> {content} </ListItemComponent>
Would be happy to know if there is a better workaround.
What's the point of having this button as required and it's value can only be true🤦♂️
In case it got drowned out: The latest status is https://github.com/mui-org/material-ui/issues/14971#issuecomment-477029285.
Probably a silly question -
From the type definition of ListItem
, I see this:
declare const ListItem: OverridableComponent<ListItemTypeMap<{ button?: false }, 'li'>> &
ExtendButtonBase<ListItemTypeMap<{ button: true }, 'div'>>;
I wonder why we are explicitly putting false
here as the type?
I wonder why we are explicitly putting false here as the type?
false | undefined
is the discriminator for when a ListItem does not use Buttontrue
for when it does use a ButtonSee http://www.typescriptlang.org/docs/handbook/advanced-types.html#discriminated-unions
I have the same issue. I would like to do <ListItem button={false} />
, but I can't now because, if specificed, boolean
must be true, which doesn't make sense for me.
Types should not contain the default value. I think the good way to do is to replace { button?: false }
by { button?: boolean }
, and in the definition of ListItem
fallback button
to false
when undefined
and li
and fallback to true
when undefined
and div
.
In my case, this particular issue causes errors with ListItem's "component" prop as well. If I manually edit the { button: true }
to { button: boolean }
and { button?: false }
to { button?: boolean }
, then the "component" prop is getting suddenly available on ListItem.
Having the same issue, I'm trying to do
const unlocked = status !== "locked";
return(<ListItem button={unlocked} />)
and I get the error message Type 'boolean' is not assignable to type 'true'.
.
Seems pretty counter-intuitive to me
Getting a similar problem when using styled-components
like so:
const StyledListItem = styled(ListItem)`
&:hover {
background-color: rgba(58, 189, 159, 0.15);
}
`;
which gives me the error No overload matches this call.
and says ListItem.d.ts(24, 38): 'button' is declared here.
. No error if I use it like <StyledListItem button>
, but without button
it does give an error. Idk if this is expected behaviour or not.
Having the same issue, I'm trying to do
const unlocked = status !== "locked"; return(<ListItem button={unlocked} />)
and I get the error message
Type 'boolean' is not assignable to type 'true'.
.Seems pretty counter-intuitive to me
@ErwanDL For me a cast worked:
`
return(<ListItem button={unlocked as true | undefined} />)
Can't the evolution provided by TS 3.5 https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-5.html#smarter-union-type-checking provide a way to correct this?
Can't the evolution provided by TS 3.5 typescriptlang.org/docs/handbook/release-notes/typescript-3-5.html#smarter-union-type-checking provide a way to correct this?
Since we rely on function overloads the feature in 3.5 does not help us. It only seems to apply to object property checking.
The following "solution" doesn't fix the root problem with the TS MUI types but provides a way to temporarily overcome the typescript errors.
Declare a custom interface:
interface CustomMenuItemProps extends MuiMenuItemProps {
button?: any;
}
Create a new component wrapping the original material-ui component and pass in the TS interface:
<MuiMenuItem
{...CustomMenuItemProps}
ref={ref}
>
{children}
</MuiMenuItem>
Landed here after trying to calculate if a ListItem
should be a button based on props. After seeing the same thing @franklixuefei talked about, I wasn't sure how we're supposed to toggle this property.
I'd like to be able to do something like <ListItem button={!isDisabled}>...
. Is the only workaround at this point to wrap the component as @ClovisLeb mentioned?
[Edit] Of course as soon as I typed this up I thought, "why not just make your bool 'any'?" That works. So now I'm using this to toggle the button:
const isButton: any = isDisabled || isLocked ? false : true;
<ListItem button={isButton} ...
As mentioned above it's just a downside of using boolean literals as a discriminator. It's necessary to conditionally show what props are available.
Either be explicit:
if (showButton) {
return <ListItem button={true} />;
} else {
return <ListItem button={false} />;
}
Or cast to any:
<ListItem button={showButton as any} />
Looks like a limitation of TypeScript. For more details see #15049. Will keep an eye on this one but you might have to use an
any
cast
@eps1lon thank you for pointing me to the same issue.
IMHO, casting with any
might be not a nice solution due the following reason:
any
in the following rule '@typescript-eslint/no-explicit-any': 'off'
, it can make someone produce/forgetting any types anywhere in the code. It is very nice to prevent using any because it can open a question of what is type shape this variable could be (Especially if the name of the variable is not meaningful enough).Typescript sometimes makes me scratch my head as this issue does to me. I was thinking about using enum
but not sure if it nice to make user import the enum in his code in order to use it. But on the other side, we don't need if
else
also (I might be wrong tho).
enum ClassOfBook {
YES = 'yes',
NO = 'no'
}
interface Book {
isBook: ClassOfBook
}
interface OverloadedComponent {
(props: Book): void;
}
declare const SomeComponent: OverloadedComponent;
function Library(props: Book) {
const { isBook } = props;
SomeComponent(props);
SomeComponent({ isBook: ClassOfBook.YES });
}
Furthermore, ts can't compile styled ListItem with button prop that is set to inline false
https://codesandbox.io/s/damp-grass-3w9vs?file=/src/App.tsx
Most helpful comment
I have the same issue. I would like to do
<ListItem button={false} />
, but I can't now because, if specificed,boolean
must be true, which doesn't make sense for me.Types should not contain the default value. I think the good way to do is to replace
{ button?: false }
by{ button?: boolean }
, and in the definition ofListItem
fallbackbutton
tofalse
whenundefined
andli
and fallback totrue
whenundefined
anddiv
.