Using async meteor.call() in meteor-react render() method - javascript

I am using Meteor and React for my app. in my app, i use account-ui, account-facebook to let user is able to sign in by using facebook account.
Now, i want to check if user is using facebook to sign-in Or regular user/pass to sign-in. I look at users collection and see that
1) if user used facebook to login, there is services.facebook in user collection
2) if user used regular user/pass to login, there is services.password in users collection
in server, i have method to return loginservice (facebook or password)
Meteor.methods({
getLoginService: function(){
let services = Meteor.user().services;
if (typeof services.facebook!=='undefined'){
return 'facebook';
}else if (typeof services.password!=='undefined'){
return 'password';
}
return 'undefined';
},
});
In client side, if will check to see if that is (facebook login) Or (User/pass login), then i can render the screen accordingly
App.Components.Dashboard = React.createClass({
render() {
var lservice='';
Meteor.call("getLoginService",function(error,service) {
if (error){
}else {
lservice = service;
}
});
if (lservice === 'facebook'){
return (
<div id="dashboard-content">
<div className="row">
<div className="col-lg-12">
<h1 className="page-header">Facebook connect</h1>
<p>put facebook information here</p>
</div>
</div>
</div>
);
}else{
return (
<div id="dashboard-content">
<div className="row">
<div className="col-lg-12">
<h1 className="page-header">Facebook connect</h1>
<p>put facebook connect button here</p>
</div>
</div>
</div>
);
}
}
});
The problem is at client side. Because Meteor.call is async so that lservice will not get the value from callback function.
I also try to find a way to put html return into callback function(error, service) but not successful.
Do you know anyway to solve this problem. Or do you have any better idea to recognize what service that user used to login (maybe a hook to account-facebook)

Start by moving your call to Meteor.call into componentWillMount so that it only runs once—just before the component is first rendered.
Code in your render method will be called each time that the state or props change and you probably only need to get the login service once.
Next, rather than storing the service string in a local variable, store it in state so that component renders each time it is updated.
getInitialState() {
return {};
},
componentWillMount() {
Meteor.call("getLoginService", (error, service) => {
if(error) {
// handle error
} else {
this.setState({ service });
}
});
},
render() {
const { service } = this.state;
if(service === 'facebook') {
// return facebook view
} else if(service === 'password') {
// return password view
} else {
// return loading view
}
}
The first time your application renders, the service probably won't be available (it'll be waiting for the request to come back), so it will render the loading view in the else clause.
However, when the callback is called (providing there are no errors), the state of the component will be updated and the component will re-render.
This time the service will be available and the correct view will be shown.

Related

firebase.auth().onAuthStateChanged getting null after refresh page in vuex

