/* Code Comments */

Using Callbacks To Reverse Data Flow In React

September 05, 2019

React’s one way data flow makes it easy to reason through, but sometimes the controlling component needs to know what’s going on inside.

For example, imagine a form comprised of multiple pieces form sketch

While form validation is often handled by the enclosing component, in this case, the PhoneInput component has standardized validation.

Since it’s a shared component, it made sense to not have to repeat it with every instance.

But if the logic for validating the input is inside, how do we get it back out? Callbacks.

Imagine a FormContainer even simpler than the drawing above. It’s just a phone number and a submit. We don’t want to be able to submit the form if the phone number is invalid (according to the PhoneInput itself.

function FormContainer() {
  const [value, setValue] = useState(‘’);
  const [isInvalid, setIsInvalid] = useState(false);

  const handleBlur = (e, isValid) => {
    value && setIsInvalid(!isValid);
  };
  return (
    <>
      <PhoneInput value={value} error={isInvalid} setValue={setValue} onBlur={handleBlur} />
      <button disabled={isInvalid}>Submit</button>
    </>
  );
}

To implement the PhoneInput itself, we created a wrapper around the intl-tel-input library which gave us access to their utility functions, including isValidNumber.1

To use it, we needed to use refs, which make this a more complicated component (at least to me), however, below, I’ve tried to focus only on the parts that are relevant:

import intlTelInput from 'intl-tel-input';
import 'intl-tel-input/build/js/utils.js';
import React, { useRef, forwardRef } from 'react';

const InputWithRef = forwardRef((props, ref) => <input ref={ref} {…props} />)

export default function PhoneInput({ error, onBlur, onError, options, setValue, value, ...rest }: IPhoneInput) {
  const refContainer = useRef(null);
  const iti = useRef(null);
  //[…]
  const handleBlur = e => {
    const { intlTelInputUtils } = window; // Unorthodox approach, but using the utils attached to the window object
    onBlur && onBlur(e, intlTelInputUtils.isValidNumber(value));
  };

  return (
    <InputWithRef
      as="input"
      error={error}
      onBlur={handleBlur}
      ref={refContainer}
      type="text"
      {...rest}
      />
    );

Here, we can see that the when the InputWithRef component emits an onBlur event, it will hit the onBlur event handler passed down from FormContainer if one exists.

In that way, we will be able to control the <button> element without wrapping the button within the PhoneInput or having the validation logic in the container level.

For more reading on refs and forwardingRefs see the React docs.2, 3

Footnotes


Stephen Weiss

Thanks for reading! My name's Stephen Weiss. I live in Chicago with my wife, Kate, and dog, Finn.
Click here to see the archives of my weeks in review and sign up yourself!