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

Next 9 - Using functional components as child of <Link/> causes ref-warnings #7915

Closed
nickluger opened this issue Jul 12, 2019 · 57 comments · Fixed by #8254
Closed

Next 9 - Using functional components as child of <Link/> causes ref-warnings #7915

nickluger opened this issue Jul 12, 2019 · 57 comments · Fixed by #8254
Assignees
Labels
kind: bug Confirmed bug that is on the backlog
Milestone

Comments

@nickluger
Copy link

Bug report

Describe the bug

Using a functional component as a child of <Link/> causes:

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

This happened only after upgrading from v8 => v9.

To Reproduce

Reproduction repo: https://github.com/nickluger/nextjs-link-ref-functional-comp-warning

Expected behavior

<Link/> should work with functional components, too / not show a warning.

System information

  • Version of Next.js: 9.0.1
@mustaphaturhan
Copy link

Same here. I am having error If I use custom component like this:

        <Link href="/href">
          <Button
            type="custom"
            color="#FCFCFC"
          >
             buttonText
          </Button>
        </Link>

This is only happening when you use a component as a child. These don't give a warning for example:

        <Link href="/href">
          <button
            type="custom"
            color="#FCFCFC"
          >
            buttonText
          </button>
        </Link>

        <Link href="/href">
          <a>
            <Button
              type="custom"
              color="#FCFCFC"
            >
              buttonText
            </Button>
          </a>
        </Link>

@timneutkens timneutkens added this to the 9.0.2 milestone Jul 12, 2019
@timneutkens timneutkens added the kind: bug Confirmed bug that is on the backlog label Jul 12, 2019
@Timer Timer modified the milestones: 9.0.2, 9.0.3 Jul 15, 2019
@joaogarin
Copy link

Is there a way around this bug? we are blocked from going to 9 for this one when some features we would really like to leverage like the typescript support. Just noticed it bumped to 9.0.3 :/

@nickluger
Copy link
Author

You could try muting it temporarily , like:

// TODO: Muting error, fix as soon as zeit/next.js/issues/7915 resolved
const originalError = console.error;

console.error = (...args) => {
  if (/Warning.*Function components cannot be given refs/.test(args[0])) {
    return;
  }
  originalError.call(console, ...args);
};

@focux
Copy link

focux commented Jul 16, 2019

I'm having the same issue here.

@paulboony
Copy link

If you use <Link/> to wrap your custom component, you could forward ref like this:

const CustomComponent = React.forwardRef(function CustomComponent(props, ref) {
  return (
    <div/>
  );
});
<Link href={"/"}>
  <CustomComponent/>
</Link>

The warning goes away.

@th0th
Copy link

th0th commented Aug 8, 2019

I have updated to 9.0.4-canary.2 but I still get the warning on the console. Am I missing something?

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

Check the render method of `Link`.

@mustaphaturhan
Copy link

@th0th You should wrap your component in React.forwardRef. Like this one:

import React from 'react'
import Link from 'next/link'

const CustomComponent = React.forwardRef((props, ref) => (
  <a ref={ref} {...props}>
    Click
  </a>
))

export default () => (
  <Link href='/' passHref>
    <CustomComponent/>
  </Link>
)

@nickluger
Copy link
Author

Wrap all functional components used within a Link manually? Shouldn't there be a easier, less repetitive solution for this? At least something like <Link href="/" forwardRef>...</Link>?

@tm1000
Copy link
Contributor

tm1000 commented Aug 20, 2019

Agree with @nickluger this seems counter-intuitive.

@srghma
Copy link

srghma commented Aug 20, 2019

Should all other libraries implement forwardRef attribute in the same manner? Or maybe react creators thought that developers will use React.forwardRef function instead of implementing forwardRef attribute?

@DonovanCharpin
Copy link

DonovanCharpin commented Aug 29, 2019

Yes agree with @nickluger, it's a pain to do this manually for all the components inside a Link.

@nickluger
Copy link
Author

Should we reopen and rename this issue (@ijjk) or did someone already open a new one?

szymonlesisz added a commit to trezor/trezor-suite that referenced this issue Sep 16, 2019
@bschwartz757
Copy link

