The Wayback Machine - https://web.archive.org/web/20201128063658/https://github.com/mui-org/material-ui/issues/23622
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

[ListItem] <ListItem component /> require to forwardRef #23622

Open
zaneclaes opened this issue Nov 19, 2020 · 16 comments
Open

[ListItem] <ListItem component /> require to forwardRef #23622

zaneclaes opened this issue Nov 19, 2020 · 16 comments

Comments

@zaneclaes
Copy link

@zaneclaes zaneclaes commented Nov 19, 2020

Prior to upgrading to the 5.x alpha, the following code from the docs worked:

const link = (p: unknown) => <Link to={route} {...p} />;
<ListItem button key={route} component={link}>
  • The issue is present in the latest release.
  • I have searched the issues of this repository and believe that this is not a duplicate.

I also looked at the release notes and the specs provided in the source code, but can't seem to find anything.

Current Behavior 😯

Throws a runtime error:

Warning: Failed prop type: Invalid prop `component` supplied to `ForwardRef(ButtonBase)`. Expected an element type that can hold a ref. Did you accidentally provide a plain function component instead?

And:

Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

The component does render correctly, though.

Expected Behavior 🤔

No runtime errors ;)

Your Environment 🌎

Tech Version
Material-UI v5.0.0-alpha16
React 17.0.0
Browser Chrome
TypeScript Yes (4x)
etc.
@oliviertassinari
Copy link
Member

@oliviertassinari oliviertassinari commented Nov 19, 2020

Why is the new warning wrong?

@zaneclaes
Copy link
Author

@zaneclaes zaneclaes commented Nov 19, 2020

@oliviertassinari I make no claims about the "warning being wrong," but obviously it broke my app when I migrated to v5 😬

If this is an intended syntax change, I am looking for documentation on the migration solution. As I said in the OP, I have examined the release notes and the specs (in the source code) and do not see an obvious migration path.

@zaneclaes
Copy link
Author

@zaneclaes zaneclaes commented Nov 19, 2020

FWIW, I've also tried using const link = React.ForwardRef<>() syntax, but the resulting object was not accepted by the ListItem's component validation.

@eps1lon
Copy link
Member

@eps1lon eps1lon commented Nov 19, 2020

FWIW, I've also tried using const link = React.ForwardRef<>() syntax, but the resulting object was not accepted by the ListItem's component validation.

Could you share that particular code? It should work.

@zaneclaes
Copy link
Author

@zaneclaes zaneclaes commented Nov 19, 2020

Sure thing. I relied on the IDE to complete the HTMLAnchorElement bits, so perhaps there's an alternate way to declare the link I'm not aware of.

function renderItem(route: string, text: string, icon: React.ReactNode, t2?: string) {
    const link = React.forwardRef<HTMLAnchorElement, Props>((p, ref) =>
      <Link to={route} {...p} ref={ref} />
    );
    return (
      <ListItem selected={isRouteActive(route)} button key={route} component={link}>
        <ListItemIcon>{icon}</ListItemIcon>
        <ListItemText primary={<Typography variant='h6'>{text}</Typography>} />
      </ListItem>
    );
  }

Exact error is:

