【问题标题】:Why child component's prop is only showing parent component's initial value?为什么子组件的 prop 只显示父组件的初始值?
【发布时间】:2020-05-26 14:02:53
【问题描述】:

我正在构建一个响应式单页应用程序,它将根据用户的输入调用 API 进行搜索,并且用户必须登录才能使用该应用程序。

组件的层次结构是:

  1. App.js(处理登录和路由)
  2. <SearchPage /> 和 <SearchResults />
  3. <SearchPage />下有<SearchNavBar />和<SearchMain />(<SearchMain />包含用户提交的表单和输入,提交后用户应该导航到<SearchResults />所在的路径。)

这里是问题: 由于 react 只允许一个方向的数据流,所以我必须将用户的输入从<SearchMain /> 向上传递给App.js,然后将其保存为App.js 的状态,并将其作为道具传递给<SearchResults />令牌来自App.js。但是,即使我已经更新了 App.js 中的状态,并将其作为 props 传递给其子组件<SearchResults />,而 console.logging 子组件 <SearchResults /> 的 props,我也只能得到初始值状态,而不是更新的,为什么??

代码:

App.js:

const myMSALObj = new UserAgentApplication(msalConfig);
class App extends Component {
  constructor(props) {
       super(props)
    this.state = {
      res:'',
      isLoggedIn:false,
      keyword:''
    }        
    this.handleLogOut = this.handleLogOut.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }    
  handleLogOut(){
    myMSALObj.logout();
    this.setState({isLoggedIn:false})
  }   
  handleSubmit (typedValue){
      this.setState({keyword:typedValue},() => console.log(this.state));//here works just fine, showing correct state value
      window.location.replace('/results')
    };

  async componentDidMount(){

    const idToken_response = await myMSALObj.loginPopup(loginRequest);
    const response = await myMSALObj.acquireTokenPopup(loginRequest);

    this.setState({isLoggedIn:true,res:response});
  }
  render(){
    return (
    <Router>
      <div>
        <Switch>
        {this.state.isLoggedIn === true ? 
        <Route exact path="/" render={() => <SearchPage 
                                             parentState = {this.state} 
                                             handleLogOut = {this.handleLogOut}
                                             handleSubmit = {this.handleSubmit} />}/>:
        <Route exact path="/" render={() => (<div>Please wait till you are logged in...</div>)}/>}
        <Route exact path="/results" render={() => <SearchResults 
                                                    parentState = {this.state.keyword} 
                                                    handleLogOut = {this.handleLogOut} />}/>          
        </Switch>
      </div>
    </Router>
    );
  }
}
export default App;

搜索页面:

class SeachPage extends Component{

    constructor (props){
        super (props);
        this.state = {
            // userInfo: this.props,
            accessToken: this.props.parentState.res.accessToken,
            name:this.props.parentState.res.account.name,
            email:this.props.parentState.res.account.userName
        }
        // console.log(this.props)
    }
    render(){
        return (
            <Fragment>
                <SearchNavBar 
                 info={this.state} 
                 handleLogOut = {this.props.handleLogOut}/>
                <SearchMain 
                 accessToken={this.state.accessToken } 
                 handleSubmit = {this.props.handleSubmit}/>
            </Fragment>

        )
    }
}

export default SeachPage;

搜索主要:

class SearchMain extends Component{
    constructor(props){

        super(props);

        this.state = {
            token:this.props.accessToken,
            keyword:'',
            typed:'',
        }

        this.handleChange = this.handleChange.bind(this);
        this.handleSubmitForm = this.handleSubmitForm.bind(this);

        // console.log(this.props)
    }

    handleChange(event) {
        let typed = event.target.value;
        let keyword = event.target.value;
        this.setState({typed: typed,keyword:keyword},() => console.log(this.state));

      };

    handleSubmitForm(event){
        const keyword = this.state.keyword;
        this.props.handleSubmit(keyword);
        event.preventDefault();
    }

    render(){
        return(
            <div className="container" id="searchBox">
            <form onSubmit = {this.handleSubmitForm } className="field is-grouped"> 
                <p className="control is-expanded">
                <input type="text" className="input" value={this.state.keyword} onChange={this.handleChange} autoComplete="off" placeholder="Find an asset" required></input>
                </p>
                <p className="control">
                    <button id="button-in-nav-bar" type="submit" className="button is-primary"><strong>Search</strong></button>
                </p>
            </form>
            </div>
        )
    } 

};

export default SearchMain;

搜索结果

class SearchResults extends Component {

    constructor(props){
        super(props);
        console.log(this.props) //here is the issue!! onlyshowing{res:'',isLoggedIn:false,keyword:''}
    }

    render (){
        return(
            <div>test results</div>
        )
    }
};

export default SearchResults;

更新 简而言之,我能够将关键字向上传递给父组件,只是似乎有点晚了,截图可能更好地描述了我的挣扎: 如果我在第一个截图中取消注释window.location.replace('/results'),页面将导航到'/results',如果我在渲染函数中console.log(this.props),道具显示4次,最后2次有部分正确的信息。@ 987654324@,仍然缺少关键字...我有一种感觉,这可能是异步性质的,但我真的对如何修复它一无所知..(附带问题,为什么它是desiplayed 4次?)

