Skip to content

React Selfie App with Cloudinary

In this article, we’ll learn how to build an application that allows us to take pictures using a webcam. In other words, we will build a selfie application using React.js and then use the rich image transformation features provided by Cloudinary to apply unique special effects to our captured images. This should be a lot of fun! Let’s get started.

This article assumes you are familiar with the basics of JavaScript and React.

Here is a link to the demo on CodeSandbox and the full source code here on GitHub.

We will be using Cloudinary to take advantage of the SDKs, so you need to create an account if you do not have one already.

Log in after creating your account, and on your dashboard page, you should see all your credentials (cloud name, etc.) Before we can apply transformations to an image, the image needs to be uploaded to Cloudinary servers. Since our uploads will be done from our client-side application, we need to define an upload preset. Upload presets can either be signed or unsigned and we would be using the unsigned presets.

If you don’t have an unsigned preset already, you can create one as follows:

  • Go to your Cloudinary dashboard and click on the settings icon.
  • Select the Upload tab and scroll down to the Upload presets section.
  • Click the Add upload preset link.
  • In the form displayed, add a name for your upload preset and set the Signing mode to unsigned, and click the Save button.

Take note of the preset name because it will be used later to upload captured images.

Open your terminal and run the following command to bootstrap a React app in a folder called selfie-app.

    npx create-react-app selfie-app

The next step is to install the dependencies that we will need in this project. Run the following command to install them.

    npm install --save @cloudinary/url-gen axios react-webcam
Code language: CSS (css)

The command above installs the Cloudinary SDKs that we will use in our application. We will be using the @Cloudinary/url-gen package to configure Cloudinary, build image URLs and apply transformations (filters and visual enhancements). Axios will be our HTTP client for uploading images, and react-webcam will be used to access the user’s camera and capture images.

A bird’s-eye view of all the moving parts that make up our app is provided below. Include the following code in your App.js file.

    import "./App.css";
    import { useRef, useState } from "react";
    import Webcam from "react-webcam";
    import axios from "axios";
    import { Cloudinary } from "@cloudinary/url-gen";
    import { Effect } from "@cloudinary/url-gen/actions/effect";

    //Add your cloud name
    let cloudName = "ADD-YOUR-CLOUD-NAME-HERE";
    const cld = new Cloudinary({
        cloud: {
            cloudName,
        },
    });
    function applyFilter(filter, image) {
      // this will be used to apply filters
    }
    const filters = [
    // strings representing a filter
    ];
    function ImagePreviewer() {
     // this will be used to display our image
    }
    function FilterItem({ imgId, setPrevURL, filterName }) {
    //this will be used to apply a filter
    }
    const App = () => {
       // this will hold our camera stream, imagePreviewer and filter components 
    };
    export default App;
Code language: JavaScript (javascript)

First, we’re importing react hooks, Axios, and some components from the Cloudinary SDK. We then create an instance of the Cloudinary class and initialized it with our cloud name. The Cloudinary instance will be used to build the delivery URLs for our assets.

Next, we define a function called applyFilter, which accepts a filter and an image to which the desired filter will be applied. We also define an array that will hold strings each representing the different filters (e.g., sepia, greyscale) that will be applied to our images.

Finally, we defined some components we’ll need and exported our App component. Over the following sections, we will be visiting each function as the need arises to update their logic as we build our application.

In your App.js file, update your App component with the following:

    const App = () => {
      const constraints = {
        width: 700,
        height: 550,
        facingMode: "user",
        aspectRatio: 9 / 16,
      };
      const camRef = useRef();
      const [loading, setLoading] = useState(false);
      return (
        <section className="main">
          <article className="media_box">
            <Webcam
              // this will be used internally to bind some useful methods to the ref variable.
              ref={camRef}
              videoConstraints={constraints}
              //this specifies the file format we want for the captured
              screenshotFormat="image/jpeg"
            />
            {/* this button will be used to capture the image*/}
            <button
              disabled={loading}
              className="capture_btn"
            ></button>
          </article>
        </section>
      );
    };
    export default App;
