Skip to content

Conversation

@s3rgiosan
Copy link
Member

@s3rgiosan s3rgiosan commented May 26, 2025

Description of the Change

This PR introduces optional callback props in the ContentPicker and ContentSearch components, enabling developers to extend queried fields and customize UI display while maintaining explicit, context-aware component customization.

This approach:

  • Makes component customization explicit and context-aware (each instance can have different behavior)
  • Improves content editor experience with relevant contextual information
  • Maintains backward compatibility (all new props are optional)
  • Provides type-safe customization with TypeScript support

Problem

Client projects frequently require additional information beyond the default fields (title, URL) when picking or searching content. Common requests include displaying post IDs, parent page titles, or custom metadata to help content editors make informed selections.

Solution

Added callback props that:

  1. Allow developers to customize fields queried from WordPress REST API endpoints
  2. Enable customization of picked items and search results display
  3. Provide explicit, per-instance control over component behavior
  4. Support TypeScript type checking for better developer experience

New Props

ContentPicker

  • queryFieldsFilter - Customizes which fields are fetched from the API for both search and picked items
import { ContentPicker } from '@10up/block-components';
import { useCallback } from '@wordpress/element';

const queryFieldsFilter = useCallback((fields, mode) => {
    if (mode === 'post') {
        fields.push('excerpt');
    }
    return fields;
}, []);

<ContentPicker
    queryFieldsFilter={queryFieldsFilter}
    // ... other props
/>
  • searchResultFilter - Customizes the normalized search result item
const searchResultFilter = useCallback((item, result) => {
    item.url = '';
    item.info = `<strong>ID:</strong> ${result.id}<br>${result.excerpt || ''}`;
    return item;
}, []);

<ContentPicker
    searchResultFilter={searchResultFilter}
    // ... other props
/>
  • pickedItemFilter - Customizes the picked item before it's displayed in the list
const pickedItemFilter = useCallback((item, result) => {
    const info = `<strong>ID:</strong> ${result.id}<br>${result.excerpt?.rendered || ''}`;
    return {...item, url: '', info };
}, []);

<ContentPicker
    pickedItemFilter={pickedItemFilter}
    // ... other props
/>

ContentSearch

  • queryFieldsFilter - Customizes which fields are fetched from the API
import { ContentSearch } from '@10up/block-components';
import { useCallback } from '@wordpress/element';

const queryFieldsFilter = useCallback((fields, mode) => {
    if (mode === 'post') {
        fields.push('excerpt');
    }
    return fields;
}, []);

<ContentSearch
    queryFieldsFilter={queryFieldsFilter}
    // ... other props
/>
  • searchResultFilter - Customizes the normalized search result item
const searchResultFilter = useCallback((item, result) => {
    item.url = '';
    item.info = `<strong>ID:</strong> ${result.id}<br>${result.excerpt || ''}`;
    return item;
}, []);

<ContentSearch
    searchResultFilter={searchResultFilter}
    // ... other props
/>

Note: For the WordPress REST API search endpoint to return additional fields like excerpt, you may need to register them via PHP:

function add_search_result_field() {
    register_rest_field(
        'search-result',
        'excerpt',
        array(
            'get_callback'    => function ( $post ) {
                return get_the_excerpt( $post['id'] );
            },
            'update_callback' => null,
            'schema'          => null,
        )
    );
}
add_action( 'rest_api_init', __NAMESPACE__ . '\add_search_result_field' );

Technical Changes

  • Added optional callback props with proper TypeScript types
  • Updated documentation with usage examples
  • Updated example blocks to demonstrate new props
  • Updated CONTRIBUTING.md to clarify workspace setup

How to test the Change

  1. Build the package: npm run build (from root directory)
  2. Build the example workspace: npm run build -w example
  3. Start the test environment: npm run start-test-env
  4. Test ContentPicker component:
    • Verify additional fields are queried from REST API (check network tab)
    • Confirm picked items display custom information via pickedItemFilter
    • Verify search results show custom formatting via searchResultFilter
    • Check that URL field can be hidden when set to empty string
  5. Test ContentSearch component:
    • Verify search results include additional queried fields
    • Confirm search result display matches custom formatting via searchResultFilter
  6. Verify backward compatibility: components work without filter props (default behavior)
  7. Test multiple component instances with different filter configurations

Changelog Entry

Added - Optional callback props in ContentPicker and ContentSearch components for explicit, context-aware customization

Credits

Props @s3rgiosan

Checklist:

@s3rgiosan s3rgiosan requested a review from fabiankaegy May 26, 2025 23:21
@s3rgiosan
Copy link
Member Author

… and to filter the search result before it is returned
@s3rgiosan s3rgiosan requested a review from rickalee May 26, 2025 23:26
# Conflicts:
#	components/content-picker/PickedItem.tsx
#	components/content-picker/SortableList.tsx
#	package-lock.json
#	package.json
@s3rgiosan
Copy link
Member Author

