Here how does updating coord( coord.x = e.clientX) inside function mouseMove works, as updating states would trigger a re-render, so that coord would again be assigned as coord = {x: 0, y: 0}, but that's not happening here, why?. Searching on websites I found out that states batches up updates, but I couldn't understand it. Please clarify it.
let coord = { x: 0, y: 0 };
const [posi, setPosi] = useState(JSON.parse(note.position));
function mouseMove(e) {
if (!currPos.current) return;
const coordX = e.clientX;
const coordY = e.clientY;
let moveDir = {
x: coord.x - coordX,
y: coord.y - coordY,
};
coord.x = e.clientX;
coord.y = e.clientY;
setPosi((prevPos) => ({
x: prevPos.x - moveDir.x,
y: prevPos.y - moveDir.y,
}));
}
Edit: Updated full code for clear understanding:
import React, { useState, useRef, useEffect } from "react";
import Trash from "../icons/Trash";
const NoteCard = ({ note }) => {
const textAreaRef = useRef(null);
const currPos = useRef(null);
let coord = { x: 0, y: 0 };
const [posi, setPosi] = useState(JSON.parse(note.position));
useEffect(() => {
if (textAreaRef.current) {
autoGrow(textAreaRef);
}
}, []);
function autoGrow(passedArea) {
if (!passedArea.current) return;
const currH = passedArea.current;
currH.style.height = "auto";
currH.style.height = currH.scrollHeight + "px";
}
function mouseDown(textAreaPos) {
coord.x = textAreaPos.clientX;
coord.y = textAreaPos.clientY;
document.addEventListener("mousemove", mouseMove);
document.addEventListener("mouseup", mouseUp);
}
function mouseMove(e) {
if (!currPos.current) return;
const coordX = e.clientX;
const coordY = e.clientY;
let moveDir = {
x: coord.x - coordX,
y: coord.y - coordY,
};
**coord.x = e.clientX;
coord.y = e.clientY;**
setPosi((prevPos) => ({
x: prevPos.x - moveDir.x,
y: prevPos.y - moveDir.y,
}));
}
function mouseUp() {
document.removeEventListener("mousemove", mouseMove);
document.removeEventListener("mouseup", mouseUp);
}
const color = JSON.parse(note.colors);
const body = JSON.parse(note.body);
return (
<div
style={{ backgroundColor: color.colorBody, left: posi.x, top: posi.y }}
id="card"
ref={currPos}
>
<div
id="card-header"
style={{ backgroundColor: color.colorHeader }}
onMouseDown={mouseDown}
>
<Trash />
</div>
<div id="card-body">
<textarea
defaultValue={body}
style={{ color: color.colorText }}
ref={textAreaRef}
onInput={() => autoGrow(textAreaRef)}
></textarea>
</div>
</div>
);
};
export default NoteCard;
Why does that starred (**) lines are needed, as we are using state update inside the function, even though we are updating it(coord) to its previous position, state update could cause re render, so that it would again change as coord = {x : 0, y : 0}, if I am wrong kindly rectify it.
Here how does updating coord( coord.x = e.clientX) inside function mouseMove works, as updating states would trigger a re-render, so that coord would again be assigned as coord = {x: 0, y: 0}, but that's not happening here, why?. Searching on websites I found out that states batches up updates, but I couldn't understand it. Please clarify it.
let coord = { x: 0, y: 0 };
const [posi, setPosi] = useState(JSON.parse(note.position));
function mouseMove(e) {
if (!currPos.current) return;
const coordX = e.clientX;
const coordY = e.clientY;
let moveDir = {
x: coord.x - coordX,
y: coord.y - coordY,
};
coord.x = e.clientX;
coord.y = e.clientY;
setPosi((prevPos) => ({
x: prevPos.x - moveDir.x,
y: prevPos.y - moveDir.y,
}));
}
Edit: Updated full code for clear understanding:
import React, { useState, useRef, useEffect } from "react";
import Trash from "../icons/Trash";
const NoteCard = ({ note }) => {
const textAreaRef = useRef(null);
const currPos = useRef(null);
let coord = { x: 0, y: 0 };
const [posi, setPosi] = useState(JSON.parse(note.position));
useEffect(() => {
if (textAreaRef.current) {
autoGrow(textAreaRef);
}
}, []);
function autoGrow(passedArea) {
if (!passedArea.current) return;
const currH = passedArea.current;
currH.style.height = "auto";
currH.style.height = currH.scrollHeight + "px";
}
function mouseDown(textAreaPos) {
coord.x = textAreaPos.clientX;
coord.y = textAreaPos.clientY;
document.addEventListener("mousemove", mouseMove);
document.addEventListener("mouseup", mouseUp);
}
function mouseMove(e) {
if (!currPos.current) return;
const coordX = e.clientX;
const coordY = e.clientY;
let moveDir = {
x: coord.x - coordX,
y: coord.y - coordY,
};
**coord.x = e.clientX;
coord.y = e.clientY;**
setPosi((prevPos) => ({
x: prevPos.x - moveDir.x,
y: prevPos.y - moveDir.y,
}));
}
function mouseUp() {
document.removeEventListener("mousemove", mouseMove);
document.removeEventListener("mouseup", mouseUp);
}
const color = JSON.parse(note.colors);
const body = JSON.parse(note.body);
return (
<div
style={{ backgroundColor: color.colorBody, left: posi.x, top: posi.y }}
id="card"
ref={currPos}
>
<div
id="card-header"
style={{ backgroundColor: color.colorHeader }}
onMouseDown={mouseDown}
>
<Trash />
</div>
<div id="card-body">
<textarea
defaultValue={body}
style={{ color: color.colorText }}
ref={textAreaRef}
onInput={() => autoGrow(textAreaRef)}
></textarea>
</div>
</div>
);
};
export default NoteCard;
Why does that starred (**) lines are needed, as we are using state update inside the function, even though we are updating it(coord) to its previous position, state update could cause re render, so that it would again change as coord = {x : 0, y : 0}, if I am wrong kindly rectify it.
Share Improve this question edited Mar 24 at 12:59 ABISHEK NS asked Mar 23 at 17:36 ABISHEK NSABISHEK NS 511 silver badge6 bronze badges 1 |1 Answer
Reset to default -1Why does that starred (**) lines are needed, as we are using state update inside the function, even though we are updating it (
coord
) to its previous position, state update could cause re render, so that it would again change ascoord = {x : 0, y : 0}
, if I am wrong kindly rectify it.
I wouldn't necessarily say that coord.x = e.clientX;
and coord.y = e.clientY;
are needed, but the way the code works is as follows:
- Each render cycle
mouseDown
andmouseMove
functions are declared and create Javascript Closures over external variables, i.e.coord
. - A user clicks the trash component/element and
mouseDown
is called and mutates its copy ofcoord
with the values of thetextarea
element's client x & y coordinates, and instantiates"mousemove"
and"mouseup"
event listeners, e.g.mouseMove
andmouseUp
are now closed over in thedocument
listeners - The user moves the mouse and the
mouseMove
callback is called, mutating its copy ofcoord
each time, using the mutated reference in the computation to update theposi
state. Theposi
state updates andNoteCard
component re-renders occur independently from the mouse event handlers which remain in memory and scope until... - The user releases the mouse button and the
mouseUp
handler is called and removes themouseMove
andmouseUp
event handlers from the document.
This code, it seems, is implementing a "drag" handler where the initial position is grabbed when the mouse button is pressed, and a delta per each movement callback call is computed, and the posi
state is updated.
From what I can tell, and tested, it seems you don't really need to compute a diff from the previous position, you can simply update the posi
state to the current mouseMove
clientX
and clientY
values. This is because the current coord
x
and y
properties are added and then subtracted back out.
-
let moveDir = { x: coord.x - coordX, y: coord.y - coordY, }; coord.x = e.clientX; coord.y = e.clientY; setPosi((prevPos) => ({ x: prevPos.x - moveDir.x, y: prevPos.y - moveDir.y, }));
-
setPosi((prevPos) => ({ x: prevPos.x - (coord.x - e.clientX), y: prevPos.y - (coord.y - e.clientY), }));
-
setPosi((prevPos) => ({ x: prevPos.x - coord.x + e.clientX, y: prevPos.y - coord.y + e.clientY, }));
And since
prevPos
is the same as the lastcoord
mutation, they cancel out and you are left withsetPosi((prevPos) => ({ x: e.clientX, y: e.clientY, }));
Updated code Suggestion:
const NoteCard = ({ note }) => {
const textAreaRef = useRef(null);
const currPos = useRef(null);
const [posi, setPosi] = useState(JSON.parse(note.position));
useEffect(() => {
if (textAreaRef.current) {
autoGrow(textAreaRef);
}
}, []);
function autoGrow(passedArea) {
if (!passedArea.current) return;
const currH = passedArea.current;
currH.style.height = "auto";
currH.style.height = currH.scrollHeight + "px";
}
function mouseDown(textAreaPos) {
// This initial position state update may actually be unnecessary,
// the state should already have been initialized to the text area's
// position.
setPosi({
x: textAreaPos.clientX,
y: textAreaPos.clientY,
});
document.addEventListener("mousemove", mouseMove);
document.addEventListener("mouseup", mouseUp);
}
function mouseMove(e) {
if (!currPos.current) return;
// Update to the current mouse event coordinate
setPosi((prevPos) => ({
x: e.clientX,
y: e.clientY,
}));
}
function mouseUp() {
document.removeEventListener("mousemove", mouseMove);
document.removeEventListener("mouseup", mouseUp);
}
const color = JSON.parse(note.colors);
const body = JSON.parse(note.body);
return (
<div
style={{ backgroundColor: color.colorBody, left: posi.x, top: posi.y }}
id="card"
ref={currPos}
>
<div
id="card-header"
style={{ backgroundColor: color.colorHeader }}
onMouseDown={mouseDown}
>
<Trash />
</div>
<div id="card-body">
<textarea
defaultValue={body}
style={{ color: color.colorText }}
ref={textAreaRef}
onInput={() => autoGrow(textAreaRef)}
></textarea>
</div>
</div>
);
};
coord
isn't really necessary andmoveDir.x
is just set to-e.clientX
andmoveDir.y
is just set to-e.clientY
, or really ultimately when updating theposi
state it'sprevPos.x + e.clientX
andprevPos.y + e.clientY
? What is the value ofcoord
withinmoveMove
each time it is called? Readers need better details and context. See How to Ask for additional tips to improve the quality of your post. – Drew Reese Commented Mar 23 at 18:49