En el método drop(props, monitor, component)
, tenemos tres argumentos. Pero en el canDrop(props, monitor)
no existe el component
arg.
Quiero que cuando un elemento se arrastra sobre otro componente, "divido" ese componente en dos partes del 50% (altura). Si el artículo se deja caer en la parte superior, se colocará encima de él, si se deja caer en la parte inferior, se colocará después.
Ya tengo el código que detecta si el componente se deja caer en la parte superior / inferior. Lo que quiero ahora es agregar una clase en función de si el mouse está en la parte superior / inferior (pero durante la acción de desplazamiento, no la acción de soltar).
No sé si puedo usar el método hover(props, monitor, component)
, porque no veo cómo se supone que debo agregar una propiedad en mi componente (que agregaría una clase CSS en el DOM o similar) desde ya que no pertenece al componente en sí y no tiene acceso a su contexto y así sucesivamente.
Supongo que me estoy perdiendo algo. :)
¡Oye, estás de suerte! He implementado casi exactamente esto antes, así que permítanme armar un poco de código.
Bien, entonces la idea es que desea establecer el estado en su componente, y luego, cuando se establece el estado, agrega la clase.
Digamos que estás arrastrando un Foo a un bar:
import React, { Component } from "react";
import classNames from "classnames";
class Bar extends Component {
constructor(props) {
super(props);
this.state = {
// Where a foo is hovering over the component. One of null, "above", or "below"
fooHover: null,
};
}
render() {
const { fooHover } = this.state;
const classes = classNames("bar", {
"bar-above": fooHover === "above",
"bar-below": fooHover === "below",
});
}
}
¡La única pregunta es cómo establecemos ese estado!
import { findDOMNode } from "react-dom";
const barTarget = {
...
hover(props, monitor, component) {
if (!monitor.canDrop()) {
return;
}
const rawComponent = undecorate(component); // undecorate described below
const { y } = monitor.getClientOffset();
const { top, height } = findDOMNode(component).getBoundingClientRect();
if (y < top + height/2) {
rawComponent.setFooHover("above"); // setFooHover described below
} else {
rawComponent.setFooHover("below");
}
},
...
}
Básicamente, desea llamar a un método de instancia de su componente, setFooHover
, que llamará setState
. Eso debería verse así por razones de rendimiento:
// In Bar
setFooHover(fooHover) {
if (fooHover !== this.state.fooHover) {
this.setState({ fooHover });
}
}
La razón por la que llamo undecorate
arriba es que react-dnd usa componentes de orden superior y queremos los componentes sin empaquetar. En mi opinión, esta es una complicación desagradable de componentes de orden superior. Tal vez podrías implementar esto enviando una acción a tu tienda de flujo o algo así, pero en caso de que decidas hacerlo como yo, aquí está mi undecorate
:
function undecorate(component) {
let curr = component;
while (typeof curr.getDecoratedComponentInstance === "function") {
curr = curr.getDecoratedComponentInstance();
}
return curr;
}
Ahora bien, si implementa esto, todo se verá bien excepto por un pequeño problema. ¡La clase nunca se va! Nadie llama a setHoverState(null)
(no existe una función endHover en su barTarget). La forma correcta de manejar esto es usar componentWillReceiveProps
así:
componentWillReceiveProps(nextProps) {
if (this.props.isFooOver && !nextProps.isFooOver) {
this.setState({ fooHover: null });
}
}
(No olvide agregar { isFooOver: monitor.isOver() }
en su función de recolección)
¡Déjame saber como va!
¡Muchas gracias por la explicación detallada! Yo lo haré saber :)
Finalmente, utilicé el enfoque de Redux, así que básicamente envié un evento a través de un reductor cuando se disparó el evento de caída.
drop: (propsTargetItem, monitor, targetItem) ->
draggedItem = monitor.getItem()# Item that has been dragged and eventually dropped.
coordsDrop = monitor.getClientOffset()# Coords of the drop action.
diff = monitor.getDifferenceFromInitialOffset()# Comparison of the initial click (dragging start) against the end of the click (dropping start).
patternId = draggedItem.patternId
patternPosition = draggedItem.position
targetId = propsTargetItem.context.patternId
targetPosition = propsTargetItem.context.position
isCursorAboveHalf = GeoService.isAboveHalf(ReactDOM.findDOMNode(targetItem), coordsDrop.y)
# Extract positions in the array of children.
oldPosition = draggedItem.position# Current position of the item, which will be the old position as soon as the item is moved. (by the server through sockets)
newPosition = calcDropPosition(targetId, patternId, patternPosition, targetPosition, isCursorAboveHalf)# Position where we will move the dragged item.
# Dispatch only if the position has changed and is set.
if oldPosition isnt newPosition and newPosition?
draggedItem.moveDnDPattern(Object.assign({}, draggedItem, {newPosition: newPosition, oldPosition: oldPosition}))
Comentario más útil
Bien, entonces la idea es que desea establecer el estado en su componente, y luego, cuando se establece el estado, agrega la clase.
Digamos que estás arrastrando un Foo a un bar:
¡La única pregunta es cómo establecemos ese estado!
Básicamente, desea llamar a un método de instancia de su componente,
setFooHover
, que llamarásetState
. Eso debería verse así por razones de rendimiento:La razón por la que llamo
undecorate
arriba es que react-dnd usa componentes de orden superior y queremos los componentes sin empaquetar. En mi opinión, esta es una complicación desagradable de componentes de orden superior. Tal vez podrías implementar esto enviando una acción a tu tienda de flujo o algo así, pero en caso de que decidas hacerlo como yo, aquí está miundecorate
:Ahora bien, si implementa esto, todo se verá bien excepto por un pequeño problema. ¡La clase nunca se va! Nadie llama a
setHoverState(null)
(no existe una función endHover en su barTarget). La forma correcta de manejar esto es usarcomponentWillReceiveProps
así:(No olvide agregar
{ isFooOver: monitor.isOver() }
en su función de recolección)¡Déjame saber como va!