【问题标题】:React.js child state not re-rendering even after calling setState?即使在调用 setState 之后,React.js 子状态也不会重新渲染?
【发布时间】:2019-08-09 03:12:23
【问题描述】:

我有一个包含一个子组件的应用程序,我想在 setState 更新父状态下的 bookInput 时重新渲染它。我正在使用 axios 从谷歌的 book api 请求信息。出于某种原因,即使状态正在更新,孩子也不会重新渲染。如果可以的话请帮忙!谢谢!

import React, { Component } from 'react';
import axios from 'axios';

class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      bookInput: 'ender',
      bookSubmitted: 'initial'
    }

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
    this.handleSubmitEmpty = this.handleSubmitEmpty.bind(this);
  }

    handleChange(e) {
    this.setState({bookInput: e.target.value});
    console.log(this.state.bookInput);
    //this.setState({bookSubmitted: false});
  }

  handleSubmit(e) {
    e.preventDefault();
    //this.setState({bookSubmitted: true})
    const name = this.state.bookInput;
    this.setState({bookInput: name});
    console.log(this.state);
    this.setState({bookSubmitted: 'userSub'});
  }

  handleSubmitEmpty(e) {
    alert('please enter an item to search for');
    e.preventDefault();
  }

  render() {

    return (

      <div className="App">
        <header className = "App-header">
          <h1>Book Search App</h1>
        </header>

        <form className = "form-style" onSubmit = {this.state.bookInput ? this.handleSubmit: this.handleSubmitEmpty}>
          <label>
            <input type="text" className = "input-style" 
              value = {this.state.bookInput} onChange = {this.handleChange}>
            </input>
          </label>
          <button type="submit">search books</button>
        </form>
        {/* <Book bookInput = {this.state.bookInput}/> */}
        {/*this.state.bookSubmitted && <Book bookInput = {this.state.bookInput}/>*/}
        {
          (this.state.bookSubmitted === 'initial' || this.state.bookSubmitted === 'userSub') && 
          <Book bookInput = {this.state.bookInput}/>
        } 
      </div>
    );
  }
}

export default App;


class Book extends Component {
  constructor(props) {
    super(props);

    this.state = {
      //bookInput2: "ender",
      bookTitles: [],
      bookExample: '',
      isLoading: false
    }

    this.bookClick = this.bookClick.bind(this);
  }

  bookClick(book) {
    console.log(book);
    console.log(book.volumeInfo.infoLink);
    const bookURL = book.volumeInfo.infoLink;
    window.open(bookURL);
  }

  componentDidMount() {
    //this.setState({ isLoading: true });
    this.setState({isLoading: true});
    axios.get(`https://www.googleapis.com/books/v1/volumes?q=${this.props.bookInput}`)
      .then((response) => {
        const bookExample1 = response.data.items;
        console.log(bookExample1);
        this.setState({bookTitles: bookExample1, isLoading: false});
      })
      .catch((error) => {
        console.error('ERROR!', error);
        this.setState({isLoading: false});
      });
  }

  render() {
    return (
      <div>
        { this.state.bookTitles ? (
          <div>
            <h2>book list</h2>
            {<ul className = 'list-style'>
              {this.state.isLoading && 
                (<div>
                  loading book list
                </div>)
              }
              {this.state.bookTitles.map(book => (
                <li key={book.id}>
                  <span className = 'book-details book-title' onClick = {() => this.bookClick(book)}> {book.volumeInfo.title}</span>
                  <br/>
                  {book.volumeInfo.imageLinks && 
                    <img src = {book.volumeInfo.imageLinks.thumbnail}/>
                  }
                  { book.volumeInfo.description && 
                    <span className = 'book-details'>{book.volumeInfo.description}</span>
                  }
                  <br/>
                  <span className = 'book-details'>Categories {book.volumeInfo.categories}</span>
                </li>
              ))}
            </ul>}
        </div>) :
        (<p>sorry, that search did not return anything</p>)}
      </div>
    );
  }
}

【问题讨论】:

  • 请删减你的代码,只保留相关的部分。

标签: reactjs components react-component


【解决方案1】:

您可能正在寻找类似的东西吗? https://stackblitz.com/edit/react-snoqkt?file=index.js

上面的代码可以进一步简化和组织,但它给了你一些想法。

代码的主要变化。

  1. 将 Api 调用从 componentDidMount 生命周期事件更改为名为 getInitialdata 的新方法,该方法在 handleSubmit 中调用。

    getInitialdata(name){
     axios.get(`https://www.googleapis.com/books/v1/volumes?q=${name}`)
      .then((response) => {
        const bookExample1 = response.data.items;
        console.log(bookExample1);
        this.setState({bookTitles: bookExample1, isLoading: false, bookSubmitted: 'userSub'});
      })
      .catch((error) => {
        console.error('ERROR!', error);
        this.setState({isLoading: false, bookSubmitted: 'userSub'});
      });
    

    }

  2. 改变了 Child 组件的使用方式。

    <Book bookTitles={this.state.bookTitles} isLoading={this.state.isLoading}/>
    

您的代码的问题是您正在组件的 didMount 方法中进行 API 调用。只有在安装组件时才会调用此生命周期事件。不是更新的时候。 当您在文本框中输入一些输入并单击“搜索书籍”时,componentDidMount 事件不会触发。这就是 API 调用从第二次开始没有发生的原因。

https://reactjs.org/docs/react-component.html#componentdidmount 上有关生命周期事件的更多信息

【讨论】:

  • 好的,我更新了它,现在可以使用了!我只是将加载保留在父组件中-您认为这重要吗?在这里:codesandbox.io/s/wyjovm13l7
  • 您可以在父组件中添加加载,因为您在那里进行 api 调用。据我所知,这不重要。我的一个建议是,可以创建一个新的加载组件,以便在需要时可以重复使用。
【解决方案2】:

我已获取您的代码并将其推断为this sandbox。正如您所说,您的父组件状态正在更新,但问题是子组件不会更改 its 状态。

状态更改总是会触发 React 中的重新渲染。唯一的问题是,您的子组件正在管理它自己的状态,而这并没有直接改变。相反,它只是一次又一次地接收新的道具,但没有对它们做任何事情。

如果您查看 &lt;Book /&gt; 组件的代码,您只会修改其在 componentDidMount 上的状态,这只会发生一次。如果您想以编程方式使其更新,您可以执行以下两项操作之一。

  1. 从子组件中移除状态,使其完全依赖于 props,使其与父组件保持同步
  2. 使用 componentDidUpdate 生命周期方法 (docs) 选择何时更改子节点的状态(这将触发重新渲染)

【讨论】:

  • 感谢您向我展示沙盒,非常方便!我让它像这样工作:codesandbox.io/s/wyjovm13l7 我想通过你的第一个建议,因为我不确定如何正确使用 componentDidUpdate。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-03-05
  • 2020-02-18
  • 1970-01-01
  • 2022-11-04
  • 2019-11-24
  • 1970-01-01
  • 2020-01-30
相关资源
最近更新 更多