Next.js: next/link doesn't work when child is function component

Created on 19 Aug 2019  路  7Comments  路  Source: vercel/next.js

Bug report

Describe the bug

When using next/link and passing a function component as the child, it does a full server side render and page refresh on click rather than a client side routing.

To Reproduce

The following link works:

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

const TestThing = () => (
  <Link as="/en/checkout" href="/[locale]/checkout" passHref>
    <a>Checkout</a>
  </Link
);

The following does a SSR load rather than client side on click:

const Button = ({ children }) => (
  <a className="button">
    {children}
  </a>
);

const TestThing = () => (
  <Link as="/en/checkout" href="/[locale]/checkout" passHref>
    <Button>Checkout</Button>
  </Link
);

Additional context

I've seen a new addition to the README in v9.0.4-canary.2:
Note: if passing a functional component as a child of <Link> you will need to wrap it in React.forwardRef

I've tried various methods of adding a ref including forwardRef on my Button component but nothing seems to work. Perhaps I'm doing it wrong. Can someone confirm whether this is a bug with the link component or just provide an example of how this should look with the correct ref setup? I feel like the docs are not clear on this.

Most helpful comment

Hi, the syntax to use React.forwardRef with a function component would be

import React from "react";
import Link from "next/link";

const Button = React.forwardRef(({ children, href, onClick }, ref) => (
  <a
    ref={ref}
    href={href}
    onClick={onClick}
    className="button"
  >
    {children}
  </a>
));

const TestThing = () => (
  <Link href="/" passHref>
    <Button>Checkout</Button>
  </Link>
);

export default TestThing;

For more information on forwardRef see the React docs here. If you are still having trouble with this feel free to reply with additional info

All 7 comments

Guess your button component should simply relay href to <a>:

-const Button = ({ children }) => (
-  <a className="button">
+const Button = ({ children, href }) => (
+  <a className="button" href={href}>
     {children}
   </a>
 );

It might be a good idea to pass title down too.

Hi, the syntax to use React.forwardRef with a function component would be

import React from "react";
import Link from "next/link";

const Button = React.forwardRef(({ children, href, onClick }, ref) => (
  <a
    ref={ref}
    href={href}
    onClick={onClick}
    className="button"
  >
    {children}
  </a>
));

const TestThing = () => (
  <Link href="/" passHref>
    <Button>Checkout</Button>
  </Link>
);

export default TestThing;

For more information on forwardRef see the React docs here. If you are still having trouble with this feel free to reply with additional info

@ijjk Thank you but I can confirm this is not working. Same problem where it creates the anchor with the correct href but clicking it does a SSR rather than client side route change. Since I am correctly applying the forwardRef and the link correctly does client side routing when using an anchor tag directly instead of a function component child, I can only assume this is a NextJS bug at the moment. Are we able to re-open this issue?

@caribou-code can you provide a link to a reproduction? We have tests covering forwardRef usage with next/link here so it doesn't seem as likely to be related to that. There might be something else not lining up which can be seen easier with a reproduction

@ijjk Ok I've discovered I was missing the onClick prop in my Button component and that's what was breaking it (I realise now you had that in your example above). I just assumed I didn't need it because I'm not using click events or useRouter and I'm only concerned with the as and href values on a next/link. I feel like this definitely needs documenting somewhere.

Bug report

Describe the bug

When using next/link and passing a function component as the child, it does a full server side render and page refresh on click rather than a client side routing.

To Reproduce

The following link works:

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

const TestThing = () => (
  <Link as="/en/checkout" href="/[locale]/checkout" passHref>
    <a>Checkout</a>
  </Link
);

The following does a SSR load rather than client side on click:

const Button = ({ children }) => (
  <a className="button">
    {children}
  </a>
);

const TestThing = () => (
  <Link as="/en/checkout" href="/[locale]/checkout" passHref>
    <Button>Checkout</Button>
  </Link
);

Additional context

I've seen a new addition to the README in v9.0.4-canary.2:
Note: if passing a functional component as a child of <Link> you will need to wrap it in React.forwardRef

I've tried various methods of adding a ref including forwardRef on my Button component but nothing seems to work. Perhaps I'm doing it wrong. Can someone confirm whether this is a bug with the link component or just provide an example of how this should look with the correct ref setup? I feel like the docs are not clear on this.

For this routing to work without refresh how does your pages look lilke?
e.g. pages -> [locale].js or is it something else

Guess your button component should simply relay href to <a>:

-const Button = ({ children }) => (
-  <a className="button">
+const Button = ({ children, href }) => (
+  <a className="button" href={href}>
     {children}
   </a>
 );

It might be a good idea to pass title down too.

I was dealing with this issue exactly--and this solved my problem!

Now I'm wondering why Next.js wants navigation links to be written like this?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

kenji4569 picture kenji4569  路  3Comments

pie6k picture pie6k  路  3Comments

swrdfish picture swrdfish  路  3Comments

jesselee34 picture jesselee34  路  3Comments

wagerfield picture wagerfield  路  3Comments