Creating an Image Upload Modal with Crop and Rotate Functionality in React
Published: 2023-07-26 15:41:16

## Introduction:

Welcome to this tutorial on creating an image modal with crop and rotate functionality in **React**! In this blog post, we'll explore how to leverage the power of [react-easy-crop](https://github.com/ValentinH/react-easy-crop) library to build an interactive image modal in react similar to those found in popular platforms like GitHub or Facebook.

![Image crop modal](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uwzmmfq6ms3evjbas98o.gif)

here is live demo [link](https://mizan-rifat.github.io/image-cropper-modal)
In the live demo, you can experience the full functionality of the image modal with crop and rotate features. 

The focus of this tutorial is to develop a solution that minimizes the need for excessive prop passing between components. To achieve this, we'll harness the capabilities of React's context API. By creating a context provider and a custom hook, we can seamlessly share data and functions across our components without the hassle of passing props down the component tree.

So, let's dive in and learn how to create an image modal that allows users to crop and rotate their uploaded images.

## Setting Up the Project:
To get started with our image modal implementation, i'll assume you already have a React project set up. For UI i’m using Tailwind CSS. But you can use any UI library as your wish.
For the image cropping and rotating functionality, we'll be utilizing the [react-easy-crop](https://github.com/ValentinH/react-easy-crop) library. This library provides a simple and intuitive way to crop and interact with images and videos within a React component.
We will also use the [heroicons](https://github.com/tailwindlabs/heroicons) and [classnames](https://www.npmjs.com/package/classnames) libraries in our tutorial. To install all the libraries and their dependencies, open your terminal and navigate to your project's directory. Run the following command:

```javascript
npm install react-easy-crop classnames @heroicons/react
#or
yarn add react-easy-crop classnames @heroicons/react
```
Now that we have `react-easy-crop` and other libraries are installed, let's move on to creating the necessary components and implementing the image modal functionality.


## Creating base components:
In this tutorial, we'll create two essential base components `Button` and `Modal` which will be needed in our image modal with crop and rotate functionality. Let's take a closer look at each of them:

```javascript
// src/components/base/Button.jsx

import classNames from 'classnames';

const Button = ({ variant, className, children, ...rest }) => {
  return (
    <button
      type="button"
      className={classNames(className, 'hover:shadow-inner px-4 py-2 text-sm rounded-3xl', {
        'bg-blue-500 text-white hover:bg-blue-700 hover:text-white': variant === 'primary',
        'bg-red-500 text-white hover:bg-red-700 hover:text-white': variant === 'secondary',
        'bg-white text-gray-900 hover:bg-white hover:text-blue-500': variant === 'light'
      })}
      {...rest}
    >
      {children}
    </button>
  );
};

export default Button;

```
The `Button` component is a reusable and versatile element that allows us to create different styles of buttons with ease.

```javascript
//src/components/base/Modal.jsx

import classNames from 'classnames';

const Modal = ({ open, children }) => {
  return (
    <div
      className={classNames('fixed z-10 overflow-y-auto top-0 w-full left-0', {
        hidden: !open
      })}
      id="modal"
    >
      <div className="flex items-center justify-center min-height-100vh pt-4 px-4 pb-20 text-center sm:block sm:p-0">
        <div className="fixed inset-0 transition-opacity">
          <div className="absolute inset-0 bg-gray-900 opacity-75"></div>
          <span className="hidden sm:inline-block sm:align-middle sm:h-screen">​</span>
          <div
            className="inline-block align-center bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-sm sm:w-full"
            role="dialog"
            aria-modal="true"
            aria-labelledby="modal-headline"
          >
            <div className="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">{children}</div>
          </div>
        </div>
      </div>
    </div>
  );
};

export default Modal;

```
The Modal component plays a crucial role in our image modal implementation. It handles the display of content in a modal overlay, providing a clean and focused interface for editing images. 

## Creating the image crop context:
One of the essential aspects of building our image modal with crop and rotate functionality is to manage the state effectively. To achieve this, we'll create a React context that will provide the necessary data and functions to components that require access to this state.

```javascript
//src/providers/ImageCropProvider.jsx

/* eslint-disable react-refresh/only-export-components */
import { createContext, useCallback, useContext, useState } from 'react';
import getCroppedImg from '../helpers/cropImage';

export const ImageCropContext = createContext({});

const defaultImage = null;
const defaultCrop = { x: 0, y: 0 };
const defaultRotation = 0;
const defaultZoom = 1;
const defaultCroppedAreaPixels = null;

const ImageCropProvider = ({
  children,
  max_zoom = 3,
  min_zoom = 1,
  zoom_step = 0.1,
  max_rotation = 360,
  min_rotation = 0,
  rotation_step = 5
}) => {
  const [image, setImage] = useState(defaultImage);
  const [crop, setCrop] = useState(defaultCrop);
  const [rotation, setRotation] = useState(defaultRotation);
  const [zoom, setZoom] = useState(defaultZoom);
  const [croppedAreaPixels, setCroppedAreaPixels] = useState(defaultCroppedAreaPixels);

  const onCropComplete = useCallback((_croppedArea, croppedAreaPixels) => {
    setCroppedAreaPixels(croppedAreaPixels);
  }, []);

  const handleZoomIn = () => {
    if (zoom < max_zoom) {
      setZoom(zoom + zoom_step * 2);
    }
  };

  const handleZoomOut = () => {
    if (zoom > min_zoom) {
      setZoom(zoom - zoom_step * 2);
    }
  };

  const handleRotateCw = () => {
    setRotation(rotation + rotation_step);
  };

  const handleRotateAntiCw = () => {
    setRotation(rotation - rotation_step);
  };

  const getProcessedImage = async () => {
    if (image && croppedAreaPixels) {
      const croppedImage = await getCroppedImg(image, croppedAreaPixels, rotation);
      const imageFile = new File([croppedImage.file], `img-${Date.now()}.png`, {
        type: 'image/png'
      });
      return imageFile;
    }
  };

  const resetStates = () => {
    setImage(defaultImage);
    setCrop(defaultCrop);
    setRotation(defaultRotation);
    setZoom(defaultZoom);
    setCroppedAreaPixels(defaultCroppedAreaPixels);
  };

  return (
    <ImageCropContext.Provider
      value={{
        image,
        setImage,
        zoom,
        setZoom,
        rotation,
        setRotation,
        crop,
        setCrop,
        croppedAreaPixels,
        setCroppedAreaPixels,
        onCropComplete,
        getProcessedImage,
        handleZoomIn,
        handleZoomOut,
        handleRotateAntiCw,
        handleRotateCw,
        max_zoom,
        min_zoom,
        zoom_step,
        max_rotation,
        min_rotation,
        rotation_step,
        resetStates
      }}
    >
      {children}
    </ImageCropContext.Provider>
  );
};

export const useImageCropContext = () => useContext(ImageCropContext);

export default ImageCropProvider;

```

In the provided code snippet, we've defined the `ImageCropProvider` component. This component serves as the context provider and wraps its children with the `ImageCropContext.Provider`. Let's take a closer look at how this works:

The `ImageCropContext` will be our centralized state management solution for image cropping and rotating.
It is a functional component that takes several optional props related to the configuration of image cropping and rotation. These props include `max_zoom`, `min_zoom`, `zoom_step`, `max_rotation`, `min_rotation`, and `rotation_step`, which can be customized based on the specific requirements of the application.

 It will encapsulate the following state variables:
- `image`: Holds the selected image that we want to crop and rotate.
- `crop`: Represents the x and y coordinates of the current cropping area.
- `rotation`: Keeps track of the rotation angle of the image.
- `zoom`: Manages the zoom level of the image.
- `croppedAreaPixels`: Stores the pixel values of the cropped area.

Additionally, we'll include several utility functions that allow us to manipulate the zoom and rotation of the image:

- `handleZoomIn`: Increases the zoom level (up to a maximum value).
- `handleZoomOut`: Decreases the zoom level (down to a minimum value).
- `handleRotateCw`: Rotates the image clockwise by a specified angle.
- `handleRotateAntiCw`: Rotates the image anti-clockwise by a specified angle.
- `resetStates`: Reset all the states. 

Furthermore, we've created a function called `getProcessedImage`. This function uses a helper function, `getCroppedImg` (not shown in this code snippet), to extract the cropped image based on the original image, the pixel values of the cropped area, and the rotation angle. The result is a new File object representing the cropped image, which can be used later to upload to the server.

Also we created a custom hook `useImageCropContext` to consume the `ImageCropContext` in other components



## Building the Custom Cropper Component
Now, it's time to build a custom Cropper component that utilizes the `react-easy-crop` library, allowing users to interactively crop and rotate their selected images.

```javascript
//src/components/cropper/Cropper.jsx

import EasyCropper from 'react-easy-crop';
import { useImageCropContext } from '../../providers/ImageCropProvider';

const Cropper = () => {
  const { image, zoom, setZoom, rotation, setRotation, crop, setCrop, onCropComplete } =
    useImageCropContext();

  return (
    <EasyCropper
      image={image || undefined}
      crop={crop}
      zoom={zoom}
      rotation={rotation}
      cropShape="round"
      aspect={1}
      onCropChange={setCrop}
      onCropComplete={onCropComplete}
      onZoomChange={setZoom}
      setRotation={setRotation}
      showGrid={false}
      cropSize={{ width: 185, height: 185 }}
      style={{
        containerStyle: {
          height: 220,
          width: 220,
          top: 8,
          bottom: 8,
          left: 8,
          right: 8
        }
      }}
    />
  );
};

export default Cropper;

```
The `Cropper` component serves as an interface between the `react-easy-crop` library and our `ImageCropContext`. It accesses the necessary state and functions from the context using the `useImageCropContext` custom hook. Here's how it works:

1. Accessing Context Data:
  - We use the `useImageCropContext` hook to access the data from the `ImageCropContext`. This hook returns an object that contains the state variables and functions we need for the cropping and rotating functionality.
  - Destructuring this object, we get access to `image`, `zoom`, `setZoom`, `rotation`, `setRotation`, `crop`, `setCrop`, and `onCropComplete`.
2. EasyCropper Component: 
   - The `EasyCropper` component is imported from the `react-easy-crop` library. It is a versatile component that provides interactive image cropping and rotation features.
   - We pass various props to this component to control its behavior based on the state from the `ImageCropContext`.
3. Props and Functionality: 
  - `image`: We provide the image from our context as a prop to `EasyCropper` so that it knows which image to crop and display. If image is undefined, the cropper will not render any image initially.
  - `crop`, `zoom`, and `rotation`: These props are linked to their respective state variables from the context, ensuring that the cropper reflects the current `crop`, `zoom`, and `rotation` values.
  - `cropShape` and `aspect`: We set `cropShape` to "round" and `aspect` to 1, indicating that the cropping area should have a circular shape and maintain a 1:1 aspect ratio.
  - `onCropChange`, `onZoomChange`, and `setRotation`: These callback props update the corresponding state variables in the context whenever the user interacts with the cropper.
4. Styling the Cropper: 
  - We provide a style object to customize the appearance of the `EasyCropper` component. In this example, we set the height and width of the cropper container to 220 pixels, with a margin of 8 pixels around the edges to create some padding.
 
## Building the Image Crop Modal Content
With the `ImageCropContext` and `Cropper` component in place, we're ready to construct the core of our image crop modal. The `ImageCropModalContent` component is where users can interact with the cropper, adjust zoom and rotation, and upload new images for cropping and rotating.

To begin, we'll need some CSS styles for the crop container. Add the following CSS in the `index.css` file:

```css
.crop-container {
  position: relative;
  width: 236px;
  height: 236px;

  background: linear-gradient(to right, #cbd4e1 8px, transparent 8px) 0 0,
    linear-gradient(to right, #cbd4e1 8px, transparent 8px) 0 100%,
    linear-gradient(to left, #cbd4e1 8px, transparent 8px) 100% 0,
    linear-gradient(to left, #cbd4e1 8px, transparent 8px) 100% 100%,
    linear-gradient(to bottom, #cbd4e1 8px, transparent 8px) 0 0,
    linear-gradient(to bottom, #cbd4e1 8px, transparent 8px) 100% 0,
    linear-gradient(to top, #cbd4e1 8px, transparent 8px) 0 100%,
    linear-gradient(to top, #cbd4e1 8px, transparent 8px) 100% 100%;

  background-repeat: no-repeat;
  background-size: 70px 70px;
}
.reactEasyCrop_CropArea {
  color: rgba(255, 255, 255, 0.8) !important;
}

```
Now, let's proceed with the `ImageCropModalContent` component implementation:

```javascript
//src/components/ImageCropModalContent.jsx

import { readFile } from '../helpers/cropImage';
import { useImageCropContext } from '../providers/ImageCropProvider';
import Button from '../components/base/Button';
import Cropper from '../components/cropper/Cropper';
import { RotationSlider, ZoomSlider } from '../components/cropper/Sliders';

const ImageCropModalContent = ({ handleDone, handleClose }) => {
  const { setImage } = useImageCropContext();

  const handleFileChange = async ({ target: { files } }) => {
    const file = files && files[0];
    const imageDataUrl = await readFile(file);
    setImage(imageDataUrl);
  };

  return (
    <div className="text-center relative">
      <h5 className="text-gray-800 mb-4">Edit profile picture</h5>
      <div className="border border-dashed border-gray-200 p-6 rounded-lg">
        <div className="flex justify-center">
          <div className="crop-container mb-4">
            <Cropper />
          </div>
        </div>
        <ZoomSlider className="mb-4" />
        <RotationSlider className="mb-4" />
        <input
          type="file"
          multiple
          onChange={handleFileChange}
          className="hidden"
          id="avatarInput"
          accept="image/*"
        />

        <Button variant="light" className="shadow w-full mb-4 hover:shadow-lg">
          <label htmlFor="avatarInput">Upload Another Picture</label>
        </Button>
        <div className="flex gap-2">
          <Button variant="secondary" onClick={handleClose}>
            Cancel
          </Button>
          <Button variant="primary" className="w-full" onClick={handleDone}>
            Done & Save
          </Button>
        </div>
      </div>
    </div>
  );
};

export default ImageCropModalContent;

```
Let's explore the code of the `ImageCropModalContent` component and the `ZoomSlider` and `RotationSlider` components, which enable users to control the zoom and rotation of the image while cropping.

- The `ImageCropModalContent` component begins by importing the `readFile` function from `helpers/cropImage`. This function is responsible for reading the image file and returning its data URL.
- We also import the `useImageCropContext` hook from `providers/ImageCropProvider` to access the `setImage` function from the context. When users select an image file, the `handleFileChange` function reads the image data URL and sets it in the context using `setImage`.
- Rendering the Cropper:
  The `ImageCropModalContent` component renders the Cropper component, allowing users to interactively crop and rotate the selected image. We place the Cropper inside a container with the class `crop-container` for styling purposes.
- Zoom and Rotation Sliders:
  We import the `ZoomSlider` and `RotationSlider` components from `components/cropper/Sliders`. These sliders are responsible for adjusting the zoom level and rotation angle of the image, respectively.
- File Input and Upload Button:
  We include an input element with type file to allow users to upload another picture for cropping. The `handleFileChange` function is triggered when users select a new image file.
 When users click on the **Upload Another Picture** button, it triggers the file input to open, enabling them to choose a new image.
- Action Buttons:
  We provide two action buttons: **Cancel** and **Done & Save**. The **Cancel** button closes the image crop modal when clicked, while the **Done & Save** button saves the cropped image and triggers the `handleDone` function, which is passed as a prop from the parent component.

## ZoomSlider and RotationSlider Components:
The `ZoomSlider` and `RotationSlider` components allow users to control the zoom level and rotation angle of the image during cropping. They interact with the `useImageCropContext` hook to access the necessary state and functions from the `ImageCropContext`.

```javascript
//src/components/cropper/Sliders.jsx

import { useImageCropContext } from '../../providers/ImageCropProvider';
import {
  ArrowUturnLeftIcon,
  ArrowUturnRightIcon,
  MinusIcon,
  PlusIcon
} from '@heroicons/react/24/solid';
import classNames from 'classnames';

export const ZoomSlider = ({ className }) => {
  const { zoom, setZoom, handleZoomIn, handleZoomOut, max_zoom, min_zoom, zoom_step } =
    useImageCropContext();

  return (
    <div className={classNames(className, 'flex items-center justify-center gap-2')}>
      <button className="p-1" onClick={handleZoomOut}>
        <MinusIcon className="text-gray-400 w-4" />
      </button>
      <input
        type="range"
        name="volju"
        min={min_zoom}
        max={max_zoom}
        step={zoom_step}
        value={zoom}
        onChange={e => {
          setZoom(Number(e.target.value));
        }}
      />
      <button className="p-1" onClick={handleZoomIn}>
        <PlusIcon className="text-gray-400 w-4" />
      </button>
    </div>
  );
};

export const RotationSlider = ({ className }) => {
  const {
    rotation,
    setRotation,
    max_rotation,
    min_rotation,
    rotation_step,
    handleRotateAntiCw,
    handleRotateCw
  } = useImageCropContext();

  return (
    <div className={classNames(className, 'flex items-center justify-center gap-2')}>
      <button className="p-1" onClick={handleRotateAntiCw}>
        <ArrowUturnLeftIcon className="text-gray-400 w-4" />
      </button>
      <input
        type="range"
        name="volju"
        min={min_rotation}
        max={max_rotation}
        step={rotation_step}
        value={rotation}
        onChange={e => {
          setRotation(Number(e.target.value));
        }}
      />
      <button className="p-1" onClick={handleRotateCw}>
        <ArrowUturnRightIcon className="text-gray-400 w-4" />
      </button>
    </div>
  );
};

```
## Helper Functions for Image Processing:

To achieve image cropping and rotating, we utilize a set of helper functions adapted from the examples of the `react-easy-crop` library. These functions simplify the image processing tasks and enable us to generate the final cropped image.

```javascript
//src/helpers/cropImage.js

export const readFile = file => {
  return new Promise(resolve => {
    const reader = new FileReader();
    reader.addEventListener('load', () => resolve(reader.result), false);
    reader.readAsDataURL(file);
  });
};

export const createImage = url =>
  new Promise((resolve, reject) => {
    const image = new Image();
    image.addEventListener('load', () => resolve(image));
    image.addEventListener('error', error => reject(error));
    image.setAttribute('crossOrigin', 'anonymous'); // needed to avoid cross-origin issues on CodeSandbox
    image.src = url;
  });

export function getRadianAngle(degreeValue) {
  return (degreeValue * Math.PI) / 180;
}

/**
 * Returns the new bounding area of a rotated rectangle.
 */
export function rotateSize(width, height, rotation) {
  const rotRad = getRadianAngle(rotation);

  return {
    width: Math.abs(Math.cos(rotRad) * width) + Math.abs(Math.sin(rotRad) * height),
    height: Math.abs(Math.sin(rotRad) * width) + Math.abs(Math.cos(rotRad) * height)
  };
}

const getCroppedImg = async (
  imageSrc,
  pixelCrop = { x: 0, y: 0 },
  rotation = 0,
  flip = { horizontal: false, vertical: false }
) => {
  const image = await createImage(imageSrc);
  const canvas = document.createElement('canvas');
  const ctx = canvas.getContext('2d');

  if (!ctx) {
    return null;
  }

  const rotRad = getRadianAngle(rotation);

  // calculate bounding box of the rotated image
  const { width: bBoxWidth, height: bBoxHeight } = rotateSize(image.width, image.height, rotation);

  // set canvas size to match the bounding box
  canvas.width = bBoxWidth;
  canvas.height = bBoxHeight;

  // translate canvas context to a central location to allow rotating and flipping around the center
  ctx.translate(bBoxWidth / 2, bBoxHeight / 2);
  ctx.rotate(rotRad);
  ctx.scale(flip.horizontal ? -1 : 1, flip.vertical ? -1 : 1);
  ctx.translate(-image.width / 2, -image.height / 2);

  // draw rotated image
  ctx.drawImage(image, 0, 0);

  // croppedAreaPixels values are bounding box relative
  // extract the cropped image using these values
  const data = ctx.getImageData(pixelCrop.x, pixelCrop.y, pixelCrop.width, pixelCrop.height);

  // set canvas width to final desired crop size - this will clear existing context
  canvas.width = pixelCrop.width;
  canvas.height = pixelCrop.height;

  // paste generated rotate image at the top left corner
  ctx.putImageData(data, 0, 0);

  // As Base64 string
  // return canvas.toDataURL('image/jpeg');

  // As a blob
  return new Promise(resolve => {
    canvas.toBlob(file => {
      resolve({ file, url: URL.createObjectURL(file) });
    }, 'image/jpeg');
  });
};

export default getCroppedImg;

```
We used `readFile`, `getCroppedImg` functions in our components. Others functions wil be user internally among this functions.
## Implementing the ImageModal and ImageCrop Interaction
In this section, we'll create  the `ImageCrop` component, which acts as the main entry point for users to interact with the image modal and crop/rotate their chosen images.

```javascript
//src/components/ImageCrop.jsx

import { useState } from 'react';
import user1 from '../assets/user_1.png';
import Modal from '../components/base/Modal';
import { readFile } from '../helpers/cropImage';
import ImageCropModalContent from './ImageCropModalContent';
import { useImageCropContext } from '../providers/ImageCropProvider';

const ImageCrop = () => {
  const [openModal, setOpenModal] = useState(false);
  const [preview, setPreview] = useState(user1);

  const { getProcessedImage, setImage, resetStates } = useImageCropContext();

  const handleDone = async () => {
    const avatar = await getProcessedImage();
    setPreview(window.URL.createObjectURL(avatar));
    resetStates();
    setOpenModal(false);
  };

  const handleFileChange = async ({ target: { files } }) => {
    const file = files && files[0];
    const imageDataUrl = await readFile(file);
    setImage(imageDataUrl);
    setOpenModal(true);
  };

  return (
    <div className="bg-gray-100 h-screen flex justify-center items-center">
      <input
        type="file"
        onChange={handleFileChange}
        className="hidden"
        id="avatarInput"
        accept="image/*"
      />
      <label htmlFor="avatarInput" className="cursor-pointer">
        <img
          src={preview}
          height={192}
          width={192}
          className="object-cover rounded-full h-48 w-48"
          alt=""
        />
      </label>

      <Modal open={openModal} handleClose={() => setOpenModal(false)}>
        <ImageCropModalContent handleDone={handleDone} handleClose={() => setOpenModal(false)} />
      </Modal>
    </div>
  );
};

export default ImageCrop;

```
Integrating with ImageCropContext:

- State and Image Preview:
  The `ImageCrop` component sets up two state variables: `openModal` and `preview`.
 <br />
  `openModal`: Controls the visibility of the image modal. It is initially set to `false` and becomes `true` when users upload an image or open the modal to crop and rotate.
  `preview`: Holds the URL of the image to be displayed as a preview before cropping. It is initially set to a default image (`user1` in this case).
- Image Upload and Modal Interaction:
  Users can click on the `img` element, which is a label for the hidden file input. When clicked, the file input dialog opens, allowing users to select an image to upload.
  The `handleUpload` function is triggered when an image is selected. It reads the image data URL using the `readFile` function and sets it in the context using `setImage`. It also sets the `openModal` state to `true`, showing the image modal.
- `ImageModal` and `ImageCropModalContent` Integration:
 The `ImageCrop` component renders an `ImageModal` component. The `ImageCropModalContent` is passed as the content of the modal.
  The open prop of Modal controls the visibility of the modal, and the `handleClose` function passed as the `handleClose` prop is used to close the modal when needed.
- Image Cropping and Preview Update:
  The `handleDone` function is called when users finish cropping and click the **Done & Save** button in the `ImageCropModalContent`.
  Inside `handleDone`, we call the `getCroppedImage` function to get the cropped image from the context.
  The resulting File object is converted into a preview URL using `URL.createObjectURL`, which is then set in the preview state, updating the image preview with the cropped version.
  Finally, we set `openModal` to false, closing the image modal.

## Integrating the ImageCrop Component

The `App` component serves as the container for the `ImageCrop` component, which provides users with an interface to interact with the image modal for cropping and rotating images. In your case it may be a different component

```javascript
//src/App.jsx

import ImageCrop from './components/ImageCrop';
import ImageCropProvider from './providers/ImageCropProvider';

const App = () => {
  return (
    <div className="bg-gray-100 h-screen flex justify-center items-center">
      <ImageCropProvider>
        <ImageCrop />
      </ImageCropProvider>
    </div>
  );
};

export default App;

```
In the `App` component, we wrap the `ImageCrop` component with the `ImageCropProvider`. This ensures that the `ImageCrop` component has access to the `ImageCropContext`, enabling it to manage the state and functionalities for image cropping and rotation effectively.
The `ImageCrop` component serves as the main user interaction point for selecting, cropping, and rotating images.
By wrapping the `ImageCrop` component with the `ImageCropProvider`, it can access the necessary context and seamlessly interact with the `Cropper` component and `ImageCropModalContent`.

## Conclusion
In conclusion, we have successfully implemented an image modal with crop and rotation functionalities using React, and the `react-easy-crop` library. By utilizing the `ImageCropProvider` context, we effectively managed the state and actions required for image manipulation, reducing the need for excessive prop passing between components.

With this image modal in place, users can now effortlessly edit their profile pictures or any other images uploaded to the application.

here is full project code link: [GitHub Code Source](https://github.com/Mizan-Rifat/image-cropper-modal)

Hope this tutorial has been helpful in expanding your knowledge of React, and that you're inspired to apply these techniques to enhance your own projects.