I've made a block for the new wordpress editor and I'm stucked. Everything works fine, except when I save my page. What get posted is the block but in a previous state.
Ex: if I focus on an editable zone add a word and press publish, the latest addition won't be in the payload. Although I can totally see it when I'm logging the components attributes. If I press publish a second time, it will work fine.
Seems to me theres some kind of race condition or some state management I'm doing wrong. I'm totally new to React and Gutenberg.
Here is my save function:
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>
);
}
and my edit:
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>
</>
);
}
}
setStateis asynchronous, so it's enqueuing an update, not writing to state immediately. You will have to either use the callback arg onsetStateor use a lifecycle method likecomponentDidUpdate()- DJ2setStatedoesn't magically update your initialized values based off an elapsed time. You could let it sit there for days and submit, it still wouldn't have your "updated" state values. - DJ2setStateis async, so that’s why you’d need to use the callback function arg to get the latest value set in state to begin with. My suggestion would be to find the appropriate lifecycle method to put that logic in, if thesetStatecallback func arg doesn’t handle your use case - DJ2