JGleason
JGleason

Reputation: 3916

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

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?

Upvotes: 222

Views: 475100

Answers (10)

hackemate
hackemate

Reputation: 537

Thi is my solution with nativewind custom component and expo router

import React, { ReactNode, forwardRef } from 'react';
import { Pressable, PressableProps, Text, View } from 'react-native';
import { tv } from 'tailwind-variants';

type ButtonProps = PressableProps & {
  title?: string;
  children?: ReactNode;
  onPress?: () => void;
  className?: string;
  color?: 'default' | 'secondary' | 'outline' | 'ghost' | 'link';
  size?: 'default' | 'sm' | 'lg' | 'icon' | 'miniIcon';
  shadow?: 'default' | 'sm' | 'lg' | 'md';
  radil?: 'default' | 'sm' | 'full' | 'md' | 'lg';
  disabled?: boolean;
};

const buttonStyle = tv({
  base: 'inline-flex items-center justify-center whitespace-nowrap active:opacity-35',
  variants: {
    color: {
      default: 'bg-primary',
      secondary: 'bg-secondary',
      outline: 'bg-white border-primary border',
      ghost: 'bg-white',
      link: '',
    },
    size: {
      default: 'h-9 px-4 py-2',
      sm: 'h-8 rounded-md px-3 text-xs',
      lg: 'h-10 rounded-md px-8',
      icon: 'h-9 w-9',
      miniIcon: 'p-0',
    },
    shadow: {
      default: 'shadow-black-50',
      sm: 'shadow-sm shadow-black-50',
      md: 'shadow-md shadow-black-50',
      lg: 'shadow-lg shadow-black-50',
    },
    radil: {
      default: 'rounded-3xl',
      sm: 'rounded-sm',
      md: 'rounded-md',
      lg: 'rounded-lg',
      full: 'rounded-full',
    },
    disabled: {
      true: 'opacity-35',
    },
  },
  defaultVariants: {
    size: 'default',
    color: 'default',
    shadow: 'default',
    radil: 'default',
  },
});

const buttonTextStyle = tv({
  base: 'text-sm font-medium',
  variants: {
    color: {
      default: 'text-primary-foreground',
      secondary: 'text-secondary-foreground',
      outline: 'text-primary',
      ghost: 'text-black',
      link: 'text-foreground',
    },
  },
  defaultVariants: {
    color: 'default',
  },
});

const Button = forwardRef<View, ButtonProps>(
  ({ title, children, onPress, className, color, size, shadow, radil, disabled, ...rest }, ref) => {
    return (
      <Pressable
        {...rest}
        ref={ref}
        onPress={disabled ? null : onPress}
        className={`${buttonStyle({ color, size, radil, shadow, disabled })} ${className} `}>
        {children ? children : <Text className={buttonTextStyle({ color })}>{title}</Text>}
      </Pressable>
    );
  }
);

export { Button, ButtonProps, buttonStyle, buttonTextStyle };

Upvotes: 0

Mohd Qasim
Mohd Qasim

Reputation: 1010

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*/)}

Upvotes: 11

JDev
JDev

Reputation: 158

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.

Upvotes: 3

Inamur Rahman
Inamur Rahman

Reputation: 3291

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

Upvotes: 25

boly38
boly38

Reputation: 1955

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

Upvotes: 0

sney-js
sney-js

Reputation: 1097

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.

Upvotes: 18

KeshavDulal
KeshavDulal

Reputation: 4144

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

Upvotes: 13

Sebastian
Sebastian

Reputation: 3594

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

Upvotes: 80

Keshav Gera
Keshav Gera

Reputation: 11244

const renderItem = ({ item, index }) => {

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

Use Fragment to solve React.forwardRef()? warning

Upvotes: 3

Hasan Sefa Ozalp
Hasan Sefa Ozalp

Reputation: 7208

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

Upvotes: 146

Related Questions