Merge conflicts were addressed. This is ready for review.

* @param {ContentSearchMode} mode - The mode of the content search.
* @returns {string[]} - The filtered fields.
*/
fields = applyFilters('tenup.contentSearch.queryFields', fields, mode) as string[];
Copy link
Member

Choose a reason for hiding this comment

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

I'm a little worried about the introduction of all these filters... Unlike in PHP here in JS they just behave oddly and are no longer really recommended by the core team...

Also I am a bit worried that there is no way to figure out the context of which component we are talking about. It just applies to all ContentSearch components on the site.

I would rather see an implementation that has the filters needed within the content connect plugin and then passes its callback function into the component as an optional prop. That feels more "safe" to me here.

Copy link
Member Author

Choose a reason for hiding this comment

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

Hi @fabiankaegy this is ready for another round of review

  • Removed all applyFilters calls from ContentSearch and ContentPicker components
  • Added optional callback props with proper TypeScript types

@s3rgiosan s3rgiosan requested a review from fabiankaegy January 8, 2026 15:20
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds extensibility to the ContentPicker and ContentSearch components through optional filter callback props, enabling developers to customize API queries and UI display on a per-instance basis while maintaining backward compatibility.

Key Changes:

  • Added three filter callbacks to ContentPicker: queryFieldsFilter, searchResultFilter, and pickedItemFilter
  • Added two filter callbacks to ContentSearch: queryFieldsFilter and searchResultFilter
  • Refactored code to support conditional field querying and result normalization
  • Updated documentation and examples to demonstrate the new functionality
  • Added PHP example for registering custom REST API fields

Reviewed changes

Copilot reviewed 14 out of 16 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
package.json Removed self-referencing workspace entry and added npm scripts for environment management
example/plugin.php Fixed PHP coding standards (removed unnecessary semicolons) and added REST API field registration example
example/package.json Minor formatting fix (added trailing newline)
example/src/blocks/content-search-example/edit.tsx Demonstrates usage of new filter props with ContentSearch component
example/src/blocks/content-picker-example/edit.tsx Demonstrates usage of new filter props with ContentPicker component
components/content-search/utils.ts Added support for field filtering and result customization in search utilities
components/content-search/types.ts Added TypeScript types for new filter callbacks
components/content-search/index.tsx Integrated filter props into ContentSearch component
components/content-search/SearchItem.tsx Updated UI to conditionally display URL and custom info fields with HTML sanitization
components/content-search/readme.md Added documentation for new filter props with usage examples
components/content-picker/index.tsx Added filter props to ContentPicker interface and passed to child components
components/content-picker/SortableList.tsx Integrated filters into picked item data fetching and normalization
components/content-picker/PickedItem.tsx Updated UI to support optional URL and custom info fields
components/content-picker/readme.md Added documentation for new filter props with usage examples
CONTRIBUTING.md Updated local environment setup instructions to clarify workspace usage
Comments suppressed due to low confidence (1)

components/content-search/readme.md:58

  • The documentation should include a note that when using queryFieldsFilter to fetch additional fields from the WordPress REST API search endpoint, developers may need to register those fields via PHP using register_rest_field. Without this note, developers might be confused when additional fields don't appear in search results. Consider adding a brief note with a link to WordPress documentation or a code example.
### Customizing Fields and Results

You can customize which fields are fetched from the API and how search results are normalized using the `queryFieldsFilter` and `searchResultFilter` props:

```js
import { ContentSearch } from '@10up/block-components';
import { useCallback } from '@wordpress/element';

function MyComponent( props ) {
    const queryFieldsFilter = useCallback( (fields, mode) => {
        if ( mode === 'post' ) {
            fields.push('excerpt');
        }
        return fields;
    }, [] );

    const searchResultFilter = useCallback( (item, result) => {
        item.url = '';
        item.info = `<strong>ID:</strong> ${result.id}<br>${result.excerpt}`;
        return item;
    }, [] );

    return (
        <ContentSearch
            onSelectItem={ (item) => { console.log(item) } }
            mode="post"
            label={ "Please select a Post or Page:" }
            contentTypes={ [ 'post', 'page' ] }
            queryFieldsFilter={queryFieldsFilter}
            searchResultFilter={searchResultFilter}
        />
    )
}
</details>



---

💡 <a href="/10up/block-components/new/develop/.github/instructions?filename=*.instructions.md" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">Add Copilot custom instructions</a> for smarter, more guided reviews. <a href="https://docs.github.com/en/copilot/customizing-copilot/adding-repository-custom-instructions-for-github-copilot" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">Learn how to get started</a>.

Copy link
Member

@fabiankaegy fabiankaegy left a comment

Choose a reason for hiding this comment

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

This is great! :) Thank you for making the changes :)

@fabiankaegy fabiankaegy merged commit 85ae130 into 10up:develop Jan 9, 2026
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants