React forwardRef - access ref within component, and in parent
Asked Answered
K

5

12

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?

Kitchen answered 2/3, 2022 at 1:39 Comment(0)
C
20

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} />;
};
Choppy answered 2/3, 2022 at 1:57 Comment(0)
K
8

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.

Kitchen answered 2/3, 2022 at 18:37 Comment(1)
If you're using typescript, for the parentRef you'll have to use something like if (typeof parentRef === "function") parentRef(ref); else if (parentRef !== null) parentRef.current = ref;Countrybred
S
5

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>
    )
  }
)
Seedling answered 3/3, 2023 at 12:39 Comment(0)
H
3

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} />
})
Healy answered 17/11, 2022 at 9:2 Comment(0)
E
3

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} />
});
Euratom answered 6/9, 2023 at 21:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.