2

I want to convert images from a URL to base 64, and store that in a state for later use.

How do I return the data from fetch()?

I can get it to work using CORS safe images and an HTML canvas, but this will not work for public domains.

  openImage = (index) => {
    const url = formatURLs[index];
    const base64 = this.getBase64Image(url);

    this.setState(prevState => ({
      currentImage: index,
      currentImagebase64: base64
    }));
  }

  getBase64Image(url) {
    fetch(url).then(r => r.blob()).then(blob => {
      var reader = new FileReader();
      reader.onload = function() {
           var b64 = reader.result.replace(/^data:.+;base64,/, '');
           return b64;
      };
      reader.readAsDataURL(blob);
    });
  }

When I console.log(reader.result), it will output base64 as expected.

However when I return b64, it returns to openImage as 'undefined'.

Ryan Bircham
  • 67
  • 1
  • 6
  • 1
    Why not just store the Blob? – Bergi Aug 04 '19 at 13:21
  • `fetch()` is *asynchronous* so you can't return a value from a function like yours. You can return a Promise and update the state from within a `.then()` callback. – Pointy Aug 04 '19 at 13:21
  • You need to [make a promise for the `b64` data in the load event handler](https://stackoverflow.com/questions/29699372/promise-resolve-before-inner-promise-resolved/29699544#29699544). Then you can `return` those so that `getBase64Image` returns a promise. Change `openImage` to wait for the result of the promise. – Bergi Aug 04 '19 at 13:24
  • Don't make a base64 version of it. As said by @Bergi, you'd be better to store the Blob. If it needs to be persistent, then use IndexedDb to store the Blob. When you need to display the image use a blobURI (URL.createObjectURL). You would only need a dataURI if you were generating a standalone document which would need to embed that file, and it seems you're not in this quite rare case. – Kaiido Aug 04 '19 at 14:07

2 Answers2

3

getBase64Image is async so when calling it in a sync way it will return undefined.

You can try something like that

  openImage = async (index) => {
    const url = formatURLs[index];
    const base64 = await this.getBase64Image(url);
    this.setState(prevState => ({
      currentImage: index,
      currentImagebase64: base64
    }));
  }

  async getBase64Image(url) => {
    const response = await fetch(url);
    const blob = await response.blob();
    const reader = new FileReader();
    await new Promise((resolve, reject) => {
      reader.onload = resolve;
      reader.onerror = reject;
      reader.readAsDataURL(blob);
    });
    return reader.result.replace(/^data:.+;base64,/, '')
  }
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
naortor
  • 2,019
  • 12
  • 26
  • 1
    Avoid the [`Promise` constructor antipattern](https://stackoverflow.com/q/23803743/1048572?What-is-the-promise-construction-antipattern-and-how-to-avoid-it)! Write a separate function that uses the `FileReader` and returns a promise for the base64 string from a blob. Also if you want to use `async`/`await`, you probably should do it in `getBase64Image` as well – Bergi Aug 04 '19 at 13:25
  • @Bergi thanks, for the second comment you are right, I was being lazy. for the first comment, can you please elaborate how would you do it? – naortor Aug 04 '19 at 13:36
  • 1
    I hope you don't mind the edit - an even cleaner solution would be to make a separate function that creates the `new Promise` :-) Fixed it @PatrickRoberts – Bergi Aug 04 '19 at 13:40
0

You have to set state inside .then()

getBase64Image(url) {
    fetch(url).then(r => r.blob()).then(blob => {
      var reader = new FileReader();
      reader.onload = function() {
           var b64 = reader.result.replace(/^data:.+;base64,/, '');
           this.setState({
             currentImagebase64: b64
           });
      };
      reader.readAsDataURL(blob);
    });
  }
Habeeb Rahman
  • 320
  • 1
  • 3
  • 15
  • You should rather return a promise from `getBase64Image`, and keep the `setState` inside the `openImage` function. – Bergi Aug 04 '19 at 13:47