Using redux-api-middleware to process image/jpeg content - reactjs

I have an RSAA (Redux Standard API-calling Action) that I'm using to retrieve image/jpeg content. All the examples I've seen deal with JSON data so I copied the getJSON function and implemented my own getImage function to deal with this content type. The problem I'm now running into is that this blob needs to be converted to base64 and that has to be done using an async function. So, my FSA gets triggered before this async operation completes.
I suspect that I need to somehow piggyback on the existing promise chain in the RSAA payload processing but I'm not sure how to do this.
Here's the snippet of code with the line commented where I need to perform the promise resolve to return this result:
export function fetchSiteThumbnailImage(endpoint) {
return {
[CALL_API]: {
endpoint,
method: 'GET',
headers: {
'Accept': 'image/jpeg'
},
types: [
LOAD_SITE_THUMBNAIL_REQUEST,
{
type: LOAD_SITE_THUMBNAIL_SUCCESS,
payload: (action, state, res) => {
return getImage(res).then((blob) => {
const reader = new FileReader();
reader.readAsDataURL(blob);
reader.onloadend = () => {
const base64data = reader.result;
return base64data; // this needs to "resolve" - how??
}
});
},
meta: (action, state, res) => {
return {
siteId,
endpoint
}
}
},
LOAD_SITE_THUMBNAIL_FAILURE
]
}
}
}
Thanks!

You have to wrap your FileReader logic into a Promise:
function readAsBase64(blob) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
const base64data = reader.result;
resolve(base64data);
}
reader.onerror = (err) => {
reject(err);
}
reader.readAsDataURL(blob);
});
}
Your payload function can then just be
(action, state, res) => getImage(res).then(readAsBase64);
A couple of notes:
reader.onloadend gets called when the reading operation is completed (either in success or in failure), while reader.onload is called only on successful completion and reader.onerror only on failed completion — you want to separate the two cases.
You should set the event handlers before you start reading the blob to avoid race conditions — so put reader.readAsDataURL at the end.

I have managed to solve this so I'll answer my own question... I just needed to return a new Promise object like this:
return new Promise((resolve, reject) => {
getImage(res).then((blob) => {
const reader = new FileReader();
reader.readAsDataURL(blob);
reader.onloadend = () => {
const base64data = reader.result;
resolve(base64data);
}
reader.onerror = () => {
reject(Error('unable to process image/jpeg blob'));
}
})
});

Related

How to convert blob into base64 in nextJs

I am getting the Image from an API response. I am trying to the get the image and display in the UI.
import FileReader from 'filereader';
export default async (req, res) => {
const { method } = req;
switch (method) {
case 'GET':
try {
const response = await fetch("imagepathurl", Constants.config);
const imageBlob = await response.blob()
console.log("imageBlob", imageBlob)
return new Promise((resolve, _) => {
const reader = new FileReader();
reader.onloadend = () => resolve(reader.result);
reader.readAsDataURL(imageBlob);
});
} catch (error) {
console.log("error", error);
}
break
default:
res.setHeader('Allow', ['GET', 'PUT', 'PATCH'])
res.status(405).end(`Method ${method} Not Allowed`)
}
}
I can able to capture imageBlob but from the fileReader() i am unable to capture and convert to base64.
In File Reader i am facing Error: cannot read as File: {}.
Please let me know the way i am doing is correct or can soneone guide me in fixing it.

getDownloadURL takes some time

