Commit 8b1bde085be for woocommerce

commit 8b1bde085be3d9156449c38b4e0c2eaf96369fee
Author: Luigi Teschio <gigitux@gmail.com>
Date:   Tue Jun 2 10:05:28 2026 +0200

    Remove async product editor category field (#65395)

    * Remove async product editor category field

    * Add changelog entries for async category field removal

    * Deprecate async category tree AJAX callback

diff --git a/plugins/woocommerce-beta-tester/changelog/49120-remove-async-product-editor-category-field b/plugins/woocommerce-beta-tester/changelog/49120-remove-async-product-editor-category-field
new file mode 100644
index 00000000000..b2078449090
--- /dev/null
+++ b/plugins/woocommerce-beta-tester/changelog/49120-remove-async-product-editor-category-field
@@ -0,0 +1,4 @@
+Significance: patch
+Type: update
+
+Remove async product editor category field from live branch feature flags.
diff --git a/plugins/woocommerce-beta-tester/userscripts/wc-live-branches.user.js b/plugins/woocommerce-beta-tester/userscripts/wc-live-branches.user.js
index 6dbf992fc85..57a53a5824d 100644
--- a/plugins/woocommerce-beta-tester/userscripts/wc-live-branches.user.js
+++ b/plugins/woocommerce-beta-tester/userscripts/wc-live-branches.user.js
@@ -170,7 +170,6 @@
 		} else {
 			// TODO: Fetch the list of feature flags dynamically from the API or something.
 			const featureFlags = [
-				'async-product-editor-category-field',
 				'coming-soon-newsletter-template',
 				'launch-your-store',
 				'minified-js',
diff --git a/plugins/woocommerce/changelog/49120-remove-async-product-editor-category-field b/plugins/woocommerce/changelog/49120-remove-async-product-editor-category-field
new file mode 100644
index 00000000000..cb980ed6661
--- /dev/null
+++ b/plugins/woocommerce/changelog/49120-remove-async-product-editor-category-field
@@ -0,0 +1,3 @@
+Significance: patch
+Type: dev
+Comment: Remove unused async product editor category field feature flag and deprecate its AJAX callback.
diff --git a/plugins/woocommerce/client/admin/client/wp-admin-scripts/product-category-metabox/all-category-list.tsx b/plugins/woocommerce/client/admin/client/wp-admin-scripts/product-category-metabox/all-category-list.tsx
deleted file mode 100644
index d1a330b63b2..00000000000
--- a/plugins/woocommerce/client/admin/client/wp-admin-scripts/product-category-metabox/all-category-list.tsx
+++ /dev/null
@@ -1,212 +0,0 @@
-/**
- * External dependencies
- */
-import { __, sprintf } from '@wordpress/i18n';
-import {
-	forwardRef,
-	useCallback,
-	useEffect,
-	useImperativeHandle,
-	useState,
-} from '@wordpress/element';
-import { addQueryArgs } from '@wordpress/url';
-import { useDebounce } from '@wordpress/compose';
-import { TreeSelectControl } from '@woocommerce/components';
-import { getSetting } from '@woocommerce/settings';
-import { recordEvent } from '@woocommerce/tracks';
-import apiFetch from '@wordpress/api-fetch';
-
-/**
- * Internal dependencies
- */
-import { CATEGORY_TERM_NAME } from './category-handlers';
-import { CategoryTerm } from './popular-category-list';
-
-declare const wc_product_category_metabox_params: {
-	search_categories_nonce: string;
-};
-
-type CategoryTreeItem = CategoryTerm & {
-	children?: CategoryTreeItem[];
-};
-
-type CategoryTreeItemLabelValue = {
-	children: CategoryTreeItemLabelValue[];
-	label: string;
-	value: string;
-};
-
-export const DEFAULT_DEBOUNCE_TIME = 250;
-
-const categoryLibrary: Record< number, CategoryTreeItem > = {};
-function convertTreeToLabelValue(
-	tree: CategoryTreeItem[],
-	newTree: CategoryTreeItemLabelValue[] = []
-) {
-	for ( const child of tree ) {
-		const newItem = {
-			label: child.name,
-			value: child.term_id.toString(),
-			children: [],
-		};
-		categoryLibrary[ child.term_id ] = child;
-		newTree.push( newItem );
-		if ( child.children?.length ) {
-			convertTreeToLabelValue( child.children, newItem.children );
-		}
-	}
-	newTree.sort(
-		( a: CategoryTreeItemLabelValue, b: CategoryTreeItemLabelValue ) => {
-			const nameA = a.label.toUpperCase();
-			const nameB = b.label.toUpperCase();
-			if ( nameA < nameB ) {
-				return -1;
-			}
-			if ( nameA > nameB ) {
-				return 1;
-			}
-			return 0;
-		}
-	);
-	return newTree;
-}
-
-async function getTreeItems( filter: string ) {
-	const resp = await apiFetch< CategoryTreeItem[] >( {
-		url: addQueryArgs(
-			new URL( 'admin-ajax.php', getSetting( 'adminUrl' ) ).toString(),
-			{
-				term: filter,
-				action: 'woocommerce_json_search_categories_tree',
-				// eslint-disable-next-line no-undef, camelcase
-				security:
-					wc_product_category_metabox_params.search_categories_nonce,
-			}
-		),
-		method: 'GET',
-	} );
-	if ( resp ) {
-		return convertTreeToLabelValue( Object.values( resp ) );
-	}
-	return [];
-}
-
-export const AllCategoryList = forwardRef<
-	{ resetInitialValues: () => void },
-	{
-		selectedCategoryTerms: CategoryTerm[];
-		onChange: ( selected: CategoryTerm[] ) => void;
-	}
->( ( { selectedCategoryTerms, onChange }, ref ) => {
-	const [ filter, setFilter ] = useState( '' );
-	const [ treeItems, setTreeItems ] = useState<
-		CategoryTreeItemLabelValue[]
-	>( [] );
-
-	const searchCategories = useCallback(
-		( value: string ) => {
-			if ( value && value.length > 0 ) {
-				recordEvent( 'product_category_search', {
-					page: 'product',
-					async: true,
-					search_string_length: value.length,
-				} );
-			}
-			getTreeItems( value ).then( ( res ) => {
-				setTreeItems( Object.values( res ) );
-			} );
-		},
-		[ setTreeItems ]
-	);
-	const searchCategoriesDebounced = useDebounce(
-		searchCategories,
-		DEFAULT_DEBOUNCE_TIME
-	);
-
-	useEffect( () => {
-		searchCategoriesDebounced( filter );
-	}, [ filter ] );
-
-	useImperativeHandle(
-		ref,
-		() => {
-			return {
-				resetInitialValues() {
-					getTreeItems( '' ).then( ( res ) => {
-						setTreeItems( Object.values( res ) );
-					} );
-				},
-			};
-		},
-		[]
-	);
-
-	return (
-		<>
-			<div className="product-add-category__tree-control">
-				<TreeSelectControl
-					alwaysShowPlaceholder={ true }
-					options={ treeItems }
-					value={ selectedCategoryTerms.map( ( category ) =>
-						category.term_id.toString()
-					) }
-					onChange={ ( selectedCategoryIds: number[] ) => {
-						onChange(
-							selectedCategoryIds.map(
-								( id ) => categoryLibrary[ id ]
-							)
-						);
-						recordEvent( 'product_category_update', {
-							page: 'product',
-							async: true,
-							selected: selectedCategoryIds.length,
-						} );
-					} }
-					selectAllLabel={ false }
-					onInputChange={ setFilter }
-					placeholder={ __( 'Add category', 'woocommerce' ) }
-					includeParent={ true }
-					minFilterQueryLength={ 2 }
-					clearOnSelect={ false }
-					individuallySelectParent={ true }
-				/>
-			</div>
-			<ul
-				// Adding tagchecklist class to make use of already existing styling for the selected categories.
-				className="categorychecklist form-no-clear tagchecklist"
-				id={ CATEGORY_TERM_NAME + 'checklist' }
-			>
-				{ selectedCategoryTerms.map( ( selectedCategory ) => (
-					<li key={ selectedCategory.term_id }>
-						<button
-							type="button"
-							className="ntdelbutton"
-							onClick={ () => {
-								const newSelectedItems =
-									selectedCategoryTerms.filter(
-										( category ) =>
-											category.term_id !==
-											selectedCategory.term_id
-									);
-								onChange( newSelectedItems );
-							} }
-						>
-							<span
-								className="remove-tag-icon"
-								aria-hidden="true"
-							></span>
-							<span className="screen-reader-text">
-								{ sprintf(
-									/* translators: %s: category name */
-									__( 'Remove term: %s', 'woocommerce' ),
-									selectedCategory.name
-								) }
-							</span>
-						</button>
-						{ selectedCategory.name }
-					</li>
-				) ) }
-			</ul>
-		</>
-	);
-} );
diff --git a/plugins/woocommerce/client/admin/client/wp-admin-scripts/product-category-metabox/category-add-new.tsx b/plugins/woocommerce/client/admin/client/wp-admin-scripts/product-category-metabox/category-add-new.tsx
deleted file mode 100644
index 37bce4870fb..00000000000
--- a/plugins/woocommerce/client/admin/client/wp-admin-scripts/product-category-metabox/category-add-new.tsx
+++ /dev/null
@@ -1,197 +0,0 @@
-/**
- * External dependencies
- */
-import { __ } from '@wordpress/i18n';
-import { useCallback, useState } from '@wordpress/element';
-import { addQueryArgs } from '@wordpress/url';
-import { getSetting } from '@woocommerce/settings';
-import {
-	useAsyncFilter,
-	__experimentalSelectControl as SelectControl,
-} from '@woocommerce/components';
-import { useUser } from '@woocommerce/data';
-import { recordEvent } from '@woocommerce/tracks';
-import apiFetch from '@wordpress/api-fetch';
-
-/**
- * Internal dependencies
- */
-import { CATEGORY_TERM_NAME } from './category-handlers';
-import { CategoryTerm } from './popular-category-list';
-
-declare const wc_product_category_metabox_params: {
-	search_categories_nonce: string;
-};
-
-function getCategoryTermLabel( item: CategoryTerm | null ): string {
-	return item?.name || '';
-}
-
-function getCategoryTermKey( item: CategoryTerm | null ): string {
-	return String( item?.term_id );
-}
-
-export const CategoryAddNew = ( {
-	selectedCategoryTerms,
-	onChange,
-}: {
-	selectedCategoryTerms: CategoryTerm[];
-	onChange: ( selected: CategoryTerm[] ) => void;
-} ) => {
-	const [ showAddNew, setShowAddNew ] = useState( false );
-	const [ newCategoryName, setNewCategoryName ] = useState( '' );
-	const [ categoryCreateError, setCategoryCreateError ] = useState( '' );
-	const [ categoryParent, setCategoryParent ] = useState< CategoryTerm >();
-	const [ fetchedItems, setFetchedItems ] = useState< CategoryTerm[] >( [] );
-	const { currentUserCan } = useUser();
-
-	const canEditTerms = currentUserCan( 'edit_product_terms' );
-
-	const onCreate = ( event: React.MouseEvent< HTMLInputElement > ) => {
-		event.preventDefault();
-		if ( ! newCategoryName ) {
-			return;
-		}
-
-		const data = {
-			name: newCategoryName,
-			parent: categoryParent?.term_id ?? -1,
-		};
-		setCategoryCreateError( '' );
-		apiFetch< {
-			id: number;
-			name: string;
-			count: number;
-			parent: number;
-		} >( {
-			path: '/wc/v3/products/categories',
-			data,
-			method: 'POST',
-		} )
-			.then( ( res ) => {
-				if ( res ) {
-					recordEvent( 'product_category_add', {
-						category_id: res.id,
-						parent_id: res.parent,
-						parent_category: res.parent > 0 ? 'Other' : 'None',
-						page: 'product',
-						async: true,
-					} );
-					onChange( [
-						...selectedCategoryTerms,
-						{ term_id: res.id, name: res.name, count: res.count },
-					] );
-					setNewCategoryName( '' );
-					setCategoryParent( undefined );
-					setShowAddNew( false );
-				}
-			} )
-			.catch( ( error ) => {
-				if ( error && error.message ) {
-					setCategoryCreateError( error.message );
-				}
-			} );
-	};
-
-	const filter: ( value: string ) => Promise< CategoryTerm[] > = useCallback(
-		async ( value = '' ) => {
-			setFetchedItems( [] );
-			return apiFetch< CategoryTerm[] >( {
-				url: addQueryArgs(
-					new URL(
-						'admin-ajax.php',
-						getSetting( 'adminUrl' )
-					).toString(),
-					{
-						term: value,
-						action: 'woocommerce_json_search_categories',
-						// eslint-disable-next-line no-undef, camelcase
-						security:
-							wc_product_category_metabox_params.search_categories_nonce,
-					}
-				),
-				method: 'GET',
-			} ).then( ( response ) => {
-				if ( response ) {
-					setFetchedItems( Object.values( response ) );
-				}
-				return [];
-			} );
-		},
-		[]
-	);
-
-	const { isFetching, ...selectProps } = useAsyncFilter< CategoryTerm >( {
-		filter,
-	} );
-
-	if ( ! canEditTerms ) {
-		return null;
-	}
-
-	return (
-		<div id={ CATEGORY_TERM_NAME + '-adder' }>
-			<a
-				id="product_cat-add-toggle"
-				href={ '#taxonomy-' + CATEGORY_TERM_NAME }
-				className="taxonomy-add-new"
-				onClick={ () => setShowAddNew( ! showAddNew ) }
-				aria-label={ __( 'Add new category', 'woocommerce' ) }
-			>
-				{ __( '+ Add new category', 'woocommerce' ) }
-			</a>
-			{ showAddNew && (
-				<div id="product_cat-add" className="category-add">
-					<label
-						className="screen-reader-text"
-						htmlFor="newproduct_cat"
-					>
-						{ __( 'Add new category', 'woocommerce' ) }
-					</label>
-					<input
-						type="text"
-						name="newproduct_cat"
-						id="newproduct_cat"
-						className="form-required"
-						placeholder={ __( 'New category name', 'woocommerce' ) }
-						value={ newCategoryName }
-						onChange={ ( event ) =>
-							setNewCategoryName( event.target.value )
-						}
-						aria-required="true"
-					/>
-					<label
-						className="screen-reader-text"
-						htmlFor="newproduct_cat_parent"
-					>
-						{ __( 'Parent category:', 'woocommerce' ) }
-					</label>
-					<SelectControl< CategoryTerm >
-						{ ...selectProps }
-						label={ __( 'Parent category:', 'woocommerce' ) }
-						items={ fetchedItems }
-						selected={ categoryParent || null }
-						placeholder={ __( 'Find category', 'woocommerce' ) }
-						onSelect={ setCategoryParent }
-						getItemLabel={ getCategoryTermLabel }
-						getItemValue={ getCategoryTermKey }
-						onRemove={ () => setCategoryParent( undefined ) }
-					/>
-					{ categoryCreateError && (
-						<p className="category-add__error">
-							{ categoryCreateError }
-						</p>
-					) }
-					<input
-						type="button"
-						id="product_cat-add-submit"
-						className="button category-add-submit"
-						value={ __( 'Add new category', 'woocommerce' ) }
-						disabled={ ! newCategoryName.length }
-						onClick={ onCreate }
-					/>
-				</div>
-			) }
-		</div>
-	);
-};
diff --git a/plugins/woocommerce/client/admin/client/wp-admin-scripts/product-category-metabox/category-handlers.js b/plugins/woocommerce/client/admin/client/wp-admin-scripts/product-category-metabox/category-handlers.js
deleted file mode 100644
index 831b455e64e..00000000000
--- a/plugins/woocommerce/client/admin/client/wp-admin-scripts/product-category-metabox/category-handlers.js
+++ /dev/null
@@ -1,25 +0,0 @@
-export const CATEGORY_TERM_NAME = 'product_cat';
-
-export function getCategoryDataFromElement( element ) {
-	if ( element && element.dataset && element.dataset.name ) {
-		return {
-			term_id: parseInt( element.value, 10 ),
-			name: element.dataset.name,
-		};
-	}
-	return null;
-}
-
-export function getSelectedCategoryData( container ) {
-	if ( container ) {
-		const selectedCategories = Array.from(
-			container.querySelectorAll( ':scope > input[type=hidden]' )
-		).map( ( categoryElement ) => {
-			const id = getCategoryDataFromElement( categoryElement );
-			categoryElement.remove();
-			return id;
-		} );
-		return selectedCategories;
-	}
-	return [];
-}
diff --git a/plugins/woocommerce/client/admin/client/wp-admin-scripts/product-category-metabox/category-metabox.tsx b/plugins/woocommerce/client/admin/client/wp-admin-scripts/product-category-metabox/category-metabox.tsx
deleted file mode 100644
index 4e863bdac8b..00000000000
--- a/plugins/woocommerce/client/admin/client/wp-admin-scripts/product-category-metabox/category-metabox.tsx
+++ /dev/null
@@ -1,156 +0,0 @@
-/**
- * External dependencies
- */
-import { __ } from '@wordpress/i18n';
-import { useRef, useState } from '@wordpress/element';
-
-/**
- * Internal dependencies
- */
-import { CATEGORY_TERM_NAME } from './category-handlers';
-import { AllCategoryList } from './all-category-list';
-import { CategoryTerm, PopularCategoryList } from './popular-category-list';
-import { CategoryAddNew } from './category-add-new';
-
-let initialTab = '';
-if ( window.getUserSetting ) {
-	initialTab = window.getUserSetting( CATEGORY_TERM_NAME + '_tab' ) || '';
-}
-
-const CATEGORY_POPULAR_TAB_ID = 'pop';
-const CATEGORY_ALL_TAB_ID = 'all';
-
-const CategoryMetabox = ( {
-	initialSelected,
-}: {
-	initialSelected: CategoryTerm[];
-} ) => {
-	const [ selected, setSelected ] = useState( initialSelected );
-	const allCategoryListRef = useRef< { resetInitialValues: () => void } >(
-		null
-	);
-	const [ activeTab, setActiveTab ] = useState(
-		initialTab === CATEGORY_POPULAR_TAB_ID
-			? initialTab
-			: CATEGORY_ALL_TAB_ID
-	);
-	return (
-		<div
-			id={ 'taxonomy-' + CATEGORY_TERM_NAME }
-			className="categorydiv category-async-metabox"
-		>
-			<ul className="category-tabs">
-				<li
-					className={
-						activeTab === CATEGORY_ALL_TAB_ID ? 'tabs' : ''
-					}
-				>
-					<a
-						href={
-							'#' + CATEGORY_TERM_NAME + '-' + CATEGORY_ALL_TAB_ID
-						}
-						onClick={ ( event ) => {
-							event.preventDefault();
-							setActiveTab( CATEGORY_ALL_TAB_ID );
-							if ( window.deleteUserSetting ) {
-								window.deleteUserSetting(
-									CATEGORY_TERM_NAME + '_tab'
-								);
-							}
-						} }
-					>
-						{ __( 'All items', 'woocommerce' ) }
-					</a>
-				</li>
-				<li
-					className={
-						activeTab === CATEGORY_POPULAR_TAB_ID ? 'tabs' : ''
-					}
-				>
-					<a
-						href={
-							'#' +
-							CATEGORY_TERM_NAME +
-							'-' +
-							CATEGORY_POPULAR_TAB_ID
-						}
-						onClick={ ( event ) => {
-							event.preventDefault();
-							setActiveTab( CATEGORY_POPULAR_TAB_ID );
-							if ( window.setUserSetting ) {
-								window.setUserSetting(
-									CATEGORY_TERM_NAME + '_tab',
-									CATEGORY_POPULAR_TAB_ID
-								);
-							}
-						} }
-					>
-						{ __( 'Most used', 'woocommerce' ) }
-					</a>
-				</li>
-			</ul>
-			<div
-				className="tabs-panel"
-				id={ CATEGORY_TERM_NAME + '-' + CATEGORY_POPULAR_TAB_ID }
-				style={
-					activeTab !== CATEGORY_POPULAR_TAB_ID
-						? { display: 'none' }
-						: {}
-				}
-			>
-				<ul
-					id={
-						CATEGORY_TERM_NAME +
-						'checklist-' +
-						CATEGORY_POPULAR_TAB_ID
-					}
-					className="categorychecklist form-no-clear"
-				>
-					<PopularCategoryList
-						selected={ selected }
-						onChange={ setSelected }
-					/>
-				</ul>
-			</div>
-			<div
-				className="tabs-panel"
-				id={ CATEGORY_TERM_NAME + '-' + CATEGORY_ALL_TAB_ID }
-				style={
-					activeTab !== CATEGORY_ALL_TAB_ID ? { display: 'none' } : {}
-				}
-			>
-				<AllCategoryList
-					selectedCategoryTerms={ selected }
-					onChange={ setSelected }
-					ref={ allCategoryListRef }
-				/>
-			</div>
-			{ ( selected || [] ).map( ( sel ) => (
-				<input
-					key={ sel.term_id }
-					type="hidden"
-					value={ sel.term_id }
-					name={ 'tax_input[' + CATEGORY_TERM_NAME + '][]' }
-				/>
-			) ) }
-			{ selected.length === 0 && (
-				<input
-					type="hidden"
-					value=""
-					name={ 'tax_input[' + CATEGORY_TERM_NAME + '][]' }
-				/>
-			) }
-			<CategoryAddNew
-				selectedCategoryTerms={ selected }
-				onChange={ ( sel ) => {
-					setSelected( sel );
-					if ( allCategoryListRef.current ) {
-						allCategoryListRef.current.resetInitialValues();
-					}
-				} }
-			/>
-		</div>
-	);
-};
-
-export default CategoryMetabox;
diff --git a/plugins/woocommerce/client/admin/client/wp-admin-scripts/product-category-metabox/index.js b/plugins/woocommerce/client/admin/client/wp-admin-scripts/product-category-metabox/index.js
deleted file mode 100644
index 463aa2eebee..00000000000
--- a/plugins/woocommerce/client/admin/client/wp-admin-scripts/product-category-metabox/index.js
+++ /dev/null
@@ -1,29 +0,0 @@
-/**
- * External dependencies
- */
-import { createRoot, Suspense, lazy } from '@wordpress/element';
-
-/**
- * Internal dependencies
- */
-import { getSelectedCategoryData } from './category-handlers';
-import './style.scss';
-
-const CategoryMetabox = lazy( () =>
-	import( /* webpackChunkName: "category-metabox" */ './category-metabox' )
-);
-
-const metaboxContainer = document.querySelector(
-	'#taxonomy-product_cat-metabox'
-);
-if ( metaboxContainer ) {
-	const initialSelected = getSelectedCategoryData(
-		metaboxContainer.parentElement
-	);
-	createRoot( metaboxContainer ).render(
-		<Suspense fallback={ null }>
-			<CategoryMetabox initialSelected={ initialSelected } />
-		</Suspense>,
-		metaboxContainer
-	);
-}
diff --git a/plugins/woocommerce/client/admin/client/wp-admin-scripts/product-category-metabox/popular-category-list.tsx b/plugins/woocommerce/client/admin/client/wp-admin-scripts/product-category-metabox/popular-category-list.tsx
deleted file mode 100644
index d3d797e81bb..00000000000
--- a/plugins/woocommerce/client/admin/client/wp-admin-scripts/product-category-metabox/popular-category-list.tsx
+++ /dev/null
@@ -1,100 +0,0 @@
-/**
- * External dependencies
- */
-import { useEffect, useState } from '@wordpress/element';
-import { addQueryArgs } from '@wordpress/url';
-import { getSetting } from '@woocommerce/settings';
-import apiFetch from '@wordpress/api-fetch';
-
-/**
- * Internal dependencies
- */
-import { CATEGORY_TERM_NAME } from './category-handlers';
-
-declare const wc_product_category_metabox_params: {
-	search_taxonomy_terms_nonce: string;
-};
-
-export type CategoryTerm = {
-	name: string;
-	term_id: number;
-	count: number;
-};
-
-export const PopularCategoryList = ( {
-	selected,
-	onChange,
-}: {
-	selected: CategoryTerm[];
-	onChange: ( selected: CategoryTerm[] ) => void;
-} ) => {
-	const [ popularCategories, setPopularCategories ] = useState<
-		CategoryTerm[]
-	>( [] );
-
-	useEffect( () => {
-		apiFetch< CategoryTerm[] >( {
-			url: addQueryArgs(
-				new URL(
-					'admin-ajax.php',
-					getSetting( 'adminUrl' )
-				).toString(),
-				{
-					action: 'woocommerce_json_search_taxonomy_terms',
-					taxonomy: CATEGORY_TERM_NAME,
-					limit: 10,
-					orderby: 'count',
-					order: 'DESC',
-					// eslint-disable-next-line no-undef, camelcase
-					security:
-						wc_product_category_metabox_params.search_taxonomy_terms_nonce,
-				}
-			),
-			method: 'GET',
-		} ).then( ( res ) => {
-			if ( res ) {
-				setPopularCategories( res.filter( ( cat ) => cat.count > 0 ) );
-			}
-		} );
-	}, [] );
-
-	const selectedIds = selected.map( ( sel ) => sel.term_id );
-
-	return (
-		<ul
-			className="categorychecklist form-no-clear"
-			id={ CATEGORY_TERM_NAME + 'checklist-pop' }
-		>
-			{ popularCategories.map( ( cat ) => {
-				const categoryCheckboxId = `in-popular-${ CATEGORY_TERM_NAME }-${ cat.term_id }`;
-				return (
-					<li key={ cat.term_id } className="popular-category">
-						<label
-							className="selectit"
-							htmlFor={ categoryCheckboxId }
-						>
-							<input
-								type="checkbox"
-								id={ categoryCheckboxId }
-								checked={ selectedIds.includes( cat.term_id ) }
-								onChange={ () => {
-									if ( selectedIds.includes( cat.term_id ) ) {
-										onChange(
-											selected.filter(
-												( sel ) =>
-													sel.term_id !== cat.term_id
-											)
-										);
-									} else {
-										onChange( [ ...selected, cat ] );
-									}
-								} }
-							/>
-							{ cat.name }
-						</label>
-					</li>
-				);
-			} ) }
-		</ul>
-	);
-};
diff --git a/plugins/woocommerce/client/admin/client/wp-admin-scripts/product-category-metabox/style.scss b/plugins/woocommerce/client/admin/client/wp-admin-scripts/product-category-metabox/style.scss
deleted file mode 100644
index bfc1564a2f4..00000000000
--- a/plugins/woocommerce/client/admin/client/wp-admin-scripts/product-category-metabox/style.scss
+++ /dev/null
@@ -1,64 +0,0 @@
-.product-add-category {
-	&__tree-control {
-		margin-top: $gap-smaller;
-
-		.woocommerce-tree-select-control {
-			.components-base-control,
-			.woocommerce-tree-select-control__tree {
-				padding: 0 5px;
-			}
-
-			.components-checkbox-control__label {
-				min-height: $gap-larger;
-			}
-
-			&__tags {
-				display: none;
-			}
-			.components-base-control .woocommerce-tree-select-control__control-input,
-			.woocommerce-tree-select-control__option {
-				font-size: 12px;
-			}
-			.components-checkbox-control__input {
-				height: $gap;
-				width: $gap;
-			}
-			.components-checkbox-control__checked {
-				height: $gap + $gap-smallest;
-				width: $gap + $gap-smallest;
-			}
-		}
-	}
-}
-
-.categorydiv.category-async-metabox {
-	#product_cat-all {
-		overflow: visible;
-
-		.categorychecklist {
-			max-height: 140px;
-			margin-left: 0;
-			> li {
-				display: flex;
-				align-items: center;
-				margin-right: $gap-small;
-			}
-			.ntdelbutton {
-				position: relative;
-				margin: 0;
-			}
-		}
-	}
-	.woocommerce-experimental-select-control__combo-box-wrapper {
-		min-height: 30px;
-		border-radius: $gap-smallest;
-	}
-	.woocommerce-experimental-select-control__menu-item {
-		padding: 5px $gap-small;
-	}
-	.category-add {
-		&__error {
-			color: $error-red;
-		}
-	}
-}
diff --git a/plugins/woocommerce/client/admin/config/core.json b/plugins/woocommerce/client/admin/config/core.json
index 9a8247c4928..c33a84a26e6 100644
--- a/plugins/woocommerce/client/admin/config/core.json
+++ b/plugins/woocommerce/client/admin/config/core.json
@@ -37,7 +37,6 @@
 		"woo-mobile-welcome": true,
 		"wc-pay-promotion": true,
 		"wc-pay-welcome-page": true,
-		"async-product-editor-category-field": false,
 		"launch-your-store": true,
 		"use-wp-horizon": false,
 		"rest-api-v4": false,
diff --git a/plugins/woocommerce/client/admin/config/development.json b/plugins/woocommerce/client/admin/config/development.json
index 89bdafe155f..94147a04818 100644
--- a/plugins/woocommerce/client/admin/config/development.json
+++ b/plugins/woocommerce/client/admin/config/development.json
@@ -37,7 +37,6 @@
 		"woo-mobile-welcome": true,
 		"wc-pay-promotion": true,
 		"wc-pay-welcome-page": true,
-		"async-product-editor-category-field": true,
 		"launch-your-store": true,
 		"use-wp-horizon": false,
 		"rest-api-v4": false,
diff --git a/plugins/woocommerce/includes/class-wc-ajax.php b/plugins/woocommerce/includes/class-wc-ajax.php
index a30cfe596cf..7c01900c137 100644
--- a/plugins/woocommerce/includes/class-wc-ajax.php
+++ b/plugins/woocommerce/includes/class-wc-ajax.php
@@ -2054,9 +2054,13 @@ class WC_AJAX {
 	/**
 	 * Search for categories and return json.
 	 *
+	 * @deprecated 10.9.0 This callback was used by the removed async product editor category field.
+	 *
 	 * @return void
 	 */
 	public static function json_search_categories_tree() {
+		wc_deprecated_function( __METHOD__, '10.9.0' );
+
 		ob_start();

 		check_ajax_referer( 'search-categories', 'security' );
diff --git a/plugins/woocommerce/phpstan-baseline.neon b/plugins/woocommerce/phpstan-baseline.neon
index 21152626014..d9197bf6882 100644
--- a/plugins/woocommerce/phpstan-baseline.neon
+++ b/plugins/woocommerce/phpstan-baseline.neon
@@ -45993,18 +45993,6 @@ parameters:
 			count: 1
 			path: src/Admin/FeaturePlugin.php

-		-
-			message: '#^Method Automattic\\WooCommerce\\Admin\\Features\\AsyncProductEditorCategoryField\\Init\:\:enqueue_scripts\(\) has no return type specified\.$#'
-			identifier: missingType.return
-			count: 1
-			path: src/Admin/Features/AsyncProductEditorCategoryField/Init.php
-
-		-
-			message: '#^Method Automattic\\WooCommerce\\Admin\\Features\\AsyncProductEditorCategoryField\\Init\:\:enqueue_styles\(\) has no return type specified\.$#'
-			identifier: missingType.return
-			count: 1
-			path: src/Admin/Features/AsyncProductEditorCategoryField/Init.php
-
 		-
 			message: '#^Access to an undefined property WooCommerce\:\:\$payment_gateways\.$#'
 			identifier: property.notFound
diff --git a/plugins/woocommerce/src/Admin/Features/AsyncProductEditorCategoryField/Init.php b/plugins/woocommerce/src/Admin/Features/AsyncProductEditorCategoryField/Init.php
deleted file mode 100644
index 89b53f0ac00..00000000000
--- a/plugins/woocommerce/src/Admin/Features/AsyncProductEditorCategoryField/Init.php
+++ /dev/null
@@ -1,86 +0,0 @@
-<?php
-/**
- * WooCommerce Async Product Editor Category Field.
- */
-
-namespace Automattic\WooCommerce\Admin\Features\AsyncProductEditorCategoryField;
-
-use Automattic\Jetpack\Constants;
-use Automattic\WooCommerce\Admin\Features\Features;
-use Automattic\WooCommerce\Internal\Admin\WCAdminAssets;
-use Automattic\WooCommerce\Admin\PageController;
-
-/**
- * Loads assets related to the async category field for the product editor.
- */
-class Init {
-
-	const FEATURE_ID = 'async-product-editor-category-field';
-
-	/**
-	 * Constructor
-	 */
-	public function __construct() {
-		if ( Features::is_enabled( self::FEATURE_ID ) ) {
-			add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_styles' ) );
-			add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
-			add_filter( 'woocommerce_taxonomy_args_product_cat', array( $this, 'add_metabox_args' ) );
-		}
-	}
-
-	/**
-	 * Adds meta_box_cb callback arguments for custom metabox.
-	 *
-	 * @param array $args Category taxonomy args.
-	 * @return array $args category taxonomy args.
-	 */
-	public function add_metabox_args( $args ) {
-		if ( ! isset( $args['meta_box_cb'] ) ) {
-			$args['meta_box_cb']          = 'WC_Meta_Box_Product_Categories::output';
-			$args['meta_box_sanitize_cb'] = 'taxonomy_meta_box_sanitize_cb_checkboxes';
-		}
-		return $args;
-	}
-
-	/**
-	 * Enqueue scripts needed for the product form block editor.
-	 */
-	public function enqueue_scripts() {
-		if ( ! PageController::is_embed_page() ) {
-			return;
-		}
-
-		WCAdminAssets::register_script( 'wp-admin-scripts', 'product-category-metabox', true );
-		wp_localize_script(
-			'wc-admin-product-category-metabox',
-			'wc_product_category_metabox_params',
-			array(
-				'search_categories_nonce'     => wp_create_nonce( 'search-categories' ),
-				'search_taxonomy_terms_nonce' => wp_create_nonce( 'search-taxonomy-terms' ),
-			)
-		);
-		wp_enqueue_script( 'product-category-metabox' );
-
-	}
-
-	/**
-	 * Enqueue styles needed for the rich text editor.
-	 */
-	public function enqueue_styles() {
-		if ( ! PageController::is_embed_page() ) {
-			return;
-		}
-		$version = Constants::get_constant( 'WC_VERSION' );
-
-		wp_register_style(
-			'woocommerce_admin_product_category_metabox_styles',
-			WCAdminAssets::get_url( 'product-category-metabox/style', 'css' ),
-			array(),
-			$version
-		);
-		wp_style_add_data( 'woocommerce_admin_product_category_metabox_styles', 'rtl', 'replace' );
-
-		wp_enqueue_style( 'woocommerce_admin_product_category_metabox_styles' );
-	}
-
-}