Same here. I am having error If I use custom component like this:

        <Link href="/href">
          <Button
            type="custom"
            color="#FCFCFC"
          >
             buttonText
          </Button>
        </Link>

This is only happening when you use a component as a child. These don't give a warning for example:

        <Link href="/href">
          <button
            type="custom"
            color="#FCFCFC"
          >
            buttonText
          </button>
        </Link>

        <Link href="/href">
          <a>
            <Button
              type="custom"
              color="#FCFCFC"
            >
              buttonText
            </Button>
          </a>
        </Link>

This will most likely throw a console error:

Warning: validateDOMNesting(...): <a> cannot appear as a descendant of <a>.

@JonCognioDigital
Copy link

JonCognioDigital commented Oct 1, 2019

I just hit this issue when upgrading Next JS. Is there a fix on the way?

I'm not sure I understand how to use forwardRef and wrap my component. My button.js file currently exports the following....

const Button = (props) => {
    return (<stuff />)
}

export default React.memo(Button)

@nickluger
Copy link
Author

See above, I opened a follow-up here: #8962

@bayraak
Copy link

bayraak commented Dec 15, 2020

I'm just creating a wrapper component for generic usage:

const CustomLinkWrapper = React.forwardRef((props: any, ref) => (
    <a ref={ref} {...props}>
        {props.children}
    </a>
));

and then I'm using this inside the Link like the following:

<Link
          href={`/[slug]?slug=${item.slug}`}
          as={`/${item.slug}`}
           >
                  <CustomLinkWrapper>
                    <ProductCard {...item}></ProductCard>
                  </CustomLinkWrapper>
</Link>

@devthejo
Copy link

import NextLink from "next/link";

function LinkWrap({ children, refAs, ...props }, ref) {
  if (refAs) {
    props[refAs] = ref;
  }
  return (
    <>
      {React.isValidElement(children)
        ? React.cloneElement(children, props)
        : null}
    </>
  );
}

const LinkWrapper = React.forwardRef(LinkWrap);

function Link({ refAs, children, ...props }) {
  return (
    <NextLink {...props}>
      <LinkWrapper refAs={refAs}>{children}</LinkWrapper>
    </NextLink>
  );
}

and use created Link component instead of next.js one

devthejo added a commit to SocialGouv/emjpm that referenced this issue Dec 17, 2020
* chore(clean-nextjs): fix boot errors

* feat(title): add page titles

* fix(bugfix-react-select): add instanceId everywhere

* fix(bugfix-ssr): fix ssr loading text conflict

* fix(fix-nextlink-ref): vercel/next.js/issues/7915#issuecomment-747433561
@machineghost

This comment has been minimized.

@Otoris
Copy link

Otoris commented Jan 28, 2021

@machineghost Be nice - there are human people on the receiving end of your words.

@adamduncan
Copy link

adamduncan commented Feb 12, 2021

