Skip to main content

Droppable

Droppable es el componente base que define una zona donde se pueden soltar elementos (Draggable) dentro del sistema de DnD.
Se integra con el DNDProvider y con el slice interno para saber:

  • QuĂ© elemento se está arrastrando encima.
  • QuĂ© se ha soltado ahĂ­.
  • El historial de drops en esa zona.

Su API está basada en render props, para que controles totalmente cómo se ve y se comporta.


Firma básica​

type DroppableChildrenProps<T> = {
/** Item que se está arrastrando encima de esta zona (o null si no hay) */
isOver: T | null;
/** Ăšltimo item soltado en esta zona (o undefined si nunca se ha soltado nada) */
data: T | undefined;
/** Historial completo de items que se han soltado aquĂ­, en orden cronolĂłgico */
history: T[];
/** Función para “deshacer” el último drop en esta zona */
antiDrop: () => void;
};

export interface DroppableProps<T> {
/** ID Ăşnico de la zona de drop (si no se pasa, se genera un UUID) */
id?: string;

/** Deshabilita completamente esta zona de drop */
disable?: boolean;

/** Estilos del contenedor externo */
style?: ViewStyle;

/** Activa animación tipo “latido” mientras hay un drag encima */
heartAnimation?: boolean;

/**
* Callback que se dispara cada vez que ocurre un drop final en esta zona.
* Recibe el dato soltado, el historial actualizado y el id de la zona.
*/
onDrop?: (info: { data: T; history: T[]; id: string }) => void;

/** Render prop que recibe el estado del droppable y devuelve JSX */
children: (props: DroppableChildrenProps<T>) => React.ReactNode;
}

Uso tĂ­pico:

<Droppable<MyData> id="answer-slot">
{({ isOver, data, history, antiDrop }) => (
// Tu UI aquĂ­
)}
</Droppable>

Props en detalle​

PropTipoDefaultDescripciĂłn
idstringUUIDIdentificador de la zona. Si no se pasa, se genera uno internamente. Se usa para registrar la zona en el store.
disablebooleanfalseSi está en true, la zona se desregistra y deja de aceptar drops.
styleViewStyle—Estilo del View externo que envuelve el área de drop.
heartAnimationbooleanfalseSi está en true, aplica una animación de “latido” mientras un draggable pasa por encima.
onDrop(info: { data: T; history: T[]; id: string }) => void—Se llama cada vez que un drop final ocurre en esta zona, con el dato actual, el historial y el id.
children(props: DroppableChildrenProps<T>) => ReactNode—Render prop que recibe el estado del droppable (isOver, data, history, antiDrop).

Props de la render prop​

Dentro de children recibes:

  • isOver: T | null

    • null: ningĂşn draggable está sobre esta zona.
    • T: hay un draggable encima, y isOver contiene su data.
    • Ăšsalo tanto como bandera (!!isOver) como para inspeccionar el contenido que se está arrastrando.
  • data: T | undefined

    • Ăšltimo dato que se soltĂł en esta zona.
    • undefined si todavĂ­a no ha caĂ­do nada.
  • history: T[]

    • Lista de todos los data que se han soltado aquĂ­, en orden.
    • En modos multi / multi-disappear se seguirá acumulando.
  • antiDrop: () => void

    • “Deshace” el Ăşltimo drop:

      • Limpia data si era el Ăşltimo elemento.
      • Hace pop() sobre history.
      • Notifica al slice (actions.antiDrop) para mantener el store sincronizado.

Ejemplo básico​

Zona de drop simple para mostrar el Ăşltimo elemento soltado:

import { View, Text } from "react-native";
import { DNDProvider, Droppable, Draggable } from "@components/DnDRevolution";

type Option = { id: number; text: string };