No overload matches this call.
  The last overload gave the following error.
    Type '{ children: Element[]; selected: boolean; button: true; key: string; component: ForwardRefExoticComponent<OwnProps & RefAttributes<HTMLAnchorElement>>; }' is not assignable to type 'IntrinsicAttributes & { button: true; } & { alignItems?: "center" | "flex-start" | undefined; autoFocus?: boolean | undefined; children?: ReactNode; ... 7 more ...; selected?: boolean | undefined; } & Pick<...> & CommonProps & Pick<...>'.
      Property 'component' does not exist on type 'IntrinsicAttributes & { button: true; } & { alignItems?: "center" | "flex-start" | undefined; autoFocus?: boolean | undefined; children?: ReactNode; ... 7 more ...; selected?: boolean | undefined; } & Pick<...> & CommonProps & Pick<...>'.  TS2769

    61 |     );
    62 |     return (
  > 63 |       <ListItem selected={isRouteActive(route)} button key={route} component={link}>
       |                                                                    ^
    64 |         <ListItemIcon>{icon}</ListItemIcon>
    65 |         <ListItemText primary={<Typography variant='h6'>{text}</Typography>} secondary={s} />
    66 |       </ListItem>
@eps1lon
Copy link
Member

@eps1lon eps1lon commented Nov 20, 2020

Thanks. I'll take a look later what's wrong with the types. In the meantime, don't create components during render.

@eps1lon eps1lon added this to the v5 milestone Nov 20, 2020
@zaneclaes
Copy link
Author

@zaneclaes zaneclaes commented Nov 20, 2020

@eps1lon copying the code exactly from the example in the link, I first get:

'to' is specified more than once, so this usage will be overwritten.  TS2783

    21 |     () =>
    22 |       React.forwardRef<HTMLAnchorElement, Props>((linkProps, ref) => (
  > 23 |         <Link ref={ref} to={to} {...linkProps} />
       |                         ^
    24 |       )),
    25 |     [to],
    26 |   );

note: Typescript required that I add the <HTMLAnchorElement, Props>.

Removing the {...linkProps} so I can keep moving, I then get:

No overload matches this call.
  The last overload gave the following error.
    Type '{ children: Element[]; selected: boolean; button: true; component: ForwardRefExoticComponent<OwnProps & RefAttributes<HTMLAnchorElement>>; }' is not assignable to type 'IntrinsicAttributes & { button: true; } & { alignItems?: "center" | "flex-start" | undefined; autoFocus?: boolean | undefined; children?: ReactNode; ... 7 more ...; selected?: boolean | undefined; } & Pick<...> & CommonProps & Pick<...>'.
      Property 'component' does not exist on type 'IntrinsicAttributes & { button: true; } & { alignItems?: "center" | "flex-start" | undefined; autoFocus?: boolean | undefined; children?: ReactNode; ... 7 more ...; selected?: boolean | undefined; } & Pick<...> & CommonProps & Pick<...>'.  TS2769

    27 | 
    28 |   return (
  > 29 |     <ListItem selected={selected} button component={CustomLink}>
       |                                          ^
    30 |       <ListItemIcon>{icon}</ListItemIcon>
    31 |       <ListItemText primary={<Typography variant='h6'>{title}</Typography>} secondary={s} />
    32 |     </ListItem>

@oliviertassinari
Copy link
Member

@oliviertassinari oliviertassinari commented Nov 21, 2020

@zaneclaes So If I summarize:

  1. the forward ref warnings in the reproduction are expected. The button needs to access the ref.
  2. we miss a reproduction that demonstrates there is an issue with TypeScript. I can't reproduce the error: https://codesandbox.io/s/listrouter-material-demo-forked-kodox?file=/demo.tsx. This seems to work fine, using the demo of the docs:
import * as React from "react";
import ListItem from "@material-ui/core/ListItem";
import { MemoryRouter } from "react-router";
import { Link, LinkProps } from "react-router-dom";

export default function ListItemLink() {
  const CustomLink = React.forwardRef<any, Omit<LinkProps, "to">>(
    (props, ref) => <Link ref={ref} to="login" {...props} />
  );

  return (
    <MemoryRouter initialEntries={["/drafts"]} initialIndex={0}>
      <li>
        <ListItem button component={CustomLink} />
      </li>
    </MemoryRouter>
  );
}
@zaneclaes
Copy link
Author

@zaneclaes zaneclaes commented Nov 22, 2020

the forward ref warnings in your demo are expected. The button needs to access the ref.

Again, I did not claim otherwise.

I can't reproduce your error:

Well, you changed the code. I explicitly said:

so perhaps there's an alternate way to declare the link I'm not aware of.

And, indeed, there is. The use of Omit<OwnProps, 'to'> fixes both problems. So, thank you!

Obviously, I know this is an alpha. But this would be a very good addition to the docs :)

