hand Inicio

SERVICIOS

Salir

nochedía

DESARROLLO WEB con
GATSBY y WORDPRESS

emoji Cursos [24]
emoji Themes Gatsby [5]
emoji Themes WordPress [2]
emoji Blog [83]
emoji Herramientas [11]

Ordenar listas con JavaScript, sort(), React y un custom hook

500 palabras
2 minutos
July 10, 2020
blogjavascript

Ordenar listas es fácil con sort(), y con con React también es fácil con useState(), y si además queremos reutilizar la rutina para siempre, más que fácil con un custom hook

  1. Función sort()
  2. Opción 1: Sin custom hooks, una función al uso
  3. Opción 2: Con un custom hook

Función sort()

Para ordenar listas, nada más sencillo que utilizar la función sort(), que funciona (puedes mirarlo aquí) simplemente comparando valores:

js
const lista = [2, 5, 4, 7]
console.log(lista.sort())
// [2, 4, 5, 7]

Esto de arriba es lo mismo que hacer lo siguiente

js
const lista = [2, 5, 4, 7]
console.log(lista.sort((a, b) => a - b))
// [2, 4, 5, 7]

sort() invoca una función que recibe dos valores, y estos son los que compararemos

Si no son números y queremos un orden alfabético, haríamos lo siguiente

js
const lista = ['juan', 'palomo', 'y', 'sus cosas']
console.log(
lista.sort((a, b) => {
if (a > b) return 1
if (a < b) return -1
return 0
})
)
// ["juan", "palomo", "sus cosas", "y"]

Pero no hace falta escribir tanto, podemos expresar lo mismo con una línea

js
const lista = ['juan', 'palomo', 'y', 'sus cosas']
console.log(lista.sort((a, b) => (a > b ? 1 : a < b ? -1 : 0)))
// ["juan", "palomo", "sus cosas", "y"]

Y si lo que tenemos son estructuras complejas, pues lo mismo

js
const lista = [
{ nombre: 'juan', edad: 50 },
{ nombre: 'alberto', edad: 40 },
{ nombre: 'aragor', edad: 2000 },
]
console.log(lista.sort((a, b) => (a.edad > b.edad ? 1 : a.edad < b.edad ? -1 : 0)))
// 0: {nombre: "alberto", edad: 40}
// 1: {nombre: "juan", edad: 50}
// 2: {nombre: "aragor", edad: 2000}

Bien

El tema es, y si esta lista la queremos mostrar en el front end?

Ningún problema, con React es tan sencillo como utilizar un estado, y en cuanto actualicemos el estado por lo que sea (un botón que se clicke, o simplemente al principio de todo) pues la lista se re-renderizará sola

Haré las dos cosas, que se ordene al principio de todo y con un botón, y con el botón cada vez la ordenaré al revés de lo que estaba

La función sort me muta el array, es decir que me modifica el array en lugar de devolver uno nuevo

Para evitarlo hago una copia del array antes de aplicar sort

La razón es porque cambiar los objetos acostumbra a ser un generador de bugs

js
import React, { useState, useEffect } from 'react'
// mi componente principal <Lista>
const Lista = () => {
// guardo el estado list de valor inicial la lista que tengo
const [list, setList] = useState([
{ nombre: 'juan', edad: 50 },
{ nombre: 'alberto', edad: 40 },
{ nombre: 'aragor', edad: 2000 },
])
// utilizo useEffect para ejecutar este código sólo una vez
useEffect(() => {
// copio la lista con [...list] y la ordeno con sort()
const sortedList = [...list].sort((a, b) => (a.edad > b.edad ? 1 : a.edad < b.edad ? -1 : 0))
// actualizo el estado con la nueva lista ya ordenada
setList(sortedList)
}, [])
// vuelco el contenido del estado `list`
return (
<>
{/* Aquí pongo el botón para reordenar la lista */}
<button
onClick={() => {
let newSortedList = [...list].sort((a, b) => (a.edad > b.edad ? 1 : a.edad < b.edad ? -1 : 0))
// si la lista después de ordenarla tiene el mismo primer elemento, lo repito a la inversa
// (claro que esto es ineficiente, lo suyo sería habilitar otro estado para guardar el tipo de ordenamiento que hemos hecho)
if (newSortedList[0] === list[0])
newSortedList = [...list].sort((b, a) => (a.edad > b.edad ? 1 : a.edad < b.edad ? -1 : 0))
setList(newSortedList)
}}
>
Ordenar
</button>
{/* Y aquí la lista, cada vez que el estado cambie este componente se va a repintar y a actualizar la vista */}
<ul>
{list.map(el => (
<li>
{el.nombre}: {el.edad}
</li>
))}
</ul>
</>
)
}
export default Lista
Ver en CodeSandBox

