17
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.
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
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.
Our reducer will be as follows (it's just a simple function that will return the state based on the action
type):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
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 .
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.
1 2 3 4 5 6 7 8 9 10 11 12 13
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// ... 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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
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);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
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;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
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);
1 2 3 4 5 6 7 8 9 10 11 12 13 14
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);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
.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; }
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.
17