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

9

203

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?

Uxmal answered 6/6, 2019 at 20:28 Comment(0)
P
134

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>
  )
}
Piliform answered 17/1, 2021 at 2:54 Comment(5)
As simple as this is - I found this to be the easiest to wrap my head around.Pahl
@Hasan seriously? This actually worked and didn't involve any significant change as mentioned in other answers.Wang
If this works why does forwardRef exist?Biphenyl
Sorcery! As easy as changing it from ref to innerRefScalp
@Biphenyl 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.Ebro
T
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

Trivandrum answered 7/6, 2019 at 8:26 Comment(2)
How about when we have a Hash Link from react-router-hash-link?Dakar
how about if you are not using react-router at all ? how do you use forwardRef with the same warning?Davisson
L
25

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}
/>
Locker answered 13/4, 2020 at 6:57 Comment(4)
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.Revareval
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.Lucillalucille
As @FilipeValente said, it is not any specific ref. It just acts as props,Locker
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.Pahl
S
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.

Supposition answered 24/8, 2021 at 20:18 Comment(1)
One suggestion on the buttonRef variable declaration, might be more concise to use optional chaining: const buttonRef = ref.current?.children[0];Elaterite
R
13

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>
)
Refill answered 29/10, 2021 at 10:16 Comment(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.Latrinalatrine
Thanks, this was precisely my issue. The fragments <></> worked in my case.Adviser
H
10

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*/)}
Hako answered 1/10, 2021 at 5:48 Comment(0)
F
3
const renderItem = ({ item, index }) => {

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

Use Fragment to solve React.forwardRef()? warning

Fairchild answered 22/3, 2021 at 9:8 Comment(0)
F
2

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.

Fluorosis answered 7/12, 2022 at 2:53 Comment(0)
L
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>
    );
  }
}
Latterday answered 12/2, 2022 at 13:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.