Meteor user always undefined in client side - javascript

I have one question, I am new in MeteorJS and think I need some help. I implemented email account verification. And next I would like log in user, but only if user is verified. And here is problem because always Meteor.user() is undefined in client. Could you help me and explain how I am doing wrong?
class App extends Component {
constructor(props){
super(props);
this.state = {
verified: this.props.currentUser.emails[0].verified
};
}
render(){
return (
<div>
<Navbar collapseOnSelect fluid>
<nav className="sideBar-menu">
<div className="pull-right sidebar-right">
<ButtonToolbar className="pull-left sidebar-toolbar">
{!this.state.verified && <Button>Sign in</Button>}
{this.state.verified && <Button>Sign out</Button>}
<Button bsStyle="danger">Sign up</Button>
</ButtonToolbar>
</div>
</nav>
</Navbar>
</div>
)
}
}
const dataHOC = withTracker((props)=>{
return {
currentUser: Meteor.user()
}
})(App);
export default dataHOC

The Meteor.user() only returns an object of the current user, once the user has signed in. You can't therefore use properties on currentUser if no user is logged in.
Your state could therefore use two values for logged-in and verified:
const currentUser = this.props.currentUser
this.state = {
loggedIn: currentUser,
verified: currentUser && currentUser.emails[0].verified
};
And your render code will then display the sign-in element if there is no logged-in status:
<ButtonToolbar className="pull-left sidebar-toolbar">
{!this.state.loggedIn && <Button>Sign in</Button>}
{ this.state.loggedIn && <Button>Sign out</Button>}
<Button bsStyle="danger">Sign up</Button>
</ButtonToolbar>
You can then use verified to display further information such as a notification that you user has not yet been verified and show only content for verified users.
Security note: Note, that this is all just UI-candy and if you really want to prevent unverified users from subscribing to data or calling certain methods / update data you should always check for user.emails[i].verified in the methods and publications.

Related

"Cannot read property 'length' of undefined" on an empty array

