64

Q1) In my reactjs application, I am trying to fetch an API from my backend Nodejs server. The API responds with an image file on request.

I can access and see image file on http://192.168.22.124:3000/source/592018124023PM-pexels-photo.jpg

But in my reactjs client side I get this error on console log.

Uncaught (in promise) SyntaxError: Unexpected token � in JSON at position 0

Reactjs:

let fetchURL = 'http://192.168.22.124:3000/source/';
  let image = name.map((picName) => {
    return picName
  })

  fetch(fetchURL + image)
  .then(response => response.json())
  .then(images => console.log(fetchURL + images));

Nodejs:

app.get('/source/:fileid', (req, res) => {
const { fileid } = req.params;
res.sendFile(__dirname + /data/ + fileid); 
});

Is there any better way to do than what I am doing above?

Q2) Also, how can I assign a value to an empty variable (which lives outside the fetch function)
jpg = fetchURL + images; So I can access it somewhere.

JKhan
  • 1,157
  • 4
  • 14
  • 23
  • 3
    The image is not in JSON format. Did you mean to use `.blob()` or something rather than `.json()`? See https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch "Checking that the fetch was successful" section – CertainPerformance May 09 '18 at 07:59
  • You can't use `response.json()` to fetch an image cause it's not a json content. – Dario May 09 '18 at 08:00
  • @CertainPerformance Using blob I get this on console log: http://192.168.22.124:3000/source/[object Blob] – JKhan May 09 '18 at 08:02
  • Did you read the section in the link I posted? – CertainPerformance May 09 '18 at 08:03
  • you should encode the image in base64 in order to use it in json. – xanadev May 09 '18 at 08:04
  • @CertainPerformance I have read it and now I am getting some random strings: blob:http://localhost:3001/5970bae1-06a7-4882-a42e-63bb01a13f5f – JKhan May 09 '18 at 08:14
  • For what it is worth, if the response body of an API endpoint has a variety of content types, it would be more appropriate to check `Content-Type` in the response header to determine whether to invoke `response.json()` or `response.blob()` – Ham Jul 20 '21 at 08:47

3 Answers3

117

The response from the server is an image file, not JSON formatted text. You'll want to read the response content with Response.blob(), blob meaning "binary large object".

In this function we fetch a blob:

async function fetchBlob(url) {
    const response = await fetch(url);

    // Here is the significant part 
    // reading the stream as a blob instead of json
    return response.blob();
}

Then, you can create an object URL and assign the source of an image to this generated URL in your React application:

const [imageSourceUrl, setImageSourceUrl] = useState("");

const downloadImageAndSetSource = async (imageUrl) => {
    const image = await fetchBlob(imageUrl);
    setImageSourceUrl(URL.createObjectURL(image));
}
maxpaj
  • 6,029
  • 5
  • 35
  • 56
  • I get this on console log: 192.168.22.124:3000/source/[object Blob] – JKhan May 09 '18 at 08:11
  • Yes, because `images` is indeed a Blob now. Basically a file. You can then use `var url = URL.createObjectURL(images)` to create a URL for that image. – maxpaj May 09 '18 at 08:26
  • Now I get this: blob:http://localhost:3001/613348fe-52e4-4baa-8e76-e48332494e19 Am I still missing something? – JKhan May 09 '18 at 08:46
  • How can I access the image file url now? When I click on localhost:3001/613348fe-52e4-4baa-8e76-e48332494e19 I get redirected to my sign up page. – JKhan May 09 '18 at 08:58
  • You can't omit the `blob:` at the beginning of the URL. What do you want to do with the image? If you simply want to show it on the page, then why not just use a `` tag? – maxpaj May 09 '18 at 09:05
  • I am trying to save the blob in a variable outside the fetch which is not working and shows undefined. I am passing that variable to another component where it returns image like this – JKhan May 09 '18 at 09:13
  • Why not just pass the `fetchUrl + image` immediately to that component? Why do you need to fetch the image data at all? How do you see that the variable is `undefined` outside of the function? Using `console.log`? – maxpaj May 09 '18 at 09:17
  • Yes, console log. Assigning value to a variable that lives outside the fetch is undefined – JKhan May 09 '18 at 09:19
  • Could be that the `console.log` prints the variable before it is assigned inside the `fetch(...).then(...)` – maxpaj May 09 '18 at 09:22
  • this worked for me! thanks a lot, any tips on how i could cache that image? could i save it to local storage as a blob? – xunux Feb 08 '19 at 03:10
  • 1
    You can save the blob as a Base64 encoded string in local storage. Check out https://stackoverflow.com/questions/18650168/convert-blob-to-base64 – maxpaj Feb 08 '19 at 07:18
10

Equivalent to solution by @maxpaj, but using async and await.

async function load_pic() {
    
        const url = '<REPLACE-WITH-URL>'
    
        const options = {
            method: "GET"
        }
    
        let response = await fetch(url, options)
    
        if (response.status === 200) {
            
            const imageBlob = await response.blob()
            const imageObjectURL = URL.createObjectURL(imageBlob);
    
            const image = document.createElement('img')
            image.src = imageObjectURL
    
            const container = document.getElementById("your-container")
            container.append(image)
        }
        else {
            console.log("HTTP-Error: " + response.status)
        }
    }
eric.mcgregor
  • 3,507
  • 2
  • 16
  • 16
3

This question is 4 years old and I think in 2022 there are many ways to solve this. This is ES6 version using async calls.

First, I don't know if you are trying to download the image or insert the image into a img tag. So I will assume we want to download the image.

The process is simple: a) fetch the image as a blob; b) convert blob to Base64 using URL.createObjectURL(blob); and c) trigger the download using a ghost a tag.

const $btn = document.getElementById('downloadImage')
const url = 'https://s3-ap-southeast-1.amazonaws.com/tksproduction/bmtimages/pY3BnhPQYpTxasKfx.jpeg'

const fetchImage = async url => {
  const response = await fetch(url)
  const blob = await response.blob()
  
  return blob
}

const downloadImage = async url => {
  const imageBlob = await fetchImage(url)
  const imageBase64 = URL.createObjectURL(imageBlob)

  console.log({imageBase64})
  
  const a = document.createElement('a')
  a.style.setProperty('display', 'none')
  document.body.appendChild(a)
  a.download = url.replace(/^.*[\\\/]/, '')
  a.href = imageBase64
  a.click()
  a.remove()
}

$btn.onclick = event => downloadImage(url)
<button id="downloadImage">Download Image</button>

Note:

StackOverflow uses a sandboxed iframe's so we cannot test the download here, but you can use my codepen

Teocci
  • 7,189
  • 1
  • 50
  • 48