React, forms and DOM nodes

To validate a form with the HTML5 constraint validation API you need to call .checkValidity() on an actual <form> DOM node. With a custom React form component, how would you access the constraint validation API inside a parent component?

ref and innerRef

Suppose we’ve got a component named App that renders a custom form component named Form, and we want to validate the form data from inside App, using the constraint validation API.

Let’s define a new prop for our Form component and let’s call the prop innerRef. innerRef should be a function. Form passes the innerRef prop as the ref prop to the <form> element.

function Form({ validated, innerRef, children, ...otherProps }) {
  return (
    <form
      noValidate
      className={formClasses}
      ref={innerRef}
      {...otherProps}
      >
      {children}
    </form>
  );
}

ref is special. When React mounts a component, React calls the ref prop and gives it the component DOM node as an argument.

In App’s render() function, pass a function that stores the DOM element in this.form:

class App extends React.Component {
  render() {
    return (
      <Form
        innerRef={el => (this.form = el)}
      >
      </Form>);
  }
}

After React has mounted the Form component, we can access the form DOM element anywhere in the parent component using this.form. To validate the form, call .checkValidity directly on this.form:

class App extends React.Component {
  isValid() {
    return this.form.checkValidity();
  }
}

Done! You can use the same technique anywhere you want to access DOM nodes inside a component from the outside.

Further Reading

I first came across the innerRef idea in Styled Components, but other React libraries use the same naming convention. The React documentation also talks about this pattern.

To brush up on React fundamentals, have a look at React for Real.