All Articles

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.