From adbccb48acba1f66ca2e387ae30dae09db03ee8f Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Wed, 4 Mar 2026 16:04:17 +1100 Subject: [PATCH 1/3] Client-side media processing: only use media upload provider when not in preview mode --- .../src/components/provider/index.js | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/packages/block-editor/src/components/provider/index.js b/packages/block-editor/src/components/provider/index.js index a4ebeed9ab2459..5b92add408cbbe 100644 --- a/packages/block-editor/src/components/provider/index.js +++ b/packages/block-editor/src/components/provider/index.js @@ -146,8 +146,21 @@ export const ExperimentalBlockEditorProvider = withRegistryProvider( const isClientSideMediaEnabled = shouldEnableClientSideMediaProcessing(); + // Preview providers must not set up their own MediaUploadProvider or + // replace the mediaUpload setting. Every MediaUploadProvider with + // useSubRegistry={false} writes to the same shared upload-media store, + // so a nested preview provider would overwrite the parent's original + // server-side mediaUpload function with the already-replaced + // client-side interceptor, causing uploads in the main editor to + // dispatch back into the upload-media store in an infinite loop. + const isPreviewMode = !! _settings?.isPreviewMode; + const settings = useMemo( () => { - if ( isClientSideMediaEnabled && _settings?.mediaUpload ) { + if ( + isClientSideMediaEnabled && + _settings?.mediaUpload && + ! isPreviewMode + ) { // Create a new object so that the original props.settings.mediaUpload is not modified. return { ..._settings, @@ -155,7 +168,7 @@ export const ExperimentalBlockEditorProvider = withRegistryProvider( }; } return _settings; - }, [ _settings, registry, isClientSideMediaEnabled ] ); + }, [ _settings, registry, isClientSideMediaEnabled, isPreviewMode ] ); const { __experimentalUpdateSettings } = unlock( useDispatch( blockEditorStore ) @@ -215,7 +228,7 @@ export const ExperimentalBlockEditorProvider = withRegistryProvider( ); - if ( isClientSideMediaEnabled ) { + if ( isClientSideMediaEnabled && ! isPreviewMode ) { return ( Date: Wed, 4 Mar 2026 17:02:13 +1100 Subject: [PATCH 2/3] Expand on comments --- .../src/components/provider/index.js | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/packages/block-editor/src/components/provider/index.js b/packages/block-editor/src/components/provider/index.js index 5b92add408cbbe..20aea5c6d4bcbe 100644 --- a/packages/block-editor/src/components/provider/index.js +++ b/packages/block-editor/src/components/provider/index.js @@ -146,13 +146,9 @@ export const ExperimentalBlockEditorProvider = withRegistryProvider( const isClientSideMediaEnabled = shouldEnableClientSideMediaProcessing(); - // Preview providers must not set up their own MediaUploadProvider or - // replace the mediaUpload setting. Every MediaUploadProvider with - // useSubRegistry={false} writes to the same shared upload-media store, - // so a nested preview provider would overwrite the parent's original - // server-side mediaUpload function with the already-replaced - // client-side interceptor, causing uploads in the main editor to - // dispatch back into the upload-media store in an infinite loop. + // Skip the interceptor replacement and MediaUploadProvider for + // preview providers. See the comment above MediaUploadProvider + // below for details. const isPreviewMode = !! _settings?.isPreviewMode; const settings = useMemo( () => { @@ -228,6 +224,20 @@ export const ExperimentalBlockEditorProvider = withRegistryProvider( ); + // MediaUploadProvider writes the mediaUpload function from + // _settings into the shared upload-media store so the store can + // hand files off to the server. useMediaUploadSettings extracts + // mediaUpload from the original _settings prop — *before* the + // interceptor replacement above — so the store receives the + // real server-side upload function. + // + // Only the top-level (non-preview) provider should do this. + // Nested preview providers (e.g. from useBlockPreview in + // core/post-template) inherit settings that already contain + // the interceptor, so their MediaUploadProvider would + // overwrite the store's server-side function with the + // interceptor, causing uploads to loop instead of reaching + // the server. if ( isClientSideMediaEnabled && ! isPreviewMode ) { return ( Date: Thu, 5 Mar 2026 16:17:27 +1100 Subject: [PATCH 3/3] Try using a key on the intercepted function instead --- .../src/components/provider/index.js | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/packages/block-editor/src/components/provider/index.js b/packages/block-editor/src/components/provider/index.js index 20aea5c6d4bcbe..296991b9caa998 100644 --- a/packages/block-editor/src/components/provider/index.js +++ b/packages/block-editor/src/components/provider/index.js @@ -146,25 +146,34 @@ export const ExperimentalBlockEditorProvider = withRegistryProvider( const isClientSideMediaEnabled = shouldEnableClientSideMediaProcessing(); - // Skip the interceptor replacement and MediaUploadProvider for - // preview providers. See the comment above MediaUploadProvider - // below for details. - const isPreviewMode = !! _settings?.isPreviewMode; + // Nested providers (e.g. from useBlockPreview) inherit settings + // where mediaUpload has already been replaced with the + // interceptor. Detect this so we skip the replacement and + // MediaUploadProvider for them — see the longer comment below. + const isMediaUploadIntercepted = + !! _settings?.mediaUpload?.__isMediaUploadInterceptor; const settings = useMemo( () => { if ( isClientSideMediaEnabled && _settings?.mediaUpload && - ! isPreviewMode + ! isMediaUploadIntercepted ) { // Create a new object so that the original props.settings.mediaUpload is not modified. + const interceptor = mediaUpload.bind( null, registry ); + interceptor.__isMediaUploadInterceptor = true; return { ..._settings, - mediaUpload: mediaUpload.bind( null, registry ), + mediaUpload: interceptor, }; } return _settings; - }, [ _settings, registry, isClientSideMediaEnabled, isPreviewMode ] ); + }, [ + _settings, + registry, + isClientSideMediaEnabled, + isMediaUploadIntercepted, + ] ); const { __experimentalUpdateSettings } = unlock( useDispatch( blockEditorStore ) @@ -231,14 +240,14 @@ export const ExperimentalBlockEditorProvider = withRegistryProvider( // interceptor replacement above — so the store receives the // real server-side upload function. // - // Only the top-level (non-preview) provider should do this. - // Nested preview providers (e.g. from useBlockPreview in + // Only the first (outermost) provider should do this. + // Nested providers (e.g. from useBlockPreview in // core/post-template) inherit settings that already contain // the interceptor, so their MediaUploadProvider would // overwrite the store's server-side function with the // interceptor, causing uploads to loop instead of reaching // the server. - if ( isClientSideMediaEnabled && ! isPreviewMode ) { + if ( isClientSideMediaEnabled && ! isMediaUploadIntercepted ) { return (