Ralph Lee
Ralph Lee

Reputation: 107

Maximum update depth Exceeded during React Easy Crop

I am trying to get the react-easy-crop library working following this guy's tutorial: https://www.youtube.com/watch?v=RmiP1AY5HFM

However, around the middle of his video, he is able to show his image after selecting the file. In my code, I can see the image for 0.5 seconds, and it disappears because React Crashes with the log: "Uncaught Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops."

I know there's an infinite loop somewhere but I cannot figure out where...I'm quite new to react.. please help!

    const inputRef = React.useRef();
    const triggerFileSelectPopup = () => inputRef.current.click();
    const [image, setImage] = useState(null);
    const [croppedArea, setCroppedArea] = useState(null);
    const [crop, setCrop] = useState({x:0, y:0});
    const [zoom, setZoom] = useState(1)

    const onCropComplete = () => {}

    const onSelectFile = (event) => {
        if (event.target.files && event.target.files.length > 0) {
            const reader = new FileReader()
            reader.readAsDataURL(event.target.files[0])
            reader.addEventListener('load', ()=> {
                console.log(reader.result);
                setImage(reader.result);
            });
        }
    };

    const CropContainer = () => {
        return (
            <div className='CropContainer'>
                <div className='container-cropper'>
                    <Cropper 
                        image={image} 
                        crop={crop} 
                        zoom={zoom} 
                        aspect={1} 
                        onCropChange={setCrop} 
                        onZoomChange={setZoom} 
                        onCropComplete={onCropComplete}
                    />
                    <Slider />
                </div>
                <div className='container-buttons'>
                    <input 
                        type='file' 
                        accept="image/jpg, image/jpeg, image/png" 
                        ref={inputRef} 
                        style={{display:'none'}}
                        onChange={onSelectFile} 
                    />

                    <Button 
                        variant='contained' 
                        onClick={triggerFileSelectPopup}>Choose New Image
                    </Button>

                    <Button 
                        variant='contained'>Save
                    </Button>
                </div>
            </div>

        );
    }
 
    return (
       <div className="NewDiv">
           <CropContainer />  
       </div>
    );

Upvotes: 2

Views: 681

Answers (2)

codmitu
codmitu

Reputation: 709

i had this problem myself, when i was opening the modal to crop the image i was getting infinite console.log from onCropComplete, because i was using an image saved in redux state i had to put the image inside a normal state first then put the image into Cropper

const IMG = useSelector((state: RootStateOrAny) => state.create.photoRaw)

const [image, setImage] = useState(URL.createObjectURL(IMG))
const [crop, setCrop] = useState<Point>({ x: 0, y: 0 });
const [zoom, setZoom] = useState(1);

const onCropComplete = useCallback(
    (croppedArea: Area, croppedAreaPixels: Area) => {
        console.log(croppedArea, croppedAreaPixels);
}, []);

<Cropper
     image={image}
     crop={crop}
     zoom={zoom}
     aspect={1 / 1}
     onCropChange={setCrop}
     onCropComplete={onCropComplete}
     onZoomChange={setZoom} />

Upvotes: 0

Drew Reese
Drew Reese

Reputation: 203051

It seems likely that something the nested CropContainer component does causes the outer component to rerender. When the outer component rerenders, it declares a new CropContainer component, which unmounts the previous "instance" and mounts a new one. This is why it is considered anti-pattern to define React components within other react components.

You can either move the JSX that CropContainer returns, as you've done

return (
  <div className="NewDiv">
    <div className="CropContainer">
      <div className="container-cropper">
        <Cropper
          image={image}
          crop={crop}
          zoom={zoom}
          aspect={1}
          onCropChange={setCrop}
          onZoomChange={setZoom}
          onCropComplete={onCropComplete}
        />
        <Slider />
      </div>
      <div className="container-buttons">
        <input
          type="file"
          accept="image/jpg, image/jpeg, image/png"
          ref={inputRef}
          style={{ display: "none" }}
          onChange={onSelectFile}
        />

        <Button variant="contained" onClick={triggerFileSelectPopup}>
          Choose New Image
        </Button>

        <Button variant="contained">Save</Button>
      </div>
    </div>  
  </div>
);

or you can move the CropContainer declaration outside the outer component. Pass all the callbacks and such in as props.

const CropContainer = ({
  crop,
  image,
  inputRef,
  onCropChange,
  onCropComplete,
  onSelectFile,
  setCrop,
  setZoom,
  triggerFileSelectPopup,
  zoom
}) => {
  return (
    <div className="CropContainer">
      <div className="container-cropper">
        <Cropper
          image={image}
          crop={crop}
          zoom={zoom}
          aspect={1}
          onCropChange={setCrop}
          onZoomChange={setZoom}
          onCropComplete={onCropComplete}
        />
        <Slider />
      </div>
      <div className="container-buttons">
        <input
          type="file"
          accept="image/jpg, image/jpeg, image/png"
          ref={inputRef}
          style={{ display: "none" }}
          onChange={onSelectFile}
        />

        <Button variant="contained" onClick={triggerFileSelectPopup}>
          Choose New Image
        </Button>

        <Button variant="contained">Save</Button>
      </div>
    </div>
  );
};

...

return (
  <div className="NewDiv">
    <CropContainer /* pass all those props here */ />  
  </div>
);

Upvotes: 1

Related Questions