Synchronous Meteor Call in React Class Component - javascript

I’ve been experimenting around with this for quite a while but I haven’t been able to quite figure this out. I am trying to get a synchronous Meteor method call going in a React class component, as part of a registration form I’m doing up. Basically I continually check if a username is taken every time the username input field changes (onChange) and update a visual indicator. I know this isn't ideal design as once you scale it out, it's costly database calls, so I will change this/throttle it as needed, but that's after I get this basic functionality down.
Here is my Meteor method on the server:
checkUsername({ username }) {
var result = false;
if (username != "") {
if (Accounts.findUserByUsername(username)) {
result = true;
}
}
return result;
},
Here is my function in a React class component:
async checkUsername(username) {
Session.set("usernameValid", true);
var syncCall = await Meteor.call(
"checkUsername",
{
username,
}
);
// Shouldn't the line of code below run only AFTER the call above completes?
if (syncCall === false) Session.set("usernameValid", false);
return Session.get("usernameValid");
}
I understand that this shouldn't need to be async, nor should I need await. Furthermore I know this code is wrong since syncCall will always be undefined. This is where I check for username availability:
if (username === "") {
this.state.username = false;
this.setFeedback("username", "", false);
}
else if (this.checkUsername(username)) {
// Username is valid and available
console.log("Username is available!");
this.state.username = username;
this.setFeedback("username", "", true);
}
else {
// Username is invalid and nonempty
console.log("Username is invalid/unavailable!");
this.state.username = false;
this.setFeedback("username", "Username is invalid/unavailable :-(", false);
}
The issue is that the checkUsername function returns before the Meteor call completes, and hence the state isn't updated in time and I can't check that either.
How do I go about forcing this to be synchronous? I read about Meteor.wrapAsync() but I couldn't nail how to incorporate it properly (or even if it's the right way to go about it). Any help is appreciated, thank you!

Meteor method calls on the client do not return a Promise, so you can't await them. You need to provide a callback function:
checkUsername(username) {
Session.set("usernameValid", true);
Meteor.call("checkUsername", {username},
(isTaken) => isTaken && Session.set("usernameValid", false));
}
However with React I wouldn't use session variables. React has it's own support for reactive state variables via useState and it's much better integrated:
const [usernameValid, setUsernameValid] = useState(false);
...
checkUsername(username) {
Meteor.call("checkUsername", {username}, setUsernameValid);
}

Related

Internet Connection Listener

I have an application which scans 2D barcodes then retrieves data from the URLs provided by the codes. In the event that the user loses connection to the internet, the application begins to store the URLs via AsyncStorage. The issue is, I need to implement a listener that upon regaining an internet connection, the application begins a given method. Are there any recommended ways to go about implementing a connection listener such as this?
Edit:
I have tried using a NetInfo EventListener however I am not sure if I'm using it incorrectly, as it always calls the passed function, even when the internet status hasn't changed.
_connectionHandler = (e) => {
this.setState({ cameraActive: false })
NetInfo.getConnectionInfo().then((connectionInfo) => {
if (connectionInfo.type === "none"){
console.log("No internet")
dataArray.push(e.data)
let barcodeData_delta = {
data: dataArray
}
AsyncStorage.mergeItem(STORAGE_KEY, JSON.stringify(barcodeData_delta));
NetInfo.isConnected.addEventListener(
'connectionChange',
this._handleConnectionChange(e.data)
);
this.setState({ cameraActive: true })
} else {
console.log("Internet available -> Going to read barcode now")
this._handleBarCodeRead(e.data);
}
})
}
React Native has a NetInfo documentation, there you can see how to add a listener his connection changes, and do what you want when its called.
Add a Handler to isConnected property
NetInfo.isConnected.addEventListener(
'connectionChange',
_connectionHandler
);
A function that handles the change, just adjust your setState with the camera, I couldn't figure out when to call it.
_connectionHandler = (isConnected) => {
this.setState({ cameraActive: false })
if (!isConnected){
console.log("No internet")
dataArray.push(e.data)
let barcodeData_delta = {
data: dataArray
}
AsyncStorage.mergeItem(STORAGE_KEY, JSON.stringify(barcodeData_delta));
this.setState({ cameraActive: true })
} else {
console.log("Internet available -> Going to read barcode now")
this._handleBarCodeRead(e.data);
}
})
}

