My Firebase app doesn't store current user data locally - javascript

After logging into my Firebase application, the user logs in, but when it renews, the current user's information is deleted. return firebase.auth().setPersistence(firebase.auth.Auth.Persistence.LOCAL).then(...) It's still the same for me using the code. My code is here:
class LogIn extends Component {
constructor(props) {
super(props);
this.app = !firebase.apps.length ? firebase.initializeApp(CONFIG) : firebase.app();
this.user = firebase.auth().currentUser;
}
render() {
function SignIn() {
return firebase.auth().setPersistence(firebase.auth.Auth.Persistence.LOCAL)
.then(function() {
return firebase.auth().signInWithEmailAndPassword(document.getElementById("email").value, document.getElementById("password").value)
.then((user) => {
if (firebase.auth().currentUser) {
document.getElementById("x").innerHTML = firebase.auth().currentUser.email;
}
})
})
}
return (
<div>
Login<br /><br />
<div>
Username<br />
<input id="email" type="text" autoComplete="new-password" />
</div>
<div style={{ marginTop: 10 }}>
Password<br />
<input id="password" type="password" autoComplete="new-password" />
</div>
<input type="submit" onClick={SignIn}/>
</div>
);
}};

When you reload the page Firebase automatically tries to refresh the user's authentication state. But since this requires a call to the server it takes some time, and for that reason the user is initially set to null.
To pick up when Firebase has finished refreshing the user's authentication state, use an auth state listener as shown in the first sample in the documentation on getting the current user:
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
// User is signed in.
} else {
// No user is signed in.
}
});

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.

axios.post is displaying user inputs in search bar

