2016-11-27 4 views
0

Я строю проект на основе React для учебных целей. Я застрял в создании компонента таблицы, который отображает себя, а затем отправляет запрос ajax в бэкэнд mbaas, чтобы получить все записи в книге и заполнить каждый в новой строке. Вот что я нашел до сих пор. Пожалуйста, простите большой кусок кода, но так как я еще не в полной мере понять взаимодействие между методами, состояния и визуализации() здесь весь класс:Реагировать на проблемы компонентов с состоянием, setState и рендером

class BooksTable extends Component { 
    constructor(props) { 
     super(props); 
     this.state = { 
      books: [] 
     }; 

     this.updateState = this.updateState.bind(this); 
     this.initBooks = this.initBooks.bind(this); 
    } 

    componentDidMount() { 
     let method = `GET`; 
     let url = consts.serviceUrl + `/appdata/${consts.appKey}/${consts.collection}`; 
     let headers = { 
      "Authorization": `Kinvey ${sessionStorage.getItem(`authToken`)}`, 
      "Content-Type": `application/json` 
     }; 

     let request = {method, url, headers}; 
     $.ajax(request) 
      .then(this.initBooks) 
      .catch(() => renderError(`Unable to connect. Try again later.`)); 
    } 

    deleteBook(id) { 
     let method = `DELETE`; 
     let url = consts.serviceUrl + `/appdata/${consts.appKey}/${consts.collection}/${id}`; 
     let headers = { 
      "Authorization": `Kinvey ${sessionStorage.getItem(`authToken`)}`, 
      "Content-Type": `application/json` 
     }; 

     let request = {method, url, headers}; 
     $.ajax(request) 
      .then(() => this.updateState(id)) 
      .catch(() => renderError(`Unable to delete, something went wrong.`)); 
    } 

    updateState(id) { 
     for (let entry of this.state.books.length) { 
      if (entry.id === id) { 
       // Pretty sure this will not work, but that is what I've figured out so far. 
       this.state.books.splice(entry); 
      } 
     } 
    } 

    initBooks(response) { 
     console.log(`#1:${this.state.books}); 
     console.log(`#2:${this}); 
     for (let entry of response) { 
      this.setState({ 
       books: this.state.books.concat([{ 
        id: entry._id, 
        name: entry.name, 
        author: entry.author, 
        description: entry.description, 
        price: Number(entry.name), 
        publisher: entry.publisher 
       }]) 
      },() => { 
       console.log(`#3${this.state.books}`); 
       console.log(`#4${this}`); 
      }); 
     } 
    } 

    render() { 
     return (
      <div id="content"> 
       <h2>Books</h2> 
       <table id="books-list"> 
        <tbody> 
         <tr> 
          <th>Title</th> 
          <th>Author</th> 
          <th>Description</th> 
          <th>Actions</th> 
         </tr> 
         {this.state.books.map(x => 
          <BookRow 
           key={x.id} 
           name={x.name} 
           author={x.author} 
           description={x.description} 
           price={x.price} 
           publisher={x.publisher} />)} 
        </tbody> 
       </table> 
      </div> 
     ); 
    } 
} 

Теперь BookRow не очень интересно, только часть onClick актуальна. Это выглядит так:

<a href="#" onClick={() => this.deleteBook(this.props.id)}>{owner? `[Delete]` : ``}</a> 

Ссылка не должна быть видимой, если вошедший в систему пользователь не является издателем книги. onClick вызывает deleteBook (id), который является методом из BookTable. При успешном ajax он должен удалить книгу из state.books (array) и render.

Меня особенно смущает метод initBooks. Я добавил журналы перед циклом, который заполняет состояние и вызывает обратные вызовы при обновлении состояния. Результаты из журнала №1 и журнала №3 идентичны, для журналов № 2 # 4. Также, если я разворачиваю log # 2 (до setState) или log # 4, оба из них показывают состояние = [1]. Как это имеет смысл? Кроме того, если вы посмотрите журналы # 1 # 3 - они печатают []. Вероятно, мне не хватает взаимодействия с внутренними компонентами, но я действительно не могу понять, что.

Спасибо.

+0

SetState действия являются асинхронными, так что изменения могут быть не отражены в this.state сразу. –

ответ

1

SetState не сразу обновляет состояние. Поэтому во второй итерации цикла for вы не будете получать новое состояние. Поэтому сначала создайте свой новый список книг, а затем установите его, как только будет подготовлен новый список.

Попробуйте это:

initBooks(response) { 
    console.log(this.state.books, "new books not set yet") 
    let newBooks = [] 
    for (let entry of response) { 
    newBooks.push({ 
       id: entry._id, 
       name: entry.name, 
       author: entry.author, 
       description: entry.description, 
       price: Number(entry.name), 
       publisher: entry.publisher 
      }) 
    } 
    this.setState({books: [...this.state.books, newBooks]},() => { 
     console.log(this.state.books, "new books set in the state") 
    }) 
} 
+0

Спасибо, что ответили. @ free-soul, спасибо. Я попробую. Дело в том, что в моем текущем тестировании цикл идет только один раз, потому что у меня есть только одна тестовая запись. Установка состояния в цикле глупа, я должен был подумать об этом, но он все еще чувствует себя странно. Позвольте мне попробовать и вернуться к вам. – Alex

+0

Если цикл проходит только один раз, он должен работать. Я скопировал ваш код и попробовал его. Это сработало для меня – Swapnil

+0

Как я думал - в то время как ваша точка для перемещения setState из цикла хороша, это не помогает моей проблеме. Запуск кода - он не печатает «новые книги, установленные в состоянии». Я только предупреждаю о ключевой собственности. Я все еще не могу объяснить, как журналы # 1 # 2 печатают разные результаты, так как они регистрируют один и тот же объект. Почему печатный объект показывает обновленное состояние длины 1 и содержит объект, полученный от ответа сервера. Я даже не инициализировал временный держатель массива. – Alex

1

попробовать это:

initBooks(response = {}) { 
    const books = Object.keys(response); 

    if (books.length > 0) { 
     this.setState((prevState) => { 
      const newBooks = books.reduce((acc, key) => { 
       const entry = response[key]; 

       return [ ...acc, { 
        id: entry._id, 
        name: entry.name, 
        author: entry.author, 
        description: entry.description, 
        price: Number(entry.name), 
        publisher: entry.publisher 
       }]; 
      }, prevState.books); 

      return { books: newBooks }; 
     }); 
    } 
} 

Что я здесь?

  • setState только при необходимости (то есть только тогда, когда есть данные из ответа API)
  • избегает состояния мутации, не setState внутри цикла
  • с помощью функции ((prevState, props) => newState), чтобы обеспечить атомное обновления для надежности.

Очки задуматься:

  • Не мутировать ваше состояние
  • Избегайте setState внутри цикла; вместо того, чтобы подготовить новое состояние объекта и сделать разовый setState)

  • Если вы хотите получить доступ к предыдущему состоянию при вызове setState, это advised сделать это следующим образом:

    this.setState((prevState, props) => { 
        return { counter: prevState.counter + props.increment }; 
    }); 
    

    setState() не сразу мутирует this.state, но создает ожидающий переход состояния . Доступ к this.state после вызова этого метода может потенциально вернуть существующее значение.

+0

Хорошие советы. Спасибо чувак. – Alex

Смежные вопросы