Code language: JavaScript (javascript)

In the code above, we created an object that defines the media constraints of the video recording before an image is captured. We also defined a reference that will give us access to our web camera using the useRef hook and a state variable to toggle between loading states when uploading an image.

Finally, we returned the Webcam component and a button that is disabled when loading.

If you open your browser on localhost:3000, should see this:

Under the hood, the Webcam component requests permission to access media devices as defined in the audio and video constraints. In this case, it requests access to the camera. When authorized, it receives a media stream object, creates a video element, and uses the media stream object as its source.

At this point, we can’t do much with what we have. Let’s add some functionalities to be able to capture and upload an image.

Add the following to your App component.

    const App = () => {
      const constraints = {
        width: 700,
        height: 550,
        facingMode: "user",
        aspectRatio: 9 / 16,
      };
      const camRef = useRef();
      const [loading, setLoading] = useState(false);
      const [id, setId] = useState("");
      const [prevURL, setPrevURL] = useState("");
      const captureAndUpload = async () => {
        // get screenshot
        const data = camRef.current.getScreenshot();
        // upload to cloudinary and get public_id
        try {
          setLoading(true);
          const imageData = new FormData();
          imageData.append("file", data);

         // Add your upload preset here
          imageData.append("upload_preset", "ADD-YOUR-UPLOAD-PRESET-HERE");
          const res = await axios.post(
            ` https://api.cloudinary.com/v1_1/${cloudName}/image/upload`,
            imageData
          );
          const imageDetails = res.data;
          setId(imageDetails.public_id);
          setPrevURL(imageDetails.url);
        } catch (error) {
          console.log(error);
        } finally {
          setLoading(false);
        }
        //set publicID
      };
      const deleteImage = () => {
        setPrevURL("");
        setId("");
      };
      return (
        <section className="main">
          <article className="media_box">
            <Webcam
              ref={camRef}
              videoConstraints={constraints}
              screenshotFormat="image/jpeg"
            />
            {/* this button will be used to capture the image*/}
            <button
              disabled={loading}
              onClick={captureAndUpload}
              className="capture_btn"
            ></button>
          </article>
        </section>
      );
    };
Code language: JavaScript (javascript)

In the code above, we added two state variables called id and previewUrl. They will hold the public_id and the delivery URL, respectively. Before applying transformations to our media assets, we need to upload them to our Cloudinary account first. Each uploaded asset will be given a unique public ID that will be used to reference it and build its delivery URL.

We also defined CaptureAndUpload and deleteImage, which updates and clears our ID and previewURL variables. As the name implies, the function CaptureAndUpload gets the image data from the call to the getScreenShot function and saves it in a variable. We then toggle our loading state to prevent a user from clicking the button to capture another image during the upload process.

Before uploading, we construct our request body using a FormData object and specify key-value pairs that signify the following:

  • File: this is the file we want to upload. Cloudinary supports different file source options, and in this case, we are using the base64 encoded string representation of our image from the call to the getScreenshot function.
  • upload_preset: this string represents the unsigned upload preset we created earlier.

Since we are doing unsigned uploads, the file and upload_preset parameters are mandatory to see a complete list of other options; check here.

Next, we hit the Cloudinary upload endpoint and pass our request body as an input. If everything goes fine, we get an upload response object which contains information about the uploaded asset. We then take the public_ID and URL and store them in our id and previewURL variables. If we get an error, we print it to the console.

We created a deleteImage function that clears the content of our state variables in our React application, but the image will still be present on our Cloudinary account.

Now we can take pictures and upload them to Cloudinary, but we also need to display the image. In our App.js file, let’s update our ImagePreviewer component.

    function ImagePreviewer({ url, deleteImage }) {
        return url ? (
            <div className="img_box">
                <img src={url} alt="my_image" />
                <button className="close_btn" onClick={deleteImage}>
                    Delete
                </button>
            </div>
        ) : null;
    }