Hi I'm new to Node/React, and I'm creating a learning project. It's platform that connects freelancers with nonprofit companies. Users (freelancers) view a list of companies, and click a button to connect to a company. Once this is clicked, the user will have that company added as a relationship in the database. This is working correctly.
Now I'm trying to have a page where the user can view all their connections (the companies they connected with). The solution below works but only if the user has at least one connection. Otherwise, I get the error Cannot read property 'length' of undefined.
To figure out which JSX to render, I'm using a conditional to see if the user has connections. If not, I wanna show "You have no connections". I'm doing this by checking if (!companies.length) then show "you have no connections". companies is set as in empty array in the state. I don't understand why it's undefined. Even if the user has no connections, companies is still an empty array. so why why would companies.length return this error? How can I improve this code to avoid this problem?
function UserConnections() {
const { currentUser } = useContext(UserContext);
const connections = currentUser.connections;
const [companies, setCompanies] = useState([]);
useEffect(() => {
const comps = connections.map((c) => VolunteerApi.getCurrentCompany(c));
Promise.all(comps).then((comps => setCompanies(comps)));
}, [connections]);
if (!companies.length) {
return (
<div>
<p>You have no connections</p>
</div>
)
} else {
return (
<div>
{companies.map(c => (
<CompanyCard
key={c.companyHandle}
companyHandle={c.companyHandle}
companyName={c.companyName}
country={c.country}
numEmployees={c.numEmployees}
shortDescription={c.shortDescription}
/>
))}
</div>
);
}
};
Edit: Sorry, I should've included that the error is being thrown from a different component (UserLoginForm). This error is thrown when the user who has no connections logs in. But in the UserConnections component (code above), if I change if (!companies.length) to if (!companies), the user can login fine, but UserConnections will not render anything at all. That's why I was sure the error is refering to the companies.length in the UserConnections component.
The UserLoginForm component has been working fine whether the user has connections or not, so I don't think the error is coming from here.
UserLoginForm
function UserLoginForm({ loginUser }) {
const [formData, setFormData] = useState({
username: "",
password: "",
});
const [formErrors, setFormErrors] = useState([]);
const history = useHistory();
// Handle form submission
async function handleSubmit(evt) {
evt.preventDefault();
let results = await loginUser(formData);
if (results.success) {
history.push("/companies");
} else {
setFormErrors(results.errors);
}
}
// Handle change function
function handleChange(evt) {
const { name, value } = evt.target;
setFormData(d => ({ ...d, [name]: value }));
}
return (
<div>
<div>
<h1>User Login Form</h1>
<div>
<form onSubmit={handleSubmit}>
<div>
<input
name="username"
className="form-control"
placeholder="Username"
value={formData.username}
onChange={handleChange}
required
/>
</div>
<div>
<input
name="password"
className="form-control"
placeholder="Password"
type="password"
value={formData.password}
onChange={handleChange}
required
/>
</div>
{formErrors.length
? <Alert type="danger" message={formErrors} />
: null
}
<button className="btn btn-lg btn-primary my-3">
Submit
</button>
</form>
</div>
</div>
</div>
);
};
Edit 2: The solutions provided in this thread actually solved the problem. The error message kept persisting due to a different problem coming from another component.
Change this:
{companies.map(c => (...)}
to this:
{companies && companies.map(c => (...)}
and this:
if (!companies.length) {
to this:
if (!companies || companies.length === 0) {
This will then check for a nullish value, before running the map operation or checking length.
Even if the user has no connections, companies is still an empty array. so why why would companies.length return this error?
Your assumption that companies is an empty array is incorrect. Somehow it is set to undefined. You can either protect against this by doing
if (!companies || companies.length == 0) {
or by making sure companies is always set to an array.

How to take items from an array that is in a React component's props and render to the page?

I am trying to make my navbar dynamically show different things depending on whether the user is signed in or not using React and Redux. And actually, that has proven to be manageable, I hooked up action creators that check whether there is a user object and if there is then to switch the isSignedIn property to true and show content accordingly.
The challenge has been that I want to show that user's profile photo in the navbar too. In order to do that, my plan was to have an action creator fire off a function that will get the user's documents form the database (firebase) and return the details in state to the navbar component. Then mapStateToProps would take that content and move it into props for me to manipulate onto the users view.
I can get this all to work to a point, the component fires up and it takes a bit of time to get the users details form firebase, so theres a couple of seconds where the props.user property is empty. That's fine but then when it does return with the users content, they are stored in object in an array and I have no idea how to access them.
So this.props.user is an array with one object that has key-value pairs like userDateOfBirth: "12-12-2020" and so on. I need to access those values. I thought either this.props.user.userDateOfBirth or this.props.user[0].userDateOfBirth would work but neither does. The first just returns 'undefined' and the second returns an error "Cannot read property 'userDateOfBirth' of undefined.
Please help, this is driving me insane.
I've updated the question to include some of the code that should make this a bit more understandable. I've left the render function out of the navbar component for the sake of brevity.
The Action Creators signedIn and signedOut work as I expected. It's the currentUser Action Creator which I'm having difficulties with. I've shared as much as I think necessary, and maybe a little too much. Thanks for any help.
Code Below:
* Navbar Component *
import './NavBar.css';
import {Link} from "react-router-dom";
import React from "react";
import {connect} from "react-redux";
import * as firebase from "firebase";
import {signedIn, signedOut, currentUser} from "../actions";
componentDidMount()
{
this.unregisterAuthObserver = firebase.auth().onAuthStateChanged((user) => {
let currentUser = user;
if (user) {
this.props.signedIn()
} else this.props.signedOut();
if (this.props.auth.isSignedIn) {
this.props.currentUser(currentUser.uid);
}
});
}
render() {
return (
this.props.auth.isSignedIn ?
//If the user is signed in, show this version of the navbar
//Overall NavBar
<div id="navBar">
{/*Left side of the NavBar*/}
<div id="navBarLeft">
<Link to ={"/"}
>
{/* OLAS Logo*/}
<img
alt={"Olas Logo"}
id="logo"
src={require("../img/Olas_Logo&Brandmark_White.png")}
/>
</Link>
</div>
{/*Right side of the NavBar*/}
<div id="navBarRight">
<div id="home" className="navBarItem">
<Link to={"/"} >
HOME
</Link>
</div>
<div id="careerPaths" className="navBarItem">
<Link to={"/careerPath"} >
CAREER PATHS
</Link>
</div>
<div id="jobPostInsights" className="navBarItem">
<Link to={"/jobPostInsights"} >
JOB POST INSIGHTS
</Link>
</div>
<div id="careerQ&AForum" className="navBarItem">
<Link to={"/forum"} >
CAREER Q&A FORUM
</Link>
</div>
<div id="userProfile" className="navBarItem">
<Link to={"/userprofile"}>
{this.props.user.length ? (<div>{this.props.user.user[0].userFirstName}</div>): <div>PROFILE</div>}
</Link>
</div>
</div>
</div>
//This is a critical part of the ternary operator - DO NOT DELETE
:
//If the user is not signed in, show this version of the navbar
<div id="navBar">
{/*Left side of the NavBar*/}
<div id="navBarLeft">
<Link to ={"/"}
>
{/* OLAS Logo*/}
<img
alt={"Olas Logo"}
id="logo"
src={require("../img/Olas_Logo&Brandmark_White.png")}
/>
</Link>
</div>
{/*Right side of the NavBar*/}
<div id="navBarRight">
<div/>
<div/>
<div id="about" className="navBarItem">
<Link to={"about"}>
<span className="navBarItemInner">ABOUT OLAS</span>
</Link>
</div>
<div id="home" className="navBarItem">
<Link to={"/"} >
<span className="navBarItemInner">HOME</span>
</Link>
</div>
<div id="signIn" className="navBarItem" >
<Link to={"/signIn"} >
<span className="navBarItemInner">SIGN IN / REGISTER</span>
</Link>
</div>
</div>
</div>
)};
const mapStateToProps = state => {
console.log(state);
return {auth: state.auth, user: state.user}
};
export default connect(mapStateToProps, {signedIn, signedOut, currentUser})(NavBar);
* currentUser Action Creator *
export const currentUser = (currentUserUid) => {
return async (dispatch) => {
const response = await getUserDocFromDB(currentUserUid);
dispatch({
type: GET_CURRENT_USER,
payload: response,
});
}
};
* getUserDocFromDB *
import getFirestoreDb from '../getFirestoreDb';
let db = getFirestoreDb();
const getUserDocFromDB = async (currentUserUid) => {
let userDoc = [];
await
db.collection("users")
.where("userFirebaseUid", "==", currentUserUid)
.onSnapshot(
(querySnapshot) => {
if (querySnapshot.empty) {
return;
}
querySnapshot.forEach((doc) => {
const {
userDateOfBirth,
userGender,
userState,
userCity,
userTitle,
userDescription,
userFirstName,
userLastName,
userProfileImageUrl,
} = doc.data();
userDoc.push({
userDateOfBirth,
userGender,
userState,
userCity,
userTitle,
userDescription,
userFirstName,
userLastName,
});
});
},
function(error) {
console.log("Error getting document Error: ", error);
},
);
return userDoc;
};
export default getUserDocFromDB;
* User Reducer *
import {GET_CURRENT_USER} from "../actions/ActionTypes";
const INITIAL_STATE = {
user: null,
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case GET_CURRENT_USER:
return {user: action.payload};
default:
return INITIAL_STATE;
}
};
* Combine Reducers *
import { combineReducers } from "redux";
export default combineReducers({
user: UserReducer,
});
The second way, this.props.user[0].userDateOfBirth is the correct way of accessing user properties after the data is loaded. What is happening is the array is empty while the data is being fetched and populated in app state. Using a guard will prevent the undefined error.
Assuming the default state is an empty array:
this.props.user.length && this.props.user[0].userDateOfBirth
In react this is a pattern called Conditional Rendering, and would likely look more like:
{this.props.user.length ? (
/* Render user component using this.props.user[0] */
) : null}
To make consuming the user object a little cleaner in components I'd convert the default state to be null, and have the reducer that handles the fetch response do the unpacking from the response array. Then the check is reduced to something like:
{this.props.user ? (
/* Render user component using this.props.user */
) : null}
I hope you are using private route then simply get pathname by using window.location.pathname === "user/dashboard" ? print username : "login "
Minimize tge filtration ..
Happy coding
Drew Reese's answer is spot on and I just wanted to add to it. You can check if the property exists, or simply render nothing if does not, as his examples show. When the state finally loads, it will force a re-render, the conditional will then be true, and you can then show the user profile photo or whatever other components require info from the returned api call.
This is why you might have seen a pattern of dispatching LOADING reducers, setting loading to true until the api returns and then it is set to false. You can check if loading is true in the component, and choose to render a spinner or something else instead.

How to run onSubmit={} only after the props have updated?

I want to run onSubmit on my form only after my props that I receive from the reducer update.
const mapStateToProps = state => {
return {
authError: state.auth.authError // I want this to update before the action.
};
};
If I console.log authError in onSutmit handler I receive the old authError. The second time I run it, I receive the updated authError.
handleSubmit = e => {
e.preventDefault();
console.log("error exists?", this.props.authError);
this.props.signIn(this.state); //dispatches a login action
this.handleHideLogin(); // hides the form after action is dispatched
};
I want to hide the form only after the action is dispatched and the error is null. (it returns null automatically if the authentication succeeds)
I tried using setTimeout() and it technically works, but I want to know if there is a more "proper" way to do it.
handleSubmit = e => {
e.preventDefault();
this.props.signIn(this.state);
setTimeout(() => {
if (this.props.authError) {
console.log("returns error =>", this.props.authError);
} else {
console.log("no error =>", this.props.authError);
this.handleHideLogin();
}
}, 500);
};
part of my component for reference
<form onSubmit={!this.props.authError ? this.handleSubmit : null}>
//the above onSubmit is different if I use the setTimeout method.
<div className="modal-login-body">
<div className="modal-login-input">
<input
type="email/text"
name="email"
autoComplete="off"
onChange={this.handleChange}
required
/>
<label htmlFor="email" className="label-name">
<span className="content-name">EMAIL</span>
</label>
</div>
<div className="modal-login-input">
<input
type="password"
name="password"
autoComplete="off"
onChange={this.handleChange}
required
/>
<label htmlFor="password" className="label-name">
<span className="content-name">PASSWORD</span>
</label>
</div>
</div>
<div className="my-modal-footer">
<p className="login-failed-msg">
{this.props.authError ? this.props.authError : null}
</p>
<button type="submit" className="complete-login-button">
LOGIN
</button>
<a href="CHANGE" className="forgot-password">
<u>Forgot your password?</u>
</a>
</div>
</form>
I am assuming that this.props.signIn is an async function.
And thus this.props.authError is updated asynchronously and that's why if you setup the timeout it works in some cases (when you get the response shorter than 5 seconds).
One way to solve it is using then and catch without updating the state of the form
handleSubmit = e => {
e.preventDefault();
this.props.signIn(this.state).then(resp => {
this.setState({userIsValid: true, failure: null})
this.onUpdate(userIsValid);
})
.catch(err => {
this.setState({userIsValid: false, failure: "Failed to login"})
});
}
and use if-else to determine whether to show the form or to display your website
class App extends React.Component {
render() {
if (this.state.isValidUser) {
return <Main />
} else {
return <LoginForm onUpdate={(isValid) => {this.setState({isValidUser: isValid})} />
}
}
}
In other words, the LoginForm component stores username, password, failure (error message why login failed) and isValidUser (to determine if login is successful).
The App has a state to determine what to show, the website or the login component. Using onUpdate that is passed as props to the login component we can update the state of App and show the website if login is successful.
I hope it helps.
I solved this issue by conditonal rendering on the entire component.
I used Redirect from react-router-dom to just not render the element if I'm logged in.
If I'm logged in I don't need to display the login form anyway.
if (this.props.loggedOut) {
<form onSubmit={this.handleSubmit}>
//...
</form>
} else {
return <Redirect to="/" />;
}

React - selected value is not displayed after refreshing page

So, I'm using react-select to let the user pick a choice from a list of options. It's supposed to update on-change. I've verified that the selected option is indeed being updated into the database, and the input is being recognized by React upon checking it in the React chrome tools. What's puzzling is how it doesn't get displayed after refreshing the page.
class ContractBasicForm extends React.Component {
constructor (props) {
super(props)
this.state = {
contractingEntity: props.contracting_entity
}
componentWillReceiveProps(nextProps) {
this.setState({
contractingEntity: nextProps.contracting_entity
})
}
autoSetState = (newState) => {
this.setState(newState)
this.props.formSubmit()
}
render () {
return(
<div className="container-fluid">
<section className="row ml-2 mr-2 mt-2">
<article className="col-12 side-modal-form">
<SelectInput
header="Contracting Entity"
name="contract[contracting_entity]"
options={this.props.contracting_entity_opts}
value={this.state.contracting_entity}
userCanEdit={this.props.user_can_edit}
multi={false}
onChange={(e) => {
this.autoSetState({contracting_entity: e.value})
}}
/>
</article>
</section>
</div>
)
}
}
I have another input called Stage which is very similar to ContractingEntity, but its value is displayed after refreshing the page:
<SelectInput
header="Stage"
name="contract[stage]"
options={this.props.stage_opts}
value={this.state.stage}
userCanEdit={this.props.user_can_edit}
multi={false}
onChange={(e) => {
this.autoSetState({stage: e.value})
}}
/>
React app state will be initialised on page refresh. You need to persist such data in localStorage if you want to keep it after page refresh. This is considered as anti-pattern in react and it is recommended not to use this unless it becomes necessity.
I hope this made things clear for you.

Showing elements based on authentication status in ReactJS

I am trying to show the 'edit-btn' only if the user is logged in. What is the best way to achieve this in ReactJS? By rendering the component without the element if not logged in and rendering the element if the user is logged in? If so, what is the best approach to achieve this? I am quite new to ReactJS.
class LeftBlock extends React.Component {
render() {
return (
<div className="left-block">
{this.props.children}
<i className="fa fa-pencil"></i>
<br className="clear" />
</div>
);
}
}
You can conditionally render components, even if they are null.
class LeftBlock extends React.Component {
render() {
var isAuth = false; // You'll need to figure out a way how to get this - From a store maybe or cookie?
var button;
if (isAuth){
button = (<i className="fa fa-pencil"></i>);
}
return (
<div className="left-block">
{this.props.children}
{button}
<br className="clear" />
</div>
);
}
}
So in this case, if isAuth is false, nothing will be rendered in place of {button}.
In terms of getting the isAuth status, I'd recommend having an AuthenticationStore which you can then get authentication information from within your components.
Checkout the Flux architecture if you're not already familiar with it. https://facebook.github.io/flux/docs/overview.html
Update
Fiddle: https://jsfiddle.net/6yq1ctcp/1/

Categories

Resources