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

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 (массива) и отобразить.

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

Спасибо.

0
Alex 27 Ноя 2016 в 17:34

2 ответа

Лучший ответ

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")
    })
} 
1
Swapnil 27 Ноя 2016 в 21:04

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

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, это рекомендуется сделать это так:

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

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

1
Yadhu Kiran 27 Ноя 2016 в 19:13