Skip to content

Commit

Permalink
Updated the "Create your First App with Gutenberg Data" how-to guide (#…
Browse files Browse the repository at this point in the history
…43633)

* Updated the Create your First App with Gutenberg Data how-to guide

* Update docs/how-to-guides/data-basics/2-building-a-list-of-pages.md

* added comment back

Co-authored-by: Adam Zielinski <[email protected]>
  • Loading branch information
dgwyer and adamziel authored Sep 2, 2022
1 parent d9504c1 commit ebf3791
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 59 deletions.
51 changes: 27 additions & 24 deletions docs/how-to-guides/data-basics/1-data-basics-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,29 +163,32 @@ add_action( 'admin_enqueue_scripts', 'load_custom_wp_admin_scripts' );

```json
{
"name": "05-recipe-card-esnext",
"version": "1.1.0",
"private": true,
"description": "Example: Recipe Card (ESNext).",
"author": "The WordPress Contributors",
"license": "GPL-2.0-or-later",
"keywords": [ "WordPress", "block" ],
"homepage": "https://github.com/WordPress/gutenberg-examples/",
"repository": "git+https://github.com/WordPress/gutenberg-examples.git",
"bugs": {
"url": "https://github.com/WordPress/gutenberg-examples/issues"
},
"main": "build/index.js",
"devDependencies": {
"@wordpress/scripts": "^18.0.1"
},
"scripts": {
"build": "wp-scripts build",
"format": "wp-scripts format",
"lint:js": "wp-scripts lint-js",
"packages-update": "wp-scripts packages-update",
"start": "wp-scripts start"
}
"name": "09-code-data-basics-esnext",
"version": "1.1.0",
"private": true,
"description": "My first Gutenberg App",
"author": "The WordPress Contributors",
"license": "GPL-2.0-or-later",
"keywords": [
"WordPress",
"block"
],
"homepage": "https://github.com/WordPress/gutenberg-examples/",
"repository": "git+https://github.com/WordPress/gutenberg-examples.git",
"bugs": {
"url": "https://github.com/WordPress/gutenberg-examples/issues"
},
"main": "build/index.js",
"devDependencies": {
"@wordpress/scripts": "^24.0.0"
},
"scripts": {
"build": "wp-scripts build",
"format": "wp-scripts format",
"lint:js": "wp-scripts lint-js",
"packages-update": "wp-scripts packages-update",
"start": "wp-scripts start"
}
}
```

Expand All @@ -209,4 +212,4 @@ Congratulations! You are now ready to start building the app!