Listos! Ya estamos ordenando a la manera React, nada de tocar el DOM sino trabajar con datos, actualizar los datos, y dejar que React actualize el DOM cuando estos datos cambien

Y si queremos reaprovechar esta función?

Para eso tenemos los custom hooks, que no son más que maneras de encapsular código para que su reutilización sea la mar de cómoda

Opción 1: Sin custom hooks, una función al uso

js
import React, { useState, useEffect } from 'react'
// Esta es mi función para reutilizar `sort`
const sort_lists = (key, list, inverse) =>
inverse
? [...list].sort((b, a) => (a[key] > b[key] ? 1 : a[key] < b[key] ? -1 : 0))
: [...list].sort((a, b) => (a[key] > b[key] ? 1 : a[key] < b[key] ? -1 : 0))
// Y este mi componente principal
const Lista = () => {
const [list, setList] = useState([
{ nombre: 'juan', edad: 50 },
{ nombre: 'alberto', edad: 40 },
{ nombre: 'aragor', edad: 2000 },
])
useEffect(() => {
setList(sort_lists('edad', list))
}, [])
return (
<>
<button
onClick={() => {
let newSortedList = sort_lists('edad', list)
if (newSortedList[0] === list[0]) newSortedList = sort_lists('edad', list, true)
setList(newSortedList)
}}
>
Ordenar
</button>
<ul>
{list.map(el => (
<li>
{el.nombre}: {el.edad}
</li>
))}
</ul>
</>
)
}
export default Lista
Ver en CodeSandBox

Ahora si queremos reordenar listas, basta con que copiemos la función sort_lists() en nuestro código y ya lo tendremos

Eso sí, en realidad hemos encapsulado exclusivamente la funcionalidad para ordenar listas

En el código seguimos necesitando implantar la lógica de utilizar un useEffect para ordenar la lista al principio, y un useState para guardar la lista en un estado

Y si pudiésemos también encapsular esta lógica?

Sí que podemos, vamos con el custom hook

Opción 2: Con un custom hook

En lugar de definir una función, definimos un hook que como tal tiene que empezar con el useLoQueSea

js
import React, { useState, useEffect } from 'react'
// Este es mi custom hook
const useSortTable = (listToSort, originalKey) => {
// definimos un estado
const [list, setList] = useState(listToSort)
// definimos la función anterior pero sin especificar la lista ya que será la principal
const sort_lists = (key, inverse) =>
inverse
? [...list].sort((b, a) => (a[key] > b[key] ? 1 : a[key] < b[key] ? -1 : 0))
: [...list].sort((a, b) => (a[key] > b[key] ? 1 : a[key] < b[key] ? -1 : 0))
// ordenamos la lista con el useEffect
useEffect(() => {
setList(sort_lists(originalKey))
}, [])
// devolvemos el estado que contiene la lista
// ..el método para actualizar el estado
// ..y el método para ordenarla
return [list, setList, sort_lists]
}
// Ahora seguimos con el componente principal
const Lista = () => {
// y aquí utilizamos el hook
const [list, setList, sort] = useSortTable(
[
{ nombre: 'juan', edad: 50 },
{ nombre: 'alberto', edad: 40 },
{ nombre: 'aragor', edad: 2000 },
],
'edad'
)
return (
<>
<button
onClick={() => {
let newSortedList = sort('edad')
if (newSortedList[0] === list[0]) newSortedList = sort('edad', true)
setList(newSortedList)
}}
>
Ordenar
</button>
<ul>
{list.map(el => (
<li>
{el.nombre}: {el.edad}
</li>
))}
</ul>
</>
)
}
export default Lista
Ver en CodeSandBox

Si te fijas verás que el hook en realidad engloba la función de antes y también la lógica del useEffect y useState

Qué le falta a este código?

  • La lógica para invertir la búsqueda es ineficiente

Si queremos un botón que nos ordene para una dirección o para otra, la manera es almacenar la dirección en otro estado y en función de esto ordenarla de una manera u otra

Aquí la estamos ordenando dos veces

  • La lógica para invertir la búsqueda no está bien integrada en el hook

Lo suyo sería tener una función para invertir la búsqueda y que esta función también te actualizase el estado, algo tipo

js
<button onClick={sort}>Ordenar</button>

Se puede hacer sin problemas siempre que antes se solucione el problema anterior

  • La función list.map genera componentes que no tienen una key asociada, y por eso nos sale el warning de rigor

Hace falta añadir la key manualmente en estos casos

js
{
list.map((el, i) => (
<li key={`lista${i}`}>
{el.nombre}: {el.edad}
</li>
))
}

Listos!

🙋‍♂️

Qué tal la entrada?
👌 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 🙏

Más entradas

Privacidad
by kuworking.com
[ 2020 >> kuworking ]