Seth Lutske
Seth Lutske

Reputation: 10686

React forwardRef - access ref within component, and in parent

I need to access the ref to a textarea inside a component. Within the component, its easy enough:

const MyComponent = () => {
  const inputRef = useRef();

  return <textarea ref={inputRef} />
}

Now the ref is available within MyComponent and I can use it for some internal logic.

There are cases where I need to access the ref from the parent component as well. In that case, I can use forwardRef:

const MyComponent = React.forwardRef((props, ref) => {
  return <textarea ref={ref} />
})

// In some parent
const MyParent = () => {
  const inputRefFromParent = useRef();
  return <MyComponent ref={inputRefFromParent} />
}

Now I can access to ref of the textarea from the parent component, and use it for logic within the parent component.

I find myself in a situation where I need to do some internal logic with the ref within MyComponent, but I may also need to get that ref from MyParent. How can I do this?

Upvotes: 12

Views: 11654

Answers (5)

Amila Senadheera
Amila Senadheera

Reputation: 13245

You can keep a ref in the MyComponent and expose what you would need in the parent component using useImperativeHandle hook using the ref passed from the MyParent.

Try like below. It exposes the focus method in the textarea to parent. And you can do any other internal things with the access to textAreaRef.

import { useRef, forwardRef, useImperativeHandle } from "react";

const MyComponent = forwardRef((props, ref) => {
  const textAreaRef = useRef();

  // all the functions or values you can expose here
  useImperativeHandle(ref, () => ({
    focus: () => {
      textAreaRef.current.focus();
    }
  }));

  const internalFunction = () => {
    // access textAreaRef
  };

  return <textarea ref={textAreaRef} />;
});

// In some parent
const MyParent = () => {
  const inputRefFromParent = useRef();

  // you can call inputRefFromParent.current.focus(); in this compoenent
  return <MyComponent ref={inputRefFromParent} />;
};

Upvotes: 20

Unmitigated
Unmitigated

Reputation: 89234

To access a ref while also forwarding it:

  • Attach a ref created inside the component to the element.
  • Call the useImperativeHandle hook on the outer ref (which is being forwarded to) and pass a function that returns the current property of the inner ref, which is the value that will be set to the current property of the outer ref. This exposes the entire ref to consumers rather than selectively picking certain properties.
import { forwardRef, useImperativeHandle, useRef } from 'react';
const MyComponent = forwardRef((props, outerRef) => {
    const innerRef = useRef(null);
    useImperativeHandle(outerRef, () => innerRef.current, []);
    // remember to list any dependencies of the function that returns the ref value, similar to useEffect
    return <textarea ref={innerRef} />
});

Or with TypeScript:

import { forwardRef, useImperativeHandle, useRef } from 'react';
const MyComponent = forwardRef<HTMLTextAreaElement>((props, outerRef) => {
    const innerRef = useRef<HTMLTextAreaElement>(null);
    useImperativeHandle(outerRef, () => innerRef.current!, []);
    return <textarea ref={innerRef} />
});

Upvotes: 4

Hans
Hans

Reputation: 1544

What's working for me is:

export const MyComponent: FC<Props> = forwardRef<HTMLInputElement, Props>(
  function myComponent(
    { /* ... */    }: Props,
    forwardedRef
  ) {
    const innerRef = useRef<HTMLInputElement | null>(null);
    return (
    <div>
      <input ref={(instance: HTMLInputElement) => {
              innerRef.current = instance;
              if (typeof forwardedRef === "function") forwardedRef(instance);
              else if (forwardedRef !== null) forwardedRef.current = instance;
            }} />
    </div>
    )
  }
)

Upvotes: 5

J&#246;cker
J&#246;cker

Reputation: 6748

You could do also the following:

const MyComponent = React.forwardRef((props, externalRef) => {
    const internalRef = useRef<HTMLElement>();
    const ref = useMemo(
        () => externalRef || internalRef,
        [externalRef, internalRef]
    ) as React.MutableRefObject<HTMLElement>;
    
    return <textarea ref={ref} />
})

Upvotes: 3

Seth Lutske
Seth Lutske

Reputation: 10686

In addition to Amila's answer, I found another way to do it, by using a ref callback:

const MyComponent = React.forwardRef((props, parentRef) => {
  const localRef = useRef();
  return <textarea ref={ref => {
    parentRef.current = ref;
    localRef.current = ref;
  }} />
})

So the callback ref keeps finer grain control of the ref to the textarea, and simply assigns its value to both the local ref and the parent ref.

Upvotes: 9

Related Questions