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

javascript - Change width on mousedown and mousemove in React - Stack Overflow

programmeradmin0浏览0评论

I want to build a hook that changes the width of an element on mousemove and mousedown events.

I'm using the following code (that is actually working):

import React, { useState, useCallback, useEffect, useRef } from "/react";
import ReactDOM from "/react-dom";

const useMouseDelta = (initialWidth) => {
  const [dragging, setDragging] = useState(false);
  const [result, setResult] = useState(initialWidth);
  const origin = useRef(initialWidth);

  const handleMouseDown = useCallback((e) => {
    origin.current = e.clientX;
    setDragging(true);
  }, []);

  const handleMouseUp = useCallback((e) => {
    setDragging(false);
  }, []);

  const handleMouseMove = useCallback((e) => {
      if (!dragging) {
        return;
      }
      setResult(result + (e.clientX - origin.current));
    },
    [dragging]
  );

  useEffect(() => {
    window.addEventListener('mousedown', handleMouseDown);
    window.addEventListener('mouseup', handleMouseUp);
    window.addEventListener('mousemove', handleMouseMove);
    return () => {
      window.removeEventListener('mousedown', handleMouseDown);
      window.removeEventListener('mouseup', handleMouseUp);
      window.removeEventListener('mousemove', handleMouseMove);
    };
  }, [dragging]);

  return result;
};


const Test = () => {
  const width = useMouseDelta(400);
  return (
    <div className="container" style={{width: width}}>
      <div className="resize"></div>
    </div> 
  )
}

ReactDOM.render(<Test />, document.getElementById('root'));

However, I'm seeing this warning in ESLint:

React Hook useCallback has a missing dependency: 'result'. Either include it or remove the dependency array. You can also do a functional update 'setResult(r => ...)' if you only need 'result' in the 'setResult' call

So I change it to:

setResult(r => r + (e.clientX - origin.current));

Now the drag is not working as desired anymore.

An example can be found here: CodePen example

I want to build a hook that changes the width of an element on mousemove and mousedown events.

I'm using the following code (that is actually working):

import React, { useState, useCallback, useEffect, useRef } from "https://cdn.skypack.dev/react";
import ReactDOM from "https://cdn.skypack.dev/react-dom";

const useMouseDelta = (initialWidth) => {
  const [dragging, setDragging] = useState(false);
  const [result, setResult] = useState(initialWidth);
  const origin = useRef(initialWidth);

  const handleMouseDown = useCallback((e) => {
    origin.current = e.clientX;
    setDragging(true);
  }, []);

  const handleMouseUp = useCallback((e) => {
    setDragging(false);
  }, []);

  const handleMouseMove = useCallback((e) => {
      if (!dragging) {
        return;
      }
      setResult(result + (e.clientX - origin.current));
    },
    [dragging]
  );

  useEffect(() => {
    window.addEventListener('mousedown', handleMouseDown);
    window.addEventListener('mouseup', handleMouseUp);
    window.addEventListener('mousemove', handleMouseMove);
    return () => {
      window.removeEventListener('mousedown', handleMouseDown);
      window.removeEventListener('mouseup', handleMouseUp);
      window.removeEventListener('mousemove', handleMouseMove);
    };
  }, [dragging]);

  return result;
};


const Test = () => {
  const width = useMouseDelta(400);
  return (
    <div className="container" style={{width: width}}>
      <div className="resize"></div>
    </div> 
  )
}

ReactDOM.render(<Test />, document.getElementById('root'));

However, I'm seeing this warning in ESLint:

React Hook useCallback has a missing dependency: 'result'. Either include it or remove the dependency array. You can also do a functional update 'setResult(r => ...)' if you only need 'result' in the 'setResult' call

So I change it to:

setResult(r => r + (e.clientX - origin.current));

Now the drag is not working as desired anymore.

An example can be found here: CodePen example

Share Improve this question edited Jul 23, 2021 at 7:24 Peter Bartels asked Jul 15, 2021 at 13:56 Peter BartelsPeter Bartels 1,5141 gold badge13 silver badges20 bronze badges 4
  • 1 When I include it to dependency array it seems to working fine. Isn't it? – Konstantin Modin Commented Jul 15, 2021 at 14:59
  • @KonstantinModin Yes that does work. But I still don't know why the setResult with a function inside isn't working, though. – Peter Bartels Commented Jul 15, 2021 at 15:34
  • When adding result it to the dependency array, it doesn't work properly anymore – Peter Bartels Commented Jul 22, 2021 at 13:01
  • Is it possible to see the eslint warnings inside codepen? – user16442705 Commented Jul 22, 2021 at 13:08
