Search code examples
wordpresswordpress-gutenberggutenberg-blocks

Prevent wp.hooks.addFilter() from Running on Certain Custom Post Types in Gutenberg


I have been tasked with preventing addFilter() from running on certain custom post types using the new Gutenberg API and not any WP PHP. It's currently fed into the editor.PostFeaturedImage hook, meaning it fires every time the Gutenberg editor loads the Featured Image box.

The Filter calls a function that adds a dropdown menu underneath the featured image box to allow users to pick a contributor (which are themselves custom post types) to credit the image to.

The filter should not run on the contributor custom post type, but should run on other custom post types. There should still be a featured image box for the contributor custom post type, but no dropdown.

Letting the hook fire and then canceling within the function works but the function itself is resource heavy and the directive is to prevent the function from firing at all. The idea being that the built-in hook/function would fire instead.

Borrowing from this ticket, I attempted to place the main function setFeaturedImageArtist within an anonymous function that also printed the post type to console in addFilter(). I was able to get the post type to print, but calling setFeaturedImageArtist function didn't work as expected.

wp.hooks.addFilter( 'editor.PostFeaturedImage', 'blocks/featured-image-artist', function() {
    console.log(wp.data.select("core/editor").getCurrentPostType())
    return setFeaturedImageArtist()
});

Placing setFeaturedImageArtistin a wrapper function like so didn't work either. I'm assuming it's because it's the same thing.

function checkPostType() {
   console.log(wp.data.select("core/editor").getCurrentPostType())
   return setFeaturedImageArtist()
}

wp.hooks.addFilter( 'editor.PostFeaturedImage', 'blocks/featured-image-artist', checkPostType);

Here is the component the filter is triggering:

function setFeaturedImageArtist( OriginalComponent ) {
  return ( props ) => {

    const artistSelect = compose.compose(

        withDispatch( function( dispatch, props ) {
            return {
                setMetaValue: function( metaValue ) {
                    dispatch( 'core/editor' ).editPost(
                        { meta: { 'featured-image-artist': metaValue } }
                    );
                }
            }
        } ),

        withSelect( function( select, props ) {
           let query = {
              per_page    : 20,
              metaKey    : '_author_type',
              metaValue  : 'artist'
            };

            let postType = select("core/editor").getCurrentPostType();
            if ( postType === 'contributor' ) {
                return null
            }

            // Please see below
            // if ( postType === 'contributor' ) {
            //  return {
            //     postType
            //   }
            // }

            return {
                posts: select( 'core' ).getEntityRecords( 'postType', 'contributor', query ),

                metaValue: select( 'core/editor' ).getEditedPostAttribute( 'meta' )[ 'featured-image-artist' ],
            }
        } ) )( function( props ) {
            var options = [];

            // This works in removing the dropdown for authors/artists
            // if (props.postType === 'contributor'){
            //   return null
            // }

            if( props.posts ) {
                options.push( { value: 0, label: __( 'Select an artist', 'blocks' ) } );
                props.posts.forEach((post) => {
                    options.push({value:post.id, label:post.title.rendered});
                });
            } else {
                options.push( { value: 0, label: __( 'Loading artists...', 'blocks' ) } )
            }

            return el( SelectControl,
                {
                    label: __( 'Art Credit:', 'blocks' ),
                    options : options,
                    onChange: ( content ) => {
                        props.setMetaValue( content );
                    },
                    value: props.metaValue,
                }
            );
        }
    );

    return (
      el( 'div', { }, [
        el( OriginalComponent, props ),
        el( artistSelect )
      ] )
    );
  }
}

wp.hooks.addFilter( 'editor.PostFeaturedImage', 'blocks/featured-image-artist', setFeaturedImageArtist  );

Here is the redacted component the filter is triggering:

function setFeaturedImageArtist( OriginalComponent ) {
  return ( props ) => {

      const artistSelect = compose.compose(
          ...
      )( function( props ) {
          ... // Cancelling out here works, but resources are loaded by this point.
      });

      return (
          el( 'div', { }, [
              el( OriginalComponent, props ),
              el( artistSelect )
          ])
      );
  }
}

wp.hooks.addFilter( 'editor.PostFeaturedImage', 'blocks/featured-image-artist', setFeaturedImageArtist  );

This is the React error being received:

Element type is invalid: expected a string (for built-in components) or
a class/function (for composite components) but got: undefined.

