Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I use forwardRef() in React?

I'm currently receiving the following error in my React app:

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

How can I fix this using forwardRef()?

My code is as follows:

const Services: FunctionComponent = (): ReactElement => {
  const servicesRef = useRef(null);

  return (
    <Layout>
      <ServicesList ref={servicesRef} />
    </Layout>
  );
};
export default Services;



const ServicesList: React.FunctionComponent = ({ children }: Props) => {
  return (
    <section className="my-24 md:my-32">
      {children && children}
    </section>
  );
};

export default ServicesList;
like image 769
ZFDev Avatar asked Mar 16 '21 22:03

ZFDev


People also ask

How do I use forwardRef in Reactjs?

forwardRef to obtain the ref passed to it, and then forward it to the DOM button that it renders: const FancyButton = React. forwardRef((props, ref) => ( <button ref={ref} className="FancyButton"> {props. children} </button> )); // You can now get a ref directly to the DOM button: const ref = React.

What does forwardRef return?

ForwardRef gives a child component a reference to a DOM entity created by its parent component in React. This helps the child to read and modify the element from any location where it is used.

How do you pass ref as props in React?

Use createRef to create the ref that you end up passing down. If you're passing a ref to a function component use React. forwardRef. If you're passing a ref down to a class component ensure that the prop name is anything except ref or you'll get a special prop warning.

What is the difference between ref and forwardRef?

forwardRef. Ref Forwarding is the passing of a ref from a component to one of its children. Ref is a mutable reference to an Object. This can be a DOM instance or a class instance.


2 Answers

The forwardRef api, (coupled with the useImperativeHandle hook) lets you customize how and where your refs are placed inside your custom components. Also, forwardRef is the only way to pass a ref to your custom function components.

First, its important to understand that refs work differently on class components, function components, and regular DOM elements.

From the docs:

The value of the ref differs depending on the type of the node:

  1. When the ref attribute is used on an HTML element, the ref created in the constructor with React.createRef() receives the underlying DOM element as its current property.
  2. When the ref attribute is used on a custom class component, the ref object receives the mounted instance of the component as its current.
  3. You may not use the ref attribute on function components because they don’t have instances.

Here are examples for the ways to use refs on the different element types:

1. Ref on a DOM element gives you a reference to the DOM Node itself:
function AutoFocusInput() {
  const inputRef = useRef(null);
  // This effect runs only once after the component mounts (like componentDidMount)
  useEffect(() => {
    // refs on regular DOM elements (e.g. the "input" tag) have access to the DOM node
    inputRef.current.focus();
  }, []);
  return <input ref={inputRef} />
}
2. Ref on a class component gives us access to the instance, with all of it's methods and fields:
class Child extends Component {
  state = {color: "red"}
  toggleColor = () => this.setState({color: this.state.color === "red" ? "blue" : "red"})
  render() {
    return <div style={{backgroundColor: this.state.color}}>yo</div>
  }
}

class Parent extends Component {
  childRef = createRef();
  handleButtonClicked = () => {
    // refs on class components: hold the class component instance, 
    // allowing us to call its methods!
    this.childRef.current.toggleColor();
  }
  render() {
    return (
      <div>
        <button onClick={this.handleButtonClicked}>toggle color!</button>
        <Child ref={childRef} />
      </div>
    );
  }
}
3. Now, to finally answer your question. Refs can't be passed to function components, because They don't have instances!

The only way to pass a ref to a function component is using forwardRef. When using forwardRef, you can simply pass the ref to a DOM element, so the parent can access it like in example 1, or you could create an object with fields and methods using the useImperativeHandle hook, which would be similar to eample 2.

3.1 Simply passing a ref to a DOM element:
// Only when using forwardRef, the function component receives two arguments, 
// props and ref (Normally the component only gets the props argument).
const RedInput = forwardRef((props, ref) => {
  // passing the ref to a DOM element, 
  // so that the parent has a reference to the DOM node
  return <input style={{color: "red"}} {...props} ref={ref} />
});

function AutoFocusInput() {
  const inputRef = useRef(null);
  // This effect runs only once after the component mounts (like componentDidMount)
  useEffect(() => {
    // ref on function component is forwarded to a regular DOM element, 
    // so now the parent has access to the DOM node including its focus method.
    // Note that the ref usage is the same as a regular 
    // DOM element, like in example 1!
    inputRef.current.focus();
  }, []);
  return <RedInput ref={inputRef} />
}
3.2 Attaching the parent ref to a custom object:

To attach functions or fields to the ref, like you could do with the instance of a class component, you need to use the `useImperativeHandle` hook:

const Child = forwardRef((props, ref) => {
  const [color, setColor] = useState("red");
  // To customize the value that the parent will get in their ref.current: 
  // pass the ref object to useImperativeHandle as the first argument. 
  // Then, whatever will be returned from the callback in the second argument, 
  // will be the value of ref.current. 
  // Here I return an object with the toggleColor method on it, for the parent to use:
  useImperativeHandle(ref, () => ({
    toggleColor: () => setColor(prevColor => prevColor === "red" ? "blue" : "red")
  }));
  return <div style={{backgroundColor: color}}>yo</div>;
});


class Parent extends Component {
  childRef = createRef();
  handleButtonClicked = () => {
    // Ref passed to a function component wrapped in forwardRef.
    // Note that nothing has changed for this Parent component
    // compared with the class component in example 2!
    this.childRef.current.toggleColor();
  }
  render() {
    return (
      <div>
        <button onClick={this.handleButtonClicked}>toggle color!</button>
        <Child ref={childRef} />
      </div>
    );
  }
}
like image 103
deckele Avatar answered Oct 24 '22 05:10

deckele


React docs explain well..

https://reactjs.org/docs/forwarding-refs.html

Wrap your component with React.forwardRef and set the ref to the intended DOM element

 const FancyButton = React.forwardRef((props,ref)=>(
      <button ref={ref} className="FancyButton">
         {props.children}
      </button>)
   );
like image 2
Tiago Coelho Avatar answered Oct 24 '22 04:10

Tiago Coelho