loading...

Uploading images to Cloudinary with a React app.

emkaydauda profile image Maaruf Dauda ・6 min read

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;

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([]);

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>

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

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;

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));
};

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";

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);
    }
  })
}

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

<button onClick={() => beginUpload()}>Upload Image</button>

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>

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>

to use the Image component we have just imported:

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

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;

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.

Posted on by:

emkaydauda profile

Maaruf Dauda

@emkaydauda

I'm passionate about creating mobile and web apps. I dabble in data science sometimes.

Discussion

pic
Editor guide
 

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

 

You need to save the id in a state probably

 

ever come up with a solution for this?

 

Hi Saga,

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

 

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

 

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.

 

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

 

Hi Alex,

Glad to hear the article helped. Thank you :)

 

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