ページ作成フォームの構築

前のパートでは、ページの編集 機能を作成しました。このパートでは、ページの作成 機能を追加します。これから構築するフォームは以下のようになります。

ステップ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>
	);
}

あとはページを更新してフォームを楽しむだけです。

次のステップ

原文

s
検索
c
新規投稿を作成する
r
返信
e
編集
t
ページのトップへ
j
次の投稿やコメントに移動
k
前の投稿やコメントに移動
o
コメントの表示を切替
esc
投稿やコメントの編集をキャンセル