To be clear, this syntax: React.forwardRef<HTMLAnchorElement, LinkProps> causes both errors:
Screen Shot 2020-11-21 at 6 52 45 PM

Excluding the {...props}, like I did in my prior post, makes the first error go away:
Screen Shot 2020-11-21 at 6 53 34 PM

But the use of Omit is required to fix the original issue:
Screen Shot 2020-11-21 at 6 54 46 PM

@zaneclaes zaneclaes closed this Nov 22, 2020
@oliviertassinari
Copy link
Member

@oliviertassinari oliviertassinari commented Nov 22, 2020

@zaneclaes Great! Happy to hear the issue is resolved. It also doesn't seem there is any room for us to improve the documentation or the implementation.

Again, I did not claim otherwise.

The objective was to bring clarity. It seemed that the conversion switched from JavaScript error to TypeScript at some point for unclear reasons. It would have probably been confusing for an external reader.

@zaneclaes
Copy link
Author

@zaneclaes zaneclaes commented Nov 22, 2020

It also doesn't seem there is any room for us to improve the documentation

I searched the docs for Omit, and it is never referenced. I strongly feel that there should be a working Typescript example in the docs. I would have never figured this out on my own. The second error (pasted above) is far too obscure to debug, and did not obviously point at a solution.

@oliviertassinari
Copy link
Member

@oliviertassinari oliviertassinari commented Nov 22, 2020

@zaneclaes Did you find this demo? https://next.material-ui.com/guides/composition/#list, it matches your use case.

@zaneclaes
Copy link
Author

@zaneclaes zaneclaes commented Nov 22, 2020

Huh, I stand corrected! I don't know how I missed that. Thanks for pointing it out. Here are the places I read during the course of this investigation:

  • The release notes (migration guide)
  • The specs in the source code
  • The link from above
  • The 5.0.0 "Typescript" docs
  • The search bar at the top of the "5.0.0" docs (searched for: "Omit")

I'm convinced you're right, though since this is a breaking change, I still think the migration path should be more obvious ;) Hopefully just before the public release, a list of all the breaking changes can be compiled to aid in migration.

@oliviertassinari
Copy link
Member

@oliviertassinari oliviertassinari commented Nov 22, 2020

@zaneclaes
Copy link
Author

@zaneclaes zaneclaes commented Nov 22, 2020

FWIW, I really like how so many of the examples have both JS and Typescript. It'd be great if that were universal, though again, I know this isn't a priority 🙂

@oliviertassinari oliviertassinari changed the title [alpha16] <ListItem component /> does not work with functional components any more [ListItem] <ListItem component /> require to forwardRef Nov 24, 2020
@oliviertassinari
Copy link
Member

@oliviertassinari oliviertassinari commented Nov 24, 2020

@zaneclaes I would propose the following diff, in case you want to work on a pull request :). I think that at this point, it's easier to remove the types than to add them.

diff --git a/docs/src/pages/guides/composition/composition.md b/docs/src/pages/guides/composition/composition.md
index 94d087025b..0c0aeebe07 100644
--- a/docs/src/pages/guides/composition/composition.md
+++ b/docs/src/pages/guides/composition/composition.md
@@ -85,15 +85,15 @@ function ListItemLink(props) {
 The solution is simple: **avoid inline functions and pass a static component to the `component` prop** instead.
 Let's change the `ListItemLink` component so `CustomLink` always reference the same component:

-```jsx
-import { Link } from 'react-router-dom';
+```tsx
+import { Link, LinkProps } from 'react-router-dom';

 function ListItemLink(props) {
   const { icon, primary, to } = props;

   const CustomLink = React.useMemo(
     () =>
-      React.forwardRef((linkProps, ref) => (
+      React.forwardRef<any, Omit<LinkProps, 'to'>>((linkProps, ref) => (
         <Link ref={ref} to={to} {...linkProps} />
       )),
     [to],
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
3 participants
You can’t perform that action at this time.