New hook; useForwardRef
When working with React.forwardRef it can be kind of a hassle to make it work, since it can be either a callback ref or object ref.
I propose adding a new hook; useForwardRef that handles this.
Example on how to implement it with React:
const useForwardRef = <T,>(
ref: ForwardedRef<T>,
initialValue: any = null
) => {
const targetRef = useRef<T>(initialValue);
useEffect(() => {
if (!ref) return;
if (typeof ref === 'function') {
ref(targetRef.current);
} else {
ref.current = targetRef.current;
}
}, [ref]);
return targetRef;
};
const InputField = React.forwardRef<HTMLInputElement, InputFieldProps>(
(props, ref) => {
const forwardedRef = useForwardRef<HTMLInputElement>(ref);
// Cool code....
}
);
nicee~
Maybe you should rename hook to useComposeRef.
I'm actually not convinced why such snippet doesn't show up in the Material UI source code. But thank you very much @lejahmie , very useful piece.
NOTE: maybe material ui chopped it into pieces, https://github.com/mui/material-ui/blob/002ab79b77d4d88add840e21c5101137a4a7de5a/packages/mui-utils/src/setRef.ts . I don't really know.
Maybe you should rename hook to
useComposeRef.
Noa, this is more like the useCopyRef, or useCloneRef, basically it's doing a clone in the React syntax.
EDIT: the original example is correct, I made a mistake wiring the old ref :)
Nice proposal @lejahmie
I copied your example into my code and it almost work, for anyone having problem with it, here is updated version:
const useForwardRef = <T,>(
ref: ForwardedRef<T>,
initialValue: any = null
) => {
const targetRef = useRef<T>(initialValue);
useEffect(() => {
if (!ref) return;
if (typeof ref === 'function') {
ref(targetRef.current);
} else {
targetRef.current = ref.current;
}
}, [ref]);
return targetRef;
};
const InputField = React.forwardRef<HTMLInputElement, InputFieldProps>(
(props, ref) => {
const forwardedRef = useForwardRef<HTMLInputElement>(ref);
// Cool code....
}
);
Basically I updated ref.current = targetRef.current with targetRef.current = ref.current. I do not know if this is only for my specific case, but it helped me so it might helped someone else.
Basically I updated
ref.current = targetRef.currentwithtargetRef.current = ref.current. I do not know if this is only for my specific case, but it helped me so it might helped someone else.
I don't think the change is correct @klis87 , you most likely didn't wire the ref returned from this hook to the element. You are not supposed to wire the old ref any more.
@windmaomao
You are not supposed to wire the old ref any more
Yes, this was the thing I did, nice guess!
sorry I have dumb question. I don't quite get how this proposed hook will work with forwardRef. With const forwardedRef = useForwardRef<HTMLInputElement>(ref);, when forwardedRef.current is set to a new value, how can ref.current be synced with the latest value? The effect won't be triggered because ref never changes.
You're right @zerofirework, if the component has conditional logic for rendering then the forwarded ref will not be synced.
The only fix for this hook is to remove dependencies from useEffect which will force it to run on every render.
This shouldn't impact performance in any meaningful way.
For anyone having trouble with this I have a simpler proposal:
Just use a ref callback that assigns the value to both the local and the forwarded ref:
<div ref={shareRef(localRef, forwardedRef)}/>