Hi, 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 login form I’m doing up.
Here is my Meteor method on the server:
'checkUsernameAsync' : async function({username}) {
var result = null;
if (username != "") {
if (Accounts.findUserByUsername(username)) {
result = "invalid";
} else result = "valid";
}
return result;
},
Here is my function in a React class component:
async checkUsername(username) {
Session.set("usernameValid", true);
var syncCall = await Meteor.call(
"checkUsernameAsync",
{
username,
}
);
// Shouldn't the line of code below run only AFTER the call above completes?
if (syncCall === "invalid") Session.set("usernameValid", false);
return Session.get("usernameValid");
}
I’ve played around with declaring the functions async/throwing in the await in different areas but I can’t seem to figure out why it isn’t working. Appreciate any help, thank you!
Server-side
- there is no need for this given method to be async
- you should never pass a username to a method, because it is inherently unsafe: an attacker could pass any name. Use
this.userId
in a method to check whether the user is logged in, and if they are, you have a valid user. If you do need more than just their userId, use Meteor.user()
. See https://docs.meteor.com/api/accounts.html#Meteor-user
Client side
- You can use
Meteor.user()
on the client too, so ultimately for what you want to achieve you don’t even need a call to a Meteor method.
But let’s assume that it would be necessary to call a Meteor method. Then:
- You don’t need your React component to be async (never, actually)
- Use React’s
useState
and useEffect
, respectively. See https://reactjs.org/docs/hooks-effect.html
- Invoke
Meteor.call
within a useEffect
without a dependency (equivalent of a call within componentDidMount
in a class component)
- Use the last parameter
asyncCallback
of Meteor.call()
. There you would invoke the setter function of your useState
hook to set the return value of your method.
- There’s no need to use
Session
in this instance.
Thank you for the critique, but for some clarification - this is for a registration form so there is no User just yet. And I’m checking if the username already exists based on what is typed in the username input field, so I can update a visual indicator showing whether or not the username is available. Is there any way to still accomplish this in a React class component? If I resort to using hooks I’m going to have to convert it into a functional component instead.
Also with regards to security - I am sanitizing the input on both the client side and server side, but I have omitted that from the example above just so I can get a simple synchronous call working first.
In that case you do need a method indeed. Yet it doesn’t need to be async, and a boolean return value would be just right.
On the client side you could use either a class or a function component, the latter is recommended. If you choose the class component, then you would need to invoke the method in componentDidUpdate
every time your input is updated. In the asyncCallback of that Meteor.call
you would probably want to set a state using this.setState()
to denote whether the username is already taken.
Good suggestion - I’ll make that a boolean return value. Currently my input field triggers this function onChange. This is definitely not ideal as database calls are being triggered every single input, and I will streamline this once I can sort this functionality out properly.
But I don’t really understand how to solve this, as setting state wouldn’t help either. The reason is as such, I carry out these checks:
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 checkUsername function runs in the else if
condition check, and I need that to return true, but I don’t have any way to synchronously return true from the Meteor method call. Even if I set state, and I check for state instead, the state update happens after I return from the function, which is why I need it to wait for the call to complete. Is there any cleaner way of getting this done?
Even if I throw in the Meteor Call itself in the else if
condition check, there isn’t really any point because there’s no return value, it’s just undefined
.
Please read State and Lifecycle in the react documentation first.
1 Like
To answer your actual questions:
Meteor calls can’t be made sync (even if they could, it would freeze the entire page, preventing typing, clicking and even scrolling)
await
isn’t sync either
You can’t await
a Meteor call, because it doesn’t return a promise. You will need to use the callback parameter (or promisify Meteor.call
)
The way to do this is to set the validity state
using setState
in the callback and display the error message in the render function.
Changing the state with setState
will cause the render function to be run again, which will pick up the new value and display the error message
2 Likes
Thank you for your responses. I refactored the validation logic into componentDidUpdate() and did not need to make the call synchronous, as per @peterfkruger’s advice.
1 Like
Great news!
One more word to the slightly related issue: react functional vs. class components. You seem to favor class over function, maybe because you are more familiar with OO-languages. That was the case with me too (with Java since its version 1.0.2), it occurred to me as a natural choice to create class components in react.
I’ve changed my mind since, and only use function components. I even successively refactor all class components in my app to function components, because, if done properly, they are easier to write and maintain, and they usually become more concise.
1 Like
Yes it’s been the natural choice for me so far, also because a tutorial I followed early on solely used class components, and I never really ventured out unless I was forced to use Hooks. And yes I’ve found that the code in general has become more concise. Thanks for the advice.
I would like to ask though - have you been in a situation you were forced to use class components? Would there ever be any reason to favor one over the other? I don’t suppose there’s any performance benefits or anything of that sort?
I’m not aware of any situation where class components are the only option, except maybe if you want to rely on shouldComponentUpdate
, but I rarely ever do that.
1 Like
Only if you’re using a library designed around extending classes. That’s pretty rare though, most use HOC functions or composition to add functionality
1 Like