172

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?

JGleason
  • 3,067
  • 6
  • 20
  • 54

9 Answers9

110

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>
  )
}
Hasan Sefa Ozalp
  • 6,353
  • 5
  • 34
  • 45
  • 1
    As simple as this is - I found this to be the easiest to wrap my head around. – Liam Apr 20 '21 at 11:33
  • 1
    @Hasan seriously? This actually worked and didn't involve any significant change as mentioned in other answers. – Souvik Ray May 25 '21 at 09:14
  • 32
    If this works why does `forwardRef` exist? – Mattia Jun 16 '21 at 12:16
  • 2
    Sorcery! As easy as changing it from `ref` to `innerRef` – David Tedman-Jones Aug 05 '21 at 07:18
  • 1
    @Mattia to keep using the standard name – `ref`. InnerRef might be a solution for someone but if you using a component you want to use standard props, `ref` is one of them. By standard I mean there is no difference in how it works as if you pass the `ref` to the HTML input, or your custom `Input` component. They both should work fine with just `ref`. So to support this `forwardRef` comes into play. I wouldn't recommend passing a ref using a non-standard prop name for this. It is the same as passing style or className under different names. It is just a bit more tricky with the ref prop. – Maksim Nesterenko Jan 25 '23 at 12:36
78

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

Sebastian
  • 3,322
  • 22
  • 34
24

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}
/>
AncientSwordRage
  • 7,086
  • 19
  • 90
  • 173
Inamur Rahman
  • 2,913
  • 1
  • 27
  • 29
  • 18
    Hello. Could you please explain the difference between ref and refs? I looked over the React documentation but could not find any reference to refs={} (plural). This acatually worked for me but would like to understand how it works on functional components. Thank you. – user2590243 Jun 29 '20 at 19:29
  • 8
    It's not that it is specifically refs. It's just that the prop "ref" is a special one (just like "key" would be). You could pass it as "foo" instead of "refs" and it would work as well. – Filipe Valente Oct 04 '20 at 14:55
  • 1
    As @FilipeValente said, it is not any specific ref. It just acts as props, – Inamur Rahman Oct 09 '20 at 03:55
  • 2
    I found myself also questioning if `refs` was a special prop in any way so I opted for `innerRef` just because it's a little clearer as to what it's doing. – Liam Apr 20 '21 at 11:35
16

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.

vsync
  • 118,978
  • 58
  • 307
  • 400
Snebhu
  • 1,077
  • 1
  • 12
  • 19
  • 1
    One suggestion on the buttonRef variable declaration, might be more concise to use optional chaining: const buttonRef = ref.current?.children[0]; – huntzinger92 Jan 18 '22 at 02:56
12

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>
)
KeshavDulal
  • 3,060
  • 29
  • 30
  • 2
    Had a similar issue creating a custom next link component that's basically a wrapper around a styled text component - solved it by adding a fragment around my wrapped component instead of a div. – pr0tipZ Nov 20 '21 at 22:14
  • 1
    Thanks, this was precisely my issue. The fragments `<>>` worked in my case. – Victor Eke Aug 31 '22 at 21:17
9

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

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

change the above code to

const AppTextField = forwardRef((props,ref) {return(/*your component*/)}
Mohd Qasim
  • 896
  • 9
  • 20
2
const renderItem = ({ item, index }) => {

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

Use Fragment to solve React.forwardRef()? warning

Keshav Gera
  • 10,807
  • 1
  • 75
  • 53
1

If you're using functional components, then React.forwardRef is a really nice feature to know how to use for scenarios like this. If whoever ends up reading this is the more hands on type, I threw together a codesandbox for you to play around with. Sometimes it doesn't load the Styled-Components initially, so you may need to refresh the inline browser when the sandbox loads.

https://codesandbox.io/s/react-forwardref-example-15ql9t?file=/src/App.tsx

// MyAwesomeInput.tsx
import React from "react";
import { TextInput, TextInputProps } from "react-native";
import styled from "styled-components/native";

const Wrapper = styled.View`
  width: 100%;
  padding-bottom: 10px;
`;

const InputStyled = styled.TextInput`
  width: 100%;
  height: 50px;
  border: 1px solid grey;
  text-indent: 5px;
`;

// Created an interface to extend the TextInputProps, allowing access to all of its properties
// from the object that is created from Styled-Components.
//
// I also define the type that the forwarded ref will be.
interface AwesomeInputProps extends TextInputProps {
  someProp?: boolean;
  ref?: React.Ref<TextInput>;
}

// Created the functional component with the prop type created above.
//
// Notice the end of the line, where you wrap everything in the React.forwardRef(). 
// This makes it take one more parameter, called ref. I showed what it looks like
// if you are a fan of destructuring.
const MyAwesomeInput: React.FC<AwesomeInputProps> = React.forwardRef( // <-- This wraps the entire component, starting here.
  ({ someProp, ...props }, ref) => {
    return (
      <Wrapper>
        <InputStyled {...props} ref={ref} />
      </Wrapper>
    );
  }); // <-- And ending down here.

export default MyAwesomeInput;

Then on the calling screen, you'll create your ref variable and pass it into the ref field on the component.

// App.tsx
import React from "react";
import { StyleSheet, Text, TextInput, View } from "react-native";
import MyAwesomeInput from "./Components/MyAwesomeInput";

const App: React.FC = () => {
  // Set some state fields for the inputs.
  const [field1, setField1] = React.useState("");
  const [field2, setField2] = React.useState("");

  // Created the ref variable that we'll use down below.
  const field2Ref = React.useRef<TextInput>(null);

  return (
    <View style={styles.app}>
      <Text>React.forwardRef Example</Text>
      <View>
        <MyAwesomeInput
          value={field1}
          onChangeText={setField1}
          placeholder="field 1"
          // When you're done typing in this field, and you hit enter or click next on a phone,
          // this makes it focus the Ref field.
          onSubmitEditing={() => {
            field2Ref.current.focus();
          }}
        />
        <MyAwesomeInput
          // Pass the ref variable that's created above to the MyAwesomeInput field of choice.
          // Everything should work if you have it setup right.
          ref={field2Ref}
          value={field2}
          onChangeText={setField2}
          placeholder="field 2"
        />
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  app: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center"
  }
});

export default App;

It's that simple! No matter where you place the MyAwesomeInput component, you'll be able to use a ref.

JDev
  • 73
  • 1
  • 8
0

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>
    );
  }
}
boly38
  • 1,806
  • 24
  • 29