hand Inicio
hand JSBloqs
hand GutenBloqs
Qu茅?
noched铆a

DESARROLLO WEB con
REACT y WORDPRESS

Ap煤ntate a la newsletter (escribo algo de tanto en cuanto)
Wallpaper)

Programar una herramienta DAFO con React

800 palabras
3 minutos
July 13, 2020
cursosreactcss

Vamos a programar una herramienta para definirnos un DAFO online con React y con paneles al estilo Trello

Pr贸logo

Por DAFO se entiende el t铆pico gr谩fico de Debilidades, Amenazas, Fortalezas y Oportunidades, al que tambi茅n se le llama FODA, o en ingl茅s SWOT

Se trata de un simple esquema a rellenar que nos ayuda a estructurar nuestras ideas y a saber d贸nde estamos, bien sea nosotros como personas, o nuestro proyecto, o nuestra empresa

Lo mejor del DAFO es que es un ejercicio gratuito y que nos da un punto de partida para situarnos y empezar (o no empezar) con cierto criterio

Lo peor son sus riesgos que se resumen en lo subjetivo del asunto, y es que al final podemos tirar de sesgo de confirmaci贸n y ganar una falsa seguridad que nos deje peor de lo que est谩bamos (tienes una lista de sesgos aqu铆)

En este curso vamos a construir una herramienta que nos va a permitir

  • Rellenar este diagrama en una p谩gina web
  • Tener movilidad al estilo Trello
  • Guardar esos datos en el storage del navegador para implementar un backend persistente dentro del navegador del usuario

C贸mo queda al final? Tienes la herramienta en dafo

Vamos all谩

  1. Pr贸logo
  2. Recordando react-beautiful-dnd
  3. useLocalStorage
  4. El c贸digo

Recordando react-beautiful-dnd

Tienes el curso de la librer铆a aqu铆, necesario para explorar c贸mo funciona esa librer铆a

Aqu铆 la estructura de datos que voy a utilizar aqu铆 tendr谩 esta forma

js
const data = [
{
columnId: 'd',
text: 'Debilidades',
ref: 0,
items: [
{
id: 'elemento-1',
idNum: 1,
text: 'Debilidad 1',
},
],
},
{
columnId: 'a',
text: 'Amenazas',
ref: 1,
items: [
{
id: 'elemento-2',
idNum: 2,
text: 'Amenaza 1',
},
],
},
{
columnId: 'f',
text: 'Fortalezas',
ref: 2,
items: [
{
id: 'elemento-3',
idNum: 3,
text: 'Fortaleza 1',
},
],
},
{
columnId: 'o',
text: 'Oportunidades',
ref: 3,
items: [
{
id: 'elemento-4',
idNum: 4,
text: 'Oportunidad 1',
},
],
},
]

Esto es, un array con 4 columnas fijas definidas por un objeto, y cada objeto cuenta con columnId, text, ref y items

  • columnId es un identificador y tiene que ser 煤nico
  • text es el texto que nos saldr谩 en cada columna (si queremos)
  • ref es el lugar donde almacenaremos las referencias de cada columna (con useRef), pero que aqu铆 s贸lo almacenan un 铆ndice
  • items es un array con cada 铆tem que tendremos en cada columna, al que definiremos con id, idNum y text

Te podr铆as preguntar porque en la variable ref no asignamos ya un useRef en lugar de un n煤mero

El motivo es que esta estructura la almacenaremos en localStorage, y all铆 no podemos almacenar funciones (que es lo que es un useRef)

La soluci贸n? Almacenar el 铆ndice que nos servir谩 de baliza para localizar la referencia real

Otra soluci贸n ser铆a crear otra estructura para almacenar las referencias, o simplemente borrar esa propiedad antes de almacenarlo en localStorage (no necesitamos almacenarla en absoluto)

Luego necesitamos volcar esta estructura, y lo hacemos con algo tipo esto

js
<DragDropContext onDragEnd={onDragEnd}>
<div>
{data.map((el, i) => (
<div key={`colum${i}`}>
<div>{el.text}</div>
<Droppable droppableId={el.columnId} key={el.columnId}>
{(provided, snapshot) => (
<div ref={provided.innerRef}>
{el.items.map((it, i) => (
<Draggable draggableId={it.id} index={i} key={it.id}>
{(provided, snapshot) => (
<div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
{it.text}
</div>
)}
</Draggable>
))}
{provided.placeholder}
</div>
)}
</Droppable>
</div>
))}
</div>
</DragDropContext>