【问题讨论】:

    标签: javascript reactjs asynchronous async-await react-router


    【解决方案1】:

    第一期

    使用window.replace 将重新加载页面,并将您的所有状态初始化回原始值。因此,您将失去keyword 状态。 所以你必须用Router 包裹你的App 并在handleSubmit 中使用history。

    第二期

    在SearchMain 中,您正在从您的状态中的道具派生所有状态,即在构造函数内部(执行一次)。因此,SearchMain 组件没有进一步的更新。 所以只使用道具(不要使用状态)

    更新的 App.js

    const myMSALObj = new UserAgentApplication(msalConfig);
    class App extends Component {
      constructor(props) {
        super(props);
        this.state = {
          res: {}, //<--- initialise as an empty object(not string)
          isLoggedIn: false,
          keyword: "",
        };
        this.handleLogOut = this.handleLogOut.bind(this);
        this.handleSubmit = this.handleSubmit.bind(this);
      }
      handleLogOut() {
        myMSALObj.logout();
        this.setState({ isLoggedIn: false });
      }
      handleSubmit(typedValue) {
        this.setState({ keyword: typedValue }, () => console.log(this.state)); //here works just fine, showing correct state value
        // window.location.replace("/results");
        this.props.history.replace("/results")
      }
    
      async componentDidMount() {
        const idToken_response = await myMSALObj.loginPopup(loginRequest);
        const response = await myMSALObj.acquireTokenPopup(loginRequest);
    
        this.setState({ isLoggedIn: true, res: response });
      }
      render() {
        return (
            <div>
              <Switch>
                {this.state.isLoggedIn === true ? (
                  <Route
                    exact
                    path="/"
                    render={() => (
                      <SearchPage
                        parentState={this.state}
                        handleLogOut={this.handleLogOut}
                        handleSubmit={this.handleSubmit}
                      />
                    )}
                  />
                ) : (
                  <Route
                    exact
                    path="/"
                    render={() => <div>Please wait till you are logged in...</div>}
                  />
                )}
                <Route
                  exact
                  path="/results"
                  render={() => (
                    <SearchResults
                      parentState={this.state.keyword}
                      handleLogOut={this.handleLogOut}
                    />
                  )}
                />
              </Switch>
            </div>
        );
      }
    }
    export default (props) => (
      <Router>
        <Route render={(rProps) => <App {...props} {...rProps} />} />
      </Router>
    );
    

    更新了 SearchMain.js

    class SeachPage extends Component{
    
        constructor (props){
            super (props);
            // this.state = {
            //     // userInfo: this.props,
            //     accessToken: this.props.parentState.res.accessToken,
            //     name:this.props.parentState.res.account.name,
            //     email:this.props.parentState.res.account.userName
            // }
            // console.log(this.props)
        }
    
        render(){
            return (
                <Fragment>
                    <SearchNavBar 
                     info={this.props} // <----- use props (not state)
                     handleLogOut = {this.props.handleLogOut}/>
                    <SearchMain 
                     accessToken={this.props.accessToken } // <----- use props (not state)
                     handleSubmit = {this.props.handleSubmit}/>
                </Fragment>
    
            )
        }
    }
    
    export default SeachPage;
    

    【讨论】:

    • hi gdh,感谢您的解释,我已根据您的示例修改了代码,提交表单后,页面保持不变,只有 url 更改为'/results',并且 console.log(this.props) 的结果也没有显示在控制台中......有什么想法吗?
    • 你好,我刚刚更新了问题,希望这次问题对你来说更清楚了!!
    • 嗨 - 在沙箱中查看代码的工作副本 ...codesandbox.io/s/… ...console.log 正在正确打印并且一切正常 ....仔细查看实施并应用于您的代码 ....一般记住——将 props 复制到 state 意味着当这个组件接收到新的 props 时,状态不会更新(也不会重新渲染)
    • 谢谢!我认为问题可能是获取登录信息的异步​​等待。谢谢你的提醒,我真的很感激。
    【解决方案2】:

    constructor 方法上的console.log props 值=> 数据显示是第一个数据。您应该在渲染方法上尝试 console.log。祝你好运!

    【讨论】:

    • 您好,Minh,感谢您的回复!我试图在渲染方法中使用 console.log ,但它仍然是空的。还有什么想法吗?
    • 你说“console.log(this.props) //这里是问题!!只显示{res:'',isLoggedIn:false,keyword:''}”。但正如我所见,您只将“parentState = {this.state.keyword}”作为道具放入 SearchResults。那么为什么你可以看到 App.js 的所有状态呢?
    • 抱歉,这是一个拼写错误,因为我一直在来回调试...删除“.keyword”后,我们将得到预期的错误输出,抱歉造成混淆。
    • 尝试替换 handleSubmit (typedValue){ this.setState({keyword:typedValue},() => console.log(this.state)) window.location.replace('/results') } ;处理提交(typedValue){ this.setState({keyword:typedValue},() => {console.log(this.state)) window.location.replace('/results')} };
    • 您好 Minh,抱歉编译器无法识别语法,您认为问题出在哪里?
    猜你喜欢
    • 2018-12-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-25
    • 2019-01-24
    • 2019-10-01
    • 2019-12-23
    • 1970-01-01
    相关资源
    最近更新 更多