setState没有更新状态

时间:2019-12-30 04:58:18

标签: reactjs

当用户登录时,我正在尝试在App.js中渲染组件,但是当我在setStateApp.js(以渲染组件)时,状态不会改变

首先,我将用户名和密码从login.js发送到App.js

  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)

这是在App.js中呈现的:

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>
    )
  }

它应该在App.js中调用登录函数(之所以这样做,是因为我console.log(username)并收到了):

 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
  }

完成此操作后,登录应该变为true,这将在lightningTalkComponent中显示App.js(另请参见上文):

{this.state.loggedIn ? <lightningTalkRender/> : null}

App.js的初始状态为:

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      lightningTalks: [],
      username: "",
      loggedIn: false
    };
  }

为什么状态没有更新?

完整代码在这里:

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;

3 个答案:

答案 0 :(得分:1)

setState是异步的。如果要在设置后查看状态,请使用回调函数

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 login={(username) => this.login(username)}/>

答案 1 :(得分:0)

login = (username) => {
  this.setState({
    loggedIn: true,
    username: username
  });
  console.log('username state', this.state.username)
  console.log('logged in state', this.state.loggedIn)
}

当您在上面进行控制台日志时,您希望状态具有新值,但这不是状态在React中的工作方式。反应批处理状态更新,因此,当您调用setState时,状态不会立即更新。在login函数内部,state.loggedIn将始终具有旧值。您必须等待重新渲染,然后再更改值。

您要重新渲染组件吗?还是假设状态未更新是因为在您控制台日志时状态为旧值?

请尝试将控制台日志放在渲染功能的顶部,并查看新状态是否正确记录在该位置,因为组件将使用新状态重新呈现。

答案 2 :(得分:0)

hillybob991,如ReactJS文档中所述。 setState()函数是一个异步函数,因此即使在执行setState之前,您的代码也会继续执行您的log语句。

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

因此,应该在代码中添加一个回调函数。以下代码正是您想要的。

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)

});

}