Crear una página de opciones en WordPress con React – Parte 3
Durante la segunda parte de este tutorial para crear una página de opciones en WordPress, mostré como crear una pequeña aplicación en React que sea mostrada dentro de nuestra página de opciones, agregamos un par de campos y estilos; y preparamos el botón de guardado para que ésta funcione. Ahora lo que resta es guardar los datos dentro de nuestra base de datos y leerla dentro de nuestra página de opciones para asegurarnos de no sobre-escribir los datos.
Tabla de Contenido
Paso 1: Guardando nuestros datos
Comenzando desde donde nos quedamos la última vez, afortunadamente WordPress ya
viene con sus utilidades para hacernos la vida más fácil, y, si recordamos que
en la segunda parte registramos nuestra opción utilizando register_setting
, podemos
utilizar el ID que le dimos para cargar las opciones guardadas en nuestra base de
datos de esta forma:
1/**2 * WordPress dependencies3 */4import {5 Button,6 Card,7 CardBody,8 CardDivider,9 CardFooter,10 TextControl,11 ToggleControl,12} from '@wordpress/components';13import { useEntityProp } from '@wordpress/core-data';14import { __ } from '@wordpress/i18n';15const Settings = () => {16 const [apiKey, setApiKey] = useEntityProp('root', 'site', 'mah_api_key');17 const [toggled, setToggled] = useEntityProp('root', 'site', 'mah_function');18 const saveSettings = () => {19 console.log('Guardar');20 };21 return (22 <>23 <h1>{__('Selecciona las opciones deseadas', 'mah-settings')}</h1>24 <Card>25 <CardBody>26 <TextControl27 help={__('Ingresa tu API key', 'mah-settings')}28 label={__('API Key', 'mah-settings')}29 onChange={setApiKey}30 value={apiKey || ''}31 />32 </CardBody>3334 <CardDivider />3536 <CardBody>37 <ToggleControl38 label={__('Activar funcionalidad', 'mah-settings')}39 onChange={setToggled}40 checked={toggled || false}41 />42 </CardBody>4344 <CardFooter>45 <Button disabled={!apiKey} onClick={saveSettings} variant='primary'>46 {__('Guardar', 'mah-settings')}47 </Button>48 </CardFooter>49 </Card>50 </>51 );52};53export default Settings;54
Con esto ya estamos leyendo el valor de nuestra opción guardado anteriormente, expliquemos un poco como funciona:
- Cargamos el hook
useEntityProp
que nos ayuda a leer propiedades ya sea del sitio, o de posts en particular - Esto es evidente en la linea 18 donde reemplazamos
useState
poruseEntityProp
, el hook mantiene en observación a nuestro valor para mantener un registro mientras se guarda - Por último agregamos una condición al valor de nuestro
TextControl
en donde básicamente decimos que is este valor está vacío (null
) entonces regrese una cadena vacía en su lugar (''
). - Hacemos lo mismo con nuestro Toggle, esto es para mantener el mismo tipo de datos aún cuando no haya nada guardado anteriormente
Ahora necesitamos guardar nuestros datos una vez que se presiona el botón de
guardar, para ello necesitamos reemplazar la función de saveSettings
por una llamada
a las funciones disponibles en los paquetes @wordpress/core-data
, y @wordpress/data
¿Cual es la diferencia?
Si la pregunta es el ¿por qué algunas funciones estan en core-data mientras que otras están en data? desafortunadamente no tengo la respuesta, sólo sabemos que el equipo que las agregó supuso que esos eran los mejores lugares para colocarlas 🤷♂️
1/**2 * WordPress dependencies3 */4import {5 Button,6 Card,7 CardBody,8 CardDivider,9 CardFooter,10 TextControl,11 ToggleControl,12} from '@wordpress/components';13import { store as coreStore, useEntityProp } from '@wordpress/core-data';14import { useDispatch } from '@wordpress/data';15import { __ } from '@wordpress/i18n';16const Settings = () => {17 const [ apiKey, setApiKey ] = useEntityProp(18 'root',19 'site',20 'mah_api_key'21 );22 const [ toggled, setToggled ] = useState( false );23 const { saveEditedEntityRecord } = useDispatch( coreStore );24 const saveSettings = () => {25 saveEditedEntityRecord(26 'root',27 'site',28 undefined,29 {30 mah_api_key: apiKey,31 mah_function: toggled,32 }33 );34 };35[…]36
He agregado solo las partes importantes de nuestras modificaciones, para entender un poco más expliquemos por partes:
- Debemos importar
store
, y le damos el aliascoreStore
(esto es una buena práctica cuando se trabaja con varios) que es en donde está guardada la función que necesitamos1 - Cargamos la opción
saveEditedEntityRecord
del store utilizando el hookuseDispatch
- Reemplazamos nuestra función de guardado para que guarde el contenido de nuestras opciones con la función que acabamos de cargar
Y eso es todo, ahora una vez hagamos cambios a nuestros controles podremos ver como su valor persiste al recargar la página, y podemos utilizarlos en el resto de nuestro sitio:

Paso 2: Mejorando la experiencia
A pesar de que ya esté guardando nuestros datos, aún no hemos terminado ¿Cierto? ¿Puedes ver que manera sencilla tenemos de mejorar la experiencia?
Paso 2.1: Indicador de carga (Spinner)
Lo más lógico que se me ocurre: no sabemos cuando ni como se han guardado nuestras opciones. No hay ningún indicador que nos diga que el botón funcionó hasta que recargamos la página y las vemos ser cargadas en los campos, por lo tanto, podemos comenzar con un simple Spinner.
Con unas pocas modificaciones y con la ayuda de nuevo del paquete @wordpress/components
podemos colocarlo facilmente:
src/components/settings.js
1/**2 * WordPress dependencies3 */4import {5 Button,6 Card,7 CardBody,8 CardDivider,9 CardFooter,10 Spinner,11 TextControl,12 ToggleControl,13} from '@wordpress/components';14import { store as coreStore, useEntityProp } from '@wordpress/core-data';15import { useDispatch } from '@wordpress/data';16import { useState } from '@wordpress/element';17import { __ } from '@wordpress/i18n';18const Settings = () => {19 const [ isLoading, setIsLoading ] = useState( false );20 const [ apiKey, setApiKey ] = useEntityProp(21 'root',22 'site',23 'mah_api_key'24 );25 const [ toggled, setToggled ] = useEntityProp(26 'root',27 'site',28 'mah_function'29 );30 const { saveEditedEntityRecord } = useDispatch( coreStore );31 const saveSettings = async () => {32 setIsLoading( true );33 await saveEditedEntityRecord(34 'root',35 'site',36 undefined,37 {38 mah_api_key: apiKey,39 mah_function: toggled,40 }41 );42 setIsLoading( false );43 };44[…]45
src/components/settings.js#49-85
1return (2 <>3 <h1>{__('Selecciona las opciones deseadas', 'mah-settings')}</h1>4 <Card>5 <CardBody>6 <TextControl7 help={__('Ingresa tu API key', 'mah-settings')}8 label={__('API Key', 'mah-settings')}9 onChange={setApiKey}10 value={apiKey || ''}11 />12 </CardBody>1314 <CardDivider />1516 <CardBody>17 <ToggleControl18 label={__('Activar funcionalidad', 'mah-settings')}19 onChange={setToggled}20 checked={toggled || false}21 />22 </CardBody>2324 <CardFooter>25 <Button disabled={!apiKey} onClick={saveSettings} variant='primary'>26 {isLoading && <Spinner />}27 {!isLoading && __('Guardar', 'mah-settings')}28 </Button>29 </CardFooter>30 </Card>31 </>32);33
Y el resultado sería:

Veamos que hicimos:
- Importamos
Spinner
de nuestros componentes de WordPress, esto es lo que nos dará el indicador de carga - Regresamos
useState
, lo utilizaremos para crear una bandera de carga, que está deshabilitada por defecto - Agregamos la condición
async
a nuestra función de guardado, esto indica al navegador que unawait
esta por venir - Cambiamos nuestra bandera de carga a habilitada (esto mostrará el Spinner), y
le indicamos al navegador que debe esperar el resultado de la función con
await
, antes de volver a deshabilitar nuestra bandera - Finalmente, indicamos las condiciones para que si la bandera esta habilitada muestre nuestro spinner, de lo contrario, que muestre nuestro texto
¡Extra!
Además de nuestro spinner, muchas veces también es bueno deshabilitar el botón para que no pueda ser utilizado mientras nuestra función carga ¿Puedes descubrir cómo hacer esto?
Tip: Estará incluido en los demás ejemplos y el repositorio al final del artículo.
Paso 2.2: Mensaje de éxito (o de error)
Otra cosa sencilla que podemos hacer es brindar un mensaje de éxito (o error) al terminar el proceso, este será un poco diferente de lo que hace el editor de WordPress por defecto2, pero aún así tenemos un componente que podemos utilizar.
Hacemos unas modificaciones la la primera parte de nuestro archivo:
1/**2 * WordPress dependencies3 */4import {5 Button,6 Card,7 CardBody,8 CardDivider,9 CardFooter,10 Notice,11 Spinner,12 TextControl,13 ToggleControl,14} from '@wordpress/components';15import { store as coreStore, useEntityProp } from '@wordpress/core-data';16import { useDispatch } from '@wordpress/data';17import { useState } from '@wordpress/element';18import { __ } from '@wordpress/i18n';19const Settings = () => {20 const [ isLoading, setIsLoading ] = useState( false );21 const [ showNotice, setShowNotice ] = useState( false );22 const [ apiKey, setApiKey ] = useEntityProp(23 'root',24 'site',25 'mah_api_key'26 );27 const [ toggled, setToggled ] = useEntityProp(28 'root',29 'site',30 'mah_function'31 );32 const { saveEditedEntityRecord } = useDispatch( coreStore );33 const saveSettings = async () => {34 setIsLoading( true );35 await saveEditedEntityRecord(36 'root',37 'site',38 undefined,39 {40 mah_api_key: apiKey,41 mah_function: toggled,42 }43 );44 setShowNotice( true );45 setIsLoading( false );46 };47 return (48 <>49 <h1>{ __( 'Selecciona las opciones deseadas', 'mah-settings' ) }</h1>50 <Card>51 { showNotice && (52 <CardBody>53 <Notice54 isDismissible55 onDismiss={ () => setShowNotice( false ) }56 status="success"57 >58 { __( 'Guardado con éxito', 'mah-settings' ) }59 </Notice>60 </CardBody>61 ) }62[…]63
Básicamente lo que estamos haciendo es:
- Importamos el componente Notice de
@wordpress/components
- Creamos una bandera que nos ayude a saber cuando mostrar el mensaje utilizando de nuevo
useState
- Cuando termine el proceso de guardado, habilitamos la bandera para mostrar el mensaje
- Por encima de nuestras opciones, preparamos el lugar en donde se mostrará el mensaje y especificamos que sólo se muestre cuando la bandera esté habilitada
- Dentro del componente
Notice
especificamos cuál será el mensaje y agregamos una función que deshabilite nuestra bandera cuando se de click al botón de cerrar
Y ahora cuando guardemos nuestras opciones, tendremos un bonito mensaje de éxito igual a este:

¿Y si algo falla?
En éste ejemplo, sólo he agregado un mensaje de éxito, pero, en caso de que queramos también saber si algo ha fallado (recomendado) podríamos logarlo con las siguientes modificaciones.
- Crear un state que guarde el mensaje que necesitemos, así como su
status
- Reemplazar nuestra función async await for una promesa de formato
.then().catch()
- Guardar el mensaje de error en nuestra variable dentro de la función
catch
- Mostrar nuestro mensaje cambiando el
status
dependiendo de si fue éxito o error
Conclusión
Creo que nuestra página de opciones se encuentra ahora en un bien lugar, no esperaba que éste tutorial se extendiera tanto pero espero que haya sido útil para alguien. Finalmente logramos crear nuestra página de opciones, para recapitular, ésto es lo que hemos hecho hasta ahora:
- Creamos nuestro ambiente, y nuestro plugin
- Creamos nuestra página de opciones, y registramos un script que se ejecute sólo en ella
- Registramos nuestras opciones globales, y agregamos nuestros controles en React
- Guardamos nuestras opciones en React, y agregamos mejoras en UX
Finalmente, si quieres ver el proyecto completo puedes hacerlo en mi repositoro en Github. Una vez más, todos los comentarios son bienvenidos y cualquier sugerencia sobre qué escribir después también. ¡Hasta la próxima!
Leer más…
- @wordpress/data – Block Editor Handbook | Developer.WordPress.org
- @wordpress/core-data – Block Editor Handbook | Developer.WordPress.org
- Usar promesas – JavaScript | MDN
Notas al pie
-
Los
stores
son el lugar en dónde la lógica de la applicación vive, en un formato de Redux; es dónde se guarda elstate
y losreducers
de la aplicación. ↩ -
Dentro del editor utilizariamos
createNotice
que mostraría nuestro mensaje junto a los demás de editor, pero aquí no hay editor así que hay que improvisar. ↩