Дочернее состояние React.js не перерисовывается даже после вызова setState?

У меня есть приложение с одним дочерним компонентом, который я хотел бы повторно визуализировать, когда setState обновляет bookInput в родительском состоянии. Я использую axios для запроса информации из книги Google 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>
    );
  }
}

person dran    schedule 18.03.2019    source источник
comment
Пожалуйста, сократите свой код и оставьте только важные части.   -  person Hamed    schedule 18.03.2019


Ответы (2)


Может быть, вы ищете что-то похожее на это? 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}/>
    

Проблема с вашим кодом заключается в том, что вы выполняете вызов API в методе didMount вашего компонента. Это событие жизненного цикла будет вызываться только при монтировании компонента. Не когда обновляется. Когда вы вводите какой-либо ввод в текстовое поле и нажимаете «Поиск книг», событие componentDidMount не срабатывает. И по этой причине вызовы API не происходят со второго раза.

Дополнительные сведения о событиях жизненного цикла см. на странице https://reactjs.org/docs/react-component.html#componentdidmount

person G_S    schedule 18.03.2019
comment
Хорошо, я обновил его, и теперь он работает! Я просто сохранил загрузку в родительском компоненте — вы думаете, это имеет значение? Он находится здесь: codesandbox.io/s/wyjovm13l7. - person dran; 19.03.2019
comment
Вы можете добавить свою загрузку в свой родительский компонент, когда вы выполняете там вызовы API. Насколько я знаю, это не должно иметь значения. У меня было одно предложение: создать новый компонент для загрузки, чтобы его можно было повторно использовать в тех случаях, когда вам это нужно. - person G_S; 19.03.2019

Я взял ваш код и экстраполировал его в эту песочницу. Как вы сказали, состояние вашего родительского компонента обновляется, как и должно быть, но проблема в том, что дочерний компонент не меняет своего состояния.

Изменение состояния всегда вызывает повторный рендеринг в React. Единственная проблема в том, что ваш дочерний компонент управляет своим собственным состоянием, которое не меняется напрямую. Вместо этого он просто получает новый реквизит снова и снова, но ничего с ним не делает.

Если вы посмотрите на свой код для компонента <Book />, вы измените его состояние только на componentDidMount, что происходит только один раз. Если вы хотите программно обновить его, вы можете сделать одну из двух вещей.

  1. Удалите состояние из дочернего компонента и сделайте так, чтобы он полностью зависел от свойств, чтобы он оставался синхронизированным с родительским.
  2. Используйте метод жизненного цикла componentDidUpdate (документы), чтобы выбрать, когда следует изменить состояние дочерний элемент (который вызовет повторный рендеринг)
person Leander Rodrigues    schedule 18.03.2019
comment
Спасибо, что показали мне песочницу, очень удобно! У меня это работает так: codesandbox.io/s/wyjovm13l7 Думаю, выполнив ваше первое предложение, так как я не совсем уверен, как правильно использовать componentDidUpdate. - person dran; 19.03.2019