Converting a class component with refs to a function component with hooks
For refs and function components React.js docs say:
You may not use the ref attribute on function components because they don’t have instances.
You should convert the component to a class if you need a ref to it, just like you do when you need lifecycle methods or state.
You can, however, use the ref attribute inside a function component as long as you refer to a DOM element or a class component.
So if we can’t use the ref, how do we convert a class component with refs to a function component (to get ready for the age of hooks)? Maybe we can convert it to a hook and a function component instead.
Let’s start with a class component. A custom text input that exposes some methods which you can call via holding a ref to it.
class CustomTextInput extends React.Component<{}, { focused: boolean }> {
state = {
focused: false,
};
textInputRef = React.createRef<HTMLInputElement>();
focus = () => {
if (this.textInputRef.current !== null) {
this.setState({ focused: true });
this.textInputRef.current.focus();
}
};
render() {
return (
<div className={this.state.focused ? "focused" : "blurred"}>
<input type="text" ref={this.textInputRef} />
</div>
);
}
}
We can now have a ref to this CustomTextInput
. We can for example have it focused when a button is pressed.
const Page = () => {
const customTextInputRef = useRef<CustomTextInput>(null);
const focusTextInput = useCallback(() => {
if (customTextInputRef.current !== null) {
customTextInputRef.current.focus();
}
}, []);
return (
<div>
<CustomTextInput ref={customTextInputRef} />
<input
type="button"
value="Focus custom text input"
onClick={focusTextInput}
/>
</div>
);
};
Let’s say we wanted to refactor this CustomTextInput
class to be a function component. As per React.js docs this wouldn’t work with function components. Let’s create a hook instead. This custom hook will hold the ref and return the CustomTextInput
component’s props and the focus
function.
const CustomTextInput: React.FC<{
focused: boolean;
textInputRef: React.RefObject<HTMLInputElement>;
}> = (props) => {
return (
<div className={props.focused ? "focused" : "blurred"}>
<input type="text" ref={props.textInputRef} />
</div>
);
};
const useCustomTextInput = () => {
const [focused, setFocused] = useState(false);
const textInputRef = useRef<HTMLInputElement>(null);
const focus = useCallback(() => {
if (textInputRef.current !== null) {
setFocused(true);
textInputRef.current.focus();
}
}, []);
return { focused, textInputRef, focus };
};
We’ll use this hook now and pass the returned props to the component.
const Page = () => {
const { focus, ...customInputProps } = useCustomTextInput();
return (
<div>
<CustomTextInput {...customInputProps} />
<input type="button" value="Focus custom text input" onClick={focus} />
</div>
);
};
This new hook is more reusable and results in less code when we want to render the same component somewhere else.