A container block — a card, a column, a callout with arbitrary content — needs the editor to nest blocks inside it, and that is one component rather than a custom editing surface.
edit() {
return (
<div className="wp-block-turkerdev-card">
<InnerBlocks
allowedBlocks={['core/heading', 'core/paragraph', 'core/image']}
template={[['core/heading', { level: 3 }], ['core/paragraph']]}
templateLock={false}
/>
</div>
);
},
save() {
return <div className="wp-block-turkerdev-card"><InnerBlocks.Content /></div>;
}
InnerBlocks.Content in save is what serialises the children into the post, and omitting it produces a block that edits correctly and saves nothing — a mistake that is only visible after a reload. allowedBlocks is worth setting because an unconstrained container accepts anything including another copy of itself. templateLock: 'all' makes the structure fixed, which suits a layout block and frustrates an editor who wants one more paragraph.