Si te fijas aqu铆 no aparece por ning煤n lado idNum

Para qu茅 me sirve? Es un an谩logo a id, pero mientras id es un texto con un n煤mero, idNum almacena s贸lo el n煤mero

Lo que yo querr铆a es s贸lo el n煤mero

Por qu茅? Porque cada vez que se a帽ada alg煤n elemento en esta estructura tendr茅 que asignarle un nuevo identificador, y para asegurarme que no repito buscar茅 el 铆ndice m谩s alto y le sumar茅 1

Si no tengo un n煤mero y tengo un texto tendr茅 que procesar el texto para extraer un n煤mero, por lo que si ya tengo un n煤mero -> perfecto

Pero y para qu茅 necesito un string? Porque nos lo piden en la documentaci贸n de la librer铆a documentaci贸n

Como antes, podr铆a fabricar ese string sobre la marcha, pero si ya lo tengo en una variable todo eso que me ahorro (y me sirve de recordatorio de este requerimiento)

Luego necesitamos establecer las funciones que reordenan los 铆tems

js
const reorder = (list, startColumn, endColumn, startIndex, endIndex) => {
const newList = Array.from(list)
const startColumnIndex = newList.findIndex(item => item.columnId === startColumn)
const endColumnIndex = newList.findIndex(item => item.columnId === endColumn)
const [removed] = newList[startColumnIndex].items.splice(startIndex, 1)
newList[endColumnIndex].items.splice(endIndex, 0, removed)
return newList
}

Esto ya lo vimos en el curso que enlazo arriba, b谩sicamente localizamos el panel (el de origen y el de llegada) y le quitamos / a帽adimos el 铆tem al panel correspondiente

Luego necesitamos definir la funci贸n que llama a la anterior en cuanto soltamos el 铆tem

js
const onDragEnd = result => {
const { destination, source, draggableId } = result
if (!destination) return
if (destination.droppableId === source.droppableId && destination.index === source.index) return
const new_elements = reorder(dafo, source.droppableId, destination.droppableId, source.index, destination.index)
setDafo(new_elements)
}

Y ya lo tenemos

useLocalStorage

Este custom hook nos permitir谩 guardar el estado, algo que normalmente har铆amos con useState, pero que ahora lo haremos en el storage del navegador por lo que a cada nueva sesi贸n seremos capaces de recuperar lo que ten铆amos en la 煤ltima sesi贸n

Por qu茅 crear un custom hook?

Porque as铆 podr茅 utilizarlo del mismo modo que utilizo useState, fant谩stico

El c贸digo se basa ampliamente en los varios usuarios que han hecho implementaciones muy similares entre ellas, por ejemplo:

脡ste es el c贸digo

js
import { useState } from 'react'
// definimos la funci贸n para recibir una key y un initialValue
export const useLocalStorage = (key, initialValue) => {
// generamos un estado donde el valor inicial es una funci贸n
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key)
return item ? JSON.parse(item) : initialValue
} catch (error) {
console.log(error)
return initialValue
}
})
// definimos una funci贸n para asignar valor
const setValue = value => {
try {
const valueToStore = value instanceof Function ? value(storedValue) : value
setStoredValue(valueToStore)
window.localStorage.setItem(key, JSON.stringify(valueToStore))
} catch (error) {
console.log(error)
}
}
// devolvemos el valor y la funci贸n para asignar valor, lo mismo que nos devuelve useState()
return [storedValue, setValue]
}

El valor inicial que le damos al useState se basa en window.localStorage (documentaci贸n oficial)

Y en la funci贸n para asignar un nuevo valor setValue, tambi茅n tenemos la opci贸n de pasarle una funci贸n en lugar de un valor, y si es el caso ejecutamos la funci贸n con el valor almacenado (algo que aqu铆 no utilizaremos, pero da m谩s versatilidad al hook)

js
const valueToStore = value instanceof Function ? value(storedValue) : value

