AJAX Library for React/REST API
The following function is commonly used in AJAX REST API calls like that which is used with React. The ajax2()
function submits the AJAX request and is a Promise
.
If not already familiar with what a REST API is then see the REST API Tutorial.
While ajax2()
should work on IE6 thanks to its synchronous operation when a Promise
is not available, React and React-DOM probably are not compatible with this old browser.
This function, ajax2()
, only supports submitting JSON in the request body.
JSX is not used here.
Download all the code for this project: REACTAJAX2.zip
ajax2.js
// ajax2.js
/*
request = {
verb: "GET POST PUT PATCH DELETE",
path: "/api/",
headers: {header1:"value1",header2:"value2"},
data: '{"is":"json"}' or Object,
onprogress: function(percent){}
};
*/
function ajax2(request) {
var obj = "object";
var undef = "undefined";
if (typeof request != obj) { request = {}; }
var canPromise = (typeof Promise != undef);
var xmlobj;
if (typeof XMLHttpRequest != undef) { // must use typeof operator for compatibility reasons
xmlobj = new XMLHttpRequest();
}
else if (typeof window.ActiveXObject != undef) {
var aVersions = ["MSXML2.XMLHttp.5.0", "MSXML2.XMLHttp.4.0", "MSXML2.XMLHttp.3.0", "MSXML2.XMLHttp", "Microsoft.XMLHttp"];
for (var i = 0; i < aVersions.length; i++) {
try {
xmlobj = new window.ActiveXObject(aVersions[i]);
break;
} catch (err) {
//void
}
}
}
if (typeof xmlobj != obj) {
return {then:function(){return{catch:function(ca){ca("XMLHttpRequest object could not be created");}}}};
}
if(typeof request.onprogress == "function" && typeof xmlobj.upload == obj) {
xmlobj.upload.addEventListener("progress", function (event) {
request.onprogress(Math.floor(event.loaded / event.total * 100));
});
}
// if no verb is specified then use "get"; if no path is specified then use the current file
xmlobj.open(request.verb || "get", request.path || location.pathname, canPromise);
xmlobj.setRequestHeader("Content-Type", "application/json; charset=UTF-8");
if(typeof request.headers == obj) {
for(var prop in request.headers) {
xmlobj.setRequestHeader(prop, request.headers[prop]);
}
}
if(typeof request.data == obj) {
try {
request.data = JSON.stringify(request.data);
}
catch {
}
}
xmlobj.send(request.data || null);
if(canPromise) {
return new Promise(function (resolve, reject) {
xmlobj.onreadystatechange = function () {
if (xmlobj.readyState == 4) {
if (xmlobj.status >= 200 && xmlobj.status < 300) {
resolve(xmlobj.responseText);
}
else {
reject(xmlobj.statusText);
}
}
};
});
}
else {
if (xmlobj.status >= 200 && xmlobj.status < 300) {
return {then:function(th){th(xmlobj.responseText);return{catch:function(){}}}};
}
else {
return {then:function(){return{catch:function(ca){ca(xmlobj.statusText);}}}};
}
}
}
Here is a screen shot of React code using the above AJAX library.