Code language: JavaScript (javascript)

This component accepts a URL and a deleteImage function. It renders an image and a button that triggers the deleteImage function when clicked.

Let’s now update our app component to include the imagePreviewer component and pass in our id and previewURL as props.

    const App = () => {
       // truncated for brevity
      return (
         <section className="main">
                  // truncated for brevity
                    {/* this button will be used to capture the image*/}
                    <button
                        disabled={loading}
                        onClick={captureAndUpload}
                        className="capture_btn"
                    ></button>
                     <ImagePreviewer url={prevURL} deleteImage={deleteImage} />
                </article>
            </section>
        );
    
    }
Code language: HTML, XML (xml)

As mentioned in the introduction, Cloudinary’s transformation features provide us with so many ways to manipulate media assets, including applying visual enhancements based on our application’s needs. We will be using only a few filters but to see all available filters check here.

Let’s populate our array with different filter options. Add the following to your filters array in your App.js file.

    const filters = [
        "none",
        "artistic",
        "sepia",
        "cartoonify",
        "vignette",
        "oilpaint",
        "grayscale",
        "vectorize",
        "pixelate",
    ];
Code language: JavaScript (javascript)

Next, we update our applyFilter function to include the logic for applying different filters.

    function applyFilter(filter, image) {
        switch (filter) {
            case "artistic":
                return image.effect(Effect.artisticFilter("fes"));
            case "sepia":
                return image.effect(Effect.sepia());
            case "cartoonify":
                return image.effect(Effect.cartoonify());
            case "vignette":
                return image.effect(Effect.vignette());
            case "oilpaint":
                return image.effect(Effect.oilPaint());
            case "grayscale":
                return image.effect(Effect.grayscale());
            case "vectorize":
                return image.effect(Effect.vectorize());
            case "pixelate":
                return image.effect(Effect.pixelate());
            default:
                return image;
        }
    }
Code language: JavaScript (javascript)

The function applyFilter accepts a string and an image object. It uses a switch statement based on the filter object to modify and return the image object after applying the desired filter. We’re using the effect action group and passing the appropriate action from the Effect factory method we imported.

Now we have our filters array and applyFilter function, let’s update our FilterItem component. Add the following to your FilterItem component in your App.js file.

    function FilterItem({ imgId, setPrevURL, filterName }) {
        let image = cld.image(imgId);
        image = applyFilter(filterName, image);
        const imgURL = image.toURL();
        return (
            <div className="filter_item" onClick={() => setPrevURL(imgURL)}>
                <img src={imgURL} alt="" />
                <span className="filter_des">{filterName}</span>
            </div>
        );
    }
Code language: JavaScript (javascript)

This function creates an image object using the id prop, and our Cloudinary instance then applies the desired filter. It also returns some JSX that includes a div that responds to click events and passes the URL representation of the transformed image object to modify the prevURL variable. It then displays the image and the filter name.

We can now update the JSX returned from our App Component.

    const App = () => {
       // truncated for brevity
        return (
            <section className="main">
                <article className="media_box">
                     // truncated for brevity
                </article>
                <article className="filter_container">
                    {id && (
                        <>
                            {filters.map((filter, index) => (
                                <FilterItem
                                    imgId={id}
                                    filterName={filter}
                                    setPrevURL={setPrevURL}
                                    key={index}
                                />
                            ))}
                        </>
                    )}
                </article>
            </section>
        );
    };
Code language: HTML, XML (xml)

In the return statement of our App component, we first checked for the URL of our uploaded image. If it exists, we iterate over our filter strings and render our FilterItem component with the expected props. If we run our app now, we should be able to take pictures using our webcam and apply any filter of our choice.

Me, mySelfie and I btw your selfie is probably way better than mine. 🙂

In this article, we were able to build an application that allows us to take pictures using a webcam component. We also used the image transformation features provided by Cloudinary to apply unique special effects to our captured images.

Some resources you may find helpful:

Back to top

Featured Post