Programmatic form submission in React

Submitting forms programmatically in React is much harder than it seems at first. Especially if we want to do it from the level of another component. In addition, the solution commonly used in React 16 usually won’t work in newer versions of the library. In this post, I will show you the solution that works in React 17 and older versions of the library as well. It is likely going to work also for future versions.

Refs

The mechanism we are going to use is called refs. It’s a kind of escape hatch when default methods of interaction between elements (props and state) are not enough. Using refs, we will be able to submit a form from another component.

Don’t overuse refs. Authors of React, in the official documentation, emphasize to use them only when it’s necessary. In all other cases, follow the default data flow.

To begin with, let’s consider the simplest example. We want to submit a form by clicking a button located outside of the form but in the same class component.

// src/App.js

import React, { Component } from "react";

class App extends Component {

    submitForm = () => {
        this.formRef.dispatchEvent(
            new Event("submit", { bubbles: true, cancelable: true })
        )
    };

    render() {
        return (
            <div className="app">
                <form
                    ref={ref => this.formRef = ref}
                    onSubmit={e => {
                        e.preventDefault();
                        console.log(e.target.test.value);
                        // Do what you want here e.g. make a HTTP request.
                    }}
                >
                    <input name="test" />
                </form>

                <button onClick={this.submitForm}>Button outside the form</button>
            </div>
        );
    }
}

export default App;

On line 17th, we passed a function to the ref attribute of the form. The function stores a ref assigned to the form in the formRef instance variable.

Starting from React 17, you can use createRef() function and attach an element to it instead of passing a function to the ref attribute. You can find an example here.

When we have the ref to our form, all we need to do is to run some function that will submit the form. Sometimes you can find on the internet solutions like this:

// Wrong solution
this.formRef.submit();

The function above will submit a form, but the code attached to onSubmit event won’t be executed. As a result, the site will reload. That’s why we need to dispatch a submit event on our form.

// Correct solution
this.formRef.dispatchEvent(
    new Event("submit", { bubbles: true, cancelable: true })
)

The parameters provided into Event constructor play very important role:

  • cancelable – has to be true, otherwise, the preventDefault() method in the onSubmit event listener won’t work and the default flow will go on
  • bubbles – this parameter is not required in React 16 and older. However, in React 17, the way events are delegated has changed. Since then, the solution won’t work without this parameter. You can read more about it here.

Using refs in the parent component

In order to create a ref in a child component and then use it in a parent, we are going to need an additional method. I called it setFormRef() and it does exactly what the name suggests. We cannot pass a variable as a prop and then attach a ref to it because props are immutable. That’s why we need a proxy method that we can pass to our child component.

// src/App.js

import React, { Component } from "react";
import TestForm from "./forms/TestForm";

class App extends Component {

    submitForm = () => {
        this.formRef.dispatchEvent(
            new Event("submit", { bubbles: true, cancelable: true })
        )
    };

    setFormRef = formRef => {
        this.formRef = formRef;
    };

    render() {
        return (
            <div className="app">
                <TestForm setFormRef={this.setFormRef} />

                <button onClick={this.submitForm}>Button outside the form</button>
            </div>
        );
    }
}

export default App;

The submit button and submitForm() method work the same as in the previous example. Now let’s take a look at the TestForm component. It gets the setFormRef() method as a prop and set a ref in the parent component. And that’s all we have to do!

// src/forms/TestForm.js

import React, { Component } from "react";

class TestForm extends Component  {

    render() {
        return(
            <form
                ref={ref => this.props.setFormRef(ref)}
                onSubmit={e => {
                    e.preventDefault();
                    console.log(e.target.test.value);
                    // Do what you want here e.g. make a HTTP request.
                }}
            >
                <input name="test" />
            </form>
        );
    }
}

export default TestForm;

Conclusion

Refs are very powerful and allow you to do things impossible with only state and props. However, they should be used with caution and only in cases where it’s necessary. Form submitting, managing input focus, and controlling media players are just a few examples.

You May Also Like

Leave a Reply

Your email address will not be published. Required fields are marked *