ServerSideRender and Media Object: attributes passing image data object to php renderer even though it’s not set

the issue I’m having is that the image data object (imgDataObj in the
js), which is saved as an attribute in the JS side, is getting passed
to the PHP side even though I’m not passing it in the serversiderender
component and I’m not listening for it in the PHP render callback

That issue could happen if you’re using WordPress 5.8 which uses Gutenberg 10.7 which has a function named __experimentalSanitizeBlockAttributes that’s defined in the @wordpress/blocks or wp.blocks package. See lines 1255-1293 in wp-includes/js/dist/blocks.js or the source at https://github.com/WordPress/gutenberg/blob/v10.7.0/packages/blocks/src/api/utils.js#L236-L277 for Gutenberg 10.7.0 release.

And what the function does is:

/**
 * Ensure attributes contains only values defined by block type, and merge
 * default values for missing attributes.
 *
 * @param {string} name       The block's name.
 * @param {Object} attributes The block's attributes.
 * @return {Object} The sanitized attributes.
 */
export function __experimentalSanitizeBlockAttributes( name, attributes ) {

And in wp-includes/js/dist/server-side-render.js (on line 220) or the source at https://github.com/WordPress/gutenberg/blob/v10.7.0/packages/server-side-render/src/server-side-render.js#L75-L77 (for Gutenberg 10.7.0), you can see that ServerSideRender applies __experimentalSanitizeBlockAttributes() on the attributes:

const sanitizedAttributes =
    attributes &&
    __experimentalSanitizeBlockAttributes( block, attributes );

Which means that the attributes will be merged with the ones set when registering the block type, so that’s exactly why in your case, the imgDataObj was passed to your render callback (in PHP) even if you didn’t set the attribute in the attributes property passed to ServerSideRender.

How to fix the issue

Because you said, “We don’t need the entire image object“, then just remove imgDataObj from the attributes when you register the block type.

And then, add a local variable/constant in your edit function like so:

// At the top in the file, add:
const { useSelect } = wp.data;

// Then in the edit function, add this:
const imgDataObj = useSelect( select => {
    const { getEntityRecord } = select( 'core' );
    return attributes.imgID && getEntityRecord( 'root', 'media', attributes.imgID );
}, [ attributes.imgID ] );

And note that there are other changes you need to make, but see my code on GitHub for more details. (see link at the bottom)

But before that, please read the following notes..

Additional Issues/Notes

  1. InspectorControls and MediaUpload should be imported from the @wordpress/block-editor or wp.blockEditor package.

  2. text is not a valid attribute type and yet in your JS and PHP, the pos attribute used text as the type. So ensure your attributes are using the correct type.

  3. Media/post IDs are numbers, so I’d change the type of the imgID attribute to number.

  4. You should use the dot notation when accessing properties in objects, unless if the property name contains characters like spaces or hypens (-). So instead of imgDataObj['id'], I’d use imgDataObj.id.

  5. Your edit function is returning an array of elements, so each top-level element should have a unique key property, e.g. <InspectorControls key="my-key">.

  6. When you register your script in PHP (using wp_register_script()), you should specify all the WordPress/Gutenberg packages that you use in your script. So instead of array( 'wp-blocks', 'wp' ), I’d use array( 'wp-element', 'wp-editor', 'wp-blocks', 'wp-block-editor', 'wp-components', 'wp-server-side-render' ).

  7. And actually, to allow bigger attributes object, you can set httpMethod to POST like so: <ServerSideRender httpMethod="POST" .../>.

Try/Check My Code

You can find the source (ESNext + JSX) on GitHub, and check this diff to see what I changed.