Skip to content

Contact sales

By filling out this form and clicking submit, you acknowledge our privacy policy.

How to Trigger a Form Submit in Child Component with Redux

Nov 25, 2019 • 10 Minute Read

Introduction

In this guide, we will learn how to trigger a form submission by dispatching an action from a component outside the form. There are often situations where different child forms are related to each other but fall into different components. In this guide, we will discuss how to consolidate data from different child components and submit them.

Building the Form Component

two fields: email and password. On input change of these fields, we will dispatch an action to update the form values in the global store object.

Let's first get the primary form component right.

      class SignInForm extends Component {
  constructor(props) {
    super(props);
    this.state = {
      values: {
        email: "",
        password: ""
      }
    };
  }

  submitForm = e => {
    e.preventDefault();
    // dispatch FORM_SUBMIT action
  };

  handleInputChange = e =>
    this.setState(
      {
        values: { ...this.state.values, [e.target.name]: e.target.value }
      },
      () => {
        // dispatch SET_FORM_VALUES action
      }
    );

  render() {
    return (
      <div>
        <form onSubmit={this.submitForm}>
          <div className="input-group">
            <label htmlFor="email">E-mail Address</label>
            <input
              type="email"
              name="email"
              id="email"
              value={this.state.values.email}
              onChange={this.handleInputChange}
              title="Email"
              required
            />
          </div>
          <div className="input-group">
            <label htmlFor="password">Password</label>
            <input
              type="password"
              name="password"
              id="password"
              value={this.state.values.password}
              onChange={this.handleInputChange}
              title="password"
              required
            />
          </div>
          <button type="submit">Sign In</button>
        </form>
      </div>
    );
  }
}

export default SignInForm;
    

As you can see in the above code, we are keeping the form values in the component state. However, for the form values to be accessible outside the form component, the values should be in the global store. Therefore, in the handleInputChange() handler, we are going to dispatch an action with the form values in the payload.

Now, let's connect the <SignInForm /> component with Redux.

      import { connect } from "react-redux";

class SignInForm extends Component {
  submitForm = e => {
    e.preventDefault();
    this.props.dispatch({
      type: "SUBMIT_FORM"
    });
  };

  handleInputChange = e =>
    this.setState(
      {
        values: { ...this.state.values, [e.target.name]: e.target.value }
      },
      () =>
        this.props.dispatch({
          type: "SET_FORMVALUES",
          payload: this.state.values
        })
    );

  render() {
    return (
      //
    );
  }
}

export default connect(null)(SignInForm);
    

Notice above in the handleInputChange() method that we are dispatching the action with the form values in the payload attribute.

The Reducer

