如何避免 onCropComplete useCallback 函数在更新其他变量状态时陷入无限循环?

问题描述 投票:0回答:1

我正在尝试让react-easy-crop发挥作用。该代码成功显示了一个按钮(上传文件),该按钮触发屏幕以选择图片。拍摄照片后,会出现一个新的模式,该模式在图片顶部显示圆形裁剪叠加。目标是调整图片大小并将其保存在croppedImage中,以便我可以将其存储到后端。

当前的问题是“onCropComplete”被一遍又一遍地调用。我可以通过打印语句来看到这一点。拆线

setCroppedAreaPixels(croppedAreaPixels);

解决了问题,尽管尚不清楚为什么会发生这种情况。

任何人都可以帮我解释为什么这个函数不断被调用吗? 我的代码附在下面。

// The `Cropper` component is on the global object as `ReactEasyCrop`. Assign to `Cropper` so we can use it like a ES6 module
window.Cropper = window.ReactEasyCrop;
const { createRoot } = ReactDOM;
const { StrictMode, useEffect, useState, useRef, useCallback } = React;

// Mock `useAxios`
function useAxios() {}

// Mock `useParams`
function useParams() {
  return {
    id: 1,
  }
}

// Mock `getCroppedImg`
function getCroppedImg() {}

const UserDetail = () => {
  const { id } = useParams();
  const [res, setRes] = useState("");
  const [dateJoined, setDateJoined] = useState("");
  const [avatar, setAvatar] = useState("");
  const [modalState, setModalState] = useState(false);
  const api = useAxios();
  const inputRef = useRef(null);
  const [selectedImage, setSelectedImage] = useState(null);
  const [crop, setCrop] = useState({ x: 0, y: 0 });
  const [zoom, setZoom] = useState(1);
  const [croppedAreaPixels, setCroppedAreaPixels] = useState(null);
  const [croppedImage, setCroppedImage] = useState(null);

  const showHideClassName = modalState
    ? "modal display-block"
    : "modal display-none";

  useEffect(() => {
    console.log("useeffect!");
    const fetchData = () => {
      // make unrelated API call to backend
    };
    fetchData();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [id]);

  const handleFileChange = (event) => {
    // works - saves file to selected image and triggers cropping modal
    console.log(event.target.files[0].name);
    setSelectedImage(event.target.files[0]);
    showModal();
  };

  const handleFileUpload = (event) => {
    // function not yet called because problem in onCropComplete
    showCroppedImage();
    const sendData = () => {
      // API call to POST newly cropped image
    };
    sendData();
    hideModal();
  };

  const showModal = () => {
    setModalState(true);
  };

  const hideModal = () => {
    setModalState(false);
  };

  const onCropComplete = useCallback((croppedArea, croppedAreaPixels) => {
    //problematic function, is called infinitely
    setCroppedAreaPixels(croppedAreaPixels);
    console.log(croppedAreaPixels);
  }, []);

  const showCroppedImage = useCallback(() => {
    //code not working yet, first onCropComplete should work correctly
    try {
      // Converted async/await function to promise as Stack Snippets does not support async/await when transpiling with Babel as Babel version is too old. See: https://meta.stackoverflow.com/questions/386097/why-wont-this-snippet-with-async-await-work-here-on-stack-overflow-snippet-edit
      const croppedImage = getCroppedImg(
        selectedImage,
        croppedAreaPixels,
        0
      ).then(() => {
        console.log("donee", { croppedImage });
        setCroppedImage(croppedImage);
      });
    } catch (e) {
      console.error(e);
    }
  }, [selectedImage, croppedAreaPixels]);

  return (
    <div className="parent">
      <div className="userdetail">
        <div className="profilebanner">
          <div className="profilepicture">
            <img src={avatar} alt="" className="avatar" />
            <input
              ref={inputRef}
              onChange={handleFileChange}
              type="file"
              style={{ display: "none" }}
            />
            <button onClick={() => inputRef.current.click()}>
              Upload File
            </button>
            <div className={showHideClassName}>
              <section className="modal-main">
                <p align="center">Modal</p>
                <div className="cropper">
                  {selectedImage && (
                    <Cropper
                      image={URL.createObjectURL(selectedImage)}
                      crop={crop}
                      aspect={5 / 5}
                      zoom={zoom}
                      zoomSpeed={4}
                      maxZoom={3}
                      zoomWithScroll={true}
                      showGrid={true}
                      cropShape="round"
                      onCropChange={setCrop}
                      onCropComplete={onCropComplete}
                      onZoomChange={setZoom}
                    />
                  )}
                </div>
                <button onClick={handleFileUpload}>Confirm!</button>
                <button type="button" onClick={hideModal}>
                  Close
                </button>
              </section>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
};

const root = createRoot(document.getElementById("root"));
root.render(<StrictMode><UserDetail /></StrictMode>);
<script crossorigin defer src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin defer src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script crossorigin defer src="https://unpkg.com/[email protected]/tslib.js"></script>
<!-- tslib assigns its functions to the global object (e.g. `window.__assign`) however React Easy Crop expects it on the tslib object (e.g. `window.tslib.__assign`). This is hacky but allows React Easy Crop to find tslib functions on `window.tslib` -->
<script type="module">window.tslib = window</script>
<script crossorigin defer src="https://unpkg.com/[email protected]/umd/react-easy-crop.js"></script>
<div id="root"></div>

javascript reactjs react-hooks crop
1个回答
0
投票

不要在

URL.createObjectURL
组件的
image
属性中调用
Cropper
,而是在设置
selectedImage
状态之前调用它。演示如下:

// The `Cropper` component is on the global object as `ReactEasyCrop`. Assign to `Cropper` so we can use it like a ES6 module
window.Cropper = window.ReactEasyCrop;
const { createRoot } = ReactDOM;
const { StrictMode, useEffect, useState, useRef, useCallback } = React;

// Mock `useAxios`
function useAxios() {}

// Mock `useParams`
function useParams() {
  return {
    id: 1,
  }
}

// Mock `getCroppedImg`
function getCroppedImg() {}

const UserDetail = () => {
  const { id } = useParams();
  const [res, setRes] = useState("");
  const [dateJoined, setDateJoined] = useState("");
  const [avatar, setAvatar] = useState("");
  const [modalState, setModalState] = useState(false);
  const api = useAxios();
  const inputRef = useRef(null);
  const [selectedImage, setSelectedImage] = useState(null);
  const [crop, setCrop] = useState({ x: 0, y: 0 });
  const [zoom, setZoom] = useState(1);
  const [croppedAreaPixels, setCroppedAreaPixels] = useState(null);
  const [croppedImage, setCroppedImage] = useState(null);

  const showHideClassName = modalState
    ? "modal display-block"
    : "modal display-none";

  useEffect(() => {
    console.log("useeffect!");
    const fetchData = () => {
      // make unrelated API call to backend
    };
    fetchData();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [id]);

  const handleFileChange = (event) => {
    // works - saves file to selected image and triggers cropping modal
    console.log(event.target.files[0].name);
    setSelectedImage(URL.createObjectURL(event.target.files[0]));
    showModal();
  };

  const handleFileUpload = (event) => {
    // function not yet called because problem in onCropComplete
    showCroppedImage();
    const sendData = () => {
      // API call to POST newly cropped image
    };
    sendData();
    hideModal();
  };

  const showModal = () => {
    setModalState(true);
  };

  const hideModal = () => {
    setModalState(false);
  };

  const onCropComplete = useCallback((croppedArea, croppedAreaPixels) => {
    //problematic function, is called infinitely
    setCroppedAreaPixels(croppedAreaPixels);
    console.log(croppedAreaPixels);
  }, []);

  const showCroppedImage = useCallback(() => {
    //code not working yet, first onCropComplete should work correctly
    try {
      // Converted async/await function to promise as Stack Snippets does not support async/await when transpiling with Babel as Babel version is too old. See: https://meta.stackoverflow.com/questions/386097/why-wont-this-snippet-with-async-await-work-here-on-stack-overflow-snippet-edit
      const croppedImage = getCroppedImg(
        selectedImage,
        croppedAreaPixels,
        0
      ).then(() => {
        console.log("donee", { croppedImage });
        setCroppedImage(croppedImage);
      });
    } catch (e) {
      console.error(e);
    }
  }, [selectedImage, croppedAreaPixels]);

  return (
    <div className="parent">
      <div className="userdetail">
        <div className="profilebanner">
          <div className="profilepicture">
            <img src={avatar} alt="" className="avatar" />
            <input
              ref={inputRef}
              onChange={handleFileChange}
              type="file"
              style={{ display: "none" }}
            />
            <button onClick={() => inputRef.current.click()}>
              Upload File
            </button>
            <div className={showHideClassName}>
              <section className="modal-main">
                <p align="center">Modal</p>
                <div className="cropper">
                  {selectedImage && (
                    <Cropper
                      image={selectedImage}
                      crop={crop}
                      aspect={5 / 5}
                      zoom={zoom}
                      zoomSpeed={4}
                      maxZoom={3}
                      zoomWithScroll={true}
                      showGrid={true}
                      cropShape="round"
                      onCropChange={setCrop}
                      onCropComplete={onCropComplete}
                      onZoomChange={setZoom}
                    />
                  )}
                </div>
                <button onClick={handleFileUpload}>Confirm!</button>
                <button type="button" onClick={hideModal}>
                  Close
                </button>
              </section>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
};

const root = createRoot(document.getElementById("root"));
root.render(<StrictMode><UserDetail /></StrictMode>);
<script crossorigin defer src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin defer src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script crossorigin defer src="https://unpkg.com/[email protected]/tslib.js"></script>
<!-- tslib assigns its functions to the global object (e.g. `window.__assign`) however React Easy Crop expects it on the tslib object (e.g. `window.tslib.__assign`). This is hacky but allows React Easy Crop to find tslib functions on `window.tslib` -->
<script type="module">window.tslib = window</script>
<script crossorigin defer src="https://unpkg.com/[email protected]/umd/react-easy-crop.js"></script>
<div id="root"></div>

© www.soinside.com 2019 - 2024. All rights reserved.