DEV Community

Maaruf Dauda
Maaruf Dauda

Posted on

Uploading images to Cloudinary with a React app.


Hi there. This year, I decided to write at least four technical articles a month. That's at least one per week. 

If I stick with this all through the year, we'll be looking at almost 60 (if not more once 2020 is done). Can't wait to see what the year brings.
Without any further ado, let's get straight to business.

First, you'll need a React app. Create one now using the create-react-app CLI. If you have a react app already, then feel free to skip this step. I've named mine cloudinary-upload, but feel free to be a little more creative with yours.

Next, we'll need a couple of elements. A button to trigger the upload process, a div element to render the selected image(s) on screen. And some states.

For simplicity, I'll be sticking to internal state, hence useState in this article but it should be easy to plug into other state management patterns as well (like Redux -_-).

Now, we'll create our parent component. I'll use App.js. Clear out all of the generated code and leave a nice and empty react component of this shape:

import React from 'react';
import './App.css';

function App() {
  return (
    <div className="App">
    </div>
  );
}
export default App;
Enter fullscreen mode Exit fullscreen mode

Next, we'll import useState from the react package and get our internal state ready. Add this next line into App component, just before the return keyword:

const [images, setImages] = useState([]);
Enter fullscreen mode Exit fullscreen mode

Because I want to render more than one image, I have chosen to make my default state an array. All images we upload will be stored in this array.

Next we'll create a react element that loops over our array of images and maps them to an actual element.

<section>
{images.map(i => <img src={i} alt="" />)}
</section>
Enter fullscreen mode Exit fullscreen mode

I have chosen to use a section tag as my containing element. Feel free again, to use what suits yours best.

Up next, we'll create an account on Cloudinary taking some important things into consideration.

Upon registration, cloudinary will ask to assign you a generic 'cloud name'. Please change this to something you can remember or at least take note of it or write it down somewhere. For my account, I have chosen the cloud name: emkaydee.

The primary interest you choose does not have an effect on our purposes so choose whatever feels the most natural.

Go over the onboarding process with Cloudinary or skip it, either way you get to your dashboard at the end. Where you will find the cloud name you have chosen and a very very helpful API key.

Now, before anything else, locate the settings page by tapping on your profile icon towards the top right of your dashboard. Click on the gear icon and you'll be redirected to the settings page.

Move to the uploads tab.

Scroll down here and 'Enable unsigned uploading'. Doing this generates an upload preset for you. An unsigned upload preset. This means you will not have to generate a signature every time you wish to upload an image.

Change the name of this preset to something that feels a bit more natural. I'll name mine 'upload' because why not?

Now, scroll to the bottom to save your changes.

Next, we'll pop over to the Security tab real quick and where it says 'Restricted media types', we choose to not restrict any media. 

Having done that, scroll to the bottom again to save your changes and find your way back to the dashboard.

Now, there are two Cloudinary parameters we would be making use of in the next section:

  1. Your cloud name. Mine is 'emkaydee'

  2. Your upload preset name. I've called mine 'upload'


It is finally time to get back to coding. We will be needing two npm packages: cloudinary-react and cloudinary-core.

Run this command in your terminal to get them both:

npm i cloudinary-react cloudinary-core
Enter fullscreen mode Exit fullscreen mode

Now, import CloudinaryContext from cloudinary-react and wrap the App component with it, passing in your cloud name as a prop. With this done correctly, your App.js file should be looking like this:

import React, {useState} from 'react';
import { CloudinaryContext } from "cloudinary-react";
import './App.css';

