2019-09-05
|~2 min read
|391 words
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
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
.
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.
The React Docs on refs and forwardingRefs are helpful, albeit somewhat confusing. A task for a future date is to try to explain them myself.
Hi there and thanks for reading! My name's Stephen. I live in Chicago with my wife, Kate, and dog, Finn. Want more? See about and get in touch!