即使调用setState后,React.js子状态也不会重新呈现?

时间:2019-03-18 17:10:54

标签: reactjs components react-component

我有一个带有一个子组件的应用,当setState在父状态下更新bookInput时,我想重新渲染该子组件。我正在使用axios从Google的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>
    );
  }
}

2 个答案:

答案 0 :(得分: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. 更改了子组件的使用方式。

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

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

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

答案 1 :(得分:0)

我已将您的代码插入到this sandbox中。就像您说的那样,您的父组件状态正在按需更新,但是问题在于子组件不会更改状态。

状态更改将始终在React中触发重新渲染。唯一的问题是,您的子组件正在管理其自己的状态,不会直接更改。取而代之的是,它只是一次又一次地收到新的道具,而不对它们做任何事情。

如果您查看<Book />组件的代码,则只能在componentDidMount上修改其状态,这种状态只会发生一次。如果您想以编程方式对其进行更新,则可以执行以下两项操作之一。

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