ページ作成フォームの構築
Topics
前のパートでは、ページの編集 機能を作成しました。このパートでは、ページの作成 機能を追加します。これから構築するフォームは以下のようになります。
ステップ1: 「Create a new page」ボタンの追加
まず、ページの作成 フォームを表示するボタンを作成します。これはパート3で作成した edit ボタンと同様です。
import { useDispatch } from '@wordpress/data';
import { Button, Modal, TextControl } from '@wordpress/components';
function CreatePageButton() {
const [isOpen, setOpen] = useState( false );
const openModal = () => setOpen( true );
const closeModal = () => setOpen( false );
return (
<>
<Button onClick={ openModal } variant="primary">
Create a new Page
</Button>
{ isOpen && (
<Modal onRequestClose={ closeModal } title="Create a new page">
<CreatePageForm
onCancel={ closeModal }
onSaveFinished={ closeModal }
/>
</Modal>
) }
</>
);
}
function CreatePageForm() {
// Empty for now
return <div/>;
}
素晴らしい ! では、MyFirstApp
で、このピカピカの新しいボタンを表示してみましょう。
function MyFirstApp() {
// ...
return (
<div>
<div className="list-controls">
<SearchControl onChange={ setSearchTerm } value={ searchTerm }/>
<CreatePageButton/>
</div>
<PagesList hasResolved={ hasResolved } pages={ pages }/>
</div>
);
}
次のようになるはずです。
ステップ2: 制御された PageForm の抽出
ボタンを設置したので、フォームの作成に集中できます。このチュートリアルはデータ管理が目的のため、完全なページエディターは作成しません。代わりに、フォームには1つのフィールド「投稿タイトル」のみを含めます。
幸運なことにパート3で作成した EditPageForm
が、全体の80%をすでに達成しています。ユーザーインターフェースの大部分を利用可能で、CreatePageForm
内でもこれを再利用します。フォームの UI を別のコンポーネントに抽出することから始めましょう。
function EditPageForm( { pageId, onCancel, onSaveFinished } ) {
// ...
return (
<PageForm
title={ page.title }
onChangeTitle={ handleChange }
hasEdits={ hasEdits }
lastError={ lastError }
isSaving={ isSaving }
onCancel={ onCancel }
onSave={ handleSave }
/>
);
}
function PageForm( { title, onChangeTitle, hasEdits, lastError, isSaving, onCancel, onSave } ) {
return (
<div className="my-gutenberg-form">
<TextControl
label="Page title:"
value={ title }
onChange={ onChangeTitle }
/>
{ lastError ? (
<div className="form-error">Error: { lastError.message }</div>
) : (
false
) }
<div className="form-buttons">
<Button
onClick={ onSave }
variant="primary"
disabled={ !hasEdits || isSaving }
>
{ isSaving ? (
<>
<Spinner/>
Saving
</>
) : 'Save' }
</Button>
<Button
onClick={ onCancel }
variant="tertiary"
disabled={ isSaving }
>
Cancel
</Button>
</div>
</div>
);
}
このコードの変更は、既存のアプリケーションの動作については影響を与えないはずです。念のため、ページを編集してみましょう。
素晴らしい ! 編集フォームはそのままで、新しく CreatePageForm
を作成する構築要素ができました。
ステップ3: CreatePageForm の構築
CreatePageForm
コンポーネントに残る唯一の作業は、PageForm
コンポーネントのレンダーに必要な、以下の7つのプロパティの提供です。
- title
- onChangeTitle
- hasEdits
- lastError
- isSaving
- onCancel
- onSave
それぞれ見ていきましょう。
Title, onChangeTitle, hasEdits
EditPageForm
は Redux のステートに存在する既存のエンティティレコードを更新し、保存しました。そのため、editedEntityRecords
セレクタに依存していました。
しかし CreatePageForm
の場合、既存のエンティティレコードはありません。空のフォームがあるだけです。ユーザーがタイプした内容はフォームに対してローカルのため、React の useState
フックを使用して追跡できます。
function CreatePageForm( { onCancel, onSaveFinished } ) {
const [title, setTitle] = useState();
const handleChange = ( title ) => setTitle( title );
return (
<PageForm
title={ title }
onChangeTitle={ setTitle }
hasEdits={ !!title }
{ /* ... */ }
/>
);
}
onSave, onCancel
EditPageForm
では、saveEditedEntityRecord('postType', 'page', pageId )
アクションをディスパッチして、Redux ステート内の編集内容を保存しました。
しかし、CreatePageForm
では、Redux のステートに編集内容はありません。pageId
もありません。この場合、ディスパッチが必要なアクションは saveEntityRecord
(名前に Edited はありません) で、pageId
の代わりに、新しいエンティティレコードを表すオブジェクトを受け取ります。
saveEntityRecord
に渡されたデータは、適切な REST API エンドポイントへの POST リクエストとして送信されます。例えば、以下のようなアクションをディスパッチします。
saveEntityRecord( 'postType', 'page', { title: "Test" } );
/wp/v2/pages
WordPress REST API のエンドポイントに対して、リクエストボディに単一フィールド title=Test
を指定して、POST リクエストをトリガーします。
これで saveEntityRecord
の詳細が分かったので、CreatePageForm
内で使用してみます。
function CreatePageForm( { onSaveFinished, onCancel } ) {
// ...
const { saveEntityRecord } = useDispatch( coreDataStore );
const handleSave = async () => {
const savedRecord = await saveEntityRecord(
'postType',
'page',
{ title }
);
if ( savedRecord ) {
onSaveFinished();
}
};
return (
<PageForm
{ /* ... */ }
onSave={ handleSave }
onCancel={ onCancel }
/>
);
}
もう一つ、詳細を触れなければならない点があります。新規に作成したページは、PagesList
にまだ登録されていません。REST API のドキュメントによると、/wp/v2/pages
エンドポイントはデフォルトで status=draft
のページを作成 (POST
リクエスト) しますが、status=publish
のページを return (GET
リクエスト) します。解決策は status
パラメータを明示的に渡すことです。
function CreatePageForm( { onSaveFinished, onCancel } ) {
// ...
const { saveEntityRecord } = useDispatch( coreDataStore );
const handleSave = async () => {
const savedRecord = await saveEntityRecord(
'postType',
'page',
{ title, status: 'publish' }
);
if ( savedRecord ) {
onSaveFinished();
}
};
return (
<PageForm
{ /* ... */ }
onSave={ handleSave }
onCancel={ onCancel }
/>
);
}
この変更をローカルの CreatePageForm
コンポーネントに適用します。残りの2つのプロパティを片付けましょう。
lastError, isSaving
EditPageForm
は getLastEntitySaveError
セレクタと isSavingEntityRecord
セレクタを使用してエラーと進行状況の情報を取得しました。どちらの場合も、3つの引数 ( 'postType', 'page', pageId )
を渡しました。
しかし、CreatePageForm
には pageId
がありません。ではどうするか ? id のないエンティティレコードの情報の取得には、pageId
引数を省略できます。これが新しく作成されたものになります。結果、useSelect
の呼び出しは、 EditPageForm
での呼び出しと非常に似たものになります。
function CreatePageForm( { onCancel, onSaveFinished } ) {
// ...
const { lastError, isSaving } = useSelect(
( select ) => ( {
// Notice the missing pageId argument:
lastError: select( coreDataStore )
.getLastEntitySaveError( 'postType', 'page' ),
// Notice the missing pageId argument
isSaving: select( coreDataStore )
.isSavingEntityRecord( 'postType', 'page' ),
} ),
[]
);
// ...
return (
<PageForm
{ /* ... */ }
lastError={ lastError }
isSaving={ isSaving }
/>
);
}
以上です ! 新しいフォームは以下のようになります。
すべてを一つに
この章で構築したすべてを一つにまとめます。
function CreatePageForm( { onCancel, onSaveFinished } ) {
const [title, setTitle] = useState();
const { lastError, isSaving } = useSelect(
( select ) => ( {
lastError: select( coreDataStore )
.getLastEntitySaveError( 'postType', 'page' ),
isSaving: select( coreDataStore )
.isSavingEntityRecord( 'postType', 'page' ),
} ),
[]
);
const { saveEntityRecord } = useDispatch( coreDataStore );
const handleSave = async () => {
const savedRecord = await saveEntityRecord(
'postType',
'page',
{ title, status: 'publish' }
);
if ( savedRecord ) {
onSaveFinished();
}
};
return (
<PageForm
title={ title }
onChangeTitle={ setTitle }
hasEdits={ !!title }
onSave={ handleSave }
lastError={ lastError }
onCancel={ onCancel }
isSaving={ isSaving }
/>
);
}
function EditPageForm( { pageId, onCancel, onSaveFinished } ) {
const { page, lastError, isSaving, hasEdits } = useSelect(
( select ) => ( {
page: select( coreDataStore ).getEditedEntityRecord( 'postType', 'page', pageId ),
lastError: select( coreDataStore ).getLastEntitySaveError( 'postType', 'page', pageId ),
isSaving: select( coreDataStore ).isSavingEntityRecord( 'postType', 'page', pageId ),
hasEdits: select( coreDataStore ).hasEditsForEntityRecord( 'postType', 'page', pageId ),
} ),
[pageId]
);
const { saveEditedEntityRecord, editEntityRecord } = useDispatch( coreDataStore );
const handleSave = async () => {
const savedRecord = await saveEditedEntityRecord( 'postType', 'page', pageId );
if ( savedRecord ) {
onSaveFinished();
}
};
const handleChange = ( title ) => editEntityRecord( 'postType', 'page', page.id, { title } );
return (
<PageForm
title={ page.title }
onChangeTitle={ handleChange }
hasEdits={ hasEdits }
lastError={ lastError }
isSaving={ isSaving }
onCancel={ onCancel }
onSave={ handleSave }
/>
);
}
function PageForm( { title, onChangeTitle, hasEdits, lastError, isSaving, onCancel, onSave } ) {
return (
<div className="my-gutenberg-form">
<TextControl
label="Page title:"
value={ title }
onChange={ onChangeTitle }
/>
{ lastError ? (
<div className="form-error">Error: { lastError.message }</div>
) : (
false
) }
<div className="form-buttons">
<Button
onClick={ onSave }
variant="primary"
disabled={ !hasEdits || isSaving }
>
{ isSaving ? (
<>
<Spinner/>
Saving
</>
) : 'Save' }
</Button>
<Button
onClick={ onCancel }
variant="tertiary"
disabled={ isSaving }
>
Cancel
</Button>
</div>
</div>
);
}
あとはページを更新してフォームを楽しむだけです。
次のステップ
最終更新日: