When I change image, useState is not changed - javascript

I have a problem with uploading an image. I have cards and all cards have an image. When I clicked Update Product, Card Details page is opening.
After I click this image, I choose another picture, the photo changes in Front, and when I clicked submit API gives me success and the correct photo URL.
But when I submitted this form, I returned all cards page ( with history.push()), the image was not changed. If I clicked empty cache and hard reload in my browser after the new image was changed. I tried to fix and first of all, I looked network and the response gave me the correct link, I opened a new tab and put the link and a new image opened. Maybe the link between old and new images is the same and useState is not working.
Api gives a new link, useState gives the old link. Both links are the same.
I tried window.location.reload(true), but it did not work first uploading. After first cleaning the cache, the image was uploaded, and after the second uploading, it worked without cleaning the cache.
My codes
It is for getting one card's data and image
const [getPhoto, setGetPhoto] = useState();
useQuery(["PRODUCT_DATA_OVERVIEW_BRANCH", id], () =>
apiService.getAdminProductDetails(id), {
onSuccess: (response) => {
setGetPhoto(response.photoUrl);
},
});
Changing data Api
const { mutate, isLoading } = useMutation(apiService.updateAdminProducts, {
onSuccess: () => {
enqueueSnackbar("Product was updated", { variant: "success" });
history.push("/admin/products");
window.location.reload(true);
},
});
onSubmit
const onSubmit = () => {
const urlProductInfo = new URLSearchParams(getProductData);
let formData = new FormData();
formData.append("photo", getPhoto);
mutate({ urlProductInfo, formData });
};
api
const ApiService = (httpService) => {
updateAdminProducts: async (variables) => {
const { urlProductInfo, formData } = variables;
const response = await httpService.patch(`/product/sme?${urlProductInfo}`, formData);
return response?.data;
},
}

Related

Stripe Payment Success URL Redirect With Parameter