Here is example React code using the AJAX library.
// customers.modern.js
'use strict';
function renderTable(obj) {
var ths = [];
ths[ths.length] = React.createElement("th", {colspan:"4"}, "Customers");
var trs = [];
trs[trs.length] = React.createElement("tr", null, ths);
var thead = React.createElement("thead", null, trs);
ths = [];
ths[ths.length] = React.createElement("th", null, "NAME");
ths[ths.length] = React.createElement("th", null, "ADDRESS");
ths[ths.length] = React.createElement("th", null, "ZIP");
ths[ths.length] = React.createElement("th");
trs = [];
trs[trs.length] = React.createElement("tr", null, ths);
for(var i = 0; obj.state.array && i < obj.state.array.length; i++) { // could use array.forEach() instead
var tds = [];
tds[tds.length] = React.createElement("td", null, obj.state.array[i].name);
tds[tds.length] = React.createElement("td", null, obj.state.array[i].addr);
tds[tds.length] = React.createElement("td", null, obj.state.array[i].zip);
var a = React.createElement("a", {class:"btn btn-danger", custid:obj.state.array[i].id, onClick:obj.delete}, "delete");
tds[tds.length] = React.createElement("td", null, a);
trs[trs.length] = React.createElement("tr", null, tds);
}
var tbody = React.createElement("tbody", null, trs);
return React.createElement("table", {class:"table table-bordered table-striped table-hover"}, [thead,tbody]);
}
function renderForm(obj) {
var span = React.createElement("span", {class:"input-group-addon"}, "Name");
var text = React.createElement("input", {class:"form-control",type:"text",name:"name",onChange:obj.change,autocomplete:"off",required:1});
var divs = [React.createElement("div", {class:"input-group input-group-lg"}, [span, text])];
divs[divs.length] = React.createElement("br");
span = React.createElement("span", {class:"input-group-addon"}, "Address");
text = React.createElement("input", {class:"form-control",type:"text",name:"addr",onChange:obj.change,autocomplete:"off",required:1});
divs[divs.length] = React.createElement("div", {class:"input-group input-group-lg"}, [span, text]);
divs[divs.length] = React.createElement("br");
span = React.createElement("span", {class:"input-group-addon"}, "Zipcode");
text = React.createElement("input", {class:"form-control",type:"text",name:"zip",onChange:obj.change,autocomplete:"off",required:1});
var span2 = React.createElement("span", {class:"input-group-btn"}, React.createElement("button",{class:"btn btn-primary",type:"submit"},"Add Customer"));
divs[divs.length] = React.createElement("div", {class:"input-group input-group-lg"}, [span, text, span2]);
var form = React.createElement("form", {onSubmit:obj.submit}, divs);
return form;
}
function setProgressBars(percent) {
var progs = document.getElementsByClassName("progress-bar");
for(var i = 0; i < progs.length; i++) {
if(100 == percent) {
progs[i].style.width = '0';
}
else {
progs[i].style.width = percent + '%';
}
}
}
class Customers extends React.Component {
constructor(props) {
super(props);
this.state = {
array: [],
form: { name: "", addr: "", zip: ""}
};
this.submit = this.submit.bind(this);
this.change = this.change.bind(this);
this.delete = this.delete.bind(this);
}
componentDidMount() {
var t = this;
ajax2({
verb: "GET",
path: "/api/customers",
onprogress: function (percent) { setProgressBars(percent); }
}).then(function (res) {
t.setState({array:JSON.parse(res), form: t.state.form});
}).catch(function (err) {
console.log(err);
});
}
delete(event) {
var t = this;
var custid = parseInt(event.target.getAttribute("custid"));
ajax2({
verb: "DELETE",
path: "/api/customers/" + custid,
onprogress: function (percent) { setProgressBars(percent); }
}).then(function (res) {
var deleted = JSON.parse(res);
for(var i = 0; i < t.state.array.length; i++) { // could use array.filter() instead
if(t.state.array[i].id == deleted.id) {
t.state.array.splice(i, 1);
break;
}
}
t.setState(t.state);
}).catch(function (err) {
console.log(err);
});
}
submit(event) {
var t = this;
ajax2({
verb: "POST",
path: "/api/customers",
data: t.state.form,
onprogress: function (percent) { setProgressBars(percent); }
}).then(function (res) {
t.state.array[t.state.array.length] = JSON.parse(res);
t.setState(t.state);
document.forms[0].reset();
}).catch(function (err) {
console.log(err);
});
event.preventDefault();
}
change(event) {
var form = event.target.form;
this.setState({ array: this.state.array, form: {name: form.name.value, addr: form.addr.value, zip: form.zip.value} });
}
render() {
return React.createElement("div", {class:"container"}, [renderTable(this), renderForm(this), React.createElement("div", {class:"progress"}, React.createElement("div",{class:"progress-bar progress-bar-striped",role:"progressbar"}))]);
}
}
ReactDOM.render(React.createElement(Customers), document.getElementById("root"));
The above code is not compatible with IE11 because it uses the class keyword among other things (the code working with arrays is old school). It is EcmaScript 6 code and IE11 is compatible with EcmaScript 5. This code does the same thing as above; it's just harder to read, but if compatibility with IE11 is paramount then this is how it must look. See JavaScript Classes for more information.
// customers.js
'use strict';
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
function renderTable(obj) {
var ths = [];
ths[ths.length] = React.createElement("th", { colspan: "4" }, "Customers");
var trs = [];
trs[trs.length] = React.createElement("tr", null, ths);
var thead = React.createElement("thead", null, trs);
ths = [];
ths[ths.length] = React.createElement("th", null, "NAME");
ths[ths.length] = React.createElement("th", null, "ADDRESS");
ths[ths.length] = React.createElement("th", null, "ZIP");
ths[ths.length] = React.createElement("th");
trs = [];
trs[trs.length] = React.createElement("tr", null, ths);
for (var i = 0; obj.state.array && i < obj.state.array.length; i++) { // could use array.forEach() instead
var tds = [];
tds[tds.length] = React.createElement("td", null, obj.state.array[i].name);
tds[tds.length] = React.createElement("td", null, obj.state.array[i].addr);
tds[tds.length] = React.createElement("td", null, obj.state.array[i].zip);
var a = React.createElement("a", { class: "btn btn-danger", custid: obj.state.array[i].id, onClick: obj.delete }, "delete");
tds[tds.length] = React.createElement("td", null, a);
trs[trs.length] = React.createElement("tr", null, tds);
}
var tbody = React.createElement("tbody", null, trs);
return React.createElement("table", { class: "table table-bordered table-striped table-hover" }, [thead, tbody]);
}
function renderForm(obj) {
var span = React.createElement("span", { class: "input-group-addon" }, "Name");
var text = React.createElement("input", { class: "form-control", type: "text", name: "name", onChange: obj.change, autocomplete: "off", required: 1 });
var divs = [React.createElement("div", { class: "input-group input-group-lg" }, [span, text])];
divs[divs.length] = React.createElement("br");
span = React.createElement("span", { class: "input-group-addon" }, "Address");
text = React.createElement("input", { class: "form-control", type: "text", name: "addr", onChange: obj.change, autocomplete: "off", required: 1 });
divs[divs.length] = React.createElement("div", { class: "input-group input-group-lg" }, [span, text]);
divs[divs.length] = React.createElement("br");
span = React.createElement("span", { class: "input-group-addon" }, "Zipcode");
text = React.createElement("input", { class: "form-control", type: "text", name: "zip", onChange: obj.change, autocomplete: "off", required: 1 });
var span2 = React.createElement("span", { class: "input-group-btn" }, React.createElement("button", { class: "btn btn-primary", type: "submit" }, "Add Customer"));
divs[divs.length] = React.createElement("div", { class: "input-group input-group-lg" }, [span, text, span2]);
var form = React.createElement("form", { onSubmit: obj.submit }, divs);
return form;
}
function setProgressBars(percent) {
var progs = document.getElementsByClassName("progress-bar");
for (var i = 0; i < progs.length; i++) {
if (100 == percent) {
progs[i].style.width = '0';
} else {
progs[i].style.width = percent + '%';
}
}
}
var Customers = function (_React$Component) {
_inherits(Customers, _React$Component);
function Customers(props) {
_classCallCheck(this, Customers);
var _this = _possibleConstructorReturn(this, (Customers.__proto__ || Object.getPrototypeOf(Customers)).call(this, props));
_this.state = {
array: [],
form: { name: "", addr: "", zip: "" }
};
_this.submit = _this.submit.bind(_this);
_this.change = _this.change.bind(_this);
_this.delete = _this.delete.bind(_this);
return _this;
}
_createClass(Customers, [{
key: "componentDidMount",
value: function componentDidMount() {
var t = this;
ajax2({
verb: "GET",
path: "/api/customers",
onprogress: function onprogress(percent) {
setProgressBars(percent);
}
}).then(function (res) {
t.setState({ array: JSON.parse(res), form: t.state.form });
}).catch(function (err) {
console.log(err);
});
}
}, {
key: "delete",
value: function _delete(event) {
var t = this;
var custid = parseInt(event.target.getAttribute("custid"));
ajax2({
verb: "DELETE",
path: "/api/customers/" + custid,
onprogress: function onprogress(percent) {
setProgressBars(percent);
}
}).then(function (res) {
var deleted = JSON.parse(res);
for (var i = 0; i < t.state.array.length; i++) { // could use array.filter() instead
if (t.state.array[i].id == deleted.id) {
t.state.array.splice(i, 1);
break;
}
}
t.setState(t.state);
}).catch(function (err) {
console.log(err);
});
}
}, {
key: "submit",
value: function submit(event) {
var t = this;
ajax2({
verb: "POST",
path: "/api/customers",
data: t.state.form,
onprogress: function onprogress(percent) {
setProgressBars(percent);
}
}).then(function (res) {
t.state.array[t.state.array.length] = JSON.parse(res);
t.setState(t.state);
document.forms[0].reset();
}).catch(function (err) {
console.log(err);
});
event.preventDefault();
}
}, {
key: "change",
value: function change(event) {
var form = event.target.form;
this.setState({ array: this.state.array, form: { name: form.name.value, addr: form.addr.value, zip: form.zip.value } });
}
}, {
key: "render",
value: function render() {
return React.createElement("div", { class: "container" }, [renderTable(this), renderForm(this), React.createElement("div", { class: "progress" }, React.createElement("div", { class: "progress-bar progress-bar-striped", role: "progressbar" }))]);
}
}]);
return Customers;
}(React.Component);
ReactDOM.render(React.createElement(Customers), document.getElementById("root"));
Here is the single HTML page that loads the JavaScript and CSS files.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>CUSTOMERS REACT EXAMPLE</title>
<link rel="stylesheet" href="bootstrap.min.css">
</head>
<body>
<div class="container">
<h1>REACT EXAMPLE</h1>
</div>
<div id="root">
</div>
<script src="ajax2.js"></script>
<script src="react.min.js"></script>
<script src="customers.js"></script> <!--customers.modern.js not compatible with IE11-->
</body>
</html>
Here is the Node.js server that serves static files and implements the REST API endpoints used by the above client code. The customer data is stored in a simple array.
// server.js (Node.js server)
const fs = require("fs");
const http = require("http");
const port = process.env.PORT || 8000;
const server = http.createServer().listen(port);
console.log(`listening on port ${port}`);
var mime = {};
mime[".html"] = "text/html";
mime[".css"] = "text/css";
mime[".js"] = "application/javascript";
mime[".json"] = "application/json; charset=UTF-8";
mime[".png"] = "image/png";
mime[".jpg"] = "image/jpeg";
mime[".ico"] = "image/x-icon";
mime[".txt"] = "text/plain";
/*
customer = {
id: 0, name: "", addr: "", zip: ""
};
*/
var customers = [];
function createCustomer(customer) {
var i = customers.length;
customers[i] = { id: i, name: customer.name, addr: customer.addr, zip: customer.zip };
return customers[i];
}
function readCustomers() {
copy = [];
customers.forEach(e => {
if(e)
copy.push(e);
});
return copy;
}
function readCustomer(i) {
if(i < customers.length && i >= 0 && customers[i])
return { id: i, name: customers[i].name, addr: customers[i].addr, zip: customers[i].zip };
return null;
}
function updateCustomer(i, customer) {
if(i < customers.length && i >= 0 && customers[i]) {
customers[i].name = customer.name;
customers[i].addr = customer.addr;
customers[i].zip = customer.zip;
return customers[i];
}
return null; // return empty object to client
}
function deleteCustomer(i) {
if(i < customers.length && i >= 0 && customers[i]) {
var customer = customers[i];
customers[i] = null;
return customer;
}
return null;
}
server.on("request", function (request, response) {
console.log(request.method + " " + request.url);
if(request.method == "GET") {
var path = request.url.substring(1, request.url.length).toLowerCase();
if(path.length == 0)
path = "index.html";
if(path.substring(0, 4) == "api/") // GET CUSTOMER(S)
{
path = path.split("/");
if(path.length >= 3 && path[1] == "customers" && !isNaN(path[2])) {
var i = path[2];
if(i < customers.length && i >= 0 && customers[i]) {
response.writeHead(200, {"Content-Type":mime[".json"]});
response.end(JSON.stringify(readCustomer(i)));
return;
}
}
else if(path.length >= 2 && path[1] == "customers") {
response.writeHead(200, {"Content-Type":mime[".json"]});
response.end(JSON.stringify(readCustomers()));
return;
}
response.writeHead(404, {"Content-Type":mime[".txt"]});
response.end(`404 - path ${request.url} not found`);
}
else // RETURN A STATIC HTML FILE
{
fs.readFile(path, function (err, data) {
if(!err) {
var ext = path.substring(path.lastIndexOf('.'), path.length);
response.writeHead(200, {"Content-Type":(mime[ext]||mime[".txt"])});
response.end(data.toString("utf8"));
}
else {
response.writeHead(404, {"Content-Type":mime[".txt"]});
response.end(`404 - path ${request.url} not found`);
}
});
}
}
else if(request.method == "POST") { // CREATE CUSTOMER
var body = "";
request.on("data", function (data) {
body += data.toString();
});
request.on("end", function () {
console.log(body);
try {
const path = request.url.toLowerCase().split("/");
if(path.length >= 3 && path[1] == "api" && path[2] == "customers") { // check for the end point
var cust = JSON.parse(body);
if(cust.name && cust.addr && cust.zip) {
response.writeHead(200, {"Content-Type":mime[".json"]});
response.end(JSON.stringify(createCustomer(cust)));
return;
}
}
}
catch {}
response.writeHead(400, {"Content-Type":mime[".txt"]}); // BAD REQUEST
response.end("400 - CLIENT ERROR: BAD REQUEST");
});
}
else if(request.method == "PUT") { // UPDATE CUSTOMER
const path = request.url.toLowerCase().split("/");
var body = "";
request.on("data", function (data) {
body += data.toString();
});
request.on("end", function () {
console.log(body);
try {
if(path.length >= 4 && path[1] == "api" && path[2] == "customers" && !isNaN(path[3]) && path[3] < customers.length && path[3] >= 0 && customers[path[3]]) {
const cust = JSON.parse(body);
if(cust.name && cust.addr && cust.zip) {
response.writeHead(200, {"Content-Type":mime[".json"]});
response.end(JSON.stringify(updateCustomer(path[3], cust)));
return;
}
else {
response.writeHead(400, {"Content-Type":mime[".txt"]}); // BAD REQUEST
response.end("400 - CLIENT ERROR: BAD REQUEST");
}
}
else {
response.writeHead(404, {"Content-Type":mime[".txt"]});
response.end(`404 - path ${request.url} not found`);
}
}
catch { // catch when the client send non-JSON data
response.writeHead(400, {"Content-Type":mime[".txt"]}); // BAD REQUEST
response.end("400 - CLIENT ERROR: BAD REQUEST");
}
});
}
else if(request.method == "DELETE") { // DELETE CUSTOMER
const path = request.url.toLowerCase().split("/");
if(path.length >= 4 && path[1] == "api" && path[2] == "customers" && !isNaN(path[3]) && path[3] < customers.length && path[3] >= 0 && customers[path[3]]) {
response.writeHead(200, {"Content-Type":mime[".json"]});
response.end(JSON.stringify(deleteCustomer(path[3])));
return;
}
response.writeHead(404, {"Content-Type":mime[".txt"]});
response.end(`404 - path ${request.url} not found`);
}
else {
response.writeHead(405, {"Content-Type":mime[".txt"]});
response.end(`method ${request.method} not allowed`);
}
});
Minified Bootstrap and React Files
These files are required for the above code to succeed and can be found in REACTAJAX2.zip.