If we're keen to use our own function component directly within Next's Link (e.g. there's a fair bit going on in the internals that we want to tap into directly, without reimplementing in for a Next app), is it bananas to create ourselves a proxy component that forwards on the refs and props? I.e. Based on child being function component

import { Button } from 'some-package';

const ButtonNext = React.forwardRef(({ children, ...rest }, ref) => (
  <span ref={ref}>
    <Button {...rest}>{children}</Button>
  </span>
));


// ...

<Link href="/" passHref>
  <ButtonNext>Go home</ButtonNext>
</Link>

We can't attach a ref to our component directly, so the wrapping span element takes it on for us. That's working, but certainly doesn't feel like it should. 🤷‍♂️

@arthurvergacas
Copy link

arthurvergacas commented Mar 9, 2021

Why is this issue closed? Was it solved? If so, where?
I solved it with the <div></div> trick, but still seems hacky.

@EverStarck
Copy link

Why is this issue closed? Was it solved? If so, where?
I solved it with the

trick, but still seems hacky.

I have a next <Image .../> component an a and it throw the same warning. I put a div inside Link and it works but yes, it seems hacky

@sshanzel
Copy link

sshanzel commented May 2, 2021

Didn't realize there are some issues like this lurking in NextJS. And by issue, I mean how the primary concern in this matter is seen as nothing to care about.

@MinyoungNa1997
Copy link

This is very much a riddle, do I spend time "fixing" this warning even though it's not a functional issue?

@OkkarMin
Copy link

OkkarMin commented Jun 2, 2021

The link that @cgarrovillo provided fixed the issue for me. The warnings went away and my custom functional component is working well with the link.

https://nextjs.org/docs/api-reference/next/link#if-the-child-is-a-function-component

@MattCollyer
Copy link

MattCollyer commented Jul 1, 2021

Wrapping it in a div tag worked, but it didn't render the Links anchor tag which could mess up SEO. Instead, I made a custom Link wrapper which wraps the component in an anchor tag- as described here. https://jasonwatmore.com/post/2021/05/31/next-js-make-the-link-component-work-like-react-router-link

@chasoft
Copy link

chasoft commented Jul 14, 2021

I've just encountered this error today! Problem fixed with this guide https://nextjs.org/docs/api-reference/next/link#if-the-child-is-a-function-component

@orangecoloured
Copy link

orangecoloured commented Aug 25, 2021

What's the use of forwarding the ref?

Can I simply have it like this?
const ComponentWithRef = React.forwardRef((props, ref) => <Component { ...props } />)

@fandy
Copy link

fandy commented Oct 7, 2021

+1 for this issue being re-opened. In the majority of cases Link is used to wrap a functional component, and using forwardRef in each component is not practical. Is there a reason the core team isn't open to implementing one of the suggested solutions?

Anyway we can contact the core team so Link is usable again?

@tri-bit
Copy link

tri-bit commented Oct 11, 2021

If I wrap the functional component in <a></a> it works fine with no forwardRef... and I've been using this method for years - does this break proper internal routing in some way I'm not seeing?

<Link href="/internal-link"><a><MyFunctionalComponent/></a></Link>

@theigwe
Copy link

theigwe commented Oct 16, 2021

So I followed this https://nextjs.org/docs/api-reference/next/link#if-the-child-is-a-custom-component-that-wraps-an-a-tag
And created this component for reuse when needed:

import Link from 'next/link';

const ButtonLink = ({ children, href, ...props }) => (
  <Link href={href} {...props} passHref>
    <a>{children}</a>
  </Link>
);

export { ButtonLink };

With this I have my component wrapped in <a href=''>

@manny-p
Copy link

manny-p commented Nov 7, 2021

If I wrap the functional component in <a></a> it works fine with no forwardRef... and I've been using this method for years - does this break proper internal routing in some way I'm not seeing?

<Link href="/internal-link"><a><MyFunctionalComponent/></a></Link>

yep this worked for me too!

@jps
Copy link

jps commented Nov 8, 2021

This might help others, this is how I've solved with TS. It does feel like a big dirty hack having to add this div I don't need in there. Problem is I don't have access to the underlying Card component to expose the ref on it's interface similar to Adam above.

import Link from 'next/link';
import { forwardRef } from 'react';

export interface MediaCardProps extends CardProps {
  headline: string;
  children: string;
  href: string;
}

type MediaCardPropsWithOptionalHref = Omit<MediaCardProps, 'href'> & {
  href?: string;
};

const CardWrapped = forwardRef<any, MediaCardPropsWithOptionalHref>(
  ({ children, headline, href }, ref) => (
    <div ref={ref}>
      <Card layout="vertical" href={href}>
          {/* redacted */}
      </Card>
    </div>
  )
);

export const MediaCard = ({ href, children, headline }: MediaCardProps) => (
  <Link href={href} passHref>
    <CardWrapped headline={headline}>{children}</CardWrapped>
  </Link>
);

@ricardo-rp
Copy link

ricardo-rp commented Nov 29, 2021

This is a small pain in the butt every time you need to use a Link. Most components are functional components nowadays.
Wrapping in <a> seems to be the most straightforward solution for most cases. I really think the nextjs team should think of a more convenient API though.

@balazsorban44
Copy link
Member

This issue has been automatically locked due to no recent activity. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.

@vercel vercel locked as resolved and limited conversation to collaborators Jan 27, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.