reactjstypescript

using ref.current in React.forwardRef


Codesandbox here

I am trying to use a ref from a parent component to listen to certain ref events in the child component where the ref is attached to the child component using React.forwardRef. However, I am getting a linting complaint in my child component when I reference ref.current, stating:

Property 'current' does not exist on type 'Ref'. Property 'current' does not exist on type '(instance: HTMLDivElement) => void'

How am I supposed to reference a ref in a React.forwardRef component? Thanks.

index.tsx:

import * as React from "react";
import ReactDOM from "react-dom";

const Component = React.forwardRef<HTMLDivElement>((props, ref) => {
  React.useEffect(() => {
    const node = ref.current;
    const listen = (): void => console.log("foo");

    if (node) {
      node.addEventListener("mouseover", listen);
    }
    return () => {
      node.removeEventListener("mouseover", listen);
    };
  }, [ref]);

  return <div ref={ref}>Hello World</div>;
});

export default Component;

const App: React.FC = () => {
  const sampleRef = React.useRef<HTMLDivElement>(null);

  return <Component ref={sampleRef} />;
};

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Solution

  • Refs are not necessarily objects with a current property. They can also be functions. So the type error is pointing out that you might be passed one of the latter. You'll need to write your code so that it can work with both variations.

    This can be a bit tricky, but it's doable. Our effect can't piggy back on the function that was passed in, since that function could be doing literally anything, and wasn't written with our useEffect in mind. So we'll need to create our own ref, which i'll call myRef.

    At this point there are now two refs: the one passed in, and the local one we made. To populate both of them, we'll need to use the function form of refs ourselves, and in that function we can assign the div element to both refs:

    const Component = React.forwardRef<HTMLDivElement>((props, ref) => {
      const myRef = useRef<HTMLDivElement | null>(null);
      React.useEffect(() => {
        const node = myRef.current;
        const listen = (): void => console.log("foo");
    
        if (node) {
          node.addEventListener("mouseover", listen);
          return () => {
            node.removeEventListener("mouseover", listen);
          };
        }
      }, [ref]);
    
      return (
        <div ref={(node) => {
          myRef.current = node;
          if (typeof ref === 'function') {
            ref(node);
          } else if (ref) {
            ref.current = node;
          }
        }}>Hello World</div>
      );
    });