Can't change Vue Instance data values

I'm building a custon social login page for my web application, and I'm stuck with a bug I can't find why it's hapenning .
Basically, I want to call a function called "connectFb" and then if all the Facebook API calls are successful, I would like to change a bunch of data in my vue instance in order to render other elements . (those are rendred conditionally via v-if)
Here's the part of my code responsible for this :
app = new Vue({
el : "#social-auth",
data: {
showTwitter : false,
showFb: true,
showPages: false,
fb_state: "unconnected",
continue_auth: false,
pages_fb: []
},
methods : {
connectFb: function() {
FB.login(function(response) {
if (response.authResponse) {
alert('You are logged in & cookie set!');
fb_token = response.authResponse.accessToken
FB.api('/me/accounts','get',{access_token: fb_token},function(pages){
if(pages["error"] !== undefined){
console.log('EROR')
}
else{
console.log("Got a list of pages");
console.log(pages);
this.pages_fb = pages.data;
this.showFb = false;
this.showPages = true;
this.continue_auth = true;
}
})
} else {
alert('User cancelled login or did not fully authorize.');
}
},{scope: 'public_profile,manage_pages'});
return false;
}
How The Code Works :
Basically, after the user is logged in to fb, it will get a list of his pages, this is not the problem, the problem is in the success callback after it (the callback related to the function fetching pages) . using the debugger I could see that the variable pages contains all the data I need and pages.data return an array of those pages info .
After this I'm trying to attribute it to my instance variable called pages_fb . when this code run pages_fb is always empty even though pages.data is not .
The problem is not only with pages_fb but also with all my instance variable that should change in the callback they are the same after the callback run .
I'm getting mad at this problem, so please help me understand what's wrong .
Extremely common mistake. this defined in your FB.login callback is not the Vue. Use an arrow function, closure, or bind to make it correct.
FB.api('/me/accounts','get',{access_token: fb_token}, pages => {
...
})
See How to access the correct this inside a callback?
When you use this. in a callback it isn't pointing to your Vue instance anymore. You can user => functions to bind this the way you want. Try this:
FB.api('/me/accounts','get',{access_token: fb_token},(pages) => {
if(pages["error"] !== undefined){
console.log('EROR')
}
else{
console.log("Got a list of pages");
console.log(pages);
this.pages_fb = pages.data;
this.showFb = false;
this.showPages = true;
this.continue_auth = true;
}
})

Meteor insert method callback should execute async

Hello Stackoverflow community,
In nuts
I am wondering why the insert callback is not being called async properly as the documentation says, having a code like:
Meteor.methods({
addUpdate: function (text) {
Updates.insert({
text: text,
createdAt: new Date(),
owner_email: Meteor.user().emails[0].address,
owner_username: Meteor.user().username
}, function(e, id) {
debugger; //<-- executed first with 'e' always undefined
});
debugger; //<-- executed after
}
});
the debugger inside the callback function is executed before the debugger afterwards, if the function is async the debugger inside the callback should be called at the end right?
More info
I am very new with meteor, the thing is that I am trying to make an small app, and experimenting, by now I wanted to confirm what I had understood about some concepts in this case the "insert" method. given the following code:
lib/collections/updateCollection.js
Update = function (params, id) {
params = params || {};
// define properties for update model such as text
this._text = params.text;
}
Update.prototype = {
// define some getters and setters, such as doc
get doc() {
return {
createdAt: this.createdAt,
text: this.text,
owner_email: this.owner_email,
owner_username: this.owner_username
};
},
notify: function notify(error, id) {
var client, notification, status;
client = Meteor.isClient ? window.Website : false;
notification = (client && window.Hub.update.addUpdate) || {}
status = (!error && notification.success) || notification.error;
if (client) {
return client.notify(status);
}
}
save: function save(callback) {
var that;
that = this;
callback = callback || this.notify;
Updates.insert(that.doc, function (error, _id) {
that._id = _id;
callback(error, _id); <-- here is the deal
});
}
}
lib/methods/updateService.js
updateService = {
add: function add(text) {
var update;
update = new Update({
text: text,
createdAt: new Date(),
owner_email: Meteor.user().emails[0].address,
owner_username: Meteor.user().username
});
update.save();
},
// methods to interact with the Update object
};
lib/methods/main/methods.js
Meteor.methods({
addUpdate: function (text) {
updateService.add(text);
}
});
My expectations here is when the client do something like Meteor.call('addUpdate', text); and everything is cool, a successful message is shown, otherwise the error is "truth" and an error message is shown. What is actually happening is that the callback is always called with error undefined (like if everything es cool), the callback also is not being called async, it is just called directly.
Even when I turn off the connection the update insertion shows a success message.
Any idea? maybe my app structure is making meteor work wrong? I really do not know. Thanks in advance.
Your code is executing inside a method. On the client, methods are executed simply to simulate what the server will do before the server responds (so that the app seems more responsive). Because the DB changes here are just simulating what the server is already doing, they are not sent to the server, and therefore synchronous. On the server, all code runs inside a Fiber, so it acts synchronous. (However, Fibers run in parallel just like normal callback-soup Node.)

Casper JS waitForResource with a restful API

We are having a little problem with a functional test with casper.js.
We request the same resource twice, first with the GET and then with POST method.
Now when waiting for the second resource (POST) it matches the first resource and directly goes to the "then" function.
We would like to be able to check for the HTTP method in the "test" function, that way we can identify the resource properly. For now we use the status code (res.status), but that doesn't solve our problem fully, we really need the http method.
// create new email
this.click(xPath('//div[#id="tab-content"]//a[#class="button create"]'));
// GET
this.waitForResource('/some/resource',
function then() {
this.test.assertExists(xPath('//form[#id="email_edit_form"]'), 'Email edit form is there');
this.fill('form#email_edit_form', {
'email_entity[email]': 'test.bruce#im.com',
'email_entity[isMain]': 1
}, true);
// POST
this.waitForResource(
function test(res) {
return res.url.search('/some/resource') !== -1 && res.status === 201;
},
function then() {
this.test.assert(true, 'Email creation worked.');
},
function timeout() {
this.test.fail('Email creation did not work.');
}
);
},
function timeout() {
this.test.fail('Email adress creation form has not been loaded');
});
Or maybe there is a better way to test this scenario? Although since this is a functional test we need to keep all those steps in one test.
You can try to alter the form action url to add some query string, therefore generating a new resource appended to the stack. Could be done this way:
casper.thenEvaluate(function() {
var form = __utils__.findOne('#email_edit_form');
form.setAttribute('action', form.getAttribute('action') + '?plop');
});
That's a hack though, and functional testing should never be achieved that way. Let's hope more information will be added to the response objects in the future.
The res parameter that is passed to the test function has an ID. I created a helper that tests against this ID and blacklists it, so the same resource won't get accepted a second time.
var blackListedResourceIds = [],
testUniqueResource = function (resourceUrl, statusCode) {
return function (res) {
// check if resource was already loaded
var resourceFound = res.url.search(resourceUrl) !== -1;
// check statuscode
if (statusCode !== undefined) {
resourceFound = resourceFound && res.status === statusCode;
}
// check blacklisting
if (!resourceFound || blackListedResourceIds[res.id] !== undefined) {
return false;
} else {
blackListedResourceIds[res.id] = true;
return true;
}
};
};

Callback function parameter issue

I'm writing a callback function for the FB.Connect.showPermissionDialog() function which accepts permissions and an optional callback function.
It's supposed to be passed null if the user rejects the permissions dialog. But for some reason, my script always makes the post request even if the permissions request failed.
echo("FB.ensureInit ( function () {
FB.Connect.showPermissionDialog('email,offline_access',
function(accepted) {
if(accepted==null) {alert('failure');} else {
$.post(\"http://www.domain.com/permissions.php\",
{ username:$userID,mode:'accepted'});}
});
});");
Not sure why it's not reading the value of accepted properly. Thanks for the help.
My first guess is that the Javascript null value is not actually being returned, and some other false-like value is being returned instead.
I'd try changing to this to test for all false-like values:
if (!accepted) {alert('failure')} else {
...
Do with firebug:
console.log(accepted)
And see what you're getting back. Could be undefined, which according to your logic still passes.
Maybe you should change your logic around:
echo("FB.ensureInit ( function () {
FB.Connect.showPermissionDialog('email,offline_access',
function(accepted) {
if(accepted !== null) {
$.post(\"http://www.domain.com/permissions.php\",
{ username:$userID,mode:'accepted'});}
} else {alert('failure');}
});
});
");

Categories