Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React hooks and functional component ref

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

like image 880
Ben Avatar asked Oct 16 '19 11:10

Ben


People also ask

Can we use REF in functional component in React?

There are basically two use cases for refs in React: Accessing underlying DOM nodes or React Elements. Creating mutable instance-like variables for functional components.

How do you ref in React hook?

In order to work with refs in React you need to first initialize a ref which is what the useRef hook is for. This hook is very straightforward, and takes an initial value as the only argument. This hook then returns a ref for you to work with.

Can we use React Hooks in functional component?

Hooks are functions that let you “hook into” React state and lifecycle features from function components. Hooks don't work inside classes — they let you use React without classes. (We don't recommend rewriting your existing components overnight but you can start using Hooks in the new ones if you'd like.)

What is use REF IN React?

The useRef Hook allows you to persist values between renders. It can be used to store a mutable value that does not cause a re-render when updated. It can be used to access a DOM element directly.


3 Answers

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.

like image 134
Andrii Golubenko Avatar answered Oct 21 '22 22:10

Andrii Golubenko


1. What's the difference between Comp1 and Comp2 refs?

Comp1 uses React.forwardRef and will be able to receive a given ref from parent.
Comp2 won't work and trigger following error:

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

<Child ref={ref1}/> is like a request from parent component: "Hey Child, please pass your component reference or similar into given mutable store box (ref), so I can invoke a method on you or manipulate a contained DOM node directly."

Problem - there is no instance for function components:
// React internally calls `new ClassComp()`, instance can be stored and passed in a ref
<ClassComp ref={compRef} />

// React just *calls* the function for re-renders, there is no plain function "instance" 
<FunctionComp ref={compRef} />
React.forwardRef solves above limitation. With
const FunctionComp = React.forwardRef((props, ref) => <div ref={ref}>Hello FnComp</div>

, FunctionComp can still pass something representative to the ref given from Parent (like div DOM node), despite having no instance. ClassComp instead passes its instance here.


2. Why do I have to use forwardRef along with useImperativeHandle in order to actually get the ref to Comp1?

You don't have to. useImperativeHandle is an extension to provide a more custom imperative call API. Following three alternatives are equivalent:

forwardRef only:

const App = () => {
  const compRef = React.useRef();
  return (
    <div>
      <Comp ref={compRef} />
      <button
        onClick={() => {
          compRef.current.focus();
        }}
      >
        Focus input
      </button>
    </div>
  );
}

const Comp = React.forwardRef((props, ref) => {
  const inputRef = React.useRef();
  return <input ref={ref} />;
});

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<div id="root"></div>
useImperativeHandle only, with custom ref prop (take a look at this answer for more info):

const App = () => {
  const compRef = React.useRef();
  return (
    <div>
      <Comp customRef={compRef} />
      <button
        onClick={() => {
          compRef.current.focus();
        }}
      >
        Focus input
      </button>
    </div>
  );
}

const Comp = ({ customRef }) => {
  const inputRef = React.useRef();
  React.useImperativeHandle(customRef, () => ({
    focus: () => {
      inputRef.current.focus();
    }
  }));
  return <input ref={inputRef} />;
};

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<div id="root"></div>
useImperativeHandle + forwardRef:

const App = () => {
  const compRef = React.useRef();
  return (
    <div>
      <Comp ref={compRef} />
      <button
        onClick={() => {
          compRef.current.focus();
        }}
      >
        Focus input
      </button>
    </div>
  );
}

const Comp = React.forwardRef((props, ref) => {
  const inputRef = React.useRef();
  React.useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    }
  }));
  return <input ref={inputRef} />;
});

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<div id="root"></div>
like image 3
ford04 Avatar answered Oct 21 '22 23:10

ford04


I will try to answer from question 2

  • useImperativeHandle described that the reference call syntax will be with some customized methods of the instance value that is exposed to parent.

  • forwardRef helps forward a ref through a Higher-order component to reference an inner DOM node.

when you will try to focus: ref1.current.focus(), the child input will be focused and not higher order component that wrapping the child input.

Look at simple example related to answer with setValue method of useImperativeHandle and forward input in ChildInput: https://codesandbox.io/s/react-hook-useimperativehandle-huktt

like image 1
Oleg Avatar answered Oct 21 '22 22:10

Oleg