I'm trying to build a React app with Hooks, with a node.js express server and postgreSQL database.
The first step is user registration but I'm experiencing some weirdness with axios (and also a proxy error which may or may not be related?)
Desired behaviour: User completes all fields and clicks submit, data is sent to backend, stored in a database and a user id assigned, response returned to front end and all fields clear.
If the user submits incomplete information, it is not stored and the response from the backend triggers an error message to the user.
Situation 1: The user completes all fields.
Outcome 1: Behaviour is as expected EXCEPT
(a) the user data also appears in the search bar, including the password in plain text, and persists after the data is saved e.g
http://localhost:3000/?first=Lucy&last=Who&email=who%40example.com&password=something#/
(b) The following error:
Proxy error: Could not proxy request /register from localhost:3000 to http://localhost:5000/.
See https://nodejs.org/api/errors.html#errors_common_system_errors for more information (ECONNRESET).
[note: I am using create-react-app, 3000 is the port used by the dev server, I'm using 5000 for my server]
Situation 2: The user enters incomplete information and clicks submit.
Outcome 2: The data appears in the search bar as above and is sent to the backend, the input fields clear but apparently no response is returned and the error message is not triggered.
Situation 2.1: user sends the same incomplete information again
Outcome 2.1: the error message is triggered.
Situation 2.2: user sends different incomplete information
Outcome 2.2: the error message clears.
Code (apologies if this is too much/not enough, not being sure where the problem lies makes it a bit tricky to work out what someone else might need to know)
register.js
import React, { useState } from "react";
import axios from "./axios";
import { Link } from "react-router-dom";
export default function Register() {
const [error, setError] = useState(false);
const [first, setFirst] = useState("");
const [last, setLast] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const formDeets = {
first: first,
last: last,
email: email,
password: password,
};
function submitReg() {
console.log("formDeets", formDeets);
axios
.post("/register", formDeets)
.then((response) => {
console.log("response.data: ", response.data);
clearAll();
if (response.data.success) {
console.log("success");
} else {
setError("true");
}
})
.catch((err) => {
console.log("register.js error in post register", err);
});
}
function clearAll() {
console.log("clear all called");
setFirst("");
setLast("");
setPassword("");
setEmail("");
}
return (
<section className="register container">
<div className="register-component">
{error && (
<div className="error">
Registration failed. Please complete all fields and try
again.
</div>
)}
<form>
<label htmlFor="first">first name</label>
<input
onChange={(event) => setFirst(event.target.value)}
type="text"
name="first"
placeholder="first name..."
value={first}
/>
<label htmlFor="first">last name</label>
<input
onChange={(event) => setLast(event.target.value)}
type="text"
name="last"
placeholder="last name..."
value={last}
/>
<label htmlFor="email">email address</label>
<input
onChange={(event) => setEmail(event.target.value)}
name="email"
type="email"
placeholder="email address..."
value={email}
/>
<label htmlFor="password">choose a password</label>
<input
onChange={(event) => setPassword(event.target.value)}
name="password"
type="password"
placeholder="choose a password..."
value={password}
/>
submit
<input
type="submit"
value="click to accept cookies and register"
onClick={() => submitReg()}
/>
</form>
</div>
</section>
);}
server.js (just the relevant part, I think)
app.post("/register", (req, res) => {
console.log("/register route hit");
console.log("req body", req.body);
const first_name = req.body.first;
const last_name = req.body.last;
const email = req.body.email;
const password = req.body.password;
let user_id;
if (!first_name || !last_name || !email || !password) {
res.json({
success: false,
});
return;
}
hash(password).then((hashpass) => {
db.addUser(first_name, last_name, email, hashpass)
.then((results) => {
console.log("results", results.rows[0]);
user_id = results.rows[0].id;
req.session.userId = user_id;
res.json({ success: true });
})
.catch((err) => {
console.log("err in addUser: ", err);
res.json({ success: false });
});
return;
});
}); //end of register route
server.listen(port, () => console.log(`listening on port ${port}`));
and finally, I'm calling axios from axios.js:
import axios from "axios";
var instance = axios.create({
xsrfCookieName: "mytoken",
xsrfHeaderName: "csrf-token"
});
export default instance;
Browsers have default behaviour for when you submit a form.
It is causing the browser to navigate to a new URL after running your JS.
You need to prevent the default behaviour of that submit event.
onClick={() => submitReg()}
There doesn't seem to be any reason to use an arrow function here. submitReg doesn't use this so binding this with an arrow function is pointless.
onClick={submitReg}
Now your function will be passed an event object. Use it to stop the default behaviour of a form submission.
function submitReg(evt) {
evt.preventDefault();
console.log("formDeets", formDeets);

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="/" />;
}

Meteor user always undefined in client side

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.

Redirecting onClick

This is a weird situation. So I have this Login component:
export default class Login extends React.Component {
componentDidMount() {
var email = document.getElementById('email').value;
var password = document.getElementById('password').value;
firebase
.auth()
.signInWithEmailAndPassword(email, password)
.then(() => document.getElementById('close').click())
.catch(e => console.log(e));
}
render() {
if (firebase.auth().currentUser === null)
return '';
else return <Redirect to='/questions'/>
}
}
And this is my LoginForm
export default class LoginFormComponent extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<Modal
show={this.props.show}
onHide={() => this.props.changeShowState(false)}>
<Modal.Header
closeButton>
<Modal.Title> Zaloguj sie </Modal.Title>
</Modal.Header>
<Modal.Body>
<form>
<FormControl
id="email"
type="email"
label="Email address"
placeholder="Enter email"/>
<FormControl id="password" label="Password" type="password"/>
<Button onClick={<Login/>}>Zaloguj</Button>
{/*The problem is ^^^^^ here*/}
</form>
</Modal.Body>
<Modal.Footer>
<Button id="close" onClick={() => this.props.changeShowState(false)}>Close</Button>
</Modal.Footer>
</Modal>
)
}
}
The whole problem is, I want to redirect the page, after the user logs in. I did some research and I figured I have to do it this way.
The problem is the <Login/> element is not rendered. I'm guessing it's just not how React works, but I have no other idea how to get it done. All I want to do is redirect a user after loging in.
You don't have to have it in two components. Your main problem is that you cannot give a React component as an onClick handler.
My take on it would be:
export default class LoginFormComponent extends React.Component {
state = { loggedIn: false, error: false }
constructor(props) {
super(props);
}
handleSubmit() {
var email = document.getElementById('email').value;
var password = document.getElementById('password').value;
firebase
.auth()
.signInWithEmailAndPassword(email, password)
.then(() => document.getElementById('close').click())
.catch(e => console.log(e));
if(firebase.auth().currentUser === null)
this.setState({ loggedIn: false, error: true });
else
this.setState({ loggedIn: true, error: false});
}
render() {
return (
<form onSubmit={this.handleSubmit}>
{ this.state.error && <span>Could not log in.</span> }
{ this.state.loggedIn && <Redirect to='/questions' /> }
<FormControl
id="email"
type="email"
label="Email address"
placeholder="Enter email"/>
<FormControl id="password" label="Password" type="password"/>
<Button type="submit">Zaloguj</Button>
</form>
)
}
}
I think that you don't have understand how React works.
First of all you don't have to use "document.getElementById('email').value", because React works using state and props.
After that you cannot pass a component to the onClick event handler. The onClick wants a function.
If you want to redirect user you can create a method like:
loginUser(){
window.location = "https:..";
}
Your button will be:
<Button onClick={this.loginUser.bind(this)}>Zaloguj</Button>
However when you work with React is really difficult that you need redirects. Seeing your components it seems like you want to handle React in the PHP way (Login component cannot read data with getElementById in the ComponentDidMount method. You have to use react-router for the app routing and you can use something like MobX or Redux to store user login data. You can even pass props and not use MobX or Redux).

Categories

Resources