Our reducer will be as follows (it's just a simple function that will return the state based on the action type):

reducer.js

      const initialState = {
  formValues: {},
  message: ""
};

const reducer = (state = initialState, action) => {
  switch (action.type) {
    case "SET_FORMVALUES":
      return {
        ...state,
        formValues: action.payload
      };
    case "SUBMIT_FORM":
      console.log("--- Triggered Form submission ---");
      console.log("Form Data - ", state.formValues);
      return {
        ...state,
        message: "Form submitted!!"
      };
    default:
      return state;
  }
};

export default reducer;
    

For SUBMIT_FORM action, we will display the values in the console. In a real application, though, you'd probably make an HTTP request to an external server to post the data or authenticate the user.

Wrapping Root with

index.js

      import { Provider } from "react-redux";
import { createStore } from "redux";

import SignInForm from "./SignInForm";

import reducer from "./reducer";

const store = createStore(reducer);

function App() {
  return (
    <Provider store={store}>
      <div className="App">
        <h1>Sign In To Your Account</h1>
        <SignInForm />
      </div>
    </Provider>
  );
}
    

At this point, if you run this example and submit the form, you should be able to see the form values in the console .

Triggering Form Submission from Another Component

Now that the form submission has been successfully triggered by the <SignInForm /> component, let's try and trigger the form submission from a separate component.

For that, let's create a simple <Button /> component and dispatch the SUBMIT_FORM action in the button click handler.

Buttton.js

      import { connect } from "react-redux";

const Button = props => (
  <div>
    Click this to
    <button onClick={e => props.dispatch({ type: "SUBMIT_FORM" })}>
      Trigger Form submit
    </button>
    from outside the form
  </div>
);

export default connect(null)(Button);
    

In the index.js file, we will import this component and place it just after the <SignInForm /> component.

index.js

      // ...
import Button from "./Button";

function App() {
  return (
    <Provider store={store}>
      <div className="App">
        <h1>Sign In To Your Account</h1>
        <SignInForm />
        <Button />
      </div>
    </Provider>
  );
}
    

Now run the application again, but this time click on the second button. You should see the Triggered Form Submission message in the console, same as before.

Complete Source Code

index.js

      import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { createStore } from "redux";

import "./styles.css";

import SignInForm from "./SignInForm";
import Button from "./Button";

import reducer from "./reducer";

const store = createStore(reducer);

function App() {
  return (
    <Provider store={store}>
      <div className="App">
        <h1>Sign In To Your Account</h1>
        <SignInForm />
        <Button />
      </div>
    </Provider>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
    

reducer.js

      const initialState = {
  formValues: {},
  message: ""
};

const reducer = (state = initialState, action) => {
  switch (action.type) {
    case "SET_FORMVALUES":
      return {
        ...state,
        formValues: action.payload
      };
    case "SUBMIT_FORM":
      console.log("--- Triggered Form submission ---");
      console.log("Form Data - ", state.formValues);
      return {
        ...state,
        message: "Form submitted!!"
      };
    default:
      return state;
  }
};

export default reducer;
    

SignInForm.js

      import React, { Component } from "react";
import { connect } from "react-redux";

class SignInForm extends Component {
  constructor(props) {
    super(props);
    this.state = {
      values: {
        email: "",
        password: ""
      }
    };
  }

  submitForm = e => {
    e.preventDefault();
    this.props.dispatch({
      type: "SUBMIT_FORM"
    });
  };

  handleInputChange = e =>
    this.setState(
      {
        values: { ...this.state.values, [e.target.name]: e.target.value }
      },
      () =>
        this.props.dispatch({
          type: "SET_FORMVALUES",
          payload: this.state.values
        })
    );

  render() {
    return (
      <div>
        <form onSubmit={this.submitForm}>
          <div className="input-group">
            <label htmlFor="email">E-mail Address</label>
            <input
              type="email"
              name="email"
              id="email"
              value={this.state.values.email}
              onChange={this.handleInputChange}
              title="Email"
              required
            />
          </div>
          <div className="input-group">
            <label htmlFor="password">Password</label>
            <input
              type="password"
              name="password"
              id="password"
              value={this.state.values.password}
              onChange={this.handleInputChange}
              title="password"
              required
            />
          </div>
          <button type="submit">Sign In</button>
        </form>
        <div className="message">
          {this.props.message.length > 0 && this.props.message}
        </div>
      </div>
    );
  }
}

const mapStateToProps = state => ({
  message: state.message
});

export default connect(mapStateToProps)(SignInForm);
    

Button.js

      import React from "react";
import { connect } from "react-redux";

const Button = props => (
  <div>
    Click this to
    <button onClick={e => props.dispatch({ type: "SUBMIT_FORM" })}>
      Trigger Form submit
    </button>
    from outside the form
  </div>
);

export default connect(null)(Button);
    

styles.css

      .App {
  font-family: sans-serif;
}

.input-group {
  margin-bottom: 10px;
}

.input-group label {
  display: block;
  margin-bottom: 5px;
}

button {
  border: none;
  padding: 8px 24px;
}

.message {
  margin-top: 20px;
  font-weight: 600;
}

.message.error {
  color: red;
}
    

Conclusion

In this guide, we learned how to dispatch actions from other components and submit form values stored in the global store. Many libraries, such as redux-form, work based on similar principles. I hope you're getting better at the Redux game every day. Until next time, keep hustling.