- Previous part: [Introduction](/docs/how-to-guides/data-basics/README.md)
- Next part: [Building a basic list of pages](/docs/how-to-guides/data-basics/2-building-a-list-of-pages.md)
- (optional) Review the [finished app](https://github.com/WordPress/gutenberg-examples/tree/trunk/09-code-data-basics-esnext) in the gutenberg-examples repository
- (optional) Review the [finished app](https://github.com/WordPress/gutenberg-examples/tree/trunk/non-block-examples/09-code-data-basics-esnext) in the gutenberg-examples repository
15 changes: 13 additions & 2 deletions docs/how-to-guides/data-basics/2-building-a-list-of-pages.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Before we start, let’s confirm we actually have some pages to fetch. Within WP
If it doesn’t, go ahead and create a few pages – you can use the same titles as on the screenshot above. Be sure to _publish_ and not just _save_ them.
Now that we have the data to work with, let’s dive into the code. We will take advantage of the [`@wordpress/core-data` package](https://github.com/WordPress/gutenberg/tree/trunk/packages/core-data) package which provides resolvers, selectors, and actions to work with the WordPress core API. `@wordpress/core-data` builds on top of the [`@wordpress/data` package](https://github.com/WordPress/gutenberg/tree/trunk/packages/data).
Now that we have the data to work with, let’s dive into the code. We will take advantage of the [`@wordpress/core-data`](https://github.com/WordPress/gutenberg/tree/trunk/packages/core-data) package which provides resolvers, selectors, and actions to work with the WordPress core API. `@wordpress/core-data` builds on top of the [`@wordpress/data`](https://github.com/WordPress/gutenberg/tree/trunk/packages/data) package.
To fetch the list of pages, we will use the [`getEntityRecords`](/docs/reference-guides/data/data-core/#getentityrecords) selector. In broad strokes, it will issue the correct API request, cache the results, and return the list of the records we need. Here’s how to use it:
Expand All @@ -53,6 +53,8 @@ wp.data.select( 'core' ).getEntityRecords( 'postType', 'page' )
If you run that following snippet in your browser’s dev tools, you will see it returns `null`. Why? The pages are only requested by the `getEntityRecords` resolver after first running the _selector_. If you wait a moment and re-run it, it will return the list of all pages.
*Note: To run this type of command directly make sure your browser is displaying an instance of the block editor (any page will do). Otherwise the `select( 'core' )` function won't be available, and you'll get an error.*
Similarly, the `MyFirstApp` component needs to re-run the selector once the data is available. That’s exactly what the `useSelect` hook does:
```js
Expand All @@ -67,6 +69,14 @@ function MyFirstApp() {
);
// ...
}

function PagesList({ pages }) {
// ...
<li key={page.id}>
{page.title.rendered}
</li>
// ...
}
```
Note that we use an `import` statement inside index.js. This enables the plugin to automatically load the dependencies using `wp_enqueue_script`. Any references to `coreDataStore` are compiled to the same `wp.data` reference we use in browser's devtools.
Expand Down Expand Up @@ -353,6 +363,7 @@ import { SearchControl, Spinner } from '@wordpress/components';
import { useState, render } from '@wordpress/element';
import { useSelect } from '@wordpress/data';
import { store as coreDataStore } from '@wordpress/core-data';
import { decodeEntities } from '@wordpress/html-entities';

function MyFirstApp() {
const [ searchTerm, setSearchTerm ] = useState( '' );
Expand Down Expand Up @@ -431,4 +442,4 @@ All that’s left is to refresh the page and enjoy the brand new status indicato
* **Previous part:** [Setup](/docs/how-to-guides/data-basics/1-data-basics-setup.md)
* **Next part:** [Building an edit form](/docs/how-to-guides/data-basics/3-building-an-edit-form.md)
* (optional) Review the [finished app](https://github.com/WordPress/gutenberg-examples/tree/trunk/09-code-data-basics-esnext) in the gutenberg-examples repository
* (optional) Review the [finished app](https://github.com/WordPress/gutenberg-examples/tree/trunk/non-block-examples/09-code-data-basics-esnext) in the gutenberg-examples repository
37 changes: 18 additions & 19 deletions docs/how-to-guides/data-basics/3-building-an-edit-form.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ Our button looks nice but doesn't do anything yet. To display an edit form, we n
```js
import { Button, TextControl } from '@wordpress/components';
export function EditPageForm( { pageId, onCancel, onSaveFinished } ) {
function EditPageForm( { pageId, onCancel, onSaveFinished } ) {
return (
<div className="my-gutenberg-form">
<TextControl
Expand Down Expand Up @@ -131,7 +131,7 @@ wp.data.select( 'core' ).getEntityRecord( 'postType', 'page', 9 ); // Replace 9
Let's update `EditPageForm` accordingly:
```js
export function EditPageForm( { pageId, onCancel, onSaveFinished } ) {
function EditPageForm( { pageId, onCancel, onSaveFinished } ) {
const page = useSelect(
select => select( coreDataStore ).getEntityRecord( 'postType', 'page', pageId ),
[pageId]
Expand All @@ -140,15 +140,15 @@ export function EditPageForm( { pageId, onCancel, onSaveFinished } ) {
<div className="my-gutenberg-form">
<TextControl
label='Page title:'
value={ page.title }
value={ page.title.rendered }
/>
{ /* ... */ }
</div>
);
}
```
Now it should look like that:
Now it should look like this:
![](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/how-to-guides/data-basics/media/edit-form/form-populated.png)
Expand All @@ -160,7 +160,7 @@ There's one problem with our _Page title_ field: you can't edit it. It receives
You may have seen a pattern similar to this one in other React apps. It's known as a ["controlled component"](https://reactjs.org/docs/forms.html#controlled-components):
```js
export function VanillaReactForm({ initialTitle }) {
function VanillaReactForm({ initialTitle }) {
const [title, setTitle] = useState( initialTitle );
return (
<TextControl
Expand All @@ -181,7 +181,7 @@ const pageId = wp.data.select( 'core' ).getEntityRecords( 'postType', 'page' )[0
wp.data.dispatch( 'core' ).editEntityRecord( 'postType', 'page', pageId, { title: 'updated title' } );
```
At this point, you may ask _how is `editEntityRecord` better than `useState`? The answer is that it offers a few features you wouldn't otherwise get.
At this point, you may ask _how_ is `editEntityRecord` better than `useState`? The answer is that it offers a few features you wouldn't otherwise get.
Firstly, we can save the changes as easily as we retrieve the data and ensure that all caches will be correctly updated.
Expand Down Expand Up @@ -222,7 +222,7 @@ We can now update `EditPageForm` accordingly. We can access the actions using th
```js
import { useDispatch } from '@wordpress/data';

export function EditPageForm( { pageId, onCancel, onSaveFinished } ) {
function EditPageForm( { pageId, onCancel, onSaveFinished } ) {
const page = useSelect(
select => select( coreDataStore ).getEditedEntityRecord( 'postType', 'page', pageId ),
[ pageId ]
Expand Down Expand Up @@ -281,7 +281,7 @@ Entity records are updated to reflect any saved changes right after the REST API
This is how the `EditPageForm` looks like with a working *Save* button:
```js
export function EditPageForm( { pageId, onCancel, onSaveFinished } ) {
function EditPageForm( { pageId, onCancel, onSaveFinished } ) {
// ...
const { saveEditedEntityRecord } = useDispatch( coreDataStore );
const handleSave = () => saveEditedEntityRecord( 'postType', 'page', pageId );
Expand All @@ -303,7 +303,7 @@ export function EditPageForm( { pageId, onCancel, onSaveFinished } ) {
It works, but there's still one thing to fix: the form modal doesn't automatically close because we never call `onSaveFinished`. Lucky for us, `saveEditedEntityRecord` returns a promise that resolves once the save operation is finished. Let's take advantage of it in `EditPageForm`:
```js
export function EditPageForm( { pageId, onCancel, onSaveFinished } ) {
function EditPageForm( { pageId, onCancel, onSaveFinished } ) {
// ...
const handleSave = async () => {
await saveEditedEntityRecord( 'postType', 'page', pageId );
Expand All @@ -324,7 +324,7 @@ We optimistically assumed that a *save* operation would always succeed. Unfortun
To tell the user when any of these happens, we have to make two adjustments. We don't want to close the form modal when the update fails. The promise returned by `saveEditedEntityRecord` is resolved with an updated record only if the update actually worked. When something goes wrong, it resolves with an empty value. Let's use it to keep the modal open:
```js
export function EditPageForm( { pageId, onSaveFinished } ) {
function EditPageForm( { pageId, onSaveFinished } ) {
// ...
const handleSave = async () => {
const updatedRecord = await saveEditedEntityRecord( 'postType', 'page', pageId );
Expand All @@ -346,7 +346,7 @@ wp.data.select( 'core' ).getLastEntitySaveError( 'postType', 'page', 9 )
Here's how we can use it in `EditPageForm`:
```js
export function EditPageForm( { pageId, onSaveFinished } ) {
function EditPageForm( { pageId, onSaveFinished } ) {
// ...
const { lastError, page } = useSelect(
select => ({
Expand All @@ -357,15 +357,15 @@ export function EditPageForm( { pageId, onSaveFinished } ) {
)
// ...
return (
<>
<div className="my-gutenberg-form">
{/* ... */}
{ lastError ? (
<div className="form-error">
Error: { lastError.message }
</div>
) : false }
{/* ... */}
</>
</div>
);
}
```
Expand All @@ -375,7 +375,7 @@ Great! `EditPageForm` is now fully aware of errors.
Let's see that error message in action. We'll trigger an invalid update and let it fail. The post title is hard to break, so let's set a `date` property to `-1` instead – that's a guaranteed validation error:
```js
export function EditPageForm( { pageId, onCancel, onSaveFinished } ) {
function EditPageForm( { pageId, onCancel, onSaveFinished } ) {
// ...
const handleChange = ( title ) => editEntityRecord( 'postType', 'page', pageId, { title, date: -1 } );
// ...
Expand All @@ -397,7 +397,7 @@ We're going to clear it up and communicate two states to the user: _Saving_ and
Let's use them in `EditPageForm`:
```js
export function EditPageForm( { pageId, onSaveFinished } ) {
function EditPageForm( { pageId, onSaveFinished } ) {
// ...
const { isSaving, hasEdits, /* ... */ } = useSelect(
select => ({
Expand All @@ -413,8 +413,7 @@ export function EditPageForm( { pageId, onSaveFinished } ) {
We can now use `isSaving` and `hasEdits` to display a spinner when saving is in progress and grey out the save button when there are no edits:
```js

export function EditPageForm( { pageId, onSaveFinished } ) {
function EditPageForm( { pageId, onSaveFinished } ) {
// ...
return (
// ...
Expand Down Expand Up @@ -479,7 +478,7 @@ function PageEditButton( { pageId } ) {
);
}

export function EditPageForm( { pageId, onCancel, onSaveFinished } ) {
function EditPageForm( { pageId, onCancel, onSaveFinished } ) {
const { page, lastError, isSaving, hasEdits } = useSelect(
( select ) => ( {
page: select( coreDataStore ).getEditedEntityRecord( 'postType', 'page', pageId ),
Expand Down Expand Up @@ -541,4 +540,4 @@ export function EditPageForm( { pageId, onCancel, onSaveFinished } ) {
* **Previous part:** [Building a list of pages](/docs/how-to-guides/data-basics/2-building-a-list-of-pages.md)
* **Next part:** Building a *New Page* form (coming soon)
* (optional) Review the [finished app](https://github.com/WordPress/gutenberg-examples/tree/trunk/09-code-data-basics-esnext) in the gutenberg-examples repository
* (optional) Review the [finished app](https://github.com/WordPress/gutenberg-examples/tree/trunk/non-block-examples/09-code-data-basics-esnext) in the gutenberg-examples repository
20 changes: 10 additions & 10 deletions docs/how-to-guides/data-basics/4-building-a-create-page-form.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ Now that the button is in place, we can focus entirely on building the form. Thi
Luckily, the `EditPageForm` we built in [part three](/docs/how-to-guides/data-basics/3-building-an-edit-form.md) already takes us 80% of the way there. The bulk of the user interface is already available, and we will reuse it in the `CreatePageForm`. Let’s start by extracting the form UI into a separate component:

```js
export function EditPageForm( { pageId, onCancel, onSaveFinished } ) {
function EditPageForm( { pageId, onCancel, onSaveFinished } ) {
// ...
return (
<PageForm
Expand All @@ -83,7 +83,7 @@ export function EditPageForm( { pageId, onCancel, onSaveFinished } ) {
);
}

export function PageForm( { title, onChangeTitle, hasEdits, lastError, isSaving, onCancel, onSave } ) {
function PageForm( { title, onChangeTitle, hasEdits, lastError, isSaving, onCancel, onSave } ) {
return (
<div className="my-gutenberg-form">
<TextControl
Expand Down Expand Up @@ -149,7 +149,7 @@ The `EditPageForm` updated and saved an existing entity record that lived in the
In case of the `CreatePageForm` however, there is no pre-existing entity record. There is only an empty form. Anything that the user types is local to that form, which means we can keep track of it using the React’s `useState` hook:

```js
export function CreatePageForm( { onCancel, onSaveFinished } ) {
function CreatePageForm( { onCancel, onSaveFinished } ) {
const [title, setTitle] = useState();
const handleChange = ( title ) => setTitle( title );
return (
Expand Down Expand Up @@ -180,7 +180,7 @@ Triggers a POST request to the [`/wp/v2/pages` WordPress REST API](https://devel
Now that we know more about `saveEntityRecord`, let's use it in `CreatePageForm`.

```js
export function CreatePageForm( { onSaveFinished, onCancel } ) {
function CreatePageForm( { onSaveFinished, onCancel } ) {
// ...
const { saveEntityRecord } = useDispatch( coreDataStore );
const handleSave = async () => {
Expand All @@ -206,7 +206,7 @@ export function CreatePageForm( { onSaveFinished, onCancel } ) {
There is one more detail to address: our newly created pages are not yet picked up by the `PagesList`. Accordingly to the REST API documentation, the `/wp/v2/pages` endpoint creates (`POST` requests) pages with `status=draft` by default, but _returns_ (`GET` requests) pages with `status=publish`. The solution is to pass the `status` parameter explicitly:

```js
export function CreatePageForm( { onSaveFinished, onCancel } ) {
function CreatePageForm( { onSaveFinished, onCancel } ) {
// ...
const { saveEntityRecord } = useDispatch( coreDataStore );
const handleSave = async () => {
Expand Down Expand Up @@ -238,7 +238,7 @@ The `EditPageForm` retrieved the error and progress information via the `getLas
In `CreatePageForm` however, we do not have a `pageId`. What now? We can skip the `pageId` argument to retrieve the information about the entity record without any id – this will be the newly created one. The `useSelect` call is thus very similar to the one from `EditPageForm`:

```js
export function CreatePageForm( { onCancel, onSaveFinished } ) {
function CreatePageForm( { onCancel, onSaveFinished } ) {
// ...
const { lastError, isSaving } = useSelect(
( select ) => ( {
Expand Down Expand Up @@ -272,7 +272,7 @@ And that’s it! Here's what our new form looks like in action:
Here’s everything we built in this chapter in one place:

```js
export function CreatePageForm( { onCancel, onSaveFinished } ) {
function CreatePageForm( { onCancel, onSaveFinished } ) {
const [title, setTitle] = useState();
const { lastError, isSaving } = useSelect(
( select ) => ( {
Expand Down Expand Up @@ -309,7 +309,7 @@ export function CreatePageForm( { onCancel, onSaveFinished } ) {
);
}

export function EditPageForm( { pageId, onCancel, onSaveFinished } ) {
function EditPageForm( { pageId, onCancel, onSaveFinished } ) {
const { page, lastError, isSaving, hasEdits } = useSelect(
( select ) => ( {
page: select( coreDataStore ).getEditedEntityRecord( 'postType', 'page', pageId ),
Expand Down Expand Up @@ -342,7 +342,7 @@ export function EditPageForm( { pageId, onCancel, onSaveFinished } ) {
);
}

export function PageForm( { title, onChangeTitle, hasEdits, lastError, isSaving, onCancel, onSave } ) {
function PageForm( { title, onChangeTitle, hasEdits, lastError, isSaving, onCancel, onSave } ) {
return (
<div className="my-gutenberg-form">
<TextControl
Expand Down Expand Up @@ -389,4 +389,4 @@ All that’s left is to refresh the page and enjoy the form:

* **Next part:** [Adding a delete button](/docs/how-to-guides/data-basics/5-adding-a-delete-button.md)
* **Previous part:** [Building an edit form](/docs/how-to-guides/data-basics/3-building-an-edit-form.md)
* (optional) Review the [finished app](https://github.com/WordPress/gutenberg-examples/tree/trunk/09-code-data-basics-esnext) in the gutenberg-examples repository
* (optional) Review the [finished app](https://github.com/WordPress/gutenberg-examples/tree/trunk/non-block-examples/09-code-data-basics-esnext) in the gutenberg-examples repository
7 changes: 3 additions & 4 deletions docs/how-to-guides/data-basics/5-adding-a-delete-button.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,9 @@ function PagesList( { hasResolved, pages } ) {
<td>{ decodeEntities( page.title.rendered ) }</td>
<td>
<div className="form-buttons">
<EditPageButton pageId={ page.id } />
<PageEditButton pageId={ page.id } />
{/* ↓ This is the only change in the PagesList component */}
<DeletePageButton pageId={ page.id }/>
{/* ↑ This is the only change in the PagesList component */}
</div>
</td>
</tr>
Expand Down Expand Up @@ -285,7 +284,7 @@ function DeletePageButton( { pageId } ) {
Great! `DeletePageButton` is now fully aware of errors. Let's see that error message in action. We'll trigger an invalid delete and let it fail. One way to do this is to multiply the `pageId` by a large number:
```js
export function DeletePageButton( { pageId, onCancel, onSaveFinished } ) {
function DeletePageButton( { pageId, onCancel, onSaveFinished } ) {
pageId = pageId * 1000;
// ...
}
Expand Down Expand Up @@ -447,4 +446,4 @@ function DeletePageButton( { pageId } ) {
## What's next?
* **Previous part:** [Building a *Create page form*](/docs/how-to-guides/data-basics/4-building-a-create-page-form.md)
* (optional) Review the [finished app](https://github.com/WordPress/gutenberg-examples/tree/trunk/09-code-data-basics-esnext) in the gutenberg-examples repository
* (optional) Review the [finished app](https://github.com/WordPress/gutenberg-examples/tree/trunk/non-block-examples/09-code-data-basics-esnext) in the gutenberg-examples repository

0 comments on commit ebf3791

Please sign in to comment.