I falling to get the url return by getDownloadURL appended on the ImageUrls state, getDownloadURL takes a second or two to return the url and it seems like the code continues running and doesn't wait for it to return the url
I am trying to upload multiple images and then create an object in firestore that has the images urls and description
const createAlbum = () => {
addDoc(albumCollection, {
name: albumName,
category: category,
images: imageUrls,
description: description,
});
};
const HandleUpload = (files) => {
files.forEach((file) => {
const storageRef = ref(
storage,
`/files/albums/${albumName}/${file.name}`
);
const uploadTask = uploadBytesResumable(storageRef, file);
uploadTask.on(
"state_changed",
(snap) => {},
(err) => {},
() => {
getDownloadURL(uploadTask.snapshot.ref).then((url) => {
setImageUrls((prev) => [...prev, url]);
});
}
);
});
createAlbum();
};
Getting the download URL (like uploading the data, and most modern cloud APIs) is an asynchronous operation. While this call is being handled, your main code indeed continues to execute. Then when the download URL is available, your then callback is invoked with it, so you can use it.
For this reason, any code that needs the download URL needs to be inside the then callback, be called from there, or be otherwise synchronized.
The simplest fix is to move createAlbum into the then callback:
uploadTask.on(
"state_changed",
(snap) => {},
(err) => {},
() => {
getDownloadURL(uploadTask.snapshot.ref).then((url) => {
setImageUrls((prev) => [...prev, url]);
createAlbum();
});
}
);
If you only want to call createAlbum() once all uploads are completed, you can keep a counter or use Promise.all():
const HandleUpload = (files) => {
let promises = files.map((file) => {
const storageRef = ref(
storage,
`/files/albums/${albumName}/${file.name}`
);
return uploadBytesResumable(storageRef, file).then(() => {
return getDownloadURL(uploadTask.snapshot.ref);
});
});
Promise.all(promises).then((urls) => {
setImageUrls(urls)
createAlbum();
})
};
This code uses the fact that the task returned by uploadBytesResumable is also a Promise, so we can use then() to know when it's completed, and then gets the download URL.
Note that if setImageUrls is a useState hook, that operation is asynchronous too. I recommend passing the image URLs to createAlbum explicitly, rather than trying to pass it through the state, so createAlbum(urls)
Also you can use promise to error and callback to execute one time createAlbum()
like this:
uploadTask.on(
"state_changed",
(snap) => {},
(err) => {},
(),
error => {
console.log('upload error: ', error.message)
},
() => {
getDownloadURL(uploadTask.snapshot.ref).then((url) => {
setImageUrls((prev) => [...prev, url]);
createAlbum();
});
}
)

I am having a problem setting state with returned json data

I am doing a project messing around with The Movie Data Base Api. I am using fetch to grab saved id's (I am storing them in a fake rest api, JSON SERVER) and then looping through them and for each id I am making a fetch to retrieve the particular movie the id is associated with. I am using Promise.All to achieve this. After Pushing the data into an empty array I am able to get back what I need in a console.log() if I log the array, but the minute I try to use useState() and pass the array in it starts infinitely making requests. Maybe Someone can point me in the right direction? Thank you
let moviesToReturn = []
const [favorites, setFavorites] = useState([])
let requests = movieId.map(id => {
return new Promise((resolve, reject) => {
request({
url: `https://api.themoviedb.org/3/movie/${id.movieId}?api_key=${movieApikey}`,
method: 'GET'
},
(err, res, body) => {
if (err) { reject(err) } //function passed to the promise
resolve(body)
})
})
})
Promise.all(requests).then((body) => {
body.forEach(res => {
if (res)
moviesToReturn.push(JSON.parse(res))
console.log(moviesToReturn, 'movies to return')
return setFavorites(moviesToReturn)
})
}).catch(err => console.log(err))
You need to call the api at the initial rendering and then set the state. Therefore, you need to use useEffect having empty dependency array as follows. The way you have done it will cause the application to render it infinitely.
Additionally, setState function doesn't return anything.
useEffect(() => {
let requests = movieId.map((id) => {
return new Promise((resolve, reject) => {
request(
{
url: `https://api.themoviedb.org/3/movie/${id.movieId}?api_key=${movieApikey}`,
method: "GET",
},
(err, res, body) => {
if (err) {
reject(err);
} //function passed to the promise
resolve(body);
}
);
});
});
Promise.all(requests)
.then((body) => {
let moviesToReturn = []; /* keep moviesToReturn array here */
body.forEach((res) => {
if (res) moviesToReturn.push(JSON.parse(res));
console.log(moviesToReturn, "movies to return");
});
setFavorites(moviesToReturn);
})
.catch((err) => console.log(err));
}, []);

React class component issue in order of execution