Para almacenar una estructura de datos utilizamos JSON.stringify, que nos convierte los objetos en strings, y luego JSON.parse que nos revierte la conversi贸n

El quid de todo esto es que cambiamos en la misma operaci贸n el estado useState, que nos repinta la aplicaci贸n, con el valor de localStorage, que no nos repinta nada

Y para utilizar el hook, pues como con el useState

js
const [dafo, setDafo] = useLocalStorage('kwdafo', data))

Es decir, le mandamos una clave de nombre kwdafo y el valor con el que queremos inicializar ese estado, que ser谩 con la variable data

El c贸digo

Ya podemos pasar al c贸digo (con cada secci贸n descrita en los comentarios)

Recuerda importar las librer铆as @emotion/core, @emotion/styled y react-beautiful-dnd en el codesandbox

jsx
import React, { useRef, useState } from 'react'
import styled from '@emotion/styled'
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd'
/**
* Custom hook para sustituir el useState para utilizar localStorage
*/
const useLocalStorage = (key, initialValue) => {
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key)
return item ? JSON.parse(item) : initialValue
} catch (error) {
console.log(error)
return initialValue
}
})
const setValue = value => {
try {
const valueToStore = value instanceof Function ? value(storedValue) : value
setStoredValue(valueToStore)
window.localStorage.setItem(key, JSON.stringify(valueToStore))
} catch (error) {
console.log(error)
}
}
return [storedValue, setValue]
}
/**
* La estructura de datos de los paneles
* Esta estructura la modificamos y luego la transformamos a HTML
*/
const data = [
{
columnId: 'd',
text: 'Debilidades',
ref: 0,
items: [
{
id: 'elemento-1',
idNum: 1,
text: 'Debilidad 1',
},
],
},
{
columnId: 'a',
text: 'Amenazas',
ref: 1,
items: [
{
id: 'elemento-2',
idNum: 2,
text: 'Amenaza 1',
},
],
},
{
columnId: 'f',
text: 'Fortalezas',
ref: 2,
items: [
{
id: 'elemento-3',
idNum: 3,
text: 'Fortaleza 1',
},
],
},
{
columnId: 'o',
text: 'Oportunidades',
ref: 3,
items: [
{
id: 'elemento-4',
idNum: 4,
text: 'Oportunidad 1',
},
],
},
]
/**
* Funci贸n para reordenar la estructura de datos
*/
const reorder = (list, startColumn, endColumn, startIndex, endIndex) => {
const newList = Array.from(list)
const startColumnIndex = newList.findIndex(item => item.columnId === startColumn)
const endColumnIndex = newList.findIndex(item => item.columnId === endColumn)
const [removed] = newList[startColumnIndex].items.splice(startIndex, 1)
newList[endColumnIndex].items.splice(endIndex, 0, removed)
return newList
}
/**
* Elemento principal
*/
export const Tool = () => {
// 4 referencias para los 4 paneles del DAFO - los 4 inputs
const refs = [useRef(), useRef(), useRef(), useRef()]
// Para guardar la estructura de datos
const [dafo, setDafo] = useLocalStorage('kwdafo', JSON.parse(JSON.stringify(data)))
// Funci贸n para resetear la estructura de datos
const resetLocalStorage = () => setDafo(JSON.parse(JSON.stringify()))
// Funci贸n que se ejecuta al dejar los elementos y que llama la funci贸n anterior de reorder
const onDragEnd = result => {
const { destination, source, draggableId } = result
if (!destination) return
if (destination.droppableId === source.droppableId && destination.index === source.index) return
const new_elements = reorder(dafo, source.droppableId, destination.droppableId, source.index, destination.index)
setDafo(new_elements)
}
// funci贸n para a帽adir un elemento
const addElement = panel => {
// generamos el que ser谩 el 铆ndice para el nuevo elemento
const highestIndex = Math.max.apply(null, dafo.map(el => el.items.map(it => it.idNum)).flat()) + 1
// encontramos la columna
const index = dafo.findIndex(el => el.columnId === panel.columnId)
// copiamos la estructura anterior para no cambiarla
const newDafo = [...dafo]
// a帽adimos a la nueva estructura el nuevo elemento
newDafo[index].items = [
{ id: 'elemento-' + highestIndex, idNum: highestIndex, text: refs[panel.ref].current.value },
...dafo[index].items,
]
// guardamos la nueva estructura
setDafo(newDafo)
// Borramos el valor del input
refs[panel.ref].current.value = ''
}
// funci贸n para borrar un elemento
const removeElement = el => {
// encontramos el 铆ndice del elemento a quitar
const index = dafo.findIndex(elem => elem.items.findIndex(it => it.id === el.id) !== -1)
// copiamos la estructura
const newDafo = [...dafo]
// volvemos a copiar los elementos filtrando el que queremos remover (una estrategia para quitar el elemento)
newDafo[index].items = dafo[index].items.filter(it => it.id !== el.id)
// volvemos a asignar la estructura al estado
setDafo(newDafo)
}
// volcamos el contenido a html siguiendo las instrucciones de la librer铆a para el drag-n-drop
return (
<>
<Reset onClick={resetLocalStorage}>Resetear</Reset>
<DragDropContext onDragEnd={onDragEnd}>
<Grid>
{dafo.map((el, i) => (
<div key={`colum${i}`}>
<div>{el.text}</div>
<Droppable droppableId={el.columnId} key={el.columnId}>
{(provided, snapshot) => (
<div ref={provided.innerRef}>
{el.items.map((el, i) => (
<Draggable draggableId={el.id} index={i} key={el.id}>
{(provided, snapshot) => (
<Item ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
{el.text}
<span onClick={() => removeElement(el)}></span>
</Item>
)}
</Draggable>
))}
{provided.placeholder}
</div>
)}
</Droppable>
<div>{/*expand*/}</div>
<div>
<TextArea ref={refs[el.ref]} /> <Button onClick={() => addElement(el)}>A帽adir</Button>
</div>
</div>
))}
</Grid>
</DragDropContext>
</>
)
}
// necesitamos exportar un default para que nos funcione el codesandbox
export default Tool
/**
* Y aqu铆 ya vienen todos los estilos con @emotion
*/
const Title = styled.div`
font-weight: 700;
text-transform: uppercase;
font-size: 1.5em;
`
const Item = styled.div`
text-transform: uppercase;
background: #eecaca;
border-radius: 8px;
padding: 10px;
margin: 10px 0px;
`
const TextArea = styled.textarea`
background: #f1efef;
border: unset;
border-radius: 8px;
width: -webkit-fill-available;
margin: 10px 0px;
resize: none;
height: 38px;
`
const Button = styled.button`
border-radius: 8px;
height: 40px;
font-weight: 700;
text-transform: uppercase;
background: #ff0000b0;
mix-blend-mode: luminosity;
color: #fff;
cursor: pointer;
border: unset;
transition: 0.2s ease;
&:hover {
background: #000;
}
`
const Grid = styled.div`
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
grid-gap: 10px;
`
const Reset = styled.div`
cursor: pointer;
border-radius: 8px;
height: 100%;
font-weight: 700;
text-transform: uppercase;
background: #ff0000b0;
mix-blend-mode: luminosity;
color: #fff;
transition: 0.2s ease;
width: fit-content;
padding: 10px;
margin-bottom: 20px;
&:hover {
background: #000;
}
`
Ver en CodeSandBox

馃檵鈥嶁檪锔

Qu茅 tal el curso?

馃憣 Bien 馃檶馃檶
馃憤 Bien, pero algunas cosas podr铆an explicarse mejor 馃槵
馃し鈥嶁檪锔 Da por sentadas demasiadas cosas 馃槖
馃し鈥嶁檪锔 A ver, hay poca chicha 馃槵
馃し鈥嶁檪锔 Los ejemplos no son muy claros 馃檱鈥嶁檪锔
馃し鈥嶁檪锔 No se entiende, est谩 mal escrito 馃憥
鉁嶏笍 Hay errores, rev铆salo en cuanto puedas 馃檹
Enviar Feedback 鉁嶏笍
El texto est谩 en blanco!
Gracias por enviarme tu opini贸n
馃憤

Si quieres explorar m谩s cursos y m谩s entradas en el blog, los tienes todos en la p谩gina principal, y si el contenido te ha ayudado si lo compartes me ayudas a dar difusi贸n 馃憤

Privacidad
by kuworking.com
[ 2020 >> kuworking ]