function App() {
  const [images, setImages] = useState([])

  return (
    <CloudinaryContext cloudName="emkaydee">
      <div className="App">
        <section>
          {images.map(i => <img src={i} alt="" />)}
        </section>
      </div>
    </CloudinaryContext>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Think of this as allowing us to pop open the cloudinary widget anywhere in our app as long as that component is a child of the App component.

Next, we'll create a util file in our src folder. Name that file CloudinaryService.js. It will contain helper methods to ease our upload and render processes.

Copy the following code into CloudinaryService.js:


import { Cloudinary as CoreCloudinary, Util } from 'cloudinary-core';

export const url = (publicId, options) => {
  const scOptions = Util.withSnakeCaseKeys(options);
  const cl = CoreCloudinary.new();
  return cl.url(publicId, scOptions);
};

export const openUploadWidget = (options, callback) => {
  const scOptions = Util.withSnakeCaseKeys(options);
  window.cloudinary.openUploadWidget(scOptions, callback);
};

export async function  fetchPhotos  (imageTag, setter)  {
  const options = {
  cloudName: 'emkaydee',
  format: 'json',
  type: 'list',
  version: Math.ceil(new Date().getTime() / 1000),
};

const urlPath = url(imageTag.toString(), options);

fetch(urlPath)
.then(res => res.text())
.then(text => (text ? setter(JSON.parse(text).resources.map(image => image.public_id)) : []))
.catch(err => console.log(err));
};
Enter fullscreen mode Exit fullscreen mode

In the fetchPhotos function where I have emkaydee as my cloudName, please be sure to replace it with yours.

Next, we'll import both these helper methods in App.js:

import { fetchPhotos, openUploadWidget } from "./CloudinaryService";
Enter fullscreen mode Exit fullscreen mode

Next we'll add a button to App.js to trigger the upload process along with a new function for our onClick:

const beginUpload = tag => {
  const uploadOptions = {
    cloudName: "emkaydee",
    tags: [tag],
    uploadPreset: "upload"
  };

  openUploadWidget(uploadOptions, (error, photos) => {
    if (!error) {
      console.log(photos);
      if(photos.event === 'success'){
        setImages([...images, photos.info.public_id])
      }
    } else {
      console.log(error);
    }
  })
}
Enter fullscreen mode Exit fullscreen mode

Add this method to the onClick of our newly created button:

<button onClick={() => beginUpload()}>Upload Image</button>
Enter fullscreen mode Exit fullscreen mode

At this point, everything should be ready to go. But if we attempt to run our app, and click on the button, we get an error: window.cloudinary is undefined. This is because we have not actually added a package for the cloudinary widget component itself.

Unfortunately, there isn't an npm package for the widget as of writing this article so we will need to use a workaround.

To do this, we'll add a script tag to the index.html file in the public folder. So, go over there and add this script at the bottom of the 'body' element:

<script src="https://widget.cloudinary.com/v2.0/global/all.js" type="text/javascript">
</script>
Enter fullscreen mode Exit fullscreen mode

Try popping open our widget now and it should display properly. You might even try uploading an image and it should show up in your media library on Cloudinary.

Our react app however remains empty with no images?


The last part of this tutorial is fetching uploaded images from our Cloudinary account.

Add a simple effect to App.js with our nifty useEffect hook, and in the hook, add: fetchPhotos("image", setImages);

This still does not make our images show up though. Because they are not delivered as source links which we can simple plug into an <img /> element.

Instead, we'll use the Image component from cloudinary-react.

In App.js, change:

<section>
  {images.map(i => <img src={i} alt="" />)}
</section>
Enter fullscreen mode Exit fullscreen mode

to use the Image component we have just imported:

<section>
  {images.map(i => <Image
         key={i}
         publicId={i}
         fetch-format="auto"
         quality="auto"
       />)}
</section>
Enter fullscreen mode Exit fullscreen mode

If we have everything nice and correct, the App.js file should be looking like this:

import React, {useState, useEffect} from 'react';
import { CloudinaryContext, Image } from "cloudinary-react";
import { fetchPhotos, openUploadWidget } from "./CloudinaryService";
import './App.css';

function App() {
  const [images, setImages] = useState([])

  const beginUpload = tag => {
    const uploadOptions = {
      cloudName: "emkaydee",
      tags: [tag, 'anImage'],
      uploadPreset: "upload"
    };
    openUploadWidget(uploadOptions, (error, photos) => {
      if (!error) {
        console.log(photos);
        if(photos.event === 'success'){
          setImages([...images, photos.info.public_id])
        }
      } else {
        console.log(error);
      }
    })
  }

  useEffect( () => {
    fetchPhotos("image", setImages);
  }, [])

  return (
   <CloudinaryContext cloudName="emkaydee">
      <div className="App">
        <button onClick={() => beginUpload("image")}>Upload Image</button>
      <section>
        {images.map(i => <Image
              key={i}
              publicId={i}
              fetch-format="auto"
              quality="auto"
            />)}
      </section>
    </div>
   </CloudinaryContext>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

That's it. That's the entire tutorial. Your app should now be connected to your cloudinary account. If you have any questions, please ask in the comments and react to this story if you found it the least bit helpful.

Discussion (11)

Collapse
sajaswalgah profile image
SajaSwalgah

when refreshing the page the uploaded photos disappear, how can i solve this problem

Collapse
mmichaud3 profile image
mmichaud3

ever come up with a solution for this?

Collapse
alextrandafir profile image
alex-trandafir

You need to save the id in a state probably

Collapse
emkaydauda profile image
Maaruf Dauda Author

Hi Saga,

I'm not sure why this would happen, but can you confirm that you have a useEffect call that fetches the images?

Collapse
ogheneovo12 profile image
ogheneovo12

yeah, but the fetch functions are being called, but the urlpath generated is wrong, returns 404

Collapse
ogheneovo12 profile image
ogheneovo12

hey nice article, it was very helpful, but I have got a minor issue, is there a way I can hook into the upload process, have the image temporarily held then when a certain condition is meant, I complete the upload, if not abort.

Collapse
jordantredaniel profile image
Jordan Christley • Edited

Great post! You should change your title to get more exposure though, bc you do MUCH MORE than just upload :)

Collapse
alextrandafir profile image
alex-trandafir

I was just looking for a quick plug&play cloudinary widget for a react + nexjs app and this was very helpful :)

Collapse
emkaydauda profile image
Maaruf Dauda Author

Hi Alex,

Glad to hear the article helped. Thank you :)

Collapse
smitarath profile image
SmitaRath • Edited

Even if I am uploading multiple images, only one image would show up on my application. Do I have to modify something to retrieve all the images which I have uploaded.

Collapse
errajeevranjan profile image
Rajeev Ranjan

This is exactly what i was looking for . thanks a lot man.