ステップ 1: 新しいページ作成ボタンを追加する

まず、ページ作成フォームを表示するボタンを作成します。これは、パート 3で作成した編集ボタンに似ています:

  1. import { useDispatch } from '@wordpress/data';
  2. import { Button, Modal, TextControl } from '@wordpress/components';
  3. function CreatePageButton() {
  4. const [isOpen, setOpen] = useState( false );
  5. const openModal = () => setOpen( true );
  6. const closeModal = () => setOpen( false );
  7. return (
  8. <>
  9. <Button onClick={ openModal } variant="primary">
  10. Create a new Page
  11. </Button>
  12. { isOpen && (
  13. <Modal onRequestClose={ closeModal } title="Create a new page">
  14. <CreatePageForm
  15. onCancel={ closeModal }
  16. onSaveFinished={ closeModal }
  17. />
  18. </Modal>
  19. ) }
  20. </>
  21. );
  22. }
  23. function CreatePageForm() {
  24. // Empty for now
  25. return <div/>;
  26. }

素晴らしい!次に、MyFirstAppが私たちの新しいボタンを表示するようにしましょう:

  1. function MyFirstApp() {
  2. // ...
  3. return (
  4. <div>
  5. <div className="list-controls">
  6. <SearchControl onChange={ setSearchTerm } value={ searchTerm }/>
  7. <CreatePageButton/>
  8. </div>
  9. <PagesList hasResolved={ hasResolved } pages={ pages }/>
  10. </div>
  11. );
  12. }

最終結果は次のようになります:

パート4:作成ページフォームの構築(Part 4: Building a Create page form) - img1

ステップ 2: 制御された PageForm を抽出する

ボタンが設置されたので、フォームの構築に完全に集中できます。このチュートリアルはデータ管理に関するもので、完全なページエディタを構築するわけではありません。代わりに、フォームには1つのフィールドのみが含まれます: 投稿タイトル。

幸運なことに、パート 3で作成したEditPageFormは、すでに80%の道のりを進んでいます。ユーザーインターフェースの大部分はすでに利用可能で、CreatePageFormで再利用します。フォーム UI を別のコンポーネントに抽出することから始めましょう:

  1. function EditPageForm( { pageId, onCancel, onSaveFinished } ) {
  2. // ...
  3. return (
  4. <PageForm
  5. title={ page.title }
  6. onChangeTitle={ handleChange }
  7. hasEdits={ hasEdits }
  8. lastError={ lastError }
  9. isSaving={ isSaving }
  10. onCancel={ onCancel }
  11. onSave={ handleSave }
  12. />
  13. );
  14. }
  15. function PageForm( { title, onChangeTitle, hasEdits, lastError, isSaving, onCancel, onSave } ) {
  16. return (
  17. <div className="my-gutenberg-form">
  18. <TextControl
  19. label="Page title:"
  20. value={ title }
  21. onChange={ onChangeTitle }
  22. />
  23. { lastError ? (
  24. <div className="form-error">Error: { lastError.message }</div>
  25. ) : (
  26. false
  27. ) }
  28. <div className="form-buttons">
  29. <Button
  30. onClick={ onSave }
  31. variant="primary"
  32. disabled={ !hasEdits || isSaving }
  33. >
  34. { isSaving ? (
  35. <>
  36. <Spinner/>
  37. Saving
  38. </>
  39. ) : 'Save' }
  40. </Button>
  41. <Button
  42. onClick={ onCancel }
  43. variant="tertiary"
  44. disabled={ isSaving }
  45. >
  46. Cancel
  47. </Button>
  48. </div>
  49. </div>
  50. );
  51. }

このコード品質の変更は、アプリケーションの動作に何も影響を与えないはずです。念のため、ページを編集してみましょう:

パート4:作成ページフォームの構築(Part 4: Building a Create page form) - img2

素晴らしい!編集フォームはまだそこにあり、今では新しいCreatePageFormを支えるビルディングブロックがあります。