I have the following code in my React class component.
For some reason, I am observing that, inside componentDidMount, despite having the keyword await before the call to this.getKeyForNextRequest(), the execution is jumping to the next call, this.loadGrids().
Am I doing something wrong here?
async componentDidMount() {
await this.getKeyForNextRequest();
await this.loadGrids();
}
getKeyForNextRequest = async () => {
const dataRequester = new DataRequester({
dataSource: `${URL}`,
requestType: "POST",
params: {
},
successCallback: response => {
console.log(response);
}
});
dataRequester.requestData();
}
loadGrids = async () => {
await this.loadGrid1ColumnDefs();
this.loadGrid1Data();
await this.loadGrid2ColumnDefs();
this.loadGrid2Data();
}
You can try using the Promise constructor:
getKeyForNextRequest = () => {
return new Promise((resolve, reject) => {
const dataRequester = new DataRequester({
dataSource: `${URL}`,
requestType: "POST",
params: {},
successCallback: response => {
console.log(response);
resolve(response);
}
});
});
}
This ensures you're waiting for a relevant promise, one that resolves only upon successCallback completing, rather than one that resolves instantly to undefined as you have it currently.
This is called "promisifying" the callback.
If DataRequester offers a promise-based mode, use that instead of promisifying the callback.

using axios with promise API

I am using a promise based hook in a React app to fetch async data from an API.
I am also using a Axios, a promise based http client to call the API.
Is it an anti-pattern to use a promise based client inside another promise? The below code does not seem to work.
const getData = () => {
return new Promise((resolve, reject) => {
const url = "/getData";
axios.get(url)
.then(function(response) {
resolve(response);
})
.catch(function(error) {
reject(error);
});
});
const useAsync = (asyncFunction) => {
const [value, setValue] = useState(null);
const execute = useCallback(() => {
setPending(true);
setValue(null);
setError(null);
return asyncFunction()
.then(response => setValue(response))
.catch(error => setError(error))
.finally(() => setPending(false));
}, [asyncFunction]);
useEffect(() => {
execute();
}, [execute]);
return { execute, pending, value, error };
};
};
const RidesList = () => {
const {
pending,
value,
error,
} = useAsync(getData);
Oh man. I think you have a fundamental misunderstanding about how Promises work.
First, axios already returns a Promise by default. So your whole first function of getData can be reduced to:
const getData = () => {
const url = "/getData"
return axios.get(url)
}
But the meat of your code seems to indicate you want a querable Promise - so you can check the status of it for whatever reason. Here's an example of how you would do it, adapted from this snippet:
function statusPromiseMaker(promise) {
if (promise.isResolved) return promise
let status = {
pending: true,
rejected: false,
fulfilled: false
}
let result = promise.then(
resolvedValue => {
status.fulfilled = true
return resolvedValue
},
rejectedError => {
status.rejected = true
throw rejectedError
}
)
.finally(() => {
status.pending = false
})
result.status = () => status
return result
}
In this way, you can then do something like let thing = statusPromiseMaker(getData()) and if you look up thing.status.pending you'll get true or false etc...
I didn't actually run what's above, I may have forgotten a bracket or two, but hopefully this helps.
I have to admit - I haven't seen anything like this ever used in the wild. I am interested in knowing what you're actually trying to accomplish by this.
Axios itself returns a promise but if you want to make a custom class having your custom logic after each API call then you can use interceptors I was having the same requirement and this is how I am returning promises after applying my custom logic on each API call.
Interceptors will get executed separately after and before each request you made so we can simply use them if we want to modify our request or response.
here is my working solution have a look at it.
callApi = (method, endpoint, params) => {
this.apiHandler.interceptors.request.use((config) => {
config.method = method
config.url = config.baseURL + endpoint
config.params = params
return config
})
return new Promise((resolve, reject) => {
this.apiHandler.interceptors.response.use((config) => {
if (config.status == 200) {
resolve(config.data)
} else {
reject(config.status)
}
// return config
}, error => reject(error))
this.apiHandler()
})
}
Below is the code to call this function
helper.callApi("get", "wo/getAllWorkOrders").then(d => {
console.log(d)
})

Resources