I'm working with the following Stripe.js file in a Next.js project:
import { loadStripe } from "#stripe/stripe-js";
export async function Stripe({ lineItems }, imageUrls) {
let stripePromise = null;
const getStripe = () => {
if (!stripePromise) {
stripePromise = loadStripe(process.env.NEXT_PUBLIC_API_KEY);
}
return stripePromise;
};
const stripe = await getStripe();
await stripe.redirectToCheckout({
mode: "payment",
lineItems,
successUrl: `http://localhost:3000/success?pdf=${imageUrls}`,
cancelUrl: window.location.origin,
});
}
When I call the Stripe function, I'm passing an imageUrls array which looks like this for example:
['blob:http://localhost:3000/2a47a926-be04-49a9-ad96-3279c540ebb4']
When the Stripe redirectToCheckout happens, I navigate to the success page and pass imageUrls.
My goal is to convert these imageUrls into png images from the success page using code like this inside of an async function:
const fileResponse = await fetch(imageUrl);
const contentType = fileResponse.headers.get("content-type");
const blob = await fileResponse.blob();
const imageFile = new File([blob], `someImage.png`, {
contentType,
});
I end up getting this error though:
GET blob:http://localhost:3000/2a47a926-be04-49a9-ad96-3279c540ebb4 net::ERR_FILE_NOT_FOUND
I'm guessing after the redirect this URL doesn't exist anymore? What is the correct way to make something like this work?
Edit to include success.js code:
import Layout from "../components/Layout/Layout";
import { useEffect } from "react";
function SuccessPage() {
useEffect(() => {
const params = new Proxy(new URLSearchParams(window.location.search), {
get: (searchParams, prop) => searchParams.get(prop),
});
let value = params.pdf;
console.log("this is value");
console.log(value);
async function getFileFromUrl(imageUrl) {
const fileResponse = await fetch(imageUrl);
const contentType = fileResponse.headers.get("content-type");
const blob = await fileResponse.blob();
const ditheredImageFile = new File([blob], `test.png`, {
contentType,
});
return ditheredImageFile;
}
let imageFile = getFileFromUrl(value);
console.log("this is imageFile");
console.log(imageFile);
}, []);
return (
<Layout>
<h3>Thank You For Your Order!</h3>
</Layout>
);
}
export default SuccessPage;
You're using blobs and you shouldn't - and I'm guessing those blobs came from a user input.
Blobs in javascript work in-memory (RAM), they will be discarded when your document unloads.
Since you're redirect the user to another page (stripe) you're unloading your document and thus loosing everything you've in memory (all your blobs are gone, they are only good while your document is loaded, after you leave it/get redirected they are cleared from memory).
To solve your problem you must simply upload the documents to a server prior to unloading the document (redirecting the user to stripe) and pass your server URL instead of your "internal" (blob) URL and all should work.
Basically, you need to save your files on a server via AJAX, have the server save the files and return their URL's (or even better, an ID for your image collection) and use those server URL's on the redirect (or an ID that you'll use to retrieve all the files you need later, simplifying your parameter usage).
More info at: https://javascript.info/blob ("Blob as URL" section)
The localhost url will not work because Stripe has no way of accessing an image on your local machine. Instead, you should upload the image to a database and provide a public URL. I'm not sure what systems (e.g. AWS, Azure) you use, so it's hard to get very specific.
The database can then be queried after landing on the checkout page. One way to do this would be to pass the ID of the item as a URL param. Another way is to store information about the product in local storage.
Either way, you should get a response from your database with a unique link or ID so you know exactly what to query later.
you should pass the image data as a base64 encoded string to the success URL and then decode it in the success page to get the actual image data.
modify your code:
On the Stripe function:
const imageUrls = lineItems.map((item) => {
return btoa(item.url);
});
// ...
successUrl: `http://localhost:3000/success?pdf=${imageUrls}`,
// ...
On the SuccessPage
async function getFileFromUrl(imageUrl) {
const decodedUrl = atob(imageUrl);
const fileResponse = await fetch(decodedUrl);
const contentType = fileResponse.headers.get("content-type");
const blob = await fileResponse.blob();
const ditheredImageFile = new File([blob], `test.png`, {
contentType,
});
return ditheredImageFile;
}
I think error is coming from this one
blob url will be only available in the same origin context, but your successurl is not the same as like blob
please try to generate dataurls from blob.
const dataUrls = await Promise.all(
imageUrls.map(async (url) => {
const response = await fetch(url);
const blob = await response.blob();
return URL.createObjectURL(blob);
})
);
await stripe.redirectToCheckout({
mode: "payment",
lineItems,
successUrl: `http://localhost:3000/success?pdf=${encodeURIComponent(dataUrls.join(","))}`,
cancelUrl: window.location.origin,
});
And then please decode urls at the success page, then you could resolve your issues.
The correct way to make this work is to use a URL query parameter instead of a URL path parameter. When you pass parameters in the URL path, they are not accessible after the redirect.
So instead, you should pass the imageUrls array as a query parameter in the successUrl like this:
successUrl: http://localhost:3000/success?pdf=${imageUrls.join(',')},
You can then access the query parameter in the success page and convert the imageUrls array into png images.

Is there a way send a file stored on a remote URL from the server to the client efficiently?

Context:
This is my first time working with files using Nodejs.
I am making a Youtube video downloader for personal use.
In the frontend I have multiple buttons representing a video quality, each button has attached to it a URL where the video can be found for the specified quality.
When a specific button is pressed, the function 'download' from 'client.js' is being called and gets passed the URL representing the button and a filename.
My first try was to create a write stream to the public folder of my app, and after the download was finished, get the video from the path in the frontend, but it was taking too long and it was really inefficient for big files.
The current way it works is mostly the same as the other one, but this method is even slower than the other method I used.
How can I make the download more efficient?
For example when the user presses a button, the download to right away.
client.js
const download = async (url, filename) => {
const error = document.querySelector(".error")
const status = document.querySelector(".status")
try {
status.textContent = "Downloading ..."
const response = await axios.get("/download", {params: {url, filename}})
window.location.href = response.request.responseURL
error.textContent = ""
status.textContent = "Download Complete"
}
catch(e) {
error.textContent = "Cannot Download The Data!"
status.textContent = ""
}
}
server.js
app.get('/download', async (request, response) => {
try {
const URL = request.query.url
const filename = request.query.filename
response.attachment(filename)
const { data } = await axios.get(URL, { responseType: 'stream' })
data.pipe(response)
}
catch (e) {
return response.status(404).send({error: "Url Not Found!"})
}
})

How to share a dynamically generated image from react with react-share?

I have a react app which generates images on the front end dynamically using Plotly.js. I'd like to add image sharing functionality. I am trying to use react-share for this. Social platforms require image URL for image sharing and do not support images in base64 encoding or alike. Backend was implemented so it can receive images in base64, store in the database and return URL to the image, which is then used for sharing with react-share.
As the image is generated dynamically (it changes each time user resizes the chart, for instance), everything should be done when user clicks on Share icon.
So after the user has clicked on the Share icon, the image generated on the front end should be saved to back end
let imgURI;
const handleClick = () => {
Plotly.toImage('chartContainer', {
format: 'png',
width: 1000,
height: 600
})
.then(dataUrl => api.post('/image/base64ToPng', { image: dataUrl })
.then(
(response) => {
imgURI = response.data.imgURI;
},
failure => console.error(failure)
));
};
after the response is received, passed down to the sharing component like this
<FacebookShareButton
url={imgURI}
>
<FacebookIcon/>
</FacebookShareButton>
The code sample is not asynchronous, so the image URI is not passed to the sharing component, therefore sharing does not work. I tried to pass the prop down using conditional depending on whether it's defined or not and did not come up with a solution. I also looked up some issues in react-share repo that dealt with async urls, but seems like none of them deals with the dynamic image sharing on click.
I'd very appreciate a hint on how to complete this task.
This is serious hack territory, and the whole thing would be a lot simpler if this PR had been completed.
However, the code below should work (see codesandbox).
The key steps are:
Have a bit of state that keeps track of whether you have a url from the service or not.
When this state is "none", disable the facebook button's default behavior (i.e. openShareDialogOnClick = false)
Add an onClick handler to the facebook button that asynchronously fetches the url and sets the state (triggering a re-render)
Use an effect + ref so that when the url is set to something real, you manually call the click event on the button (which now has a real address in its url prop), and then re-sets the url to "none"
import { useEffect, useRef, useState } from "react";
import { FacebookIcon, FacebookShareButton } from "react-share";
async function getUrFromService(): Promise<string> {
// The real implementation would make a network call here.
await new Promise((resolve) => setTimeout(resolve, 1000));
return "https://via.placeholder.com/150";
}
export default function App() {
const shareButton = useRef<HTMLButtonElement>(null);
const [url, setUrl] = useState<string>("none"); // Unfortunately, we have to have a dummy string here, or FacebookShareButton will blow up.
// Provide an onClick handler that asyncronously fetches the url and sets it in the state.
const onClick = async () => {
// Be sure to check for the "none" state, so we don't trigger an infinite loop.
if (url === "none") {
const newUrl = await getUrFromService();
setUrl(newUrl);
}
};
// Whenever "url" changes and we re-render, we manually fire the click event on the button, and then re-set the url.
useEffect(() => {
if (url !== "none") {
shareButton.current?.click();
setUrl("none");
}
}, [url, shareButton]);
return (
<FacebookShareButton
ref={shareButton}
// Disable calling the dialog if we don't have a url yet.
openShareDialogOnClick={url !== "none"}
url={url}
onClick={onClick}
>
<FacebookIcon />
</FacebookShareButton>
);
}
Use navigator.share api if possible.
Having, said that you could write a wrapper that manages loading state and disables icon of component.
Create a async share component
//AsyncShareLoader.jsx
const AsyncShareLoader = ({ url, children }) => {
const loading = !url;
return (
<div style={{ filter: `grayscale(${loading ? "100%" : "0%"}` }}>
{React.Children.map(children, (child) =>
React.cloneElement(child, {
disabled: loading,
url: loading ? "none" : url,
openShareDialogOnClick: !loading
})
)}
</div>
);
};
Now, use this as a wrapper for your actual react-share icons. Something like this
const Share = () => {
const [url, setUrl] = useState()
useEffect(() => {
fetch('/imgUrl').then(getUrlFromRes).then(setUrl)
}, [])
return (
<AsyncShareLoader url={url}>
<FacebookShareButton>
<FacebookIcon />
</FacebookShareButton>
</AsyncShareLoader>
);
}
Extending this techinique you could manually steal click as Andrew suggested
const getBase64ImageFromUrl = url =>
fetch(url)
.then(response => response.blob())
.then(
blob =>
new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onloadend = () => resolve(reader.result);
reader.onerror = reject;
blob = new Blob([blob], {type: 'image/png'});
reader.readAsDataURL(blob);
}),
);
const shareData = async () => {
await getBase64ImageFromUrl('https://url of image').then(base64 => {
const shareOption = {
title: App Link,
url: base64,
message:
'Download app now: ,AppLink:""',
};
try {
const shareResponse = Share.open(shareOption);
console.log('share response ,', shareResponse);
} catch (error) {
alert(error.message);
}
});
};

