React hooks and functional component ref

Ben picture Ben · Oct 16, 2019 · Viewed 11.2k times · Source
const Comp1 = forwardRef((props, ref) => {
    useImperativeHandle(ref, () => ({
    print: () => {
      console.log('comp1')
    }
  }), []);
  return <div>comp1</div>
});

const Comp2 = () => <div>comp2</div>;

const App = () => {
  const ref1 = useRef(null);
  const ref2 = useRef(null);

  useEffect(() => {
    console.log(ref1); // prints ref1 with the expected current  
    console.log(ref2); // prints ref2 with current: null
  })
  return <div><Comp1 ref={ref1}/><Comp2 ref={ref2}/></div>
}
  1. What's the difference between Comp1 and Comp2 refs?
  2. Why do I have to use forwardRef along with useImperativeHandle in order to actually get the ref to Comp1?

https://codepen.io/benma/pen/mddEWjP?editors=1112

Answer

Andrii Golubenko picture Andrii Golubenko · Oct 16, 2019

React documentation says:

You may not use the ref attribute on function components because they don’t have instances. (more)

This means that you can't bind your refs to functional components. That's why your ref2.current is null. If you want to bind ref to component, you need to use class components. Your ref1 is not a ref to the Comp1 component. It actually contains an object that you passed in useImperativeHandle hook. i.e. it contains the next object:

{
    print: () => {
      console.log('comp1')
    }
}

You have to use forwardRef with functional components if you want to bind your ref with some HTML element or class component that your component renders. Or you could bind your ref with some object with using of useImperativeHandle hook.

UPDATE

The using of useImperativeHandle is the same as adding methods to your class component:

class Comp1 extends React.Component {
    print() {
        console.log('comp1');
    }

    render() {
        return (<div>comp1</div>)
    }
}

is the same as

const Comp1 = forwardRef((props, ref) => {
    useImperativeHandle(ref, () => ({
    print: () => {
      console.log('comp1')
    }
  }), []);
  return <div>comp1</div>
});

You asked in a comment:

So after moving to hooks (and still avoid using classes), the only way to use ref is to use useImperativeHandle (and actually use a "fake" ref)? Is this a good practice?

Answer: Using of useImperativeHandle is the same bad practice as calling child component methods by refs in class components. React doc says that you should avoid calling child component methods by refs, you should avoid using of useImperativeHandle. Also, you need to avoid using refs where you can do things without them.