React/React Native: Execution order in a Component - reactjs

I am not able to figure out the sequence of code execution in react native. The following code logs test, test 2 and then test 1. I want to change value of submittedURI.
constructor(props) {
super(props);
this.state = {
enterURI: ''
};
}
onUriTextChanged(event) {
this.setState({ enterURI: event.nativeEvent.text });
}
onSubmitPress() {
var submittedURI = this.state.enterURI;
console.log("test "+submittedURI);
var url = encodeURIComponent(submittedURI), data = null;
var xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.urlmeta.org/?url=" + url);
xhr.send(data);
xhr.addEventListener("readystatechange", function () {
if (this.readyState === this.DONE) {
// console.log(this.responseText);
var responseJSON = JSON.parse(this.responseText);
submittedURI = responseJSON.meta.image;
console.log("test 1 "+submittedURI);
}
});
this.props.onURISubmit(submittedURI);
console.log("test 2 "+submittedURI);
}

The function in the xhr.addEventListner("readystatechange", function() { ... }) is a callback and get's called after the request is DONE. Therefore console.log("test 2 "+submittedURI); is called before the request is completed.
See the docs here (https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/onreadystatechange)
If you want to change submittedURI you are going to have to call this.props.onURISubmit(submittedURI); function within the readystatechange callback. Make sure the callback has the proper context to access this.props.

Related

how to correctly setState in the reactjs function

In my case, I want to change the image url to base64 file using the "base64-img" library.
I intend to save the results of converting the file into a state. I tried saving it to a variable also failed. I wrote the script as follows:
var base64Img = require('base64-img');
base64Img.requestBase64('url_image', function(err, res, body) {
this.setState ({ b64:body });
});
I get an error Uncaught TypeError: Cannot read property 'setState' of undefined
I try to set the value to a variable then console_log works well, but the value cannot be accessed from outside.
let b64;
base64Img.requestBase64('url_image', function(err, res, body) {
b64 = body;
console.log(b64);
});
I want to use the value b64 outside of the function. Thanks
You can use ES6 arrow function to call setState and to access b64 outside the function or in custom function, you can bind the function inside the constructor and access using this
const base64Img = require('base64-img');
//convert image to base 64
convertImgtoBase64(imageUrl) {
base64Img.requestBase64(imageUrl,(err, res, body) => {
this.setState ({ b64:body });
});
}
Like this
constructor(props){
super(props);
this.state = {
b64: null
};
this.convertImgtoBase64 = this.convertImgtoBase64.bind(this);
this.showBase64Value = this.showBase64Value.bind(this);
}
showBase64Value(){
console.log(this.state.b64);
}
You used
var base64Img = require('base64-img');
base64Img.requestBase64('url_image', function(err, res, body) {
this.setState ({ b64:body });
});
Here this will indicate the function instance, so that that instance won't contain setState method
So I suggest to try to use like this
var base64Img = require('base64-img');
let _self = this;
base64Img.requestBase64('url_image', function(err, res, body) {
_self.setState ({ b64:body });
});

Extracting a value from Two API calls in ReactJS

How will I get the value from the first API call and use it in the second API call?
var custAcctNum = QueryString.getValue("customer");
ProxyData.getData('/customer/api/customers/' + custAcctNum, (data) => {
this.setState({ dataCust: dataCust });
});
var deviceId =  dataCust.ioTDeviceId;
ProxyData.getData('device/api/devices/' + deviceId, (data) => {
this.setState({ data: data });
});
You can do something like below. Calling the second async function in the callback of first function.
var custAcctNum = QueryString.getValue("customer");
ProxyData.getData('/customer/api/customers/' + custAcctNum, (data) => {
this.setState({ dataCust: dataCust });
var deviceId = dataCust.ioTDeviceId;
ProxyData.getData('device/api/devices/' + deviceId, (data) => {
this.setState({ data: data });
});
});

React ES6 with chai/sinon: Unit testing XHR in component

