How to use React.forwardRef() with own ref in the component?
Asked Answered
P

2

18

Task
I'm trying to use React.forwardRef() and in the same time use React.useRef() in a component.

Problem
I can use either only myOwnRef or ref from forwardRef prop. So, I have no idea how to use them together. Example code:

interface IExampleInputProps {
    value: string,
    onChange: (event: ChangeEvent<HTMLInputElement>) => void
}
const ExampleInput = forwardRef<HTMLInputElement | null, IExampleInputProps>(({value, onChange}, ref) => { // can't to use "ref" parameter
    const myOwnRef = React.useRef<HTMLInputElement | null>(null);

    return <input ref={el => {
        myOwnRef.current = el; // => OK
        if (ref) { // trying to use the ref passed from the forwardRef function
            ref.current = el; // => ERROR
        }
    }} value={value} onChange={onChange}/>
})

Thanks a lot for your answers.
Papilionaceous answered 15/3, 2023 at 22:57 Comment(0)
T
29

To access a ref while also forwarding it, it is simpler to do the following:

  • 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.
import { forwardRef, useImperativeHandle, useRef } from 'react';
const MyComponent = forwardRef<HTMLInputElement, IExampleInputProps>(({value, onChange}, outerRef) => {
    const innerRef = useRef<HTMLInputElement>(null);
    useImperativeHandle(outerRef, () => innerRef.current!, []);
    // remember to list any dependencies of the function that returns the ref value, similar to useEffect
    return <input ref={innerRef} value={value} onChange={onChange} />;
});
Tamp answered 6/9, 2023 at 22:36 Comment(3)
Why does the innerRef.current has a ! sign at the end?Warchaw
@Warchaw That's for TypeScript (non-null assertion). You should remove it if you're not using TypeScript.Tamp
@Allen I rejected your edit as the non-null assertion is required for TypeScript (as also mentioned in the comment above).Tamp
W
1

The suggestion of useImperativeHandle hasn't worked on my use case.
So, I made like this:

import { forwardRef, useRef } from 'react';
const MyComponent = forwardRef<HTMLInputElement, IExampleInputProps>(({value, onChange}, outerRef) => {
    const innerRef = useRef<HTMLInputElement>(null);

    const refToUse = !outerRef  || typeof outerRef === 'function' ? innerRef : outerRef;

    useEffect(() => {
       // Example
       refToUse.current.focus();
    })

    return <input ref={refToUse } value={value} onChange={onChange} />;
});

Although, I haven't liked this solution I made, it has worked on my use case.
Important to note that if you pass a function to the outerRef won't work as expected.
On my project I only pass MutableRefObject to it.

Wesleyanism answered 24/7 at 13:59 Comment(2)
Can you provide a minimal reproducible example where my solution fails? I might be able to help with that.Tamp
It's a specific use case, where I use the ref that I have passed to "MyComponent" to a anchor element of popover using MUI library. And the ref it wasn't updating right using useImperativeHandle. Maybe my explanation was confuse, but it is really a specific case.Wesleyanism

© 2022 - 2024 — McMap. All rights reserved.