Submit Form in React without jQuery AJAX
Dec 26, 2019 • 12 Minute Read
Introduction
In this guide, we are going to look at how we can avoid using jQuery and use native JavaScript code to submit a form to React. Let's start by understanding why it's a bad idea to include jQuery with React.
jQuery in React
Well, when you come into the React ecosystem, you don't need jQuery anymore. I'm not saying that they won't work together, they definitely will. But when you are using jQuery in React, you must be very careful and more thoughtful about the DOM updates and the state of the application.
Both jQuery and React serve the same purpose of manipulating the DOM. jQuery does that by direct DOM scripting and React by using the Virtual DOM. Modifying the DOM outside of the React Application means that React is possibly no longer handling state, events moreover UI rendering. Additionally, it also means that you are building one application with two different approaches, along with sending extra dependencies to the browser, which is inevitably going to increase the application size.
There's nothing in jQuery that native JavaScript cannot handle on its own because, in actuality, jQuery uses Vanilla Javascript. There are particular use cases when you want to use jQuery, like, for example, a jQuery plugin for creating sortable lists. But, even in that case, there probably is a React component available out there in the open-source community for doing the same.
Let's Code Form
For this demo, we will create a sign-in form with two fields: email and password.
We will keep our form state as follows:
this.state = {
values: {
email: "",
password: ""
},
isSubmitting: false,
isError: false
};
isSubmitting will be set to true when the form submission is triggered, and isError will be set to true when there is an error during the submission.
<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>
handleInputChange = e =>
this.setState({
values: { ...this.state.values, [e.target.name]: e.target.value }
});
submitForm = e => {
e.preventDefault();
console.log(this.state.values);
}
In the submitForm() function, we will be submitting the form to a login API endpoint. I hacked up a dummy API, using node and express for this demo. In the handleInputChange() function, I have used the spread operator [...]. For those of you who are not familiar with it, it's the modern way (ES6 syntax) of expanding an iterable and spreading its element.
The Fetch API
Earlier, if we had to make an API request, we would use the XML HTTP Request (XHR). I understand why most of you guys preferred jQuery AJAX over XHR; it had cleaner syntax compared to XHR which was horrific and messy.
But now JavaScript has a new native, Promise-based method to make network requests- the Fetch API.
"The Fetch API provides a JavaScript interface for accessing and manipulating parts of the HTTP pipeline, such as requests and responses. It also provides a global fetch() method that provides an easy, logical way to fetch resources asynchronously across the network." - MDN web docs
The Fetch API differs from jQuery.ajax() in two main ways:
-
The Promise returned by fetch() won't reject on HTTP error status, even if the response is an HTTP 404 or 500. Instead, it will typically resolve (with ok status set to false), and it will only reject on a network failure or if anything stopped the request from completing.
-
By default, fetch won't send any cookies to the server; for that, the init option must set.
General Syntax
fetch(url, { ...options })
.then(response => {
// Handle the response and return the data
})
.then(data => {
// Handle the data that is returned from the server
})
.catch(err => {
// Handle the error, if any
})
fetch() optionally accepts a second "options" parameter which can include the method of the request (GET, POST, etc.), the body of the request, and, also, the headers. By default, the method of request is GET.
GET Request Example
fetch(`https://jsonplaceholder.typicode.com/posts`)
.then(res => res.json())
.then(data => console.log(data))
The json() method resolves the response data to JSON object. Alternatively, we can also use text() and blob() methods.
POST Request Example
fetch(`https://jsonplaceholder.typicode.com/posts`, {
method: 'POST',
body: JSON.stringify(values),
headers: {
'Content-Type': 'application/json'
}
}).then(res => res.json())
.then(data => console.log(data))
.catch(err => console.error("Error:", err));
Let's Get Back to React
Now that we are familiar with fetch(), let's use it in our submitForm() method.
submitForm = e => {
e.preventDefault();
this.setState({ isSubmitting: true });
fetch("https://61m46.sse.codesandbox.io/login", {
method: "POST",
body: JSON.stringify(this.state.values),
headers: {
"Content-Type": "application/json"
}
})
.then(res => {
this.setState({ isSubmitting: false });
return res.json();
})
.then(data => {
console.log(data);
!data.hasOwnProperty("error")
? this.setState({ message: data.success })
: this.setState({ message: data.error, isError: true });
});
I'm not a big fan of the callbacks - .then().then().then(), I prefer async-await. Async/Await is a special syntax for handling Promises.
A Little Bit About Async-Await
The async keyword is placed before the function, to instruct JavaScript that it is an asynchronous function and will return a promise. Even if the function returns a non-promise value, JavaScript will still wrap that value in a resolved promise.
The await keyword makes the JavaScript wait until the Promise is resolved or rejected before moving on to the next line of code to be executed.
Lets Refactor the submitForm() Method
submitForm = async e => {
e.preventDefault();
this.setState({ isSubmitting: true });
const res = await fetch("https://61m46.sse.codesandbox.io/login", {
method: "POST",
body: JSON.stringify(this.state.values),
headers: {
"Content-Type": "application/json"
}
});
this.setState({ isSubmitting: false });
const data = await res.json();
!data.hasOwnProperty("error")
? this.setState({ message: data.success })
: this.setState({ message: data.error, isError: true });
That was pretty easy, right? No messy syntax and bloating our application with jQuery. Have a look at the complete source code.
Complete Source Code
Let's combine the above-discussed code all together:
index.js
import React, { Component } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
class SignInForm extends Component {
constructor(props) {
super(props);
this.state = {
values: {
email: "",
password: ""
},
isSubmitting: false,
isError: false
};
}
submitForm = async e => {
e.preventDefault();
console.log(this.state);
this.setState({ isSubmitting: true });
const res = await fetch("https://61m46.sse.codesandbox.io/login", {
method: "POST",
body: JSON.stringify(this.state.values),
headers: {
"Content-Type": "application/json"
}
});
this.setState({ isSubmitting: false });
const data = await res.json();
!data.hasOwnProperty("error")
? this.setState({ message: data.success })
: this.setState({ message: data.error, isError: true });
setTimeout(
() =>
this.setState({
isError: false,
message: "",
values: { email: "", password: "" }
}),
1600
);
};
handleInputChange = e =>
this.setState({
values: { ...this.state.values, [e.target.name]: e.target.value }
});
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.state.isError && "error"}`}>
{this.state.isSubmitting ? "Submitting..." : this.state.message}
</div>
</div>
);
}
}
function App() {
return (
<div className="App">
<h1>Sign In To Your Account</h1>
<SignInForm />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
styles.css -
The styling is not that fancy; the purpose of this code was to demonstrate form submission, and not the look of it.
.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;
}
The URL in the fetch() may not work for you because the node container might be inactive. So, I suggest that if you are going to run this code, create your dummy API and use that URL in fetch(). You can refer my nodejs code from down here:
const app = require("express")();
const cors = require("cors");
const bodyParser = require("body-parser");
app.use(cors());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.get("/", (req, res) => {
res.send("Server running");
});
app.post("/login", (req, res) => {
const { email, password } = req.body;
if (email !== "[email protected]" || password !== "test")
return res.status(401).json({ error: "Invalid Credentials" });
res.json({ success: "Logged In successfully" });
});
const port = process.env.PORT || 8080;
app.listen(port);
Conclusion
That is all for this guide; I hope you are going to experiment with some APIs out there. If you have any queries regarding this topic, feel free to contact me at CodeAlphabet. Cheers and happy coding!