最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Confusion on how state is being updated here - Stack Overflow

programmeradmin6浏览0评论

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
  • Difficult to say without more context, e.g. a complete minimal reproducible example, but is it possible that coord isn't really necessary and moveDir.x is just set to -e.clientX and moveDir.y is just set to -e.clientY, or really ultimately when updating the posi state it's prevPos.x + e.clientX and prevPos.y + e.clientY? What is the value of coord within moveMove 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
Add a comment  | 

1 Answer 1

Reset to default -1

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.

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:

  1. Each render cycle mouseDown and mouseMove functions are declared and create Javascript Closures over external variables, i.e. coord.
  2. A user clicks the trash component/element and mouseDown is called and mutates its copy of coord with the values of the textarea element's client x & y coordinates, and instantiates "mousemove" and "mouseup" event listeners, e.g. mouseMove and mouseUp are now closed over in the document listeners
  3. The user moves the mouse and the mouseMove callback is called, mutating its copy of coord each time, using the mutated reference in the computation to update the posi state. The posi state updates and NoteCard component re-renders occur independently from the mouse event handlers which remain in memory and scope until...
  4. The user releases the mouse button and the mouseUp handler is called and removes the mouseMove and mouseUp 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.

  1. 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,
    }));
    
  2. setPosi((prevPos) => ({
      x: prevPos.x - (coord.x - e.clientX),
      y: prevPos.y - (coord.y - e.clientY),
    }));
    
  3. setPosi((prevPos) => ({
      x: prevPos.x - coord.x + e.clientX,
      y: prevPos.y - coord.y + e.clientY,
    }));
    
  4. And since prevPos is the same as the last coord mutation, they cancel out and you are left with

    setPosi((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>
  );
};
发布评论

评论列表(0)

  1. 暂无评论