Building the Edit Component
The Edit Component
The Edit component is a React component that renders in the editor. It receives props including attributes and setAttributes for managing block data.
Basic Edit Structure
// src/edit.js
import { __ } from '@wordpress/i18n';
import { useBlockProps, RichText } from '@wordpress/block-editor';
import './editor.scss';
export default function Edit( { attributes, setAttributes } ) {
const { content, author, rating } = attributes;
const blockProps = useBlockProps();
return (
<div { ...blockProps }>
<RichText
tagName="blockquote"
className="testimonial-content"
value={ content }
onChange={ ( newContent ) => setAttributes( { content: newContent } ) }
placeholder={ __( 'Write testimonial...', 'testimonial-block' ) }
/>
<RichText
tagName="cite"
className="testimonial-author"
value={ author }
onChange={ ( newAuthor ) => setAttributes( { author: newAuthor } ) }
placeholder={ __( 'Author name', 'testimonial-block' ) }
/>
</div>
);
}
Always Use useBlockProps: This hook adds required classes and attributes for proper block editor integration.
RichText Component
RichText provides inline editing with formatting options:
import { RichText } from '@wordpress/block-editor';
<RichText
tagName="p" // Output element
className="my-text" // CSS class
value={ attributes.text } // Current value
onChange={ ( text ) => setAttributes( { text } ) }
allowedFormats={ [ 'core/bold', 'core/italic', 'core/link' ] }
placeholder={ __( 'Enter text...' ) }
keepPlaceholderOnFocus={ true }
/>Available Formats
| Format | Description |
|---|---|
core/bold | Bold text |
core/italic | Italic text |
core/link | Hyperlinks |
core/code | Inline code |
core/strikethrough | Strikethrough |
core/subscript | Subscript |
core/superscript | Superscript |
MediaUpload for Images
Add image selection with the media library:
import { MediaUpload, MediaUploadCheck } from '@wordpress/block-editor';
import { Button } from '@wordpress/components';
// In your Edit component:
<MediaUploadCheck>
<MediaUpload
onSelect={ ( media ) => setAttributes( {
mediaId: media.id,
mediaUrl: media.url
} ) }
allowedTypes={ [ 'image' ] }
value={ attributes.mediaId }
render={ ( { open } ) => (
<div className="testimonial-avatar">
{ attributes.mediaUrl ? (
<>
<img src={ attributes.mediaUrl } alt="" />
<Button onClick={ open } variant="secondary">
{ __( 'Replace Image', 'testimonial-block' ) }
</Button>
</>
) : (
<Button onClick={ open } variant="primary">
{ __( 'Select Avatar', 'testimonial-block' ) }
</Button>
) }
</div>
) }
/>
</MediaUploadCheck>Using WordPress Components
Import and use standard UI components:
import {
TextControl,
RangeControl,
SelectControl,
ToggleControl,
ColorPicker,
PanelBody
} from '@wordpress/components';
// Text input
<TextControl
label={ __( 'Author Title', 'testimonial-block' ) }
value={ attributes.authorTitle }
onChange={ ( authorTitle ) => setAttributes( { authorTitle } ) }
/>
// Number slider
<RangeControl
label={ __( 'Rating', 'testimonial-block' ) }
value={ attributes.rating }
onChange={ ( rating ) => setAttributes( { rating } ) }
min={ 1 }
max={ 5 }
/>
// Dropdown
<SelectControl
label={ __( 'Style', 'testimonial-block' ) }
value={ attributes.style }
options={ [
{ label: 'Default', value: 'default' },
{ label: 'Boxed', value: 'boxed' },
{ label: 'Minimal', value: 'minimal' },
] }
onChange={ ( style ) => setAttributes( { style } ) }
/>
// Toggle
<ToggleControl
label={ __( 'Show Rating', 'testimonial-block' ) }
checked={ attributes.showRating }
onChange={ ( showRating ) => setAttributes( { showRating } ) }
/>Complete Edit Component
import { __ } from '@wordpress/i18n';
import {
useBlockProps,
RichText,
MediaUpload,
MediaUploadCheck,
InspectorControls
} from '@wordpress/block-editor';
import { PanelBody, RangeControl, Button } from '@wordpress/components';
import './editor.scss';
export default function Edit( { attributes, setAttributes } ) {
const { content, author, rating, mediaId, mediaUrl } = attributes;
const blockProps = useBlockProps( {
className: `rating-${ rating }`,
} );
// Generate star rating display
const stars = 'โ
'.repeat( rating ) + 'โ'.repeat( 5 - rating );
return (
<>
<InspectorControls>
<PanelBody title={ __( 'Settings', 'testimonial-block' ) }>
<RangeControl
label={ __( 'Rating', 'testimonial-block' ) }
value={ rating }
onChange={ ( newRating ) => setAttributes( { rating: newRating } ) }
min={ 1 }
max={ 5 }
/>
</PanelBody>
</InspectorControls>
<div { ...blockProps }>
<MediaUploadCheck>
<MediaUpload
onSelect={ ( media ) => setAttributes( {
mediaId: media.id,
mediaUrl: media.url
} ) }
allowedTypes={ [ 'image' ] }
value={ mediaId }
render={ ( { open } ) => (
<div className="testimonial-avatar" onClick={ open }>
{ mediaUrl ? (
<img src={ mediaUrl } alt="" />
) : (
<div className="avatar-placeholder">
{ __( 'Add Photo', 'testimonial-block' ) }
</div>
) }
</div>
) }
/>
</MediaUploadCheck>
<div className="testimonial-body">
<RichText
tagName="blockquote"
className="testimonial-content"
value={ content }
onChange={ ( newContent ) => setAttributes( { content: newContent } ) }
placeholder={ __( 'Write testimonial...', 'testimonial-block' ) }
/>
<div className="testimonial-rating">{ stars }</div>
<RichText
tagName="cite"
value={ author }
onChange={ ( newAuthor ) => setAttributes( { author: newAuthor } ) }
placeholder={ __( 'Customer Name', 'testimonial-block' ) }
/>
</div>
</div>
</>
);
}Editor Styles
// src/editor.scss
.wp-block-theme-testimonial {
display: flex;
gap: 20px;
padding: 24px;
background: #f8f9fa;
border-radius: 8px;
.testimonial-avatar {
width: 80px;
height: 80px;
border-radius: 50%;
overflow: hidden;
cursor: pointer;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
.avatar-placeholder {
width: 100%;
height: 100%;
background: #ddd;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
color: #666;
}
}
.testimonial-rating {
color: #f59e0b;
font-size: 18px;
}
}Next Steps
In the next lesson, we'll build the Save component and understand how block content is serialized.
๐ฏ Lesson Complete! You can now build interactive Edit components with various input controls.