Add a ment  | 

2 Answers 2

Reset to default 4 +50
  1. You don't need to store origin, but you need to store previousClientX with useRef() and add just a change of clientX between two events.
  2. You don't need to store dragging with useState(), useRef() is enough.
  3. You need to add all handlers to the dependencies list in your useEffect().

The improved code:

CodePen

import React, {
  useState,
  useCallback,
  useEffect,
  useRef
} from "https://cdn.skypack.dev/react";
import ReactDOM from "https://cdn.skypack.dev/react-dom";

const useMouseDelta = (initialWidth) => {
  const [result, setResult] = useState(initialWidth);
  const dragging = useRef(false);
  const previousClientX = useRef(0);

  const handleMouseMove = useCallback((e) => {
    if (!dragging.current) {
      return;
    }

    setResult((result) => {
      const change = e.clientX - previousClientX.current;
      previousClientX.current = e.clientX;
      return result + change;
    });
  }, []);

  const handleMouseDown = useCallback((e) => {
    previousClientX.current = e.clientX;
    dragging.current = true;
  }, []);

  const handleMouseUp = useCallback((e) => {
    dragging.current = false;
  }, []);

  useEffect(() => {
    window.addEventListener("mousedown", handleMouseDown);
    window.addEventListener("mouseup", handleMouseUp);
    window.addEventListener("mousemove", handleMouseMove);

    return () => {
      window.removeEventListener("mousedown", handleMouseDown);
      window.removeEventListener("mouseup", handleMouseUp);
      window.removeEventListener("mousemove", handleMouseMove);
    };
  }, [handleMouseDown, handleMouseUp, handleMouseMove]);

  return result;
};

const Test = () => {
  const width = useMouseDelta(400);

  return (
    <div className="container" style={{ width }}>
      <div className="resize" />
    </div>
  );
};

ReactDOM.render(<Test />, document.getElementById("root"));

In this Effect you can add all the dependencies like this:

useEffect(() => {
    window.addEventListener('mousedown', handleMouseDown);
    window.addEventListener('mouseup', handleMouseUp);
    window.addEventListener('mousemove', handleMouseMove);
    return () => {
      window.removeEventListener('mousedown', handleMouseDown);
      window.removeEventListener('mouseup', handleMouseUp);
      window.removeEventListener('mousemove', handleMouseMove);
    };
  }, [dragging, handleMouseDown, handleMouseUp, handleMouseMove]);

In the handleMouseMove it's a bit more tricky. It's ommited on purpose, so that it will only see the 'currentWidth' of the ponent and not the updates versions. The setResult can be used like this:

  const handleMouseMove = useCallback((e) => {
      if (!dragging) {
        return;
      }
      setResult((result) => result + (e.clientX - origin.current));
    },
    [dragging]
  );

Then the warning will not e up, but you will see that it will always see the updated value and it will cause extra additions.

A solution would be to store the current witdth in a different state like this in useMouseDelta and then it can be added as a dependency:

const useMouseDelta = (initialWidth: number) => {
  // The distance the mouse has moved since `mousedown`.
  const [dragging, setDragging] = useState(false);
  const [result, setResult] = useState(initialWidth);
  const [currentWidth, setCurrentWidth] = useState(initialWidth);

  // Original position independent of any dragging.  Updated when done dragging.
  const origin = useRef(initialWidth);

  const handleMouseDown = useCallback((e) => {
    origin.current = e.clientX;
    setDragging(true);
  }, []);

  const handleMouseUp = useCallback(
    (e) => {
      setDragging(false);
      setCurrentWidth(result);
    },
    [result]
  );

  const handleMouseMove = useCallback(
    (e) => {
      if (!dragging) {
        return;
      }
      setResult(() => currentWidth + e.clientX - origin.current);
    },
    [currentWidth, dragging]
  );

  useEffect(() => {
    window.addEventListener("mousedown", handleMouseDown);
    window.addEventListener("mouseup", handleMouseUp);
    window.addEventListener("mousemove", handleMouseMove);
    return () => {
      window.removeEventListener("mousedown", handleMouseDown);
      window.removeEventListener("mouseup", handleMouseUp);
      window.removeEventListener("mousemove", handleMouseMove);
    };
  }, [dragging, handleMouseDown, handleMouseUp, handleMouseMove]);

  return result;
};
发布评论

评论列表(0)

  1. 暂无评论