¿Hay alguna forma de actualizar la lista de categorías que se usa en un componente personalizado si un usuario agrega una nueva categoría usando el propio editor?

5 minutos de lectura

He creado un componente personalizado para el editor gutenberg de wordpress. Necesitaba una forma de seleccionar una sola categoría de una lista de categorías ya seleccionadas. Pude lograr este tipo de funcionalidad con el siguiente código. El único problema con mi componente es que no actualiza su lista de categorías si el usuario agrega una categoría completamente nueva mientras está en el propio editor, al agregar una categoría como esa, la categoría se selecciona automáticamente y, por lo tanto, debería estar presente en el menú desplegable personalizado. .

He estado buscando en la documentación y no encuentro una manera de lograr este efecto, parece que select().getEntityRecords() está almacenando en caché el primer conjunto de resultados que obtiene y no consultará datos nuevos sin actualizar la página.

Nota al margen: hay una funcionalidad adicional para limitar la cantidad de categorías regulares que un usuario puede verificar. Actualmente, mi código lo limita a 3 y no permitirá que el usuario guarde la publicación si ha verificado más de 3.

índice.js

// WordPress dependencies.
import { createElement as el, Fragment } from '@wordpress/element';
import { __ } from '@wordpress/i18n';

// Internal dependencies.
import PostPrimaryCategory from './post-primary-category';
/**
 * Add new field to category content block
 * Also add a limit check for categories
 * 
 * @param {*} OriginalComponent 
 */
function wrapPostPrimaryCategory( OriginalComponent ) {
    return function( props ) { 
    // create content block 
    let originalElement = el( OriginalComponent, props );
    let errorMessage    = null;
    // if the content block is category
    if ( 'category' === originalElement.props.slug ) {      
      // turn on update/publish button
      jQuery( ".editor-post-publish-button" ).prop( "disabled", false );
      if ( 3 < originalElement.props.terms.length ) {
        // if number of categories is more then 3, show error and disable publish/edit button
        errorMessage = el( 'p', { class: 'error-message' }, __( 'Too many categories have been selected', 'post-categories-error' ) );
        jQuery( ".editor-post-publish-button" ).prop( "disabled", true );
      }
    }

    // compile all elements of the content block together
    let elements="category" !== originalElement.props.slug ? el(
      Fragment, null,
      originalElement
    ) : (
      el(
        Fragment, null,        
        el( 'h4', null, __( 'Categories', 'post-categories' ) ),
        // show error message if there is one
        errorMessage,
        originalElement,    
        // Show a custom heading
        el( 'h4', null, __( 'Primary Category', 'post-primary-category' ) ),
        // add new field
        <PostPrimaryCategory selectedTerms={ originalElement.props.terms } />    
      )
    );

    return elements;
    };
}
// hook to get access to the category ( and post tags ) content blocks in the editor
wp.hooks.addFilter(
    'editor.PostTaxonomyType',
    'authentic-child/assets/js/post-primary-category',
    wrapPostPrimaryCategory
);

categoría post-primaria.js

// WordPress dependencies.
import { SelectControl } from '@wordpress/components';
import { compose } from '@wordpress/compose';
import { withSelect, withDispatch } from '@wordpress/data';

// Whenever the post is edited, this would be called. And we use it to pass the
// updated metadata to the above function.
const applyWithSelect = withSelect( ( select ) => {
    return {
        primaryCategory: select( 'core/editor' ).getEditedPostAttribute( 'meta' ).primary_category,
        categories: select( 'core' ).getEntityRecords( 'taxonomy', 'category', { per_page:-1, hide_empty:false } )
    };  
} );

// Whenever the post is edited, this would also be called. And we use it to update
// the metadata through the above function. But note that the changes would only
// be saved in the database when you click on the submit button, e.g. the "Update"
// button on the post editing screen. :)
const applyWithDispatch = withDispatch( ( dispatch ) => {
    const { editPost } = dispatch( 'core/editor' );
    return {
        onSetPrimaryCategory( primaryCategory ) {
            const meta = { primary_category: primaryCategory };
            editPost( { meta } );
        }
    };
} );

// This basically simply renders the select drop down.
function PostPrimaryCategory( {
    // passsed in from the wrap function in index.js
    selectedTerms,
    // These these props are passed by applyWithSelect().
    primaryCategory,
    categories,
    // Whereas this is passed by applyWithDispatch().
    onSetPrimaryCategory,
} ) {
    return (
        <>
            <SelectControl
                label="This category will be displayed on the post when it is on the home/search pages"
        value={ primaryCategory }
        onChange={ onSetPrimaryCategory }
        options={ null === categories || undefined === categories ? [] : 
          categories
            .filter( ( { id, name } ) => ( "Uncategorized" === name || -1 === selectedTerms.indexOf( id ) ? false : true ) )
            .map( ( { id, name } ) => ( { label: name, value: name } ) ) }
            />
        </>
    );
}

// And finally, 'compose' the above functions.
export default compose( applyWithSelect, applyWithDispatch )( PostPrimaryCategory );

  • no entiendo. ¿Cómo el usuario agrega una nueva categoría? select-control no tiene esa opción. ¿Se puede compartir algún código de esa parte? o imagen

    – armin yahya

    7 dic 2020 a las 16:07

  • @arminyahya No, esta es una funcionalidad integrada de WordPress. WordPress permite al usuario agregar nuevas categorías y la mayoría de las otras taxonomías relacionadas con las publicaciones, sobre la marcha al editar/crear una publicación en el editor. Entonces no tengo una manera fácil de compartir el código que controla eso. Lo siento.

    – Josh Balcitis

    7 dic 2020 a las 17:03

Puedes usar el useSelect gancho React personalizado dentro de un componente de función. useSelect se “suscribirá” a los cambios y volverá a renderizar automáticamente el componente si los valores cambian (es decir, el usuario selecciona otra categoría).

El componente para crear un <SelectControl> que le permite al usuario seleccionar una “Categoría principal” podría verse así:

/**
 * WordPress dependencies
 */
import { __ } from '@wordpress/i18n';
import { SelectControl } from '@wordpress/components';
import { useSelect } from '@wordpress/data';
import { useEntityProp } from '@wordpress/core-data';

function PostPrimaryCategory() {
    const categories = useSelect((select) => {
        /**
         * Get the currently selected categories for a post. Since we are using 
         * useSelect, this will get updated any time the user adds or removes a 
         * category from the post.
         */
        const catIds = select('core/editor').getEditedPostAttribute('categories');

        /**
         * The line of code above just gets us an array of category IDs, so here
         * we get the full category details (name, slug, id, etc) that we can
         * use to populate the SelectControl.
         */
        return !!catIds && catIds.length > 0 ?
            select('core').getEntityRecords('taxonomy', 'category', {
                include: catIds.join(','),
                per_page: -1,
            }) : [];
    });

    // We need the post type for setting post meta
    const postType = useSelect((select) => {
        return select('core/editor').getCurrentPostType();
    });

    // Get and set the post meta
    const [meta, setMeta] = useEntityProp('postType', postType, 'meta');

    return (
        <SelectControl
            label={ __('Primary Category', 'text-domain') }
            value={ meta.primary_category }
            options={ categories.map(cat => {
                return {
                    label: cat.name,
                    value: cat.id,
                }
            }) }
            onChange={ (value) => setMeta({primary_category: value}) }
        />
    );
};

¿Ha sido útil esta solución?