Skip to content

DataViews: lazy load elements data#71930

Closed
dinhtungdu wants to merge 15 commits intoWordPress:trunkfrom
dinhtungdu:feat/lazy-load-elements
Closed

DataViews: lazy load elements data#71930
dinhtungdu wants to merge 15 commits intoWordPress:trunkfrom
dinhtungdu:feat/lazy-load-elements

Conversation

@dinhtungdu
Copy link
Copy Markdown
Member

@dinhtungdu dinhtungdu commented Sep 26, 2025

What?

Part of #70834

This PR updates the elements prop to accept a static array or Promise factory, allowing the fetching of the elements data on demand.

Why?

See #70834

How?

  • Update the elements prop to accept Promise or factory, so the prop now accepts three types of data:
  // Static list
  elements: [
        { value: 1, label: 'News' },
        { value: 2, label: 'Tutorials' },
  ],

  // Factory
  elements: async () => { 
        const postType = getSelectedPostType();
        const posts =
                ( await resolveSelect( wp.coreData.store )
                        .getEntityRecords( 'postType', postType, {
                                status: 'publish',
                        } ) ) ?? [];
        return posts.map( ( { id, title } ) => ( {
                value: id,
                label: title?.rendered || `Post #${ id }`,
        } ) );
  },
  • Add a new hook useFieldElements which returns:
    • elements
    • isResolving
    • error

Notes:

  • I intentionally skipped search and pagination to keep this PR simple for review.

Testing Instructions

I've included the new story for lazy-loaded elements and unit tests to verify the new behavior.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Sep 26, 2025

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: dinhtungdu <dinhtungdu@git.wordpress.org>
Co-authored-by: oandregal <oandregal@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@oandregal oandregal added [Type] Enhancement A suggestion for improvement. [Feature] DataViews Work surrounding upgrading and evolving views in the site editor and beyond labels Sep 29, 2025
Comment on lines +40 to +44
export type FieldElementsSource< Value extends any = any > =
| Option< Value >[]
| Promise< Option< Value >[] >
| ( () => Option< Value >[] | Promise< Option< Value >[] > );

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be simplified to:

export type FieldElementsSource =
	| Option[]
	| Promise< Option[] >
	| ( () => Promise< Option[] > );

I was thinking that elements could be either Option[] or an async function, as the async function enables the framework to trigger retrieval when necessary. However, having the Promise<Option[]> as well enables consumers to trigger retrieval instead. Nothing against it, I am just curious if you have any more thoughts about this.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Promise<Option[]> gives consumers an easy path to get started; it makes the code less verbose than an async function. That's the only benefit of accepting a Promise. In my opinion, it doesn't pollute the type, so I think we should accept that.

Regarding the async function, I think we should still support returning a synchronous array. By doing so, we can better accommodate use cases when the data is already available or computed synchronously, for example, derived lists built from other props or localStorage (not a fan, though :D). We're giving consumers flexibility without trading much complexity, so I think we should do it.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After consideration, I decided to support only array or async, as you suggested. We should start by focusing on what matters most.

);