ステップ 3: CreatePageForm を構築する

  1. - タイトル
  2. - onChangeTitle
  3. - hasEdits
  4. - lastError
  5. - isSaving
  6. - onCancel
  7. - onSave
  8. それをどのように行うか見てみましょう:
  9. #### タイトル、onChangeTitle、hasEdits
  10. `````EditPageForm`````は、Reduxステートに存在するエンティティレコードを更新して保存しました。そのため、`````editedEntityRecords`````セレクタに依存しました。
  11. しかし、`````CreatePageForm`````の場合、事前に存在するエンティティレコードはありません。空のフォームだけがあります。ユーザーが入力する内容はそのフォームにローカルであり、React`````useState`````フックを使用して追跡できます:
  12. ``````bash
  13. function CreatePageForm( { onCancel, onSaveFinished } ) {
  14. const [title, setTitle] = useState();
  15. const handleChange = ( title ) => setTitle( title );
  16. return (
  17. <PageForm
  18. title={ title }
  19. onChangeTitle={ setTitle }
  20. hasEdits={ !!title }
  21. { /* ... */ }
  22. />
  23. );
  24. }
  25. `

onSave、onCancel

EditPageFormでは、Reduxステートに存在する編集内容を保存するためにsaveEditedEntityRecord('postType', 'page', pageId )アクションをディスパッチしました。

しかし、CreatePageFormでは、Reduxステートに編集内容はなく、pageIdもありません。この場合、ディスパッチする必要があるアクションは、saveEntityRecord(名前にEditedという単語が含まれていない)と呼ばれ、新しいエンティティレコードを表すオブジェクトを受け取ります。

saveEntityRecordに渡されるデータは、適切なREST APIエンドポイントにPOSTリクエストとして送信されます。たとえば、次のアクションをディスパッチすると:

  1. saveEntityRecord( 'postType', 'page', { title: "Test" } );

/wp/v2/pages WordPress REST APIエンドポイントにリクエストボディに1つのフィールドを持つPOSTリクエストがトリガーされます: title=Test

  1. ``````bash
  2. function CreatePageForm( { onSaveFinished, onCancel } ) {
  3. // ...
  4. const { saveEntityRecord } = useDispatch( coreDataStore );
  5. const handleSave = async () => {
  6. const savedRecord = await saveEntityRecord(
  7. 'postType',
  8. 'page',
  9. { title }
  10. );
  11. if ( savedRecord ) {
  12. onSaveFinished();
  13. }
  14. };
  15. return (
  16. <PageForm
  17. { /* ... */ }
  18. onSave={ handleSave }
  19. onCancel={ onCancel }
  20. />
  21. );
  22. }
  23. `

もう1つの詳細に対処する必要があります: 新しく作成したページはまだPagesListによって取得されていません。REST APIのドキュメントによれば、/wp/v2/pagesエンドポイントはデフォルトで(POSTリクエスト)ページを作成しますが、返す(GETリクエスト)ページはstatus=publishです。解決策は、statusパラメータを明示的に渡すことです:

  1. function CreatePageForm( { onSaveFinished, onCancel } ) {
  2. // ...
  3. const { saveEntityRecord } = useDispatch( coreDataStore );
  4. const handleSave = async () => {
  5. const savedRecord = await saveEntityRecord(
  6. 'postType',
  7. 'page',
  8. { title, status: 'publish' }
  9. );
  10. if ( savedRecord ) {
  11. onSaveFinished();
  12. }
  13. };
  14. return (
  15. <PageForm
  16. { /* ... */ }
  17. onSave={ handleSave }
  18. onCancel={ onCancel }
  19. />
  20. );
  21. }

この変更をローカルのCreatePageFormコンポーネントに適用し、残りの2つのプロパティに取り組みましょう。

lastError、isSaving

EditPageFormは、getLastEntitySaveErrorおよびisSavingEntityRecordセレクタを介してエラーと進行状況の情報を取得しました。両方の場合において、次の3つの引数を渡しました: ( 'postType', 'page', pageId )

しかし、CreatePageFormではpageIdがありません。どうすればよいでしょうか?pageId引数をスキップして、idなしでエンティティレコードに関する情報を取得できます - これは新しく作成されたものになります。useSelect呼び出しは、EditPageFormからのものと非常に似ています:

  1. function CreatePageForm( { onCancel, onSaveFinished } ) {
  2. // ...
  3. const { lastError, isSaving } = useSelect(
  4. ( select ) => ( {
  5. // Notice the missing pageId argument:
  6. lastError: select( coreDataStore )
  7. .getLastEntitySaveError( 'postType', 'page' ),
  8. // Notice the missing pageId argument
  9. isSaving: select( coreDataStore )
  10. .isSavingEntityRecord( 'postType', 'page' ),
  11. } ),
  12. []
  13. );
  14. // ...
  15. return (
  16. <PageForm
  17. { /* ... */ }
  18. lastError={ lastError }
  19. isSaving={ isSaving }
  20. />
  21. );
  22. }

