I'm struggling to figure out how to wait for the Meteor.user()
subscription in a react component. I know the user is logged in because Meteor.userId()
returns the _id
, but trying to access the email address show Meteor.user()
returns undefined. I assume because it's not available just yet.
Alas, since Meteor.user() isn't a publication I manually subscribe to, I'm not sure how to wait for it in a React component. Can someone point me towards an example?
import { Meteor } from 'meteor/meteor';
import React from 'react';
import { Link } from 'react-router';
import { createContainer } from 'meteor/react-meteor-data';
import './BaseLayout.scss';
class BaseLayout extends React.Component {
constructor(props) {
super(props);
this.state = {
isAuthenticated: (Meteor.userId() !== null) ? true : false,
}
}
componentWillMount() {
if (!this.state.isAuthenticated) {
this.context.router.push('/login');
}
}
componentDidUpdate(prevProps, prevState) {
if (!this.state.isAuthenticated) {
this.context.router.push('/login');
}
}
toggleUserMenu() {
this.refs.userMenu.style.display = (this.refs.userMenu.style.display === 'block') ? 'none' : 'block';
}
logout(e) {
e.preventDefault();
Meteor.logout();
this.context.router.push('/login');
}
render() {
return(
<div id="base">
<div id="header">
<div id="top-bar" className="clear-fix">
<div className="float-left" id="logo"></div>
<div className="float-right" id="user-menu">
<div onClick={this.toggleUserMenu.bind(this)}>
<span>{this.props.currentUser.emails[0].address}</span>
<div className="arrow-down"></div>
</div>
<div id="user-menu-content" ref="userMenu">
<a onClick={this.logout.bind(this)}>Sign Out</a>
</div>
</div>
</div>
</div>
<div id="bodyContainer">
{this.props.children}
</div>
<div id="footer">
<ul>
<li><a href="mailto:test@example.com">Made by Us</a></li>
</ul>
</div>
</div>
)
}
}
BaseLayout.contextTypes = {
router: React.PropTypes.object.isRequired,
}
export default BaseLayoutContainer = createContainer(() => {
var handle = Meteor.subscribe("user.classrooms");
return {
currentUser: Meteor.user(),
dataLoading: !handle.ready(),
classrooms: Classrooms.find({}).fetch(),
};
}, BaseLayout);
The accepted answer is good however after awhile you will notice that you will have to repeat this many times on many components. Alternatively you can listen to make sure that the
Accounts.loginServicesConfigured()
is true before even rendering at the top level component withTracker
. Quoting the meteor docs: "The function Accounts.loginServicesConfigured() is a reactive data source that will return true once the login service is configured". I am using meteor, react, redux, and react router.Hopefully this alternative helps someone else out. Good luck!
You could use a ternary operation
Also a great tip to keep components organized is to always
destructure
for example instead of doingconst currentUser = this.props.currentUser
you can doconst { currentUser } = this.props;
Have a look:Add dataLoading: !handle.ready() || !Meteor.user() to the createContainer.
Meteor.user()
will be undefined inside the component before loading so make sure you prevent it's calling by adding a condition such asdataLoading ? "" : Meteor.user()
.You can also check if user is logged in with
!!Meteor.user()
. NormallyMeteor.userId()
is faster to load but if you need to access user's data such as email you need to callMeteor.user()
in that case no need to call both.