Anton Strogonoff
Anton Strogonoff

Reputation: 34052

How can I access current component’s top-level element ref inside a hook?

I want to implement a specific mouse over behavior that can be added to any part of the app.

I want this to not create extra nested markup, if that can be avoided at all.

In Angular this can be achieved with something like <some-component myBehavior="…">, where I implement a myBehavior attribute directive.

In React with functional components, the mainstream way to achieve this appears to be with hooks, where I could associate a ref with the top-level element in my component and pass that ref to my hook (useMyBehavior(mainElRef, …)).

While it seems to make sense, unfortunately, I’d have to do this with each component that wants this behavior (which could be many). Additionally, if the top-level element is another custom component, getting the ref of the “concrete” underlying DOM element (div, span, etc.) could involve yet more logic.

All of that is doable, and is better than extra nested markup (which comes with many implications for existing styling), but it’s a lot of effort and extra logic I’d rather avoid if possible. After all, all I need my hook to be aware of is when mouse pointer enters current component’s element’s bounding box.

Hence the question: what’s the most feasible way of letting my hook smartly get a hold of the concrete DOM element that is the top-level wrapper for the current component without having to pass it a ref?

Upvotes: 2

Views: 1193

Answers (1)

joshwilsonvu
joshwilsonvu

Reputation: 2689

Unfortunately there's no shortcut to getting the ref to the top-level DOM element of a component. React Hooks don't "know" in which component they are rendered. And adding this functionality would break one of React's principles:

React components hide their implementation details, including their rendered output.

However, you can create and return a ref in your hook, so it doesn't need to be passed a ref.

function useMyBehavior() {
  const ref = useRef();
  useEffect(() => {
    function onMouseover(e) {
      // do whatever with ref.current
    }
    ref.current.addEventListener("mouseover", onMouseover);
    return () => ref.current.removeEventListener("mouseover", onMouseover);
  }, [ref.current]);
  return ref;
}

function Component() {
  const ref = useMyBehavior();
  return (
    <NestedComponent ref={ref} prop={"prop"} />
  );
}

const NestedComponent = React.forwardRef((props, ref) => {
  return (
    <div ref={ref}>
      {props.prop}
    </div>
  );
}

So long as custom components use React.forwardRef to forward the ref down to a DOM element, the hook will work. See the docs section on Forwarding Refs.

Upvotes: 3

Related Questions