Expand my image upload from 1 to 5 photos; map/foreach?

I am creating an app in which you can upload a photo, with some other data, to Firebase. The uploading part worked perfect with one picture. However I have now added a multiple-image picture (select 1 to 5 pictures) and would like my image upload function to upload the 5 pictures in stead of the 1.
The image upload works with 1 image provided, so how can I rearrange my code to upload the x-amount of photos in the array?
The pictures are added in the photos array with the following data (output shown below is a console.log from the images fetched);
Array [
Object {
"exists": true,
"file": "ph://8905951D-1D94-483A-8864-BBFDC4FAD202/L0/001",
"isDirectory": false,
"md5": "f9ebcab5aa0706847235887c1a7e4740",
"modificationTime": 1574493667.505371,
"size": 104533,
"uri": "ph://8905951D-1D94-483A-8864-BBFDC4FAD202/L0/001",
},
With this didFocus I check if the fethedImages param is set and set the photos array to the fetched images (So all the data that is shown above)
const didFocusSubscription = props.navigation.addListener(
'didFocus', () => {
let fetchedImages = props.navigation.getParam('fetchedImages')
console.log(fetchedImages)
setPhotos(fetchedImages)
setImageValid(true)
calculateImageDimensions()
}
);
When I save the page and start dispatching the data I run the following command the uploadImage function is ran and returns an uploadurl, this is then saved later on in the dispatch function to the Firebase Database to be fetched later;
uploadurl = await uploadImageAsync(photos)
SO the uploadImageAsync starts with the photos array forwarded. How can I make sure the function below is started for every photo.uri in the array? Can I use .map of for each for this, and in what context should I be using this?
Also I am not quite sure how I can send back an array of URLs to be saved together with the rest of the information.
async function uploadImageAsync(photos) {
console.log('uploadImage is gestart')
// Why are we using XMLHttpRequest? See:
// https://github.com/expo/expo/issues/2402#issuecomment-443726662
const blob = await new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.onload = function () {
resolve(xhr.response);
};
xhr.onerror = function (e) {
console.log(e);
reject(new TypeError('Network request failed'));
};
xhr.responseType = 'blob';
xhr.open('GET', photos, true);
xhr.send(null);
});
const ref = firebase
.storage()
.ref()
.child(uuid.v4());
const snapshot = await ref.put(blob);
// We're done with the blob, close and release it
blob.close();
return await snapshot.ref.getDownloadURL();
}
==============edited because of progress with uploading====================
Once again I am a little bit further. However the image upload function is now running, and because of is being multiple images I would like to await the response of all the images before continuing.
try {
uploadurl = await uploadImageAsync()
address = await getAddress(selectedLocation)
console.log(uploadurl)
if (!uploadurl.lenght) {
Alert.alert('Upload error', 'Something went wrong uploading the photo, plase try again', [
{ text: 'Okay' }
]);
return;
}
dispatch(
At this moment when I start the uploadImageAsync function. With he help of console.log I see it uploading the images, they also show up online. But while the pictures are uploading the upload url already returns with 0 and shows the Alert and stops the function.
uploadImageAsync = async () => {
const provider = firebase.database().ref(`providers/${uid}`);
let imagesArray = [];
try {
await photos.map(img => {
let file = img.data;
const path = "Img_" + uuid.v4();
const ref = firebase
.storage()
.ref(`/${uid}/${path}`);
ref.putString(file).then(() => {
ref
.getDownloadURL()
.then(images => {
imagesArray.push({
uri: images
});
console.log("Out-imgArray", imagesArray);
})
return imagesArray <== this return imagesArray is fired to early and starts the rest of my upload function.
} catch (e) {
console.error(e);
}
};
So a Discord chat pointed me in the way of a promise.all function for this to work. I tried that, but opened another stack overflow topic for getting this to work.
await response of image upload before continue function
The solution for my image upload function is in the topic above;
uploadImages = () => {
const provider = firebase.database().ref(`providers/${uid}`);
// CHANGED: removed 'let imagesArray = [];', no longer needed
return Promise.all(photos) // CHANGED: return the promise chain
.then(photoarray => {
console.log('all responses are resolved successfully');
// take each photo, upload it and then return it's download URL
return Promise.all(photoarray.map((photo) => { // CHANGED: used Promise.all(someArray.map(...)) idiom
let file = photo.data;
const path = "Img_" + uuid.v4();
const storageRef = firebase // CHANGED: renamed 'ref' to 'storageRef'
.storage()
.ref(`/${uid}/${path}`);
let metadata = {
contentType: 'image/jpeg',
};
// upload current photo and get it's download URL
return storageRef.putString(file, 'base64', metadata) // CHANGED: return the promise chain
.then(() => {
console.log(`${path} was uploaded successfully.`);
return storageRef.getDownloadURL() // CHANGED: return the promise chain
.then(fileUrl => ({uri: fileUrl}));
});
}));
})
.then((imagesArray) => { // These lines can
console.log("Out-imgArray: ", imagesArray) // safely be removed.
return imagesArray; // They are just
}) // for logging.
.catch((err) => {
console.error(err);
});
};

require.context doesn't update with image upload

When I upload a new image in upload.js and go back to the gallery.js, the require.context doesn't update with the new image import. I see the file in the directory so it is getting uploaded.
I've tried forcing a re-render, but nothing changes. I'm not sure if it has to do with the react-router or something has to happen asynchronously.
//Gallery.js
componentDidMount() {
const interval = setInterval(this.importAll, 3000);
this.setState({interval});
}
componentWillUnmount() {
clearInterval(this.state.interval);
}
importAll() {
const images = [];
//requires all the images from the uploads directory
let context = require.context('../../uploads', false, /\.(png|jpe?g|PNG|JPE?G)$/);
context.keys().forEach(item => {
images.push(context(item));
});
this.setState({images});
}
...
//Upload.js
axios.post('http://localhost:3000/upload', data)
.then (res => {
console.log(res);
this.props.history.push("/");
})
.catch((error) => {
console.log(error);
});
}
...
I am going to go off the assumption that the context is in fact cached by webpack and that is the reason why you don't see updates to it, even with rerender, so you would need to refresh the page to see the change.
That is probably different in production of course.
What I would do is have a addSingleImage method
addSingleImage(imagepath) {
const images = this.state.images;
images.push(imagepath);
this.setState({images});
}
and call that inside of your successful post with the path to the image you have succeeded in uploading.

Categories

Resources