I have one GET
request at first to retrieve a data structure. Then I perform some changes and perform a PUT
request to send the data structure to the server and update the record. When I click the submit button on the on the first form it works as expected and when I submit the second one it executes as expected, but it logs 2 errors. Furthermore, when I make 2 edits successively it crashes the app and gives an RangeError: Maximum call stack size exceeded
error. The code related to the RangeError: Maximum call stack size exceeded
error is at the bottom.
Image showing initial errors on update
I have been told that this is because of improper asynchronous code but I cant see the error myself.
Range Error Console Log
Here is my client side script:
<script>
const capitalize = ([first, ...rest]) =>
${first.toUpperCase()}${rest.join('')};
let patientDataRetrieved = false;
let _xhr,_xhr2 = null;
let patientID ;
let first_name;
let last_name;
let email;
let sex;
let date_of_birth;
let patient_phone;
let marital_status;
let emergency_contact1_rel ;
let emergency_contact1_phone;
let emergency_contact2_rel ;
let emergency_contact2_phone;
let spouse_phone;
$("body").on("contextmenu",function(e){
return false;
});
$('body').bind('cut copy ', function (e) {
e.preventDefault();
});
document
.getElementById("search")
.addEventListener("submit",ev => {
ev.preventDefault();
patientID = ev.target.patientID.value;
let uid = patientID;
getPatientDetails()
function getPatientDetails(){
if(_xhr2!= null)
_xhr2.abort();
if(patientDataRetrieved===false){
$(document).ready(()=>{
_xhr = $.ajax({
url: '/profile/'+uid,
type: 'GET',
cache: false,
success: function(data){
//Instantiate Variables of User Data
let userdata = data;
console.log(userdata);
first_name = userdata.first_name;
last_name = userdata.last_name;
sex = userdata.sex;
email = userdata.email;
let blood_type = userdata.blood_type;
date_of_birth = userdata.date_of_birth;
spouse_phone = userdata.spouse_phone;
patient_phone = userdata.patient_phone;
marital_status =userdata.marital_status;
spouse_phone = userdata.spouse_phone;
emergency_contact1_rel = userdata.emergency.contact1.relationship;
emergency_contact1_phone = userdata.emergency.contact1.phone;
emergency_contact2_rel = userdata.emergency.contact2.relationship;
emergency_contact2_phone = userdata.emergency.contact2.phone;
let chronic_illnesses = userdata.chronic_illnesses;
let past_surgeries = userdata.past_surgeries;
let allergies = userdata.allergies;
let long_term_medication = userdata.long_term_medication;
//Append User Data to respective td in table
$(".firstname").append(capitalize(first_name));
$(".lastname").append(capitalize(last_name));
$("#blood_type").val(blood_type);
$(".sex").append(capitalize(sex));
$(".date_of_birth").append(date_of_birth);
$(".patient_phone").append(patient_phone);
$(".marital_status").append(capitalize(marital_status));
$(".spouse_phone").append(spouse_phone);
$(".emergency_contact1_rel").append(capitalize(emergency_contact1_rel));
$(".emergency_contact1_rel").append(", Phone: "+emergency_contact1_phone);
$(".emergency_contact2_rel").append(capitalize(emergency_contact2_rel));
$(".emergency_contact2_rel").append(", Phone: "+emergency_contact2_phone);
$("#chronic_illnesses").val(chronic_illnesses);
$("#past_surgeries").val(past_surgeries);
$("#allergies").val(allergies);
$("#long_term_medication").val(long_term_medication);
document.getElementById("patientData").style.display ="block";
patientDataRetrieved = true;
return true;
}
, error: function(jqXHR, textStatus, err){
alert('text status '+textStatus+', err
'+err)
}
});
});
}
}
});
document.getElementById("editForm").addEventListener("submit", ev =>{
ev.preventDefault();
updatePatientInfo()
function updatePatientInfo() {
if(_xhr!=null){_xhr.abort()}
let formData = {
first_name: first_name,
last_name: last_name,
email: email,
blood_type: $("#blood_type").val(),
sex: sex,
date_of_birth:date_of_birth,
patient_phone:patient_phone,
marital_status:marital_status,
spouse_phone: spouse_phone,
emergency_contact1_relationship:emergency_contact1_rel,
emergency_contact1_phone:emergency_contact1_phone,
emergency_contact2_relationship:emergency_contact2_rel,
emergency_contact2_phone: emergency_contact2_phone,
chronic_illnesses: $("#chronic_illnesses").val(),
past_surgeries: $("#past_surgeries").val(),
allergies: $("#allergies").val(),
long_term_medication: $("#long_term_medication").val(),
uid: patientID,
};
_xhr2 = $.ajax({
url: "/edit_record_process",
type: "PUT",
data: formData,
datatype:"text",
encoded:true,
cache: false,
success:function(data){
if(data){
window.location.reload()
}
},
error: (err)=>{
alert(err);
}
});
}
});
Here's the server side code:
app.get("/profile/:uid", function (req, res){
//const current_user =
//const current_user =
const uid = req.params.uid;
db.ref("user_file_ref").on("value", snapshot=>{
snapshot.forEach( dataSnapshot=>{
if(uid === dataSnapshot.val().user_id){
//line referenced in range error
getFileFromNet(dataSnapshot.val().ipfs_ref).then( result =>
{
const hash = JSON.parse(result);
let decrypted_string = decrypt(hash);
let return_data = JSON.parse(decrypted_string);
return res.send(return_data);
}).catch((err) =>{
return console.log(err);
});
}else{
}
})
})
return true;
});
app.put("/edit_record_process", (req,res)=>{
let first_name = req.body.first_name;
let last_name= req.body.last_name;
let blood_type = req.body.blood_type ;
let sex = req.body.sex ;
let date_of_birth= req.body.date_of_birth ;
let email = req.body.email ;
let patient_phone= req.body.patient_phone ;
let userID = req.body.uid;
let marital_status= req.body.marital_status ;
let emergency= {
contact1:
{
"relationship":req.body.emergency_contact1_relationship,
"phone": req.body.emergency_contact1_phone
},
contact2:
{
"relationship":req.body.emergency_contact2_relationship,
"phone": req.body.emergency_contact2_phone
}
};
let chronic_illnesses = req.body.chronic_illnesses ;
let past_surgeries = req.body.past_surgeries ;
let allergies = req.body.allergies ;
let long_term_medication = req.body.long_term_medication ;
const userdata = {
"first_name" : first_name,
"last_name" : last_name,
"sex": sex,
"blood_type": blood_type,
"date_of_birth":date_of_birth,
"email":email,
"patient_phone": patient_phone,
"marital_status": marital_status,
"emergency": emergency,
"chronic_illnesses": chronic_illnesses,
"past_surgeries":past_surgeries,
"allergies": allergies,
"long_term_medication": long_term_medication,
}
let userdata_string = JSON.stringify(userdata);
let encrypted_user_data = JSON.stringify(encrypt(userdata_string));
sendFileToNet(encrypted_user_data).then((ipfs_object)=>{
let ipfs_path = ipfs_object.path;
db.ref("user_file_ref").on("value", snapshot=>{
snapshot.forEach(dataSnapshot => {
if(userID === dataSnapshot.val().user_id){
dataSnapshot.ref.update({ipfs_ref:ipfs_path}).then(()=>
{
return res.sendStatus(201);
}).catch((err)=>{
console.log(err)
})
}
})
})
});
});
IPFS API code:
const sendFileToNet = async (file_data) => {
const real_cid = await client.add(file_data);
return real_cid;
}
const getFileFromNet = async (cid) =>{
const stream = client.cat(cid)
let data = ''
for await (const chunk of stream) {
// chunks of data are returned as a Buffer, convert it back to a
string
data += chunk.toString()
}
return data;
}
db.ref.on("value").{} //realtime reference
db.ref.once("value").{} //onetime
The problem was that the real-time reference was formed hence every time the database node was read or updated all the code in other functions would be reiterated. Both functions were in turn triggering each other causing a sort of race condition. The solution was to use the
admin.database().ref("user_ref").once().then(function(snapshot){...})
hence avoiding an inevitable race condition.