Crop An Image Before Upload With React

Publish date

In this short tutorial we'll learn how to automatically crop images before they're uploaded using React and Pintura.

We'll start from scratch with a blank project, if you already have a project you want to add an image cropper component, then you can skip to Installing Pintura Image Editor

Let's get started!

Creating A React Project

We'll type the following in our terminal to create a new React project

npx create-next-app@latest

We'll press return a couple times to choose default answers to the tech stack questions. This will start the installation process. We'll assume you've called the app my-app

⠹ Installing packages (npm)...

That'll run for a couple seconds until it shows.

Success! Created my-app

Let's navigate to the my-app folder.

cd my-app

We can now install Pintura.

Installing Pintura Image Editor

Let's install the Pintura component, this will make it super easy to build our image crop functionality.

npm install @pqina/pintura

Now we can start the development server by typing:

npm run dev

It'll show the following message if all is fine.

 ✓ Ready in 3s

Let's open the project in our browser at http://localhost:3000/, it'll show the default React project page, our next step is altering this page so it shows a file input so we can receive user data.

Crop images before upload with Pintura

We first create a component directory and add a file called CropInput.tsx

Add the following code to the file.

// 1. This means this component will only run in the browser
'use client';

// 2. We'll return a basic file input element for now
export default function CropInput() {
    return <input type="file" />;
}

Now we'll open the app/page.tsx page and will replace all contents so it looks like this:

// 1. We need to get the CropInput
import CropInput from './component/CropInput';

export default function Home() {
    return (
        <div>
            {/* 2. Add CropInput component */}
            <CropInput></CropInput>
        </div>
    );
}

Let's go back to the CropInput.tsx file and update our file input field so we can listen for changes.

// 1. Import the ChangeEvent type from React so we can type the event
import { ChangeEvent } from 'react';

// 2. Add this function to handle changes
function handleFileChange(e: ChangeEvent<HTMLInputElement>) {
    /* Deal with file changes here */
}

export default function Home() {
    return (
        <div>
            {/* 3. Add accept attribute to filter images and onChange handler to listen to change events */}
            <input type="file" accept="image/*" onChange={handleFileChange} />
        </div>
    );
}

When the file input value is changed the handleFileChange function runs, lets implement the function now.

function handleFileChange(e: ChangeEvent<HTMLInputElement>) {
    // 1. Check if we got files to work with
    const fileInput = e.target;
    if (!fileInput || !fileInput.files || fileInput.files.length === 0) return;

    // 2. Check if a file is present and if it is image (just to be sure)
    const file = fileInput.files[0];
    if (!file || !file.type.startsWith('image')) return;

    // We now have a file to work with
}

Let's edit the file with Pintura.

import { ChangeEvent } from 'react';

// 1. Import the Pintura modal factory
import { openDefaultEditor } from '@pqina/pintura';

// 2. Import the Pintura styles
import '@pqina/pintura/pintura.css';

function handleFileChange(e: ChangeEvent<HTMLInputElement>) {
    // Check if we got files to work with
    const fileInput = e.target;
    if (!fileInput || !fileInput.files || fileInput.files.length === 0) return;

    // Check if a file is present and if is image
    const file = fileInput.files[0];
    if (!file || !file.type.startsWith('image')) return;

    // 3. Open the user image with Pintura
    const editor = openDefaultEditor({
        // 4. We'll set the user file as source
        src: file,

        // 5. We want a square crop
        imageCropAspectRatio: 1,
    });
}

Okay, now if we select an image the editor will open, but after editing we also need to store the file.

We can do so by updating the input files property. Alternatively you can upload the resulting file with fetch or XMLHttpRequest, see this article on uploading images with NodeJS.

import { ChangeEvent } from 'react';
import { openDefaultEditor } from '@pqina/pintura';
import '@pqina/pintura/pintura.css';

function handleFileChange(e: ChangeEvent<HTMLInputElement>) {
    // Check if we got files to work with
    const fileInput = e.target;
    if (!fileInput || !fileInput.files || fileInput.files.length === 0) return;

    // Check if a file is present and if is image
    const file = fileInput.files[0];
    if (!file || !file.type.startsWith('image')) return;

    // Open the user image with Pintura
    const editor = openDefaultEditor({
        src: file,
        imageCropAspectRatio: 1,
    });

    // 1. We listen for the resulting image
    editor.on('process', ({ dest }) => {
        // 2. Add our `dest` (the output File) to a DataTransfer so we can then get a FileList
        const dataTransfer = new DataTransfer();
        dataTransfer.items.add(dest);

        // 3. Update the file input with the new files list
        fileInput.files = dataTransfer.files;
    });
}

Now if you read out the files list you can upload the resulting file to a server of your choosing.

We're done!

That's it! I hope you found this an interesting read, feel free to reach out with any questions.

You can find a Pintura React example project on GitHub it includes a link to a live code environment where you can run the project online.