margin-js | kuworking

Gatsby11tyAstro|UnoCSS|SolidJS

Ordenar listas con un custom hook en React | JavaScript

Ojo, este artículo no se ha actualizado desde hace más de 12 meses

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

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

Esto de arriba es lo mismo que hacer lo siguiente

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

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

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

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

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}

Esto en React sería así

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

Pero con los hooks podemos extraer la lógica del tema y nos queda todo más bonito y útil

Primero lo extraemos en una función al uso, sort_lists

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

Y ahora extraemos la función y el comportamiento en nuestro custom hook

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, i) => (
          <li key={`lista${i}`}>
            {el.nombre}: {el.edad}
          </li>
        ))}
      </ul>
    </>
  )
}

export default Lista