Skip to main content

Crear una página de opciones en WordPress con React – Parte 3

Publicado hace
Actualizado hace
15 minutos de lectura

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 dependencies
3 */
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 <TextControl
27 help={__('Ingresa tu API key', 'mah-settings')}
28 label={__('API Key', 'mah-settings')}
29 onChange={setApiKey}
30 value={apiKey || ''}
31 />
32 </CardBody>
33
34 <CardDivider />
35
36 <CardBody>
37 <ToggleControl
38 label={__('Activar funcionalidad', 'mah-settings')}
39 onChange={setToggled}
40 checked={toggled || false}
41 />
42 </CardBody>
43
44 <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 por useEntityProp, 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

Advertencia

¿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 dependencies
3 */
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 alias coreStore (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 hook useDispatch
  • 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:

Guardando datos en WordPress

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 dependencies
3 */
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 <TextControl
7 help={__('Ingresa tu API key', 'mah-settings')}
8 label={__('API Key', 'mah-settings')}
9 onChange={setApiKey}
10 value={apiKey || ''}
11 />
12 </CardBody>
13
14 <CardDivider />
15
16 <CardBody>
17 <ToggleControl
18 label={__('Activar funcionalidad', 'mah-settings')}
19 onChange={setToggled}
20 checked={toggled || false}
21 />
22 </CardBody>
23
24 <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:

Spinner en WordPress

Veamos que hicimos:

  1. Importamos Spinner de nuestros componentes de WordPress, esto es lo que nos dará el indicador de carga
  2. Regresamos useState, lo utilizaremos para crear una bandera de carga, que está deshabilitada por defecto
  3. Agregamos la condición async a nuestra función de guardado, esto indica al navegador que un await esta por venir
  4. 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
  5. Finalmente, indicamos las condiciones para que si la bandera esta habilitada muestre nuestro spinner, de lo contrario, que muestre nuestro texto
Nota

¡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 dependencies
3 */
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 <Notice
54 isDismissible
55 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:

  1. Importamos el componente Notice de @wordpress/components
  2. Creamos una bandera que nos ayude a saber cuando mostrar el mensaje utilizando de nuevo useState
  3. Cuando termine el proceso de guardado, habilitamos la bandera para mostrar el mensaje
  4. 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
  5. 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:

Mensaje de éxito en WordPress
Precaución

¿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…

Notas al pie

  1. 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 el state y los reducers de la aplicación.

  2. 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.