I'm trying to render components in App.js
when a user logs in but when I setState
in App.js
(in order to render components), the state does not change
Firstly, I send the username and password from login.js
to App.js
here:
loginSubmit= (e) => {
e.preventDefault()
if(this.state.username === "") {
this.showValidationErr("username", "Username cannot be empty")
} if (this.state.password === "") {
this.showValidationErr("password", "Password cannot be empty")
}
const username = this.state.username
this.props.login(username)
This is rendered in App.js here:
render() {
return (
<div className="container">
<h1>Lightning Talks!</h1>
<Login login={this.login}/>
{this.state.loggedIn ? <lightningTalkRender/> : null}
<h3 className="form-header"> Submit your talk</h3>
<Form postInApp={this.postInApp}/>
</div>
)
}
It should call the login function in App.js (which is done because I console.log(username) and it is received):
login = (username) => {
console.log('username', username)
console.log('username state', this.state.username)
this.setState({
loggedIn: true,
username: username
});
console.log('username', username) // username typed in is logged
console.log('username state', this.state.username) //username in state is empty
console.log('logged in state', this.state.loggedIn) // logged-In is still false
}
When this is done, logged-In should become true, which renders the lightningTalkComponent
in App.js
(see above also):
{this.state.loggedIn ? <lightningTalkRender/> : null}
The initial state in App.js is this:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
lightningTalks: [],
username: "",
loggedIn: false
};
}
Why isn't the state updating?
Full code is here:
import React from 'react';
import LightningTalk from './components/lightning-talk-component.js';
import Form from './components/form.js';
import Login from './components/login.js';
import './App.css'
// initialized state of App to hold an empty lightningTalks compoennt. componentDidMount sets its state depends
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
lightningTalks: [],
username: "",
loggedIn: false
};
}
// componentDidMount is called and sets the state of the lightningTalks array in constructor(props)
componentDidMount = () => {
fetch("http://localhost:4000/talks.json")
.then(response => response.json())
.then((data) => {
// sorts the data when component mounts from largest to smallest votes
data.sort((a, b) => b.votes - a.votes)
this.setState((state) => {
return {
lightningTalks: data
};
});
});
}
// sends a post request to the API
postInApp = (titleDescription) => {
const talk = {}
talk.title = titleDescription.title
talk.description = titleDescription.description
talk.votes = 0
fetch("http://localhost:4000/talks", {
headers: {
"Content-Type": "application/json"
},
method: "POST",
body: JSON.stringify({ "talk": talk })
})
.then(response => response.json())
.then((data) => {
console.log(data);
});
}
login = (username) => {
console.log('username', username)
console.log('username state', this.state.username)
this.setState({
loggedIn: true,
username: username
});
console.log('username', username)
console.log('username state', this.state.username)
console.log('logged in state', this.state.loggedIn)
}
// increments/decrements the votes in an object of lightningTalks
incrementInApp = (id) => {
// creates a new array based off current state of lightningTalks
const nextLightningTalks = this.state.lightningTalks.map((currentLightningTalk) => {
// if the id in the parameters equals the id of the current objects ID then place back into the array
if (currentLightningTalk.id !== id) {
return currentLightningTalk
}
// whatever remains (the one whose ID does match), += 1 votes of that object
const nextLightningTalk = {...currentLightningTalk, votes: currentLightningTalk.votes + 1,
};
return nextLightningTalk
})
// sorts when number of votes increases
nextLightningTalks.sort((a, b) => b.votes - a.votes)
// set new state of lightningTalks to equal the result of the new array above (the .map)
this.setState({lightningTalks: nextLightningTalks})
}
decrementInApp = (id) => {
const nextLightningTalks = this.state.lightningTalks.map((currentLightningTalk) => {
if (currentLightningTalk.id !== id) {
return currentLightningTalk
}
const nextLightningTalk = {...currentLightningTalk, votes: currentLightningTalk.votes - 1,
};
return nextLightningTalk
})
// sorts when number of votes decreases
nextLightningTalks.sort((a, b) => b.votes - a.votes)
this.setState({lightningTalks: nextLightningTalks})
}
lightningTalkRender(props) {
return <div className="talks">
{this.state.lightningTalks.votes}
{this.state.lightningTalks.map((talk) => {
return <LightningTalk lightningTalk={talk} incrementInApp={this.incrementInApp} decrementInApp={this.decrementInApp}/>
})}
</div>
}
// now the state of lightning talks depends on what is on the API. Below there is a loop(.map) which is set by componentDidMount
render() {
return (
<div className="container">
<h1>Lightning Talks!</h1>
<Login login={this.login}/>
{this.state.loggedIn ? <lightningTalkRender/> : null}
<h3 className="form-header"> Submit your talk</h3>
<Form postInApp={this.postInApp}/>
</div>
)
}
}
export default App;
import React from "react"
import './login.css';
class Login extends React.Component {
constructor(props) {
super(props);
this.loginSubmit = this.loginSubmit.bind(this)
this.state = {
username: '',
password: '',
errors: [],
pwdStrength: null
}
}
showValidationErr (e, msg) {
this.setState((prevState) => ( { errors: [...prevState.errors, { e, msg }] } ));
}
clearValidationErr (e) {
this.setState((prevState) => {
let newArr = [];
for(let err of prevState.errors) {
if(e !== err.e) {
newArr.push(err);
}
}
return {errors: newArr};
})
}
onUsernameChange= (e) => {
this.setState({ username: e.target.value })
this.clearValidationErr("username");
}
onPasswordChange= (e) => {
this.setState({ password: e.target.value })
this.clearValidationErr("password");
// set state of password strength based on length. Render these as CSS below
if (e.target.value.length <= 8) {
this.setState({ pwdStrength: "pwd-weak"})
} if (e.target.value.length > 8) {
this.setState({ pwdStrength: "pwd-medium" })
} if (e.target.value.length > 12) {
this.setState({ pwdStrength: "pwd-strong" })
}
}
// on submit, time is logged (new Date) and state of title and description is changed
loginSubmit= (e) => {
e.preventDefault()
if(this.state.username === "") {
this.showValidationErr("username", "Username cannot be empty")
} if (this.state.password === "") {
this.showValidationErr("password", "Password cannot be empty")
}
const username = this.state.username
this.props.login(username)
// call onSubmit in LightningTalk so that new talk is added from form
// this.props.postInApp(usernamePassword)
}
render() {
let usernameErr = null;
let passwordErr = null;
for(let err of this.state.errors) {
if(err.e === "username") {
usernameErr = err.msg
} if (err.e === "password") {
passwordErr = err.msg
}
}
return (
<form className="form-container">
<label>
<p className="form-title">Username:</p>
<input className="input-username"
placeholder="enter your username"
value={this.state.username}
onChange={this.onUsernameChange}
/>
<small className = "danger-error"> { usernameErr ? usernameErr : "" }</small>
</label>
<br />
<label>
<p className="form-description">Password:</p>
<input className="input-password"
placeholder="enter your password"
value={this.state.password}
onChange={this.onPasswordChange}
type="password"
/>
<small className="danger-error"> { passwordErr ? passwordErr : "" }</small>
{this.state.password && <div className="password-state">
<div
className={"pwd " + (this.state.pwdStrength)}></div>
</div>}
{/*when the button is clicked, call the loginSubmit function above. E (event) is passed into loginSubmit function (above)*/}
</label>
<br />
<button onClick={e => this.loginSubmit(e)}>Login</button>
</form>
);
}
}
export default Login;