How do I avoid 'Function components cannot be given refs' when using react-router-dom?

Viewed 227534

I have the following (using Material UI)....

import React from "react";
import { NavLink } from "react-router-dom";
import Tabs from "@material-ui/core/Tabs";
import Tab from "@material-ui/core/Tab";
function LinkTab(link){
    return <Tab component={NavLink}
        to={link.link}
        label={link.label}
        value={link.link}
        key={link.link}
    />;
}

In the new versions this causes the following warning...

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 ForwardRef. in NavLink (created by ForwardRef)

I tried changing to...

function LinkTab(link){
    // See https://material-ui.com/guides/composition/#caveat-with-refs
    const MyLink = React.forwardRef((props, ref) => <NavLink {...props} ref={ref} />);
    return <Tab component={MyLink}
        to={link.link}
        label={link.label}
        value={link.link}
        key={link.link}
    />;
}

But I still get the warning. How do I resolve this issue?

8 Answers

NavLink from react-router is a function component that is a specialized version of Link which exposes a innerRef prop for that purpose.

// required for react-router-dom < 6.0.0
// see https://github.com/ReactTraining/react-router/issues/6056#issuecomment-435524678
const MyLink = React.forwardRef((props, ref) => <NavLink innerRef={ref} {...props} />);

You could've also searched our docs for react-router which leads you to https://mui.com/getting-started/faq/#how-do-i-use-react-router which links to https://mui.com/components/buttons/#third-party-routing-library. The last link provides a working example and also explains how this will likely change in react-router v6

Just give it as innerRef,

// Client.js
<Input innerRef={inputRef} />

Use it as ref.

// Input.js
const Input = ({ innerRef }) => {
  return (
    <div>
      <input ref={innerRef} />
    </div>
  )
}

You can use refs instead of ref. This only works as it avoids the special prop name ref.

<InputText
  label="Phone Number"
  name="phoneNumber"
  refs={register({ required: true })}
  error={errors.phoneNumber ? true : false}
  icon={MailIcon}
/>

If you find that you cannot add a custom ref prop or forwardRef to a component, I have a trick to still get a ref object for your functional component.

Suppose you want to add ref to a custom functional component like:

 const ref = useRef();

 //throws error as Button is a functional component without ref prop
 return <Button ref={ref}>Hi</Button>;

You can wrap it in a generic html element and set ref on that.

 const ref = useRef();

 // This ref works. To get button html element inside div, you can do 
 const buttonRef = ref.current && ref.current.children[0];
 return (
  <div ref={ref}>
   <Button>Hi</Button>
  </div>
 );

Of course manage state accordingly and where you want to use the buttonRef object.

In our case, we were was passing an SVG component (Site's Logo) directly to NextJS's Link Component which was a bit customized and we were getting such error.

Header component where SVG was used and was "causing" the issue.

import Logo from '_public/logos/logo.svg'
import Link from '_components/link/Link'

const Header = () => (
  <div className={s.headerLogo}>
    <Link href={'/'}>
      <Logo /> 
    </Link>
  </div>
)

Error Message on Console

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

Customized Link Component

import NextLink from 'next/link'
import { forwardRef } from 'react'

const Link = ({ href, shallow, replace, children, passHref, className }, ref) => {
  return href ? (
    <NextLink
      href={href}
      passHref={passHref}
      scroll={false}
      shallow={shallow}
      replace={replace}
      prefetch={false}
      className={className}
    >
      {children}
    </NextLink>
  ) : (
    <div className={className}>{children}</div>
  )
}

export default forwardRef(Link)

Now we made sure we were using forwardRef in the our customized Link Component but we still got that error.

In order to solve it, I changed the wrapper positioning of SVG element to this and :poof:

const Header = () => (
  <Link href={'/'}>
    <div className={s.headerLogo}>
      <Logo />
    </div>
 </Link>
)

to fix this warning you should wrap your custom component with forwardRef function as mention in this blog very nicely

    const AppTextField =(props) {return(/*your component*/)}

change following code to

const AppTextField = forwardRef((props,ref) {return(/*your component*/)}
const renderItem = ({ item, index }) => {

        return (
            <>          
            <Item
                key={item.Id}
                item={item}
                index={index}
            />
            </>
        );
    };

Use Fragment to solve React.forwardRef()? warning

I just paste here skychavda solution, as it provide a ref to a child : so you can call child method or child ref from parent directly, without any warn.

source: https://github.com/reactjs/reactjs.org/issues/2120

/* Child.jsx */
import React from 'react'

class Child extends React.Component {
  componentDidMount() {
    const { childRef } = this.props;
    childRef(this);
  }
  componentWillUnmount() {
   const { childRef } = this.props;
    childRef(undefined);
  }
  alertMessage() {
    window.alert('called from parent component');
  }
  render() {
    return <h1>Hello World!</h1>
  }
}

export default Child;
/* Parent.jsx */
import React from 'react';
import Child from './Child';

class Parent extends React.Component {
  onClick = () => {
    this.child.alertMessage(); // do stuff
  }
  render() {
    return (
      <div>
        <Child childRef={ref => (this.child = ref)} />
        <button onClick={this.onClick}>Child.alertMessage()</button>
      </div>
    );
  }
}
Related