Reordenar elementos con Drag & Drop

Por 9.99€ al mes tendrás acceso completo a todos los cursos. Sin matrícula ni permanencia.

npm i react-beautiful-dnd

La última vez que intenté instalar este módulo en React 18, dió error. Para evitarlo, usaremos:

npm install react-beautiful-dnd --legacy-peer-deps

En el useEffect llamaremos al método que devolverá los datos ordenados en función de la propiedad order:

import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";

const getObras = (userId) => getObrasFromUser(userId).then((data) =>  setObras(data.sort(compare)));
<DragDropContext onDragEnd={onDragEnd}>
          <Droppable droppableId="boxes" direction="horizontal">
            {(provided) => (
              <div className="boxes" {...provided.droppableProps} ref={provided.innerRef}>
                {
                  items.map((item, index) => <Draggable key={item.id} index={index} draggableId={item.id}>{(provided) => (<div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>{item.txt}</div>)}</Draggable>)
                }
              </div>)}
          </Droppable>
</DragDropContext>

Cuando soltemos el elemento que estamos desplazando, debemos gestionar su nueva posición

const onDragEnd = (result) => {
    if (!result.destination) {
      return;
    }

    const itms= reorder(
      obras,
      result.source.index,
      result.destination.index
    );

    setItems(itms) // Esto actualiza el estado de los items para que se pinten reordenados en pantalla
    updateItems(items); //Esto actualiza el valor en firebase 
}
export const reorder = (list, startIndex, endIndex) => {
    const result = Array.from(list); const [removed] = result.splice(startIndex, 1);
    result.splice(endIndex, 0, removed);
    return result.map((data, index) => ({ ...data, order: index }));
};

En la base de datos nuestros items tendrán una propiedad order cuyo valor debemos actualizar en función de la nueva posición de los elementos:

export const updateItems = async (items) => {
    const batch = writeBatch(db)
    const subColRef = collection(db, 'items');

    items.forEach(({ id, ...data }, i) => {
        batch.update(doc(subColRef, id), { ...data, order: i });
    });

    return batch.commit();
}

Reordenar componentes funcionales en lugar de elementos del DOM

Tendremos que recoger las props y pasárselos al componente funcional:

{
    lines.map((line, index) => (
       <Draggable key={line.id} index={index} draggableId={line.id}>   
           {
             (provided) => (
               <Line
                 ref={provided.innerRef}
                 draggableProps={provided.draggableProps}
                 dragHandleProps={provided.dragHandleProps}
                 key={line.id}
             />)
            }
       </Draggable>))
}

Cuando queremos referenciar un componente funcional desde el componente padre, lo exportaremos utilizando forwardRef. Es como si en lugar de definir el useRef dentro del componente Line, lo hiciésemos fuera.

Código en el componente funcional:
import { forwardRef } from 'react';
const Line = ({ draggableProps, dragHandleProps }, ref) => {
   return <div ref={ref} {...draggableProps} {...dragHandleProps}>hola</div>
}
export default forwardRef(Line);

Error típico

Este módulo puede dar errores cuando se usa en strict mode. Por tanto, puede ser necesario deshabilitarlo borrando las etiquetas correspondintes en el index.js.

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

Por 9.99€ al mes tendrás acceso completo a todos los cursos. Sin matrícula ni permanencia.