export const BasicDroppableExample = () => (
<DNDProvider<Option> mode="remplacement-multi">
<Droppable<Option> id="drop-1">
{({ isOver, data, history, antiDrop }) => (
<View
style={{
width: 220,
height: 80,
borderWidth: 2,
borderRadius: 12,
borderColor: isOver ? "green" : "gray",
alignItems: "center",
justifyContent: "center",
}}
>
<Text>
{data?.text ?? "Suelta una opciĂłn aquĂ­"}
</Text>

{history.length > 0 && (
<Text style={{ fontSize: 10, marginTop: 4 }}>
Historial: {history.map((h) => h.text).join(", ")}
</Text>
)}

{history.length > 0 && (
<Text onPress={antiDrop} style={{ marginTop: 4 }}>
Deshacer Ăşltimo drop
</Text>
)}
</View>
)}
</Droppable>

<Draggable<Option> data={{ id: 1, text: "OpciĂłn A" }}>
{({ data, isDragging }) => (
<View
style={{
marginTop: 24,
paddingHorizontal: 16,
paddingVertical: 8,
borderRadius: 8,
backgroundColor: isDragging ? "#4f46e5" : "#6366f1",
}}
>
<Text style={{ color: "white" }}>{data.text}</Text>
</View>
)}
</Draggable>
</DNDProvider>
);

Ejemplo con heartAnimation​

<Droppable<Option>
id="animated-drop"
heartAnimation
onDrop={({ data, history, id }) => {
console.log("Dropped on", id, ":", data, "historial:", history.length);
}}
>
{({ isOver, data }) => (
<View
style={{
width: 200,
height: 80,
borderRadius: 16,
borderWidth: 2,
borderColor: isOver ? "#22c55e" : "#9ca3af",
alignItems: "center",
justifyContent: "center",
}}
>
<Text>{data?.text ?? "Arrastra algo aquĂ­ đź’«"}</Text>
</View>
)}
</Droppable>

Mientras un Draggable está encima de esta zona y heartAnimation es true, el componente aplica internamente:

  • Una animaciĂłn de escala repetida (tipo “latido”) usando withRepeat + withSequence + withTiming.

Flujo interno (cómo se integra con el store)​

Internamente Droppable:

  1. Genera/usa un id (idString):

    • Si no proporcionas id, se crea un UUID via react-native-uuid.
  2. Se registra en el slice de DnD:

    • registerDroppable({ id }) al montarse (si disable es false).
    • unregisterDroppable({ id }) al desmontarse o cuando cambias disable a true.
  3. Mide su rectángulo en pantalla:

    • Usa ref.current?.measureInWindow(...) para obtener { x, y, w, h }.
    • EnvĂ­a estas coordenadas al slice con updateDroppableArea({ id, ...rect }).
    • Vuelve a medir cuando el estado moved del slice cambia (cada vez que se arrastra algo).
  4. Escucha el estado global:

    • overId: ID del droppable que está actualmente “debajo” del draggable.
    • draggingData: datos (T) del draggable activo.
    • lastDrop: Ăşltimo drop confirmado por el slice.
    • mode: modo global (remplacement-multi, multi, multi-disappear, etc.).
    • deleteData: bandera global que se activa cuando se limpia todo (cleanAll()).
  5. Actualiza su estado local:

    • isOver se calcula como: overId === idString && draggingData !== null ? draggingData : null.

    • Si lastDrop.droppableId === idString y el drop es válido:

      • Actualiza droppedData (para la prop data).
      • Hace push en history.
      • Llama onDrop con { data, history, id }.
    • Si deleteData es true, resetea droppedData (para integrarse con el botĂłn de limpiar).

  6. Expone todo a la UI vĂ­a children({ isOver, data, history, antiDrop }).

Esto te permite construir logĂ­sticas de correcciĂłn, feedback visual y resets sin tener que pelearte directamente con Redux.


Cuándo usar Droppable directo​

Usa Droppable cuando:

  • Quieres definir una zona de drop custom (layout libre).
  • Necesitas lĂłgica especial para cĂłmo se muestran los datos/historial.
  • Estás creando un patrĂłn nuevo que no encaja con PadDroppable u otros componentes de alto nivel.

Si tu caso ya es un “pad” de respuesta (números, letras, etc.), normalmente será más cómodo usar:

y dejar Droppable como bloque base subyacente.


Siguiente sección recomendada: 👉 MultipleDraggables