React JS State Updates для коллекций объектов - PullRequest
0 голосов
/ 07 ноября 2018

Я довольно новичок в React и пытаюсь попрактиковаться в создании простого приложения для заметок. Afaik / могу сказать, что все идет отлично, но! Я прочитал, что состояние не должно обновляться вручную, поэтому я копирую свой массив состояний и отфильтровываю результат для операции удаления.

Но это не удалось! Скорее, если я консольный журнал, он правильно удаляет подлежащий удалению элемент из массива состояний , однако, когда я вызываю setState () для копии, чтобы обновить мое представление, список неверен!

По какой-то причине мой список React всегда визуально удаляет последний элемент со страницы и затем не синхронизируется с моим состоянием.

Само приложение представляет собой своего рода контейнер Form с вложенным списком и компонентом list-item, который использует реквизиты из класса формы для управления.

Может кто-нибудь помочь мне понять, что я делаю не так? Спасибо!

Форма класса

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

    const list = [
     { text: "Build out UI" },
     { text: "Add new note" },
     { text: "delete notes" },
     { text: "edit notes" }
   ];

    this.state = {
      'notes': list
    };

    // this.notes = list;
    this.handleSubmit = this.handleSubmit.bind(this);
    this.deleteNote = this.deleteNote.bind(this);
  }

  handleSubmit(e) {
    e.preventDefault();
    if (this.input.value.length === 0) { return; }

    this.state.notes.push({text: this.input.value});
    this.setState({ notes: this.state.notes });

    this.input.value = "";
  }

  // BUG - deletes WRONG note!!
  deleteNote(note) {
    console.log({'DELETE_NOTE': note.text})
    // var list = _.clone(this.state.notes);
    var list = [...this.state.notes];

    var filteredNotes = _.filter(list, function(n) {
      return (n.text !== note.text);
    })

    console.log({
      'list': list,
      'filteredNotes': filteredNotes
    })

    this.setState({ notes: filteredNotes });
  }

  render() {
    return (
      <div className="row notes-form">
        <div className="col-xs-12">
          <form onSubmit={this.handleSubmit}>
            <input type="text" className="new-note-input" ref={(input) => this.input = input} />
            <br />
            <button className="add-btn btn btn-info btn-block" type="button" onClick={this.handleSubmit}>Add</button>
            <br />
            <NotesList notes={this.state.notes} deleteNote={this.deleteNote} />
          </form>
        </div>
      </div>
    );
  }
}

Список классов

class NotesList extends Component {
  constructor(props) {
    super(props);
  }

  render() {
    return (
      <ul className="notes-list">
        {this.props.notes.map((n, index) => <NotesListItem key={index} note={n} deleteNote={this.props.deleteNote} />)}
      </ul>
    );
  }
}

Элемент списка Класс

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

    this.state = {
      'text': props.note.text
    };

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

  delete() {
    this.props.deleteNote(this.props.note);
  }

  render() {
    return (

      <li className="notes-list-item">
        <span className="item-text">{this.state.text}</span>
        <div className="notes-btn-group btn-group" role="group">
          <button className="delete-btn btn btn-danger" type="button" onClick={this.delete}>&times;</button>
        </div>
      </li>

    );
  }
}

Ответы [ 2 ]

0 голосов
/ 07 ноября 2018

Конструктор компонента запускается только один раз. React будет повторно использовать экземпляры компонентов, передавая им новые реквизиты. Проблема здесь в том, что NodeListItem кэширует текст заметки в собственном локальном состоянии и использует этот текст в методе рендеринга. Когда его Родитель передает новую записку ему через реквизит, он не использует его. Он использует состояние, которое сейчас устарело.

Дочерние компоненты обычно должны использовать данные из реквизита, переданного Родителем.

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

    // The problem is this statement here
    this.state = {
      'text': props.note.text
    };

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

Вот исправленная версия класса NotesListItem.

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

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

  delete() {
    this.props.deleteNote(this.props.note);
  }

  render() {
    return (
      <li className="notes-list-item">
        <span className="item-text">{this.props.note.text}</span> {/* <-- using props */}
        <div className="notes-btn-group btn-group" role="group">
          <button
            className="delete-btn btn btn-danger"
            type="button"
            onClick={this.delete}
          >
            &times;
          </button>
        </div>
      </li>
    );
  }
}
0 голосов
/ 07 ноября 2018

Попробуйте использовать что-то вроде уникального идентификатора вместо index в качестве key для каждого NotesListItem в NotesList. Смотрите этот связанный вопрос (может быть, дубликат на самом деле):

import React, { Component } from 'react';
import NotesListItem from './NotesListItem';

class NotesList extends Component {
  constructor(props) {
    super(props);
  }

  render() {
    return (
      <ul className="notes-list">
        {this.props.notes.map((n, index) => <NotesListItem key={n.id} note={n} deleteNote={this.props.deleteNote} />)}
      </ul>
    );
  }
}

export default NotesList;

Вы можете использовать что-то вроде uuid для генерации "уникального" идентификатора. Существует множество способов создания уникального ключа, но это зависит от вашей структуры данных. Кроме того, использование уникального идентификатора и фильтрация на основе идентификатора могут помочь избежать ситуации, когда два примечания в массиве имеют одинаковый текст, поскольку фильтрация на основе значения text удалит их обоих.

import uuidv1 from 'uuid/v1';

// ...

handleSubmit(e) {
  e.preventDefault();
  if (this.input.value.length === 0) { return; }

  this.state.notes.push({id: uuidv1(), text: this.input.value});
  this.setState({ notes: this.state.notes });

  this.input.value = "";
}

Я только предлагаю использовать что-то подобное, поскольку возможно, что ваш текст может быть продублирован. Возможно, вы даже можете использовать что-то вроде:

{this.props.notes.map((n, index) => <NotesListItem key={index + n.text} note={n} deleteNote={this.props.deleteNote} />)}

Кроме того, вы не должны напрямую изменять состояние, как this.state.notes.push({text: this.input.value});. Попробуйте что-то вроде этого:

handleSubmit(e) {
  e.preventDefault();
  if (this.input.value.length === 0) { return; }

  const note = { id: uuidv1(), text: this.input.value };
  const notes = [...this.state.notes, note];

  this.setState({ notes });

  this.input.value = "";
}

Кроме того, я бы не использовал ref для обработки контролируемых входов, особенно для установки значения. Почему бы не создать свойство для состояния, которое обрабатывает значение ввода в сочетании с простым обработчиком событий onChange. Это будет соответствовать документации React Forms и «стандартному» способу обработки обновлений входных значений React:

handleChange(e) {
  this.setState({ text: e.target.value });
}

handleSubmit(e) {
  e.preventDefault();
  if (this.state.text.length === 0) { return; }

  const note = { id: uuidv1(), text: this.state.text };
  const notes = [...this.state.notes, note];

  this.setState({ text: '', notes });
}

render() {
  // ...
  <input type="text" className="new-note-input" value={this.state.text} onChange={this.handleChange} />
  // ...
}

Вот пример в действии.

Другого ответа может быть достаточно для решения вашей проблемы. Я бы рекомендовал ознакомиться со следующей статьей , упомянутой / связанной в документации React Keys , в которой обсуждаются потенциальные негативные последствия использования индекса в качестве ключа.

Надеюсь, это поможет!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...