const fieldElements = field?.elements ?? [];
const { elements: fieldElements, isResolving } = useFieldElements(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This approach triggers retrieval at the leaf: elements are fetched when the components that need them are mounted (dataform controls, filters). This is good for minimizing memory/computing. However, I am concerned about how this impacts user perceived performance. There are some other places where we should also trigger "leaf" retrieval: the render methods of the field types.

What would you think of triggering the retrieval (call useFieldElements) in normalizeFields instead? That way, the elements may already be loaded when the user opens a filter, and we centralize this process for all components that work with elements (render, filters, etc.).

Copy link
Copy Markdown
Member Author

@dinhtungdu dinhtungdu Oct 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would you think of triggering the retrieval (call useFieldElements) in normalizeFields instead? That way, the elements may already be loaded when the user opens a filter, and we centralize this process for all components that work with elements (render, filters, etc.).

We should consider the trade-offs involved. While this can make the UI feel snappier and faster, I have some concerns with this approach.

  • Firstly, scalability is an issue. A list can have many filters, and fetching all data at once can lead to performance issues.
  • Secondly, we know that we need search and pagination. So we still need to retrieve the data at the leaf. We are creating two mechanisms for fetching the data, and I'm afraid it makes our codebase more complex.
  • Lastly, if we want to retrieve everything in a normalized format, we may need to introduce another cache layer to store data. We have one for entities already. I don't think we need another one.

Given these pros and cons, I believe it's best to keep retrieval at the leaf for now. wdyt?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's try this way and re-evaluate when we have everything working (searching, etc.). This means we need fetching them every time they are potentially used, because it may be the first time. For example, we need to fetch them in render as well.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I corrected some words in my comment. I used dictation but missed fixing them.

Let's try this way and re-evaluate when we have everything working (searching, etc.).

@oandregal So you mean moving the fetch to normalizeFields, right?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, leave it as you proposed in this PR (fetching at the leaves). But we need to cover all use cases (every place that uses elements, including render).

Comment on lines +97 to +98
error,
reloadElements,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need these? I don't think we should retrigger element retrieval. Though perhaps error could be displayed in controls and render as in Elements could not be loaded.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need these?

We don't have it now. But I believe we need retriggering for search and pagination. I can remove it for now and reconsider the API when adding search and pagination support.

@oandregal
Copy link
Copy Markdown
Member

I wanted to share this use case that may be worth considering as well for this PR or for a follow-up: #70834 (comment)

@dinhtungdu dinhtungdu force-pushed the feat/lazy-load-elements branch from 85ae0c0 to 3fd04c2 Compare September 30, 2025 16:39
@dinhtungdu
Copy link
Copy Markdown
Member Author

I wanted to share this use case that may be worth considering as well for this PR or for a follow-up: #70834 (comment)

Yes, I agree we will need it. In the PR description, I also mentioned search and pagination. I want to address that in a dedicated PR to keep the size reasonable.

My rough idea is that we can pass the search keyword and/or current to the function for consumers to use like:

  const { elements, isResolving } = useFieldElements(
        field.elements,
        {
                search,
                page
        }
  );

Then pass that data inside the hooks to the elements callback.

@oandregal
Copy link
Copy Markdown
Member

I've spent some time today exploring this problem and prepared #72254

Working:

  • render
  • filters
  • dataform components
  • Site Editor > Pages > Author field
  • Storybook > DataViews > Field Types

Not working:

  • validation

I need to look at this a bit more, but I'm not convinced we can solve this problem without making validation reactive first.

@dinhtungdu dinhtungdu force-pushed the feat/lazy-load-elements branch from d03e582 to 3914e19 Compare October 13, 2025 03:45
@dinhtungdu dinhtungdu force-pushed the feat/lazy-load-elements branch from 3914e19 to c9b1642 Compare October 13, 2025 03:56
@dinhtungdu
Copy link
Copy Markdown
Member Author

@oandregal pushed some updates to this PR:

  • I simplified the type as you suggested, now elements only accepts a static array or an async function returning a Promise.
  • I updated the author field to use the async elements. Mostly like your implemention in Field API: support async loading elements #72254.
  • I added support to render fields using async elements.

About the validation, I agree, we need to make the validation work with async first. We can solve it using a custom hook, like dinhtungdu#68. It works, and the implementation is simple as we're now loading everything. But it will get more complex when we support pagination.

Screen.Recording.2025-10-13.at.13.16.34.mov

@oandregal
Copy link
Copy Markdown
Member

Thanks for adding support for authors. All DataForm controls need to communicate loading status, so users know what's going on. This is the current experience:

Screen.Recording.2025-10-13.at.11.58.40.mov

Over at #71412 I explored implementing async elements as a separate function getElements. By doing so, elements can act as a fallback in case the promise triggered some error, or even as an initial value while the updated ones aren't loaded.

Though, for this to land, we need to solve validation first. I also explored async validation a few weeks ago at #71412 but ran into some issues due to component validation works. I'll investigate what we can do here and will report back.

@oandregal
Copy link
Copy Markdown
Member

@dinhtungdu one thing we can do is removing the elements checks from custom #72325 We still want to make custom async (see rationale) in case someone wants to access elements there. But it's good to disconnect concerns anyway.

@oandregal
Copy link
Copy Markdown
Member

@dinhtungdu I've updated #71412 (comment) (that enables async validation) and engaged folks to review.

@dinhtungdu
Copy link
Copy Markdown
Member Author

Closing in favor of #72254

@dinhtungdu dinhtungdu closed this Oct 17, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Feature] DataViews Work surrounding upgrading and evolving views in the site editor and beyond [Type] Enhancement A suggestion for improvement.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants