Next.js: Next 9 - Using functional components as child of <Link/> causes ref-warnings

Created on 12 Jul 2019  ·  32Comments  ·  Source: vercel/next.js

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
bug

Most helpful comment

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>?

All 32 comments

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>

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 :/

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

I'm having the same issue here.

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.

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`.

@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>
)

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>?

Agree with @nickluger this seems counter-intuitive.

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?

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

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

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>.

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)

See above, I opened a follow-up here: https://github.com/zeit/next.js/issues/8962

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)

In this case, you should be able to just do

import {forwardRef} from 'react';
const Button = forwardRef(props, ref) => {
    return (<stuff ref={ref} />)
}

for the forwardRef part. The docs also mention https://nextjs.org/docs#forcing-the-link-to-expose-href-to-its-child, though I haven't tried that. I've found that doing this

            <Link
              href={href}
            >
              <a>
                {text}
              </a>
            </Link>

works (despite looking very odd.)

Encountering this issue in 9.0.2

Using <a></a> around may work, but will trigger other error like "must have href", etc. when using a11y and similar.

Edit: Spent 15mn trying to figure this out, it's very boring, such a waste of time, and seems to behave properly despite the warning.

Personally, going for https://github.com/zeit/next.js/issues/7915#issuecomment-511792629 much easier.

What about this solution using a react fragment as a bearing wrapper?

import React, { Fragment, useRef } from 'react';
import { MyFunctionalComponent } from './my-functional-component';

const App = props => {
  const myRef = useRef();

  return ( 
    <Link href={href}>
      <Fragment>
          <MyFunctionalComponent ref={myRef} />
      </Fragment>
    </Link>
  );
}

Where <MyFunctionalComponent> forwards the ref:

import React, { forwardRef } from 'react';

const MyFunctionalComponent = forwardRef(props, ref) => (
  <div ref={ref} />
);

This doesn't add any additional HTML wrapper in the output (<div />) and should not trigger any related error.

@hill84 Interesting, but where does myRef comes from in your example?

@Vadorequest I've added more details to my previous comment.

Following @hill84's suggestion was a nice workaround:
```
const ButtonLink: FC = ({ children, href, ...props }) => (
<>

Following @hill84's suggestion was a nice workaround:

const ButtonLink: FC<ButtonLinkProps> = ({ children, href, ...props }) => (
  <Link href={href}>
    <><Button link {...props}>{children}</Button></>
  </Link>
);

I use <><Component/></> but Link can not action. I have to use <div></div> instead of.

I suggest a solution where a function returns an object with props instead of a component :

const propsUserBtn = user => ({
    className: "h-10 w-10 font-black rounded-full bg-orange-200 text-teal-700",
    title: user.first_name + ' ' + user.last_name,
    children: (user.first_name.charAt(0) + user.last_name.charAt(0)).toUpperCase()
})
<Link href='/profile'>
    <button { ...propsUserBtn(user) } />   
</Link>

Checked, it works!

Why is this ref even needed? Is it to guarantee the correct placement of the href in the underlying wrapped component when passHref is used in the <Link />?

This is super annoying as my custom

Or at least have it be an opt in feature if you need to specify the placement of the href in your component. <Link href="/" passHref useRef />?

I don't think that having to create another module to forward the ref to a custom component is a good solution and also it's too repetitive.

The way that I'm using to handle this issue is by wrapping the custom component with a div element like suggested by @namlq93 but it also doesn't seem to be clean...

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>

It works, thank mate!!

Am having a similar issue

still there

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

This is sad.

I was watching the NextJS conference the other day thinking "Wow Next is the future". I really like Next and the Vercel ecosystem and I want to honestly be able to say that and be an advocate for Next. However, issues like these being closed and not addressed make it hard to do so. I've seen this type of pattern multiple times throughout the issues here, specifically with the Router.

There's 3 solutions to this problem from the user side and none of them are great DX.

  1. The recommended way React.forwardRef. wrap the component in React.forwardRef. This can be very repetitive
  2. Wrap the functional component in a tag like div, span, a, etc.
  3. Instead of Link, pull in useRouter and use router.push

No matter which solution you use, they're all strange and require comments linking to this issue or the docs to explain to coworkers/contributors why its needed to prevent accidental removal/cleanup.

I think this issue should be reopened for discussion.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

kenji4569 picture kenji4569  ·  3Comments

irrigator picture irrigator  ·  3Comments

timneutkens picture timneutkens  ·  3Comments

havefive picture havefive  ·  3Comments

pie6k picture pie6k  ·  3Comments