I'm not sure what the best approach to this would be, and the documentation is scant/barely applicable. Is creating a custom hook that mimics editor.PostFeaturedImage but only fires on certain custom post types a possibility? Or is there some way to call a function like setFeaturedImageArtist within a wrapper that checks the post type?


Solution

  • I tried to recreate the script and fixed some issues:

    const { createElement: el } = wp.element;
    const { compose } = wp.compose;
    const { withSelect, withDispatch } = wp.data;
    const { SelectControl } = wp.components;
    const { __ } = wp.i18n;
    
    const ArtistSelect = compose(
        withDispatch(function(dispatch, props) {
            return {
                setMetaValue: function(metaValue) {
                    dispatch("core/editor").editPost({
                        meta: { "featured-image-artist": metaValue }
                    });
                }
            };
        }),
    
        withSelect(function(select, props) {
            let query = {
                per_page: 20,
                metaKey: "_author_type",
                metaValue: "artist"
            };
    
            return {
                postType: select("core/editor").getCurrentPostType(),
                posts: select("core").getEntityRecords("postType", "contributor", query),
    
                metaValue: select("core/editor").getEditedPostAttribute("meta")[
                    "featured-image-artist"
                ]
            };
        })
    )(function(props) {
        var options = [];
    
        // This works in removing the dropdown for authors/artists
        if (props.postType === "contributor") {
            return null;
        }
    
        if (props.posts) {
            options.push({ value: 0, label: __("Select an artist", "blocks") });
            props.posts.forEach(post => {
                options.push({ value: post.id, label: post.title.rendered });
            });
        } else {
            options.push({ value: 0, label: __("Loading artists...", "blocks") });
        }
    
        return el(SelectControl, {
            label: __("Art Credit:", "blocks"),
            options: options,
            onChange: content => {
                props.setMetaValue(content);
            },
            value: props.metaValue
        });
    });
    
    function setFeaturedImageArtist(OriginalComponent) {
        return props => {
            return el("div", {}, [el(OriginalComponent, props), el(ArtistSelect)]);
        };
    }
    
    wp.hooks.addFilter(
        "editor.PostFeaturedImage",
        "blocks/featured-image-artist",
        setFeaturedImageArtist
    );
    

    ArtistSelect is a component so we take it outside of setFeaturedImageArtist function. withSelect had a check for the postType that made it return null. Instead of that we pass that variable and then return null in the components render. An alternative would be to check inside setFeaturedImageArtist. This is a fixed version using JSX. Hope its clear:

    const { compose } = wp.compose;
    const { withSelect, withDispatch, select } = wp.data;
    const { SelectControl } = wp.components;
    const { __ } = wp.i18n;
    const { addFilter } = wp.hooks;
    
    const ArtistSelect = compose(
        withDispatch(dispatch => {
            return {
                setMetaValue: metaValue => {
                    dispatch("core/editor").editPost({
                        meta: { "featured-image-artist": metaValue }
                    });
                }
            };
        }),
        withSelect(select => {
            const query = {
                per_page: 20,
                metaKey: "_author_type",
                metaValue: "artist"
            };
    
            return {
                posts: select("core").getEntityRecords("postType", "contributor", query),
                metaValue: select("core/editor").getEditedPostAttribute("meta")[
                    "featured-image-artist"
                ]
            };
        })
    )(props => {
        const { posts, setMetaValue, metaValue } = props;
        const options = [];
    
        if (posts) {
            options.push({ value: 0, label: __("Select an artist", "blocks") });
    
            posts.forEach(post => {
                options.push({ value: post.id, label: post.title.rendered });
            });
        } else {
            options.push({ value: 0, label: __("Loading artists...", "blocks") });
        }
    
        return (
            <SelectControl
                label={__("Art Credit:", "blocks")}
                options={options}
                onChange={content => setMetaValue(content)}
                value={metaValue}
            />
        );
    });
    
    const setFeaturedImageArtist = OriginalComponent => {
        return props => {
            const post_type = select("core/editor").getCurrentPostType();
    
            if (post_type === "contributor") {
                return <OriginalComponent {...props} />;
            }
    
            return (
                <div>
                    <OriginalComponent {...props} />
                    <ArtistSelect />
                </div>
            );
        };
    };
    
    wp.hooks.addFilter(
        "editor.PostFeaturedImage",
        "blocks/featured-image-artist",
        setFeaturedImageArtist
    );