これで完了です!新しいフォームが動作している様子は次のとおりです:

パート4:作成ページフォームの構築(Part 4: Building a Create page form) - img3

パート4:作成ページフォームの構築(Part 4: Building a Create page form) - img4

すべてをつなぎ合わせる

この章で構築したすべてのものを1つの場所にまとめました:

  1. function CreatePageForm( { onCancel, onSaveFinished } ) {
  2. const [title, setTitle] = useState();
  3. const { lastError, isSaving } = useSelect(
  4. ( select ) => ( {
  5. lastError: select( coreDataStore )
  6. .getLastEntitySaveError( 'postType', 'page' ),
  7. isSaving: select( coreDataStore )
  8. .isSavingEntityRecord( 'postType', 'page' ),
  9. } ),
  10. []
  11. );
  12. const { saveEntityRecord } = useDispatch( coreDataStore );
  13. const handleSave = async () => {
  14. const savedRecord = await saveEntityRecord(
  15. 'postType',
  16. 'page',
  17. { title, status: 'publish' }
  18. );
  19. if ( savedRecord ) {
  20. onSaveFinished();
  21. }
  22. };
  23. return (
  24. <PageForm
  25. title={ title }
  26. onChangeTitle={ setTitle }
  27. hasEdits={ !!title }
  28. onSave={ handleSave }
  29. lastError={ lastError }
  30. onCancel={ onCancel }
  31. isSaving={ isSaving }
  32. />
  33. );
  34. }
  35. function EditPageForm( { pageId, onCancel, onSaveFinished } ) {
  36. const { page, lastError, isSaving, hasEdits } = useSelect(
  37. ( select ) => ( {
  38. page: select( coreDataStore ).getEditedEntityRecord( 'postType', 'page', pageId ),
  39. lastError: select( coreDataStore ).getLastEntitySaveError( 'postType', 'page', pageId ),
  40. isSaving: select( coreDataStore ).isSavingEntityRecord( 'postType', 'page', pageId ),
  41. hasEdits: select( coreDataStore ).hasEditsForEntityRecord( 'postType', 'page', pageId ),
  42. } ),
  43. [pageId]
  44. );
  45. const { saveEditedEntityRecord, editEntityRecord } = useDispatch( coreDataStore );
  46. const handleSave = async () => {
  47. const savedRecord = await saveEditedEntityRecord( 'postType', 'page', pageId );
  48. if ( savedRecord ) {
  49. onSaveFinished();
  50. }
  51. };
  52. const handleChange = ( title ) => editEntityRecord( 'postType', 'page', page.id, { title } );
  53. return (
  54. <PageForm
  55. title={ page.title }
  56. onChangeTitle={ handleChange }
  57. hasEdits={ hasEdits }
  58. lastError={ lastError }
  59. isSaving={ isSaving }
  60. onCancel={ onCancel }
  61. onSave={ handleSave }
  62. />
  63. );
  64. }
  65. function PageForm( { title, onChangeTitle, hasEdits, lastError, isSaving, onCancel, onSave } ) {
  66. return (
  67. <div className="my-gutenberg-form">
  68. <TextControl
  69. label="Page title:"
  70. value={ title }
  71. onChange={ onChangeTitle }
  72. />
  73. { lastError ? (
  74. <div className="form-error">Error: { lastError.message }</div>
  75. ) : (
  76. false
  77. ) }
  78. <div className="form-buttons">
  79. <Button
  80. onClick={ onSave }
  81. variant="primary"
  82. disabled={ !hasEdits || isSaving }
  83. >
  84. { isSaving ? (
  85. <>
  86. <Spinner/>
  87. Saving
  88. </>
  89. ) : 'Save' }
  90. </Button>
  91. <Button
  92. onClick={ onCancel }
  93. variant="tertiary"
  94. disabled={ isSaving }
  95. >
  96. Cancel
  97. </Button>
  98. </div>
  99. </div>
  100. );
  101. }

残っているのはページをリフレッシュしてフォームを楽しむことだけです:

パート4:作成ページフォームの構築(Part 4: Building a Create page form) - img5

次は何ですか?