Почему мой блок сохраняет только предыдущее состояние, а не текущее?

Я заблокировал новый редактор wordpress и застрял. Все работает нормально, кроме тех случаев, когда я сохраняю свою страницу. Публикуется блок, но в предыдущем состоянии.

Пример: если я сосредоточусь на редактируемой зоне, добавляю слово и нажимаю «Опубликовать», последнее добавление не будет в полезной нагрузке. Хотя я полностью это вижу, когда регистрирую атрибуты компонентов. Если я нажму «Опубликовать» второй раз, все будет нормально.

Мне кажется, есть какое-то состояние гонки или какое-то управление состоянием, которое я делаю неправильно. Я новичок в React и Gutenberg.

Вот моя функция сохранения:

import { RichText } from '@wordpress/editor';
import classnames from 'classnames';

export default function save({ attributes }) {
    const { align, figures } = attributes;

    const className = classnames({ [ `has-text-align-${ align }` ]: align });

    return (
        <ul className={ className }>
            { figures.map( (figure, idx) => (
                <li key={ idx }>
                    <RichText.Content tagName="h6" value={ figure.number } />
                    <RichText.Content tagName="p" value={ figure.description } />
                </li>
            ) ) }
        </ul>
    );
}

и мое редактирование:

import { Component } from '@wordpress/element';
import { AlignmentToolbar, BlockControls, RichText } from '@wordpress/editor';
import { Toolbar } from '@wordpress/components';

import classnames from 'classnames';

import { normalizeEmptyRichText } from '../../utils/StringUtils';
import removeIcon from './remove-icon';

const MIN_FIGURES = 1;
const MAX_FIGURES = 4;

export default class FigureEdit extends Component {

    constructor() {
        super(...arguments);

        this.state = {};
        for(let idx = 0; idx < MAX_FIGURES; idx++){
            this.state[`figures[${idx}].number`] = '';
            this.state[`figures[${idx}].description`] = '';
        }
    }

    onNumberChange(idx, number) {
        const { attributes: { figures: figures }, setAttributes } = this.props;
        figures[idx].number = normalizeEmptyRichText(number);
        this.setState({ [`figures[${idx}].number`]: normalizeEmptyRichText(number) });
        return setAttributes({ figures });
    }

    onDescriptionChange(idx, description) {
        const { attributes: { figures: figures }, setAttributes } = this.props;
        figures[idx].description = normalizeEmptyRichText(description);
        this.setState({ [`figures[${idx}].description`]: normalizeEmptyRichText(description) });
        return setAttributes({ figures });
    }

    addFigure() {
        let figures = [...this.props.attributes.figures, {number:'', description:''}];
        this.props.setAttributes({figures});
    }

    removeFigure() {
        let figures = [...this.props.attributes.figures];
        figures.pop();
        this.resetFigureState(figures.length);
        this.props.setAttributes({figures});
    }

    resetFigureState(idx) {
        this.setState({
            [`figures[${idx}].number`]: '',
            [`figures[${idx}].description`]: ''
        });
    }

    render() {
        const {attributes, setAttributes, className} = this.props;
        const { align, figures } = attributes;

        const toolbarControls = [
            {
                icon: 'insert',
                title: 'Add a figure',
                isDisabled: this.props.attributes.figures.length >= MAX_FIGURES,
                onClick: () => this.addFigure()
            },
            {
                icon: removeIcon,
                title: 'Remove a figure',
                isDisabled: this.props.attributes.figures.length <= MIN_FIGURES,
                onClick: () => this.removeFigure()
            }
        ];

        return (
            <>
                <BlockControls>
                    <Toolbar controls={ toolbarControls } />
                    <AlignmentToolbar
                        value={ align }
                        onChange={ align=>setAttributes({align}) }
                    />
                </BlockControls>
                <ul className={ classnames(className, { [ `has-text-align-${ align }` ]: align }) }>
                    { figures.map( (figure, idx) => (
                        <li key={ idx }>
                            <RichText
                                className="figure-number"
                                formattingControls={ [ 'link' ] }
                                value={ figure.number }
                                onChange={ number => this.onNumberChange(idx, number) }
                                placeholder={ !this.state[`figures[${idx}].number`].length ? '33%' : '' }
                            />
                            <RichText
                                className="figure-description"
                                formattingControls={ [ 'link' ] }
                                value={ figure.description }
                                onChange={ description => this.onDescriptionChange(idx, description) }
                                placeholder={ !this.state[`figures[${idx}].description`].length ? 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor' : '' }
                            />
                        </li>
                    ) ) }
                </ul>
            </>
        );
    }
}

person Nicolas Reynis    schedule 14.08.2019    source источник
comment
Обратите внимание, что есть еще один грязный прием, который я сделал с заполнителем, который, как мне кажется, имеет отношение к нему. Мои заполнители перекрывали мои значения, пока я не размыл компонент.   -  person Nicolas Reynis    schedule 14.08.2019
comment
setState является асинхронным, поэтому обновление ставится в очередь, а не записывается в состояние немедленно. Вам нужно будет либо использовать аргумент обратного вызова для setState, либо использовать метод жизненного цикла, например componentDidUpdate()   -  person DJ2    schedule 14.08.2019
comment
Это не совсем объясняет, почему мое состояние не обновляется на момент публикации. Между вводом и отправкой есть промежуток в десятки секунд. SetState, даже если он асинхронный, должен выполняться за несколько тиков.   -  person Nicolas Reynis    schedule 14.08.2019
comment
setState не обновляет ваши инициализированные значения волшебным образом в зависимости от прошедшего времени. Вы можете оставить его там в течение нескольких дней и отправить, но у него все равно не будет ваших обновленных значений состояния.   -  person DJ2    schedule 14.08.2019
comment
Проблема в том, что setState является асинхронным, поэтому вам нужно использовать функцию обратного вызова arg, чтобы для начала получить последнее значение, установленное в состоянии. Я предлагаю найти подходящий метод жизненного цикла для этой логики, если setState callback func arg не обрабатывает ваш вариант использования   -  person DJ2    schedule 14.08.2019
comment
Итак, что вызывает обновление состояния? Также я могу неправильно понять, как работает Гутенберг, но сохраняйте только «атрибуты», а не состояние.   -  person Nicolas Reynis    schedule 14.08.2019
comment
Понятия не имею о материалах Wordpress, так как никогда им не пользовался. Я не уверен, что вы имеете в виду под So what does trigger a state update? Изменением состояния, когда происходят изменения в props или непосредственно из setState. Это вызовет повторную визуализацию. Попробуйте сделать то, что я описал в своем ответе. Ваше описание вашей проблемы, несомненно, связано с тем, что не было получено самое последнее значение, потому что setState является асинхронным.   -  person DJ2    schedule 14.08.2019


Ответы (1)


Проблема была связана с неизменяемостью, в моем методе onChange я изменял атрибут figures. Похоже, ты не должен этого делать. Вы должны предоставить новый объект, если хотите, чтобы реквизит правильно обновлялся.

Мой обработчик изменений теперь выглядит примерно так:

onChange(idx, field, value) {
    const figures = this.props.attributes.figures.slice();
    figures[idx][field] = normalizeEmptyRichText(value);
    return this.props.setAttributes({ figures });
}

Как я и подозревал, это также устранило проблему с заполнителем. Теперь, когда заполнитель работает должным образом, я могу удалить весь код состояния и конструктора. Что подтверждает, что это не имеет ничего общего с setState.

person Nicolas Reynis    schedule 16.08.2019