ステップ 1: 新しいページ作成ボタンを追加する
まず、ページ作成フォームを表示するボタンを作成します。これは、パート 3で作成した編集ボタンに似ています:
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 を構築する
- タイトル
- onChangeTitle
- hasEdits
- lastError
- isSaving
- onCancel
- onSave
それをどのように行うか見てみましょう:
#### タイトル、onChangeTitle、hasEdits
`````EditPageForm`````は、Reduxステートに存在するエンティティレコードを更新して保存しました。そのため、`````editedEntityRecords`````セレクタに依存しました。
しかし、`````CreatePageForm`````の場合、事前に存在するエンティティレコードはありません。空のフォームだけがあります。ユーザーが入力する内容はそのフォームにローカルであり、Reactの`````useState`````フックを使用して追跡できます:
``````bash
function CreatePageForm( { onCancel, onSaveFinished } ) {
const [title, setTitle] = useState();
const handleChange = ( title ) => setTitle( title );
return (
<PageForm
title={ title }
onChangeTitle={ setTitle }
hasEdits={ !!title }
{ /* ... */ }
/>
);
}
`
onSave、onCancel
EditPageForm
では、Reduxステートに存在する編集内容を保存するためにsaveEditedEntityRecord('postType', 'page', pageId )
アクションをディスパッチしました。
しかし、CreatePageForm
では、Reduxステートに編集内容はなく、pageId
もありません。この場合、ディスパッチする必要があるアクションは、saveEntityRecord
(名前にEditedという単語が含まれていない)と呼ばれ、新しいエンティティレコードを表すオブジェクトを受け取ります。
saveEntityRecord
に渡されるデータは、適切なREST APIエンドポイントにPOSTリクエストとして送信されます。たとえば、次のアクションをディスパッチすると:
saveEntityRecord( 'postType', 'page', { title: "Test" } );
/wp/v2/pages
WordPress REST APIエンドポイントにリクエストボディに1つのフィールドを持つPOSTリクエストがトリガーされます: title=Test
。
``````bash
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 }
/>
);
}
`
もう1つの詳細に対処する必要があります: 新しく作成したページはまだPagesList
によって取得されていません。REST APIのドキュメントによれば、/wp/v2/pages
エンドポイントはデフォルトで(POST
リクエスト)ページを作成しますが、返す(GET
リクエスト)ページはstatus=publish
です。解決策は、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
がありません。どうすればよいでしょうか?pageId
引数をスキップして、idなしでエンティティレコードに関する情報を取得できます - これは新しく作成されたものになります。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 }
/>
);
}
これで完了です!新しいフォームが動作している様子は次のとおりです:
すべてをつなぎ合わせる
この章で構築したすべてのものを1つの場所にまとめました:
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>
);
}
残っているのはページをリフレッシュしてフォームを楽しむことだけです: