Redux logo

Hace dos semanas empecé a desarrollar un generador de formularios dinámicos con tecnologías frontend JavaScript y hoy comparto algunas de las cosas que he hecho.

Si bien podría explicarlas desde cero, paso a paso, como ya hacen algunos tutoriales excelentes de React, al final he preferido hacerlo de modo no lineal por decirlo de alguna manera, supongo que para hacer algo distinto.

Así el cerebro intenta conectar los puntos creando nuevas interconexiones neuronales sin que te des cuenta.

Espero que el post de hoy resulte de ayuda a los que ya contáis con algunos conocimientos de programación. ¡Descubre cómo hago algunas cosas, o qué aplicaciones y métodos utilizo!

La meta es escribir código sencillo, una app que hace cosas aparentemente complejas con pocas líneas de código.

Una vez más, estáis invitados a navegar por el historial de commits del repo programarivm/formbuilder en GitHub y a seguir mis #100DaysOfCode en Twitter. Intento dar pasos pequeños cada día con cada commit sin hacer grandes cosas de una sola vez.

Ahora voy a revisar mi diario de aprendizaje de programación. Está en la agenda 2021 que me regalaron estas navidades porque aunque no os lo creáis hago apuntes en una agenda de papel de las de toda la vida. A veces es más rápido que utilizar una agenda digital.

27 de enero de 2021. Es el decimoquinto día del reto de programación del que ya hablé en ¿Cuáles son algunos ejemplos reales de React con Redux y Hooks?

Por un momento he dudado entre escribir decimoquinto o quinceavo, que es un numeral partitivo. La RAE dice que un quinceavo es una de las quince partes iguales en que se divide un todo. No hay que confundirlo con decimoquinto, que es el ordinal del número quince.

Un momento por favor. Estoy apuntando la diferencia entre quinceavo (numeral partitivo) y decimoquinto (ordinal) en mi diario.

Ya está. Como decía, os explico algunas de las cosas que he hecho durante este tiempo breve.


Primero preparé la ordenación de elementos

Los días ocho y nueve me puse a pensar un poco. Utilicé Redux para programar una estructura sólida que más adelante permitiría añadir la funcionalidad drag-and-drop.

¿Sabías que gané algo de experiencia con Redux al escribir una aplicación Frankenstein llamada programarivm/warthog? Tiene de todo un poco: API, ACL, JWT, CRUD, Redux, Docker, Laravel, SSL, HTTPS, tests. Full stack SPA, todo en uno.

Básicamente esta idea consiste en llevar el conteo de los elementos de formulario que el usuario va añadiendo sobre la marcha, para lo cual se utiliza src/reducers/counterReducer.js.

import counterActionTypes from 'constants/counterActionTypes';

const initialState = {
  count: 0,
};

const reducer = (state = initialState, action) => {
  switch (action.type) {
    case counterActionTypes.INCREASE:
      return {
        ...state,
        count: state.count + 1,
      };
    case counterActionTypes.DECREASE:
      return {
        ...state,
        count: state.count - 1,
      };
    default:
      return state;
  }
};

export default reducer;

Este es el src/reducers/rootReducer.js correspondiente que aglutina todos los reducers de la aplicación:

import { combineReducers } from 'redux';
import counterReducer from './counterReducer';
import inputReducer from './inputReducer';
import selectReducer from './selectReducer';
import textareaReducer from './textareaReducer';

const rootReducer = combineReducers({
  counter: counterReducer,
  input: inputReducer,
  select: selectReducer,
  textarea: textareaReducer,
});

export default rootReducer;

Cada elemento de formulario almacena el orden que le corresponde en una propiedad que se llama order, tal y como se muestra en el siguiente estado de ejemplo.

Figura 1

Figura 1. Depurando la aplicación en Google Chrome con Redux DevTools

Espero que en esta imagen se vea bien el orden de los elementos. El de Username es 0, el de Email es 1 y el de Comments es 2.

Puse cajas de texto con chips

Los días once y diez trasteé con la librería material-ui-chip-input.

$ npm i material-ui-chip-input

Como veis estoy utilizando componentes Material-UI en el diseño UX y UI de mi aplicación. material-ui-chip-input permite añadir fichas o trocitos (como prefieras llamarlo) a las cajas de texto a medida que vas escribiendo palabras y pulsando Intro. Las palabras se amontonan formando una pila y pueden borrarse fácilmente haciendo clic sobre el aspa situada a la derecha de la palabra.

Este campo de texto especial es ideal para crear las etiquetas HTML <option> de un <select> tal y como muestra la siguiente figura.

Figura 2

Figura 2. Las etiquetas <option> se crean en cajas de texto con chips

He reordenado tarjetas Material-UI arrastrando y soltando

Días doce y trece. Al final tras darle varias vueltas, decido diseñar mi app basándome en la idea de arrastrar y soltar tarjetas, supongo que por cuestiones de simplicidad. Las tarjetas muestran fragmentos de código HTML como se ve en la siguiente figura.

Figura 3

Figura 3. Arrastrar y soltar tarjetas de contenido HTML

Esto significa que las personas que utilicen mi app tienen que estar familiarizadas con el lenguaje HTML, o bien desean aprenderlo. Un poquito más adelante, en próximos días la sección To do: Display the HTML form here! mostrará cómo se ve el formulario HTML en realidad a medida que se va creando.

Conseguí reordenar los elementos fácilmente después de hacer pruebas con React DnD, una librería que proporciona precisamente eso, la funcionalidad de arrastrar y soltar (drag and drop).

Así queda el código del componente src/components/Elements.js que da forma a estas tarjetas.

import React from 'react';
import { useDispatch, useSelector } from "react-redux";
import { set as setInput } from "actions/inputActions";
import { set as setSelect } from "actions/selectActions";
import { set as setTextarea } from "actions/textareaActions";
import htmlTagTypes from 'constants/htmlTag/Types';
import DndCard from './Dnd/Card';

export default function Elements() {
  const dispatch = useDispatch();

  const state = useSelector(state => state);

  let elems = [
    ...state.input.items,
    ...state.select.items,
    ...state.textarea.items
  ]
  .sort((a, b) => (a.order - b.order))
  .map((elem, i) => {
    elem.order = i;
    return elem;
  });

  const reorder = (elems, dragIndex, hoverIndex) => {
    return elems.map((elem) => {
      if (elem.order === hoverIndex) {
        elem.order = dragIndex;
      } else if (elem.order === dragIndex) {
        elem.order = hoverIndex;
      }
      return elem;
    }).sort((a, b) => (a.order - b.order));
  };

  const moveCard = (dragIndex, hoverIndex) => {
    reorder(elems, dragIndex, hoverIndex);
    dispatch(
      setInput({
        items: elems.filter(elem => elem.type === htmlTagTypes.INPUT)
      })
    );
    dispatch(
      setSelect({
        items: elems.filter(elem => elem.type === htmlTagTypes.SELECT)
      })
    );
    dispatch(
      setTextarea({
        items: elems.filter(elem => elem.type === htmlTagTypes.TEXTAREA)
      })
    );
  }

  const renderCard = (elem, i) => {
    return (
      <DndCard
        key={i}
        index={elem.order}
        elem={elem}
        moveCard={moveCard}
      />
    )
  };

  return (
    <div>{elems.map((elem, i) => renderCard(elem, i))}</div>
  );
}

Ayer publiqué la app en GitHub pages

Ayer subí el form builder a GitHub Pages para que todo el mundo pueda ver el resultado de mis #100DaysOfCode, solo tuve que seguir los pasos descritos a continuación.

Instalar gh-pages:

npm install gh-pages --save-dev

Actualizar el archivo package.json de mi app añadiendo estas entradas:

  "homepage": "https://programarivm.github.io/formbuilder",
  "scripts": {
    "predeploy": "npm run build",
    "deploy": "gh-pages -d build",
    ...

Crear los ficheros finales para subirlos a producción:

$ npm run build

> my-app@0.1.0 build
> react-scripts build

Creating an optimized production build...
Compiled successfully.

File sizes after gzip:

  123.93 KB  build/static/js/2.f9321b94.chunk.js
  3.92 KB    build/static/js/main.8846478e.chunk.js
  781 B      build/static/js/runtime-main.befb0c24.js

The project was built assuming it is hosted at /formbuilder/.
You can control this with the homepage field in your package.json.

The build folder is ready to be deployed.

Find out more about deployment here:

  https://cra.link/deployment

Desplegar la aplicación:

$ npm run deploy

> my-app@0.1.0 predeploy
> npm run build

> my-app@0.1.0 build
> react-scripts build

Creating an optimized production build...
Compiled successfully.

File sizes after gzip:

  123.93 KB  build/static/js/2.f9321b94.chunk.js
  3.92 KB    build/static/js/main.8846478e.chunk.js
  781 B      build/static/js/runtime-main.befb0c24.js

The project was built assuming it is hosted at /formbuilder/.
You can control this with the homepage field in your package.json.

The build folder is ready to be deployed.

Find out more about deployment here:

  https://cra.link/deployment

> my-app@0.1.0 deploy
> gh-pages -d build

Published

Conclusión

Así las cosas, estoy practicando programación en cien días de código. El objetivo es construir un sencillo generador de formularios dinámicos con React, Redux y Hooks. Se implementa una arquitectura SPA (single-page application).

Primero preparé la ordenación de elementos del formulario. Escribí la estructura Redux necesaria que habilita la ordenación. También he hecho pruebas con cajas de texto con chips, y terminé reordenando las tarjetas que proporciona Material-UI gracias a la librería drag-and-drop React DnD.

El form builder está publicado en GitHub Pages. De momento esto es todo, espero que os haya gustado esta forma de compartir y aprender React y Redux.

También te puede interesar leer esto...

Previous Post Next Post