Search code examples
node.jsreactjscloudinary

How to receive Cloudinary image URL immediately upon upload (React JS and Node Js)?


I can successfully upload images to Cloudinary. But my question is how can I get the Cloudinary url of the successfully uploaded image sent back to me immediately upon upload?

I know it's sent back as part of const uploadedResponse = await cloudinary.uploader.upload(fileStr, {upload_preset: 'dev_setups'}), but this is on the backend (see code #2 below), I would like to receive the URL on the frontend (see code #1 below) so I can set it to React state. What is the best approach to accomplishing this?

Please let me know if you need more details.

Code #1: Below is my code to upload a picture to Cloudinary (Cloudinary specific code is commented below for reference as /* Cloudinary upload */)

import React, { useState } from 'react'
import { Card, Button, CardContent } from '@material-ui/core';
import { post, makePostAction } from '../actions';
import { useSelector, useDispatch } from 'react-redux';

export default function MakePost() {
  const [title, setTitle] = useState("")
  const dispatch = useDispatch();
  const usernameHandle = useSelector(state => state.username)
  const [fileInputState, setFileInputState] = useState('') /* new */
  const [previewSource, setPreviewSource] = useState('') /* new */
  const [selectedFile, setSelectedFile] = useState('') /* new */

  const onInputChange = (event) => {
    setTitle(event.target.value);
  }

  const handleSubmit = (evt) => {
    evt.preventDefault();
    if (!title) return
    dispatch(makePostAction({
      title,
      comment: false,
      comments_text: "",
      handle: usernameHandle,
      post_date: new Date().getTime()
    }))
    setTitle("")
  }

/* Cloudinary upload */
  const handleFileInputChange = (e) => {
    const file = e.target.files[0]
    previewFile(file)
  }

  const previewFile = (file) => {
    const reader = new FileReader();
    reader.readAsDataURL(file);
    reader.onloadend = () => {
      setPreviewSource(reader.result)
    }
  }

  const handleSubmitFile = (e) => {
    e.preventDefault();
    if(!previewSource) return;
    uploadImage(previewSource)
  }

  const uploadImage = async (base64EncodedImage) => {
    console.log(base64EncodedImage)
    try {
      await fetch('/api/upload', {
        method: 'POST',
        body: JSON.stringify({data: base64EncodedImage}),
        headers: {'Content-type': 'application/json'}
      })
    } catch (error) {
      console.error(error)
    }
  }
/* Cloudinary upload */

  return (
    <div>
      <Card>
        <CardContent>
          <form onSubmit={handleSubmit}>
            <input type="text" value={title} onChange={onInputChange} />
          </form>
            {/* new */}
            <form onSubmit={handleSubmitFile} className="form">
              <input type="file" name="image" onChange={handleFileInputChange} value={fileInputState} className="form-input" /> 
              <button className="btn" type="submit">Submit</button>
            </form>
            {/* new */}

          {previewSource && (
            <img 
              src={previewSource} 
              alt="chosen"
              style={{height: '300px'}} 
            />
          )}
        </CardContent>
      </Card>
    </div>
  )
}

Code #2: Here is my server.js

const express = require('express');
const app = express();
const {cloudinary} = require('./utils/cloudinary');

app.use(express.json({limit: '50mb'}));
app.use(express.urlencoded({limit: '50mb', extended: true}))

app.get('/api/images', async (req, res) => {
    const {resources} = await cloudinary.search.expression('folder:dev_setups')
.sort_by('public_id', 'desc')
.max_results(1)
.execute()
const publicIds = resources.map(file => file.secure_url)
res.send(publicIds)
})


app.post('/api/upload', async (req, res) => {
    try {
        const fileStr = req.body.data;
        const uploadedResponse = await cloudinary.uploader.upload(fileStr, {upload_preset: 'dev_setups'})
        res.json({msg: "Success"})
    } catch (error){
        console.error(error)
        res.status(500).json({err: 'Something went wrong'})
    }
})
const port = process.env.PORT || 3001
app.listen(port, () => {
    console.log(`listening on port ${port}`)
});

Solution

  • The Cloudinary upload response object includes a secure_url attribute which you can send back to the front end. Looking at code #2, it seems that you're currently sending a "Success" msg (res.json({msg: "Success"})). Sounds like you want to change that line to -

    res.json({url: uploadedResponse.secure_url})

    In your front end (code #1), I'd consider switching from async/await to .then mechanism, as you don't want to the entire app to wait for the response -

    const uploadImage = (base64EncodedImage) => {
      console.log(base64EncodedImage);
      fetch('/api/upload', {
          method: 'POST',
          body: JSON.stringify({data: base64EncodedImage}),
          headers: {'Content-type': 'application/json'}
        })
        .then(doWhateverYouWant)
        .catch((error) => console.error(error))
    }
    
    const doWhateverYouWant = async (res) => {
    // you can use res.url
    }