I have a React utility component that reads the contents of a URL:
'use strict';
export class ReadURL {
getContent = (url) => {
return new Promise((resolve, reject) => {
console.log('Promise')
let xhr = new XMLHttpRequest();
xhr.open("GET", url, false);
xhr.onreadystatechange = () => {
console.log('onreadystatechange', xhr.readyState)
if (xhr.readyState === 4) {
if (xhr.status === 200 || xhr.status == 0) {
console.log('200')
var allText = xhr.responseText;
resolve(allText);
} else {
reject('ajax error:' + xhr.status + ' ' + xhr.responseText);
}
}
};
xhr.send(null);
});
};
}
I have been trying to use Sinon's useFakeXMLHttpRequest() to stub the xhr, but no matter how I try, I can't get it to actually process - It currently passes with a false positive, without ever receiving onreadystatechange event.
I've tried with XHR and Axios packages as well as native XMLHttpRequest, with the request wrapped in a promise and not, a whole bunch of different tacks, and read untold blog posts, docs and SO questions and I'm losing the will to live... The component itself works perfectly.
I've managed to get tests working with promises and with stubbed module dependancies, but this has me stumped.
This is the test:
import chai, { expect } from 'chai';
import sinon, { spy } from 'sinon';
import {ReadURL} from './ReadURL';
describe('ReadURL', () => {
beforeEach(function() {
this.xhr = sinon.useFakeXMLHttpRequest();
this.requests = [];
this.xhr.onCreate = (xhr) => {
console.log('xhr created', xhr)
this.requests.push(xhr);
};
this.response = 'Response not set';
});
afterEach(function() {
this.xhr.restore();
this.response = 'Response not set';
});
it('should get file content from an xhr request', () => {
const readURL = new ReadURL(),
url = 'http://dummy.com/file.js',
urlContent = `<awe.DisplayCode
htmlSelector={'.awe-login'}
jsxFile={'/src/js/components/AncoaAwe.js'}
jsxTag={'awe.Login'}
componentFile={'/src/js/components/Login/Login.js'}
/>`;
readURL.getContent(url).then((response) =>{
console.log('ReadURL-test response', response)
expect(response).to.equal(urlContent);
});
window.setTimeout(() => {
console.log('ReadURL-test trigger response')
this.requests[0].respond(200,
{
'Content-Type': 'application/json'
},
urlContent
)
, 10});
});
});
The console.log('xhr created', xhr) is triggered, and output confirms that it's a sinon useFakeXMLHttpRequest request.
I have created a repo of the app with the bare minimum required to see the components functions:
https://github.com/DisasterMan78/awe-testcase
I haven't got a sample online currently, as I don't know of any online sandboxes that run tests. If I can find a service for that I'll try to add a proof of failed concept.
Help me Obi Wan-Kenobi. You're my only hope!
Well, that was fun. I got back to it today after working on an older project, but it still took my far too long. Stupid, small syntactic errors. Of course...
There were two critical errors.
Firstly, in order for the promise to complete, done needs to be passed as a parameter to the function of the test's it() function:
it('should get file content from an xhr request', (done) => {
...
}
Now we can complete the promise with done:
...
readURL.getContent(url)
.then((response) => {
expect(response).to.equal(this.response.content);
done();
})
.catch((error) => {
console.log(error);
done();
});
...
Somehow none of the documentation or articles I read seemed to flag this, but few of them were also dealing with promises, and my ExtractTagFromURL test, which also relies on promises did not require this, but I have confirmed it is absolutely critical to this test.
With the promise working properly, timeouts are not required around the respond() call:
...
this.requests[0].respond(this.response.status,
{
'Content-Type': this.response.contentType
},
this.response.content
);
...
And finally, my largest cock up, in order for the values of this for this.requests and the other shared properties to be properly set and read, the function parameter of beforeEach() and afterEach() need to use arrow syntax:
...
beforeEach(() => {
...
}
...
The reduced test case on Github has been updated, passes and can be cloned from https://github.com/DisasterMan78/awe-testcase
I guess this makes me Obi Wan-Kenobe, huh?
Feel free to ask for clarification if needed!

Firebase React. How can I access my Auth values in React / Webpack?

I'm not sure how to write this question so please let me know if you need any more information.
I'm building an online shop web application. I'm coding the login functionality using Google's Firebase authorisation API.
It logs in fine and inside the: user.getToken().then function, I can console.log the displayName just fine. If it was a static webpage, i could append it to the DOM too.
Inside react/webpack though, I want to set the displayName string as my usernameState state. It doesn't work, when I run this.setState inside the user.getToken().then function, it fires an error, see below.
I would like to be able to use that value on the webpage/react states.
constructor(){
super();
this.state = {
usernameState: ""
};
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
// User is signed in.
var displayName = user.displayName;
var email = user.email;
var emailVerified = user.emailVerified;
var photoURL = user.photoURL;
var uid = user.uid;
var providerData = user.providerData;
user.getToken().then(function(accessToken) {
console.log(displayName);
this.setState({
usernameState:JSON.stringify(displayName) //cannot read property 'setState' of undefined
});
});
} else {
// User is signed out.
console.log("signed out");
}
}, function(error) {
console.log(error);
});
};
make a bind or save real this
constructor(){
super();
this.state = {
usernameState: ""
};
const self=this;
...
self.setState(...)
...
}

