reactjs - React + PIXI + Zustand - Estado desactualizado en los manejadores del ratón
Tengo un lienzo de Pixi con manejadores de puntero. Puedes hacer clic en un punto de una ‘secuencia’ y moverlo.
Los controladores del ratón tenían un estado obsoleto. Para solucionarlo, estoy recreando los controladores cada vez que se modifica la secuencia, de modo que siempre tengan un estado actualizado. Esto solucionó el error de estado. Pero no funciona si el estado se modifica al soltar el ratón. Crear nuevos controladores desde dentro de un controlador, comprensiblemente, conserva el estado obsoleto, incluso mediante un temporizador de callback. Así que ahora todos los errores están solucionados, excepto que no se puede arrastrar un punto dos veces :)
Soy nuevo en React y siento que lo estoy haciendo todo mal. ¿Alguna sugerencia?
Así es como obtengo el estado de la secuencia:
const SequenceCanvas = React.forwardRef((props: any, ref) => {
const canvasRef = useRef(null)
const sequence: Sequence = useSequenceStore(state => { return state.sequence} )
Aquí es como recreo los controladores:
const addHandlers = () : void => {
console.log(`new mouse handlers: numsteps ${sequence.numSteps}`)
parentContainer.removeAllListeners()
parentContainer.on('pointerdown', (e: any) => {
handlePointerDown(e)
})
parentContainer.on('dblclick', (e: any) => {
handleDoubleClick(e)
})
parentContainer.on('pointerup', (e: any) => {
handlePointerUp(e)
})
parentContainer.on('mouseupoutside', (e: any) => {
handlePointerUp(e)
})
parentContainer.on('pointermove', (e: any) => {
handlePointerMove(e)
})
}
useEffect(() => {
if (!mouseIsDown) {
// Recreate mouse handlers each time the sequence changes. Otherwise the sequence state is stale
addHandlers()
}
}, [sequence]);
const handlePointerUp = (e: any) => {
console.log(`handlePointerUp ${mouseIsDown}`)
mouseIsDown = false;
dragStepNum = -1
isDraggingVertical = false;
isDraggingHorizontal = false;
setTimeout(() => {
addHandlers()
}, 100)
}
Aquí hay un ejemplo de estado obsoleto. El objeto sequence aquí está obsoleto a menos que vuelva a crear el manejador de punteros cada vez que la secuencia cambie.
const handlePointerMoveImp = (e: any) => {
const sequence = useSequenceStore.getState().sequence
if (mouseIsDown) {
var x = e.data.global.x
var y = e.data.global.y
if (x !== null && y !== null && x !== Infinity && y !== Infinity) {
// console.log(`drag ${JSON.stringify(e.data)}`)
if (dragTarget === DragTargets.EnvelopePoint) {
console.log(`handlePointerMoveImp - drag target is EnvelopePoint - tapPointNum ${tapPointNum}`)
const midiOutputDeviceName : string = sequence.midiSettings.midiOutputDeviceName
const midiChart:MidiChart = findMidiChart(midiOutputDeviceName, midiMaps)
const envelope = findEnvelope(sequence.currentEnvelopeId);
if (envelope) {
// const point = envelope.points[tapPointNum]
const controllerInfo: ControllerInfo = MidiDeviceDataService.getControllerInfo(midiChart, envelope.controller)
const time = Math.max(getXTime(sequence, x), 0)
const value: number = getYValue(sequence, y, controllerInfo.min, controllerInfo.max)
console.log(`handlePointerMoveImp - drag EnvelopePoint to ${time}/${value} - envelope <${sequence.currentEnvelopeId}>`)
moveEnvelopePoint(sequence.currentEnvelopeId, tapPointNum, time, value)
}
}
Respuesta #1
El estado obsoleto de React en los manejadores de callbacks generalmente se soluciona almacenando en caché una copia del estado actual en un ref de React, y haciendo referencia al valor actual del ref en los callbacks. No estoy muy familiarizado con zustand, pero creo que algo como lo siguiente debería estar muy cerca de lo que buscas.
const SequenceCanvas = React.forwardRef((props: any, ref) => {
const canvasRef = useRef(null);
const sequence: Sequence = useSequenceStore(state => state.sequence);
const sequenceRef = useRef<Sequence>(sequence);
useEffect(() => {
sequenceRef.current = sequence;
}, [sequence]);
...
useEffect(() => {
parentContainer.on('pointerdown', handlePointerDown);
parentContainer.on('dblclick', handleDoubleClick);
parentContainer.on('pointerup', handlePointerUp);
parentContainer.on('mouseupoutside', handlePointerUp);
parentContainer.on('pointermove', handlePointerMove);
return () => {
parentContainer.removeAllListeners()
};
}, []);
...
const handlePointerUp = (e: any) => {
console.log(`handlePointerUp ${mouseIsDown}`)
mouseIsDown = false;
dragStepNum = -1
isDraggingVertical = false;
isDraggingHorizontal = false;
}
...
const handlePointerMoveImp = (e: any) => {
const sequence = sequenceRef.current;
if (mouseIsDown) {
const { x, y } = e.data.global.x;
if (x !== null && y !== null && x !== Infinity && y !== Infinity) {
if (dragTarget === DragTargets.EnvelopePoint) {
const midiOutputDeviceName : string = sequence.midiSettings.midiOutputDeviceName
const midiChart:MidiChart = findMidiChart(midiOutputDeviceName, midiMaps)
const envelope = findEnvelope(sequence.currentEnvelopeId);
if (envelope) {
const controllerInfo: ControllerInfo = MidiDeviceDataService.getControllerInfo(midiChart, envelope.controller)
const time = Math.max(getXTime(sequence, x), 0)
const value: number = getYValue(sequence, y, controllerInfo.min, controllerInfo.max)
moveEnvelopePoint(sequence.currentEnvelopeId, tapPointNum, time, value)
}
}
}
}
}
