LESSON 2 โฑ๏ธ 14 min read

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

FormatDescription
core/boldBold text
core/italicItalic text
core/linkHyperlinks
core/codeInline code
core/strikethroughStrikethrough
core/subscriptSubscript
core/superscriptSuperscript

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.