Reputation: 97
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;
Upvotes: 0
Views: 478
Reputation: 4859
setState
is async.If you want to see the state after setting it use a callback function
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)
});
}
Login function takes in a parameter, so the correct way to pass it as a prop is
<Login login={(username) => this.login(username)}/>
Upvotes: 2
Reputation: 907
hillybob991, As stated in ReactJS Docs. The setState() function is an Async Function and thus your code continues to your log statement, even before the setState is executed.
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 LINE WOULD BE EXECUTED EVEN THOUGH YOUR SETSTATE IS NOT EXECUTED
So you are supposed to add a callback function in your code. The following code is exactly what you want.
login = (username) => {
console.log('username', username)
console.log('username state', this.state.username)
this.setState({
loggedIn: true,
username: username
}, () => {
//WHATEVER YOU WANT TO BE EXECUTED AFTER SETTING STATE..
//MAY BE YOUR CONSOLE LOG STATEMENT TOO
const username = this.state.username
this.props.login(username)
});
}
Upvotes: 0
Reputation: 2964
login = (username) => {
this.setState({
loggedIn: true,
username: username
});
console.log('username state', this.state.username)
console.log('logged in state', this.state.loggedIn)
}
You expect the state to have the new values when you console log above, but that is not how state works in React. React batches state updates, so when you call setState
, the state is not updated immediately. Inside the login
function, state.loggedIn
will always have the old value. You have to wait for a rerender before the value is changed.
Is your component rerendering? Or are you assuming that state is not updating because when you console log, the state has the old value?
Try putting a console log in the top of your render function, and see if the new state gets logged correctly there, as the component will rerender with the new state.
Upvotes: 0