Apparent race condition getting firebase.User from controller in Firebase 3.x

I'm using Firebase 3.x and the auth state change event to assign the current user in my service -
.service('FireService', function ($q, $http,$state) {
var accessToken;
var user;
firebase.auth().onAuthStateChanged(function (usr) {
if (usr) {
// User signed in!
user = usr;
} else {
user = null;
$state.go('login');
}
});
return {
currentUser: function () {
return user;
}
}
But I need the current user to a reference to my child objects from the database in the controller
var getLog = function () {
var logRange = currentDate.format('MMMYYYY').toUpperCase();
var userId = FireService.currentUser().uid;
var ref = LogService.logs(userid,logRange);
// download the data into a local object
ref.once('value').then(
function (result) {
logObj = result;
})
};
When I set breakpoints, I see that the currentUser() is always empty because the state change hasn't fired yet. I've also tried to assign my userid directly from firebase.User but this is also not yet initialized by the time my controllers are ready. How do I get the currently logged in user so that it's available before I start routing around in my app?
I would recommend using the observer directly within the getLog method, so it waits until the current user is set:
var getLog = function () {
var unsubscribe = firebase.auth().onAuthStateChanged(function(user) {
if (user) {
var logRange = currentDate.format('MMMYYYY').toUpperCase();
var userId = user.uid;
var ref = LogService.logs(userid,logRange);
// download the data into a local object
ref.once('value').then(
function (result) {
logObj = result;
});
}
unsubscribe(); // So this observer is only triggered once.
});
};
As the ref.once('value') resolves asynchronously anyway, it will just add a bit of latency to the method but won't change its behavior.
Using this "one off" observer pattern (an observer that calls its unsubscribe function the first time it is called) allows you to make sure Firebase Auth is fully initialized before executing the code. If Firebase Auth is already initialized by the time you create the observer, then its resolution will be triggered immediately. Remember that observers are always called asynchronously.
If you end up using this pattern often, you could add a method on FireService:
...
ready: function() {
return $q(function(resolve, reject) {
var unsubscribe = firebase.auth().onAuthStateChanged(function(user) {
unsubscribe();
resolve(user);
});
});
}
...
And use it within your getLog method:
var getLog = function () {
FireService.ready().then(function(user) {
if (!user) { return; }
var logRange = currentDate.format('MMMYYYY').toUpperCase();
var userId = user.uid;
var ref = LogService.logs(userid,logRange);
// download the data into a local object
ref.once('value').then(
function (result) {
logObj = result;
});
});
};

Resources