-
Notifications
You must be signed in to change notification settings - Fork 4.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add status
entity and use it in "admin views" experiment
#55050
Conversation
Size Change: +55 B (0%) Total Size: 1.65 MB
ℹ️ View Unchanged
|
a6e9255
to
04a2e64
Compare
04a2e64
to
1347cec
Compare
The statuses endpoint returns a collection as object, being the keys the corresponding slug of the status. However, the entities expect to work with arrays. Providing the key to the entity config they'll be automatically adapted from the object collection to an array.
path: '/wp/v2/statuses', | ||
} ).then( setPostStatuses ); | ||
}, [] ); | ||
const { records: statuses } = useEntityRecords( 'root', 'status' ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wanted to optimize the status retrieval to:
- Only return
name
andslug
, not all schema fields. - Only return the statuses we actually expect to use (
publish
,draft
), as the page entity only requests pages with those statuses.
Though it doesn't seem the statuses endpoint supports that kind of filtering.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sometimes it's better to load the full object for performance, that way it's cached and shared with other usage of the same entity.
Not sure it applies here but yeah, this seems fine to me I think
@@ -136,7 +139,8 @@ export default function PagePages() { | |||
header: 'Status', | |||
id: 'status', | |||
cell: ( props ) => | |||
postStatuses[ props.row.original.status ]?.name, | |||
postStatuses[ props.row.original.status ] ?? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought I'd also update the fallback we show when the post statuses retrieval goes wrong (statuses are unavailable or just take a while to load): instead of leaving them blank, we can use the already available status
property of the page.
statuses
entitystatus
entity and use it in "admin views" experiment
@@ -136,7 +139,8 @@ export default function PagePages() { | |||
header: 'Status', | |||
id: 'status', | |||
cell: ( props ) => | |||
postStatuses[ props.row.original.status ]?.name, | |||
postStatuses[ props.row.original.status ] ?? | |||
props.row.original.status, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where does this come from?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh you show the key. Fine 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to show the key here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's an advantage to pre-filling the table with the slug status over starting with an empty string: it acts as a better placeholder for the content.
By using the slug as fallback, the table already has the size of the content (because the slug for Publish
is publish
and for Draft
is draft
, etc.). This avoids "visual resizes of the table" later to adapt the column to the new content. I've uploaded some before/after videos to the description. While it's not the same issue, it can serve as an example of the visual effect resizing has when the status starts as an empty string in trunk
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In trunk, if you navigate away and pack to the page, you'll notice that the status take some time to show up. This PR fixes that 👍
That said, the loading of this screen need special care. For instance right now, the pagination is visible while the table is still hidden and the total switches from 0 to the actual number while loading. I think we should have a dedicated todo list item about the loading stage on this page.
const postStatuses = | ||
statuses === null | ||
? EMPTY_OBJECT | ||
: statuses.reduce( ( acc, status ) => { | ||
acc[ status.slug ] = status.name; | ||
return acc; | ||
}, EMPTY_OBJECT ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@oandregal: This breaks a few things in the component — even though right now that's asymptomatic. :)
The cause is the use of EMPTY_OBJECT
inside the Array#reduce
call, because the reducer will mutate that initial value. This means that:
postStatuses
will always have the exact same object reference, regardless of whetherstatuses === null
, and thus any subsequent logic in the component that relies on reference changes (e.g.useMemo( ..., [ postStatuses ] )
will not fire correctly.EMPTY_OBJECT
will become non-empty as soon asstatuses
resolves.
Proof of bug:
const EMPTY_OBJECT = {};
function getPostStatuses( rawStatuses ) {
return rawStatuses === null
? EMPTY_OBJECT
: rawStatuses.reduce( ( acc, status ) => {
acc[ status.slug ] = status.name;
return acc;
}, EMPTY_OBJECT );
}
const before = getPostStatuses( null );
const after = getPostStatuses( [ { slug: 'draft', name: 'Draft' } ] );
console.log( 'Has the reference changed?', before !== after );
console.log(
'Is EMPTY_OBJECT empty?',
Object.keys( EMPTY_OBJECT ).length === 0
);
The immediate fix is to not use EMPTY_OBJECT
in the mutating context:
const postStatuses =
statuses === null
? EMPTY_OBJECT
: statuses.reduce( ( acc, status ) => {
acc[ status.slug ] = status.name;
return acc;
- }, EMPTY_OBJECT );
+ }, {} );
But of course this means that postStatuses
will always have a new reference even when nothing has changed, so the solution is to use useMemo
. But, while we are here, I recommend against using Array#reduce
in favour of clearer higher-level constructs:
const postStatuses = useMemo(
() =>
statuses === null
? EMPTY_OBJECT
: Object.fromEntries(
statuses.map( ( { slug, name } ) => [ slug, name ] )
),
[ statuses ]
);
I'll turn this into a PR, but I wanted to give you my full explanation first!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-> #55314
Part of #55083
Follow-up to #54966
What?
Creates a new entity for the
statuses
endpoint and updates the pages list to using it.Why?
https://github.com/WordPress/gutenberg/pull/54966/files#r1342580462 By using the core-data package, we leverage some basic behaviors such as caching. Note the before/after for the page status when navigating away and back to the page:
Before (trunk). The status is blank until the fetching is resolved:
Gravacao.do.ecra.2023-10-05.as.14.30.36.mov
After (this PR). The status is immediately shown, as a result of being available in the core store:
Gravacao.do.ecra.2023-10-05.as.14.32.37.mov
How?
status
entity.Testing Instructions