Tyler JonesHomeSay Hi

Passing and Using Refs in React


Photo by 青 晨 on Unsplash

In our code base, we had a TextInput component. This component helps with some focus managing, so it had a focusOnRender prop that, when set, would focus this input when rendered. This is a simplication:

function TextInput({ focusOnRender, ...props }) {
	const inputRef = useRef();

	useEffect(() => {
		if (focusOnRender) inputRef.current?.focus();
	}, []);

	return <input ref={inputRef} {...props} />;
}

When the input is rendered, it will be focused via the focusOnRender prop.

As usage of this really basic TextInput evolved, we ended up wanting to expose a ref to other components. Normally, we would create the component like this:

const TextInput = forwardRef(
	({ focusOnRender, ...props }, ref) => {
		return <input ref={ref} {...props} />;
	}
);

When using forwardRef, you cannot use the ref inside of the component.

For a while, we were able to use the use-forwarded-ref hook like this:

const TextInput = forwardRef(
	({ focusOnRender, ...props }, ref) => {
		const inputRef = useForwardedRef(ref);

		useEffect(() => {
			if (focusOnRender) inputRef.current?.focus();
		}, []);

		return <input ref={inputRef} {...props} />;
	}
);

But for reasons I can't quite explain, this did not work. The forwarded ref would get discarded and be unusable by the higher components.

I found a better way in the React docs, using useImperativeHandle. This hook customizes the value that is exposed in a ref. In a way, this is like exposing a ref API. You could use this handle to expose a specific API as a ref. That API would, of course, be used for imperative function calls.

const TextInput = forwardRef(
	({ focusOnRender, ...props }, ref) => {
		const inputRef = useRef();
		useImperativeHandle(ref, () => ({
			focus: () => {
				inputRef.current.focus();
			},
		}));

		useEffect(() => {
			if (focusOnRender) inputRef.current?.focus();
		}, []);

		return <input ref={inputRef} {...props} />;
	}
);

This doesn't expose the same ref, per se, but allows you to selectively allow the functionality you want higher components to use.