Contents
Introducción
Este tutorial explica con todo lujo de detalles cómo sincronizar metacampos (post meta) y atributos de bloque en el editor de bloques de WordPress (Gutenberg) usando JavaScript. Cubriremos el registro del meta en PHP, la definición de atributos de bloque, técnicas para sincronización unidireccional y bidireccional, cómo evitar condiciones de carrera y recomendaciones de seguridad y UX.
Requisitos previos
- WordPress 5.3 (idealmente actualizado a la versión más reciente).
- Conocimientos básicos de bloques de Gutenberg (registerBlockType, edit, save).
- Capacidad de editar archivos del plugin o tema (para añadir register_post_meta).
- Familiaridad con React hooks (useEffect, useState) y la API data de wp.data (useSelect, useDispatch).
1. Registrar el metacampo en PHP
Primero hay que exponer el metacampo al REST API para que Gutenberg lo lea/escriba. Use register_post_meta (o register_meta) indicando show_in_rest => true.
true, single => true, type => string, auth_callback => function() { // Opcional: lógica de permisos más fina return current_user_can( edit_posts ) }, ) ) } )
Notas:
- b>single determina si el meta es único por post (true/false).
- El type ayuda a la validación en la REST API.
- Si necesita compatibilidad con custom post types, cambie post por el tipo correspondiente.
2. Definir atributos del bloque (dos opciones)
Tiene dos formas comunes de definir atributos para sincronizar con metacampos:
Opción A — Usar source: meta (directa)
Defina el atributo con source: meta y el nombre del meta. WordPress leerá/escribirá automáticamente el meta a través de la REST API cuando el bloque se edite.
( function( blocks, editor ) { var el = wp.element.createElement var registerBlockType = blocks.registerBlockType registerBlockType( mi-plugin/mi-bloque, { title: Bloque con Meta, category: common, attributes: { miMeta: { type: string, source: meta, meta: mi_meta_clave }, textoInterno: { type: string, default: Hola } }, edit: function( props ) { var miMeta = props.attributes.miMeta var setAttributes = props.setAttributes return el( div, null, el( input, { value: miMeta , onChange: function( e ) { setAttributes( { miMeta: e.target.value } ) } } ) ) }, save: function() { return null } // o guardado según el bloque } ) } )( window.wp.blocks, window.wp.editor )
Ventajas:
- Mínima lógica: Gutenberg ya enlaza el atributo con el meta.
- Funciona bien cuando sólo se necesita que el bloque refleje el meta del post.
Limitaciones:
- El meta se gestiona a nivel de post si hay cambios fuera del bloque, es necesario detectar y actualizar la UI del bloque (ver sección de sincronización).
- Al guardar, los atributos con source:meta no necesariamente se serializan en el HTML del bloque, depende del diseño del bloque.
Opción B — Atributo independiente sincronización manual
Defina un atributo normal y sincronice manualmente con el meta usando la API de datos (wp.data). Esto da más control (por ejemplo, debounce, validación o escritura en otros metakeys).
registerBlockType( mi-plugin/mi-bloque-avanzado, { title: Bloque con Sincronización Manual, category: common, attributes: { miAttr: { type: string, default: } }, edit: function( props ) { // ver sección de ejemplo avanzado con useSelect/useDispatch }, save: function() { return null } } )
3. Sincronización bidireccional recomendada (useSelect useDispatch)
Para mantener el atributo del bloque sincronizado con el metacampo incluso si el meta cambia por otros medios (edición rápida, otro bloque, plugin), use a) useSelect para leer el meta actual y b) useDispatch para escribir en el post meta. Combine esto con useEffect para reaccionar a cambios y con debounce para evitar llamadas excesivas al store REST.
const { registerBlockType } = wp.blocks const { useSelect, useDispatch } = wp.data const { useEffect, useState, useRef } = wp.element registerBlockType( mi-plugin/mi-bloque-sync, { title: Bloque Sync Meta, category: common, attributes: { miAttr: { type: string, default: } }, edit: function( props ) { const { attributes, setAttributes, clientId } = props const { miAttr } = attributes // Leer el meta actual del post (si cambia externamente) const postMeta = useSelect( ( select ) => { return select( core/editor ).getEditedPostAttribute( meta ) }, [] ) // Dispatch para editar el post (post meta) const { editPost } = useDispatch( core/editor ) // Local state para control y debounce const [ localValue, setLocalValue ] = useState( miAttr ) const debounceTimer = useRef() // Si el post meta cambia por otro medio, actualizamos atributo del bloque useEffect( () => { if ( postMeta postMeta.mi_meta_clave !== undefined ) { const metaValue = postMeta.mi_meta_clave if ( metaValue !== miAttr ) { setAttributes( { miAttr: metaValue } ) setLocalValue( metaValue ) } } }, [ postMeta ] ) // Cuando localValue cambia (por input), actualizamos atributo del bloque y el post meta con debounce useEffect( () => { setAttributes( { miAttr: localValue } ) // Debounce para no spamear el store if ( debounceTimer.current ) { clearTimeout( debounceTimer.current ) } debounceTimer.current = setTimeout( () => { editPost( { meta: { mi_meta_clave: localValue } } ) }, 500 ) // 500ms, ajustar según necesidad return () => { if ( debounceTimer.current ) clearTimeout( debounceTimer.current ) } }, [ localValue ] ) return wp.element.createElement( div, {}, wp.element.createElement( label, {}, Meta sincronizado: ), wp.element.createElement( input, { value: localValue, onChange: ( e ) => setLocalValue( e.target.value ) } ) ) }, save: function() { return null } } )
Explicación del flujo:
- useSelect obtiene la copia editada del post y su objeto meta.
- Si el meta cambia desde fuera, useEffect detecta el cambio y actualiza el atributo del bloque y el estado local.
- Cuando el usuario cambia el input, el estado local cambia inmediatamente (mejor UX). Otro useEffect actualiza el atributo del bloque y, tras debounce, llama a editPost para actualizar el meta en el post editado.
4. Alternativa: escuchar cambios globales con subscribe
Para casos complejos (varios bloques que reaccionan a cambios en meta), puede usar wp.data.subscribe para reaccionar a cualquier cambio en el store y luego propagar actualizaciones locales. Tenga cuidado con rendimiento y siempre comparar valores previos para evitar loops infinitos.
const { subscribe, select } = wp.data let prevMeta = null const unsubscribe = subscribe( () => { const meta = select( core/editor ).getEditedPostAttribute( meta ) if ( meta meta.mi_meta_clave !== prevMeta ) { prevMeta = meta.mi_meta_clave // Aquí puede disparar un evento personalizado o usar un store propio // para notificar a bloques individuales que actualicen su estado. } } ) // Llamar a unsubscribe() cuando deje de ser necesario
5. Guardado y comportamiento en el servidor
Si el atributo del bloque está basado en source:meta, el valor del meta quedará en los metadatos del post, no necesariamente incrustado en el HTML guardado del bloque. Si necesita que el contenido se represente en el HTML del front-end, implemente una función save adecuada o use render_callback en register_block_type en PHP.
register_block_type( mi-plugin/mi-bloque-sync, array( render_callback => function( attributes ) { meta = get_post_meta( get_the_ID(), mi_meta_clave, true ) return} ) )
6. Consideraciones de seguridad y permisos
- Asegúrese de que register_post_meta tenga un auth_callback apropiado si la información es sensible.
- Valide y sanee valores antes de guardarlos (en PHP al guardar, y opcionalmente en JS antes de enviar).
- Compruebe capacidades del usuario si el meta sólo lo deben editar ciertos roles.
7. Problemas comunes y soluciones
- Loop infinito: si al escribir en el store se dispara un select que vuelve a escribir, asegúrese de comparar valores previos y evitar escribir si no hay cambios reales.
- Race condition: use debounce y asegúrese de que useEffect tenga dependencias correctas.
- No se ven los cambios externos: confirme que register_post_meta tiene show_in_rest true y que la clave es la misma que usa en JS.
- Valor null/undefined: normalice los valores (por ejemplo usar como valor por defecto) para evitar inputs controlados no válidos en React.
8. Ejemplo completo mínimo (PHP JS)
Resumen compacto con los fragmentos clave: registro del meta, bloque con sincronización manual.
// PHP: register meta (ejemplo mínimo) add_action( init, function() { register_post_meta( post, mi_meta_clave, array( show_in_rest => true, single => true, type => string, ) ) } )
// JS: bloque con sincronización (resumen) const { registerBlockType } = wp.blocks const { useSelect, useDispatch } = wp.data const { useEffect, useState, useRef } = wp.element registerBlockType( mi-plugin/mi-bloque-completo, { title: Bloque Completo Sync, category: common, attributes: { miAttr: { type: string, default: } }, edit: function( props ) { const { attributes, setAttributes } = props const { miAttr } = attributes const postMeta = useSelect( select => select( core/editor ).getEditedPostAttribute( meta ), [] ) const { editPost } = useDispatch( core/editor ) const [ localValue, setLocalValue ] = useState( miAttr ) const timer = useRef() useEffect( () => { if ( postMeta postMeta.mi_meta_clave !== undefined postMeta.mi_meta_clave !== miAttr ) { setAttributes( { miAttr: postMeta.mi_meta_clave } ) setLocalValue( postMeta.mi_meta_clave ) } }, [ postMeta ] ) useEffect( () => { setAttributes( { miAttr: localValue } ) if ( timer.current ) clearTimeout( timer.current ) timer.current = setTimeout( () => { editPost( { meta: { mi_meta_clave: localValue } } ) }, 400 ) return () => clearTimeout( timer.current ) }, [ localValue ] ) return wp.element.createElement( div, {}, wp.element.createElement( input, { value: localValue, onChange: e => setLocalValue( e.target.value ) } ) ) }, save: function() { return null } } )
9. Checklist antes de desplegar en producción
- Confirmar que register_post_meta usa show_in_rest y auth_callback si es necesario.
- Probar edición simultánea: cambios en meta desde Quick Edit o plugins externos.
- Verificar que no hay loops y que useEffect tiene dependencias correctas.
- Comprobar la experiencia de usuario: usar debounce, feedback visual (spinner/guardado) si la operación tarda.
- Escapar/sanitizar valores al mostrar en el front-end (esc_html, esc_attr en PHP).
Recursos útiles
- Documentación oficial del Block Editor
- Documentación REST API de WordPress
- Referencia wp.data core/editor
Resumen final
Sincronizar metacampos y atributos de bloque puede ser tan simple como utilizar source:meta para que Gutenberg realice el enlace automáticamente, o puede aprovechar useSelect/useDispatch para un control fino en escenarios complejos (debounce, validación, sincronización entre múltiples bloques). Es crucial registrar correctamente el meta en PHP (show_in_rest) y manejar con cuidado efectos secundarios para evitar loops y problemas de rendimiento.
|
Acepto donaciones de BAT's mediante el navegador Brave 🙂 |