i am using Vuex and Firebase Authentication. I got stuck when reload the page. firebase.auth().onAuthStateChanged take time to response. But i need at the same time when reload the page. I have seen many tutorials in internet, most of them is router guard, but that i don’t want. I have some route where the user has login, then can navigate to this route.
App.vue where i am applying.
created () {
firebase.auth().onAuthStateChanged(async user =>{
if (user){
await this.$store.dispatch('autoSignIn',user)
}
})
}
Here is my vuex action theat trigger when page reload to auto sign in if a user was logged before reload the page.
autoSignIn ({commit}, payload) {
commit('setUser',{email:payload.email, userId:payload.uid})
}
This is my getter
isAuthenticated:state => {
return state.user !== null && state.user !== undefined ? state.user : null
}
Here is where i am calling my getter isAuthenticated.
getEventsByUser({getters,commit}){
let data = [];
firebase.database().ref('usuario/' + getters.isAuthenticated.userId + '/eventos/')
.on("value", eventos =>{
eventos.forEach(evento =>{
data.push({"id":evento.key, ...evento.val()})
});
commit('setEventsByUser',data)
})
},
And this is the component which dispatch the action
<template>
<div>
<div v-for="(event,id) in getEventsByUser" :key="id">
{{event}}
</div>
</div>
</template>
<script>
export default {
name: "MyEvents",
computed:{
getEventsByUser(){
return this.$store.getters.getEventsByUser;
},
},
mounted() {
this.$store.dispatch('getEventsByUser')
},
}
Here is the error when i reload the page
When the page loads, Firebase checks whether the ID token that is stored for the user is still valid. This requires that it calls the server, so it may take a moment. During this check the user will be null, so your code needs to handle that everywhere.
In your onAuthStateChanged handler you handle this correctly with:
firebase.auth().onAuthStateChanged(async user =>{
if (user){
But then in getEventsByUser you assume there is a user, which (as shown by the error message) is not true. So you'll want to add a check there, to see if there's a user, before attaching the listener to the database:
getEventsByUser({getters,commit}){
let data = [];
if (getters.isAuthenticated) {
firebase.database().ref('usuario/' + getters.isAuthenticated.userId + '/eventos/')
...

Meteor minimongo shows user logged in on client before server

It appears that if a user is logged in to a Meteor application, then loses and regains their DDP connection, there is a short moment where the client believes that it is logged in before the server does.
For example, I have a container component that updates according to the result of Meteor.loggingIn():
const MainNavigationContainer = createContainer(props => {
return {
meteorReady: Meteor.loggingIn() === false
}
}, MainNavigation);
In the MainNavigation component, I run a Meteor method which should return a result based on the user's _id (I have tried to remove irrelevant code):
class MainNavigation extends Component {
componentWillReceiveProps(nextProps) {
this.setInitialRoute(nextProps);
}
setInitialRoute = (props) => {
// Set up initial route
if (props.meteorReady) {
if (!Meteor.user()) {
this.setState({initialRoute: routes[1]});
} else {
Meteor.call('/user/events/isActive', (e, eventId) => {
if (eventId) {
// Do some stuff
} else {
// Do some other stuff
}
});
}
}
};
render() {
return (
this.props.meteorReady && this.state.initialRoute ?
<Navigator
ref={navigator => this.navigator = navigator}
initialRoute={this.state.initialRoute}
renderScene={(route, navigator) => { ... }}
/> : (
<View style={styles.container}>
<ActivityIndicator animating={true}/>
</View>
)
)
}
}
The /user/events/isActive method call should only be run if Meteor.user() is defined (which should mean the user is logged in). However, when I look at the server call:
Meteor.methods({
'/user/events/isActive': function () {
console.log('userId:', this.userId);
if (this.userId) {
const member = Members.findOne({userId: this.userId});
if (member) {
return member.eventId;
}
return false;
}
return false;
}
});
The first call of this method (after a DDP disconnect and reconnect) ends up with this.userId being equal to null.
Basically, if Meteor.user() is defined on the client, I expect this.userId to be defined on the server. However, it appears that the minimongo on the client is giving a false positive before they are actually logged in (when they disconnect and reconnect).
My question is: If Meteor.user() is defined on the client, can I safely assume that this.userId will be defined on the server? As of now, I would say I cannot, so is there any other way for me to reliably determine if the user is truly logged in from the client side?
After a lot of debugging, I have finally figured out what was going on.
The container sends new props to its child component any time a reactive computation is invalidated. Also, Meteor methods are asynchronous, and if they are not resolved on the server, the client will continue to try to get a response until a reconnect. However, the props that are passed into setInitialRoute are the nextProps from componentWillReceiveProps.
So, what was happening was that a call was being made right as the meteor server was disconnected, and the resolution of that call occurred after the reconnect. So the previous call of the meteor method was being evaluated, giving me a null for the this.userId.
To solve this, I just had to put a conditional within the callback of the meteor method to ensure that it was evaluated when the user is actually logged in (using the current props rather than the passed in nextProps):
Meteor.call('/user/events/isActive', (e, eventId) => {
if (this.props.meteorReady) {...}
});
This prevents the result from that call of the method from being evaluated on the client, and solved my problem.

how to create a asynchronous function to return user state after login using Meteor.loginWithPassword()

i try to create a function that return user state after login with Meteor.loginWithPassword() but it's asynchronous, the function always return undefined, how can i solve that?
var state;
Meteor.loginWithPassword(action.email, action.password,
(err) => {
if (err) {
alert('Đăng nhập thất bại')
} else {
state = "login success"
}
})
return state
Your state variable is not a reactive variable, so if value of state update it will not update the spacebar code inside html template.
You can solve this problem using any reactivating thing like Session, reactive var or reactive dict.
//Make sure you have install reactive var package
var state = new ReactiveVar('');
Template['name'].helpers({
'getState': function (userId) {
Meteor.loginWithPassword(action.email, action.password,(err) => {
if (err) {
alert('Đăng nhập thất bại')
} else {
state.set("login success");
}
})
console.log(len.get()); // You will get 2 when response come from you method call.
return state.get();
}
});
You can also use spacbar helper from Account UI named 'currentUser'. So your html will be like:
{{#if currentUser}}
<!-- Do something when user is login -->
{{else}}
<!-- Do something when user is not login -->
{{/if}}
Ask a callback argument/parameter to your function which you will call when login is successful
function login(action, callback) {
Meteor.loginWithPassword(action.email, action.password, err => {
if ( err ) {
//...
} else {
callback('login success');
}
});
}

Ember.js with Cloudkit JS

I have built a small prototype project using CloudKit JS and am now starting to build the next version of it and am wanting to use Ember as I have some basic experience with it. However, I am not too sure where to place the CloudKit JS code. For example where should I add the configure part and the auth function? I think that once I find the spot for the auth code, I could then add some of my query functions into the individual views and components, right?
Here is my configure code (with the container and id removed):
CloudKit.configure({
containers: [{
containerIdentifier: '###',
// #todo Must generate a production token for app store version
apiToken: '###',
auth: {
persist: true
},
// #todo Must switch to production for app store version
environment: 'development'
}]
});
Here is the auth function:
function setupAuth() {
// Get the container.
var container = CloudKit.getDefaultContainer();
//Function to call when user logs in
function gotoAuthenticatedState( userInfo ) {
// Checks if user allows us to look up name
var userName = '';
if ( userInfo.isDiscoverable ) {
userName = userInfo.firstName + ' ' + userInfo.lastName;
} else {
userName = 'User record name: ' + userInfo.userRecordName;
}
//Calls out initialization function
init();
//Sets up UI for logged in users
setAuthenticatedUI( userName );
//Register logged out function
container
.whenUserSignsOut()
.then( gotoUnauthenticatedState );
}
//Function to call when user logs out
function gotoUnauthenticatedState( error ) {
//Checks if error occurred
if ( error && error.ckErrorCode === 'AUTH_PERSIST_ERROR' ) {
displayError( logOutError, 'Error code: AUTH_PERSIST_ERROR' );
}
// Sets up the UI for logged out users
setUnauthenticatedUI();
//Register logged in function
container
.whenUserSignsIn()
.then( gotoAuthenticatedState )
.catch( gotoUnauthenticatedState );
}
// Check a user is signed in and render the appropriate button.
return container.setUpAuth()
.then( function( userInfo ) {
// userInfo is the signed-in user or null.
if ( userInfo ) {
gotoAuthenticatedState( userInfo );
} else {
gotoUnauthenticatedState();
}
});
}
The init() then calls functions to setup the queries to adds a chart to the page using records. The setAuthenticatedUI() and setUnauthenticatedUI() functions simply apply and remove classes once the user has been authenticated.
The answer pretty much depends on the version of Ember you're using and if how you are planning on using it. With routes? Simple routes? RouteHandlers?
For example, if you are at Ember v2.3.0, you could consider using dependency injection (https://guides.emberjs.com/v2.3.0/applications/dependency-injection/) to provide a configured container instance to the rest of your app, e.g.:
export function initialize(application) {
var container = CloudKit.configure(config).getDefaultContainer();
application.register('ckcontainer:main', container);
application.inject('route', 'ckcontainer', 'ckcontainer:main');
}
export default {
name: 'ckcontainer',
initialize: initialize
};
Then in a route, you can obtain a reference like so:
export default Ember.Route.extend({
activate() {
// The ckcontainer property is injected into all routes
var db = this.get('ckcontainer').privateCloudDatabase;
}
});
-HTH

Sign user in ONLY if email and password are correct?

I have a form that when you click submit it goes to the path /signin no matter what, even if you don't enter anything into the form. However I want it to check if there is a user with the email entered and if their password is correct before going to /signin. If the email is not known or the password is not correct then I want it to display an error (and stay on the log in page). I've been trying to get this to work but I'm not sure how to do it. Does anyone know how to do this? This is the code I have so far:
html:
<div id="topbar">
<h1><strong>chattly</strong></h1>
{{#unless currentUser}}
{{> signIn}}
{{> alert}}
{{else}}
{{> signOut}}
{{/unless}}
</div>
javascript:
// Sign In Template
Template.signIn.events({
'submit #signInForm': function(e, t) {
e.preventDefault();
var signInForm = $(e.currentTarget),
email = trimInput(signInForm.find('.email').val().toLowerCase()),
password = signInForm.find('.password').val();
if (isNotEmpty(email) && isEmail(email) && isNotEmpty(password) && isValidPassword(password)) {
Meteor.loginWithPassword(email, password, function(err) {
if (err) {
Session.set('alert', 'We\'re sorry but these credentials are not valid.');
} else {
Sesson.set('alert', 'Welcome back New Meteorite!');
}
});
}
return false;
},
});
You tagged this with iron-router so I assume you are using the iron router package.
You could prevent un-authenticated users from getting to any page beyond your login page by using before hooks on all the routes for restricted pages. The before hook would check Meteor.user() if it doesn't return an object there is no user logged in and it could re-route to the login page.
Checkout the iron-router documentation, here is the section on before and after hooks. It even shows using a before hook as a filter to prevent un-authenticated users from going to a route.
https://github.com/EventedMind/iron-router/#before-and-after-hooks
It looks something like this:
Router.map(function () {
this.route('postShow', {
path: '/posts/:_id',
before: function () {
if (!Meteor.user()) {
// render the login template but keep the url in the browser the same
this.render('login');
// stop the rest of the before hooks and the action function
this.stop();
}
},
action: function () {
// render the main template
this.render();
// combine render calls
this.render({
'myCustomFooter': { to: 'footer' },
'myCustomAside': { to: 'aside' }
});
},
after: function () {
// this is run after our action function
}
});
});

Categories

Resources