Unable to update hook after image is uploaded to firebase - reactjs

First of all, thanks for the help...I'm quite new to React and I have NO IDEA why won't my hook update.
So I want to upload an image to firebase, grab the url from firebase, update it with the hook and send it to my db.
const [image, setImage] = useState(null);
const [imageURL, setImageURL] = useState({ preview: null, data: null });
// show preview every time the input changes
useEffect(() => {
// ensure image is present on the page and NOT null
if (image) setImageURL({ preview: URL.createObjectURL(image), data: null });
}, [image]);
const handleSubmit = async e => {
e.preventDefault();
try {
if (image) {
// create reference point to where the image is uploaded to
const imageRef = ref(storage, `${Date.now()} -- ${image.name}`);
// upload image & retrieve public url to save in db
await uploadBytes(imageRef, image);
const imageUrl = await getDownloadURL(imageRef);
setImageURL(prev => ({ ...prev, data: imageUrl })); <-- can't update hook
}
...other code
} catch (err) {
... handle error
}
<form onSubmit={handleSubmit}>
<label>
<span>Upload an image</span>
<input
type='file'
accept='image/*'
onChange={e => setImage(e.target.files[0])}
/>
</label>
</form>
I am using React 18 and Firebase 9.
I'm not sure what I'm doing wrong...so any help is greatly appreciated!
I have tried using async/await and then/catch, but both no luck

I don't know if I'm doing the right thing, but instead of updating the state, I simply post the returned URL from firebase to the db
Updated code:
const [image, setImage] = useState({ file: null, previewUrl: null });
useEffect(() => {
if (image.file)
setImage(prev => ({
...prev,
previewUrl: URL.createObjectURL(image.file),
}));
}, [image.file]);
const handleSubmit = async e => {
e.preventDefault();
let uploadedImage = null;
try {
if (image.file) {
uploadedImage = await uploadImage(image.file);
}
const res = await axios.post('/api/drinks', {
image: uploadedImage,
});
console.log(res);
} catch (err) {
...handle error
}
}
Everything works fine now, but again, I'm not sure if this is the right way to do it, so any feedbacks/comments are appreciated!

Related

how do I post data to my nextjs api endpoint

I am making a test e-commerce sight to learn nextjs. I am trying to implement the checkout through stripe and I have it working if all of the information is static. when I make any of the information set to a variable it stops working and tells me that I am not passing any values into my variables. to test this I am making all of my data that needs to be passed, static data except for one which is when I get the error that I am not passing in information properly
obviously I am not sending the data to the api correctly. I think that my code looks just like the guides and docs so I am stuck. any help would be greatly appreciated.
here is the error message that I get:
"Missing required param: line_items[0][price_data][product_data][name]."
even if I change the state variable 'title' to a single value instead of an array, and in the updateState function settitle("title") I still get the same error
here is the front end code where I try to send the data to the api endpoint:
basket is an array of objects containing all of the products that the user has chosen.
const [id, setId] = useState([]);
const [price, setprice] = useState([]);
const [description, setdescription] = useState([]);
const [title, settitle] = useState([]);
const updateState = () => {
basket.forEach(element => {
setId([...id, element.id]);
setdescription([...description, element.description]);
setprice([...price, element.price]);
settitle([...title, basket.title]);
});
console.log(id);
console.log(description);
console.log(price);
console.log(title);
}
//send data to the api
const postData = async () => {
const response = await fetch("/api/checkout_sessions", {
method: "POST",
body: JSON.stringify(
id,
price,
description,
title,
),
});
return response.json();
};
return (
<form action="/api/checkout_sessions" method="POST">
<button
type="submit"
role="link"
className="button"
onClick={() => {
updateState;
postData;
}}
>
proceed to checkout
</button>
</form>
)}
here is the api code where I try to get that data and use it which is not working how I expect:
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
export default async function handler(req, res) {
// var priceVariable ='price_1MB8P4FqoomU2P4qrVmtxCvp';
if (req.method === 'POST') {
const items = req.body.id
const amount = req.body.price
const description = req.body.description
const title = req.body.title
try {
// Create Checkout Sessions from body params.
const session = await stripe.checkout.sessions.create({
// shipping_options: ["shr_1MBn0HFqoomU2P4qZk4vqOQ3"],
shipping_address_collection: {
allowed_countries: ["US", "CA", "GB"],
},
line_items:[{
price_data: {
unit_amount: 1000,
currency: 'usd',
product_data: {
name: title,
description: "description",
},
},
quantity: 1,
}],
mode: 'payment',
success_url: `${req.headers.origin}/?success=true`,
cancel_url: `${req.headers.origin}/?canceled=true`,
});
res.redirect(303, session.url);
} catch (err) {
res.status(err.statusCode || 500).json(err.message);
}
} else {
res.setHeader('Allow', 'POST');
res.status(405).end('Method Not Allowed');
}
}
you can see in the line_items that everything is static except for the one variable that I am testing.
JSON.stringify expects an object (https://www.w3schools.com/js/js_json_stringify.asp)
const postData = async () => {
const response = await fetch("/api/checkout_sessions", {
method: "POST",
body: JSON.stringify({
id,
price,
description,
title,
}),
});
return response.json();
};
And on the api side you may have to parse the body before accessing any properties like so
const body = JSON.parse(req.body)
const title = body.title
(https://www.w3schools.com/Js/js_json_parse.asp)
It's unclear if the array/string mismatch is due to your testing changes, but you'll need to ensure a single string is supplied for name.
Your actual issue is likely here:
onClick={() => {
updateState;
postData;
}}
I'm surprised this is invoking the functions without (), but even if it were your postData() would start before the react state change happened.
I suspect if you initialized title with a value your endpoint would receive that.
const [title, setTitle] = useState("some default title");
You'll need to review how your application is tracking state here, and perhaps calculate that title and pass it through to the request alongside updating the state.

React component doesn't render unless I refresh & duplicate render

I've been working on React/Redux app with firestore database
In my app I have simple POST request sent when the user send a message in the input field
and the data the user enters supposed to render in the same page without the need to refresh but I do still need to refresh even without deps in my useEffect!
Here's my code :
Post component
{posts.length > 0 &&
[...posts].map(({ id, data: { message, name, job, avatarUrl } }) => (
<Post
key={id}
name={name}
job={job}
message={message}
avatarUrl={avatarUrl}
/>
))}
However I also encounter a weird behavior after I refresh which is the components are rendered twice!Although my database be containing only one unique data for each messageThe react app renders it twice ( The querySnapshot from the database being added to the state arrayposts twice
useEffect
useEffect(() => {
querySnapshot();
});
}, []);
Database query:
const q = query(
collection(db, "posts"),
where("type", "==", "post"),
orderBy("postDate", "desc")
);
Retrieving the data :
const [input, setInput] = useState("");
const [posts, setPosts] = useState([]);
const [nextId, setNextId] = useState("0");
const addPost = (e) => {
e.preventDefault();
const docData = {
name: "mo",
job: "zoo",
message: input,
avatarUrl: "https://",
postDate: firebase.firestore.FieldValue.serverTimestamp(),
type: "post",
};
setDoc(doc(db, "posts", nextId.toString()), docData);
setNextId(parseInt(nextId) + 1);
setInput("");
};
async function querySnapshot() {
const querySnapshot = await getDocs(q);
console.log(querySnapshot.docs[0].data().message);
setNextId(querySnapshot.docs.length)
querySnapshot.docs.forEach((doc) => {
let data = {
id: doc.id,
data: doc.data(),
};
if (data && !posts.includes(data.id)) {
setPosts((current) => [...current, data]);
console.log("psts now", posts);
}
});
}
I tried to use the JavaScript Set by creating
useState(new Set()) but the same problem occurred of duplicate elements
I also tried to change deps of useEffect to render when the posts state array changes still not rendering untill I refresh
The duplication reason would be caused by setPosts(), I have updated the code as below, try to avoid setting the value inside the loop.
async function querySnapshot() {
const querySnapshot = await getDocs(q);
setNextId(querySnapshot.docs.length)
const data = querySnapshot.docs.map((doc)=>{
return {id:doc.id, data: doc.data()}
})
setPosts((current) => [...current, data])
}

Better option for image upload (cloud) in NextJS

I'm currently developing my first real project for a client with NextJS and MongoDB and I'm having problems uploading images. I'm working with Cloudinary but it can't receive multiple files and I'm also having issues with state management because when the form is submitted my database doesn't receive the files whereas Cloudinary does.
The API works fine so I post here the code of the form (REACT).
export default function NewProduct() {
const initialState = {
title: "",
price: 0,
description: "",
content: "",
images: [],
category: "tortas",
};
const [product, setProduct] = useState(initialState);
const { title, price, description, content, category } = product;
const [files, setFile] = useState("");
//const handleChangeInput = (e) => {
// setProduct({ ...product, [e.target.name]: e.target.value });
//};
const handleUploadInput = async (e) => {
const uploadFiles = [...e.target.files];
setFile([...files, uploadFiles]);
};
const handleSubmit = async (e) => {
e.preventDefault();
const formData = new FormData();
for (let file of files) {
formData.append("file", file);
}
formData.append("upload_preset", "balbla");
const res = await fetch(
"https://api.cloudinary.com/v1_1/blabla/image/upload",
{
method: "POST",
body: formData,
}
);
const data = await res.json();
setProduct((p) => ({ ...p, images: data.secure_url}));
await createProduct();
setProduct(initialState);
};
const createProduct = async () => {
try {
const res = await fetch("http://localhost:3000/api/products", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(product),
});
const data = await res.json();
console.log(data);
} catch (err) {
console.log(err);
}
};
return (
<Layout>
<div className={styles.formDiv}>
<form className={styles.form} onSubmit={handleSubmit}>
<input
type="file"
name="file"
onChange={handleUploadInput}
multiple
accept="image/*"
/>
<button type="submit">Crear</button>
</form>
</div>
</Layout>
);
}
In case using Cloudinary isn't the best option with NextJS, what other cloud or stuff I could use?
I hope I made myself clear.
Thank you in advance.
The Cloudinary Upload API unfortunately doesn't support uploading multiple resources within a single request, so you would need to likely loop through all of your media items and upload them each individually.
As far as the production creation is concerned, I can't tell for sure, but you may be trying to create the product before your product state is updated.
Inside createProduct have you checked to see if all of the data you expect is available at the time it's being ran?
You could try listening to updates to the product state and create a product based off of that with a useEffect hook, for instnace:
useEffect(() => {
// Check if product is available or perform
// a check that you know isn't ready to create yet
if ( !product ) return;
(async function run() {
await createProduct(product);
setProduct(initialState);
})()
}, [product])

Upload multiple files and query Firestore DB - ReactJS

I have been reading quite a lot posts like this one How to upload multiple files to Firebase? about this question before and I tried different options but can’t get my result.
I am using React Hook Form for an insanely huge form that now I have to add an input more (to upload files), and need help doing everything at once.
My code to update one file:
const onChangeFile = async (e: any) => {
const file = e.target.files[0];
const storageRef = app.storage().ref();
const fileRef = storageRef.child(file.name);
await fileRef.put(file);
setFileUrl(await fileRef.getDownloadURL());
};
const onSubmit = async (data: any) => {
try {
await db
.collection('listings')
.doc()
.set({ ...data, created: new Date(), uid: currentUser.uid, file: fileUrl });
reset();
} catch (error) {
}
};
So, as you can see, uploading only one file is quite straightforward, but a different story is when uploading many. Thing is, I would need more functions inside my onSubmit, which is itself an async funcion, so that limits the amount of things I can do inside of it.
Does anyone have an simple workaround?
If e.target.files has multiple files then you can push the upload operations to an array and then upload them using Promise.all():
const onChangeFile = async (e: any) => {
const storageRef = app.storage().ref();
const fileUploads = e.target.files.map(file => storageRef.child(file.name).put(file));
const fileURLReqs = (await Promise.all(fileUploads)).map(f => f.ref.getDownloadURL())
const fileUrls = await Promise.all(fileURLReqs)
// setFileUrl(await fileRef.getDownloadURL());
};
fileUrls will be an array of URLs of all the uploads images which onSubmit can be uploaded to Firestore if needed.
I'm not sure if you are adding a new document for each image but if you are, try this:
// in onSubmit
const listingsCol = db.collection("listings")
const files = [] // get that fileUrls array from your state
const updates = files.map(file => listingsCol.add({...data, file}))
await Promise.all(updates)
// this will add a new document for each new file uploaded
Thanks to #Dharmaraj, I have arrived to a simple working version of this popular question, I think he has given a very clear and straightforward solution for it and the credit is totally his.
Nonetheless, my implementation could have weaknesses even though its working (so far), so I would like to post it, you can use it or point out possible red flags.
const Form: React.FC = () => {
const [filesUrl, setFilesUrl] = useState<any[]>([]);
const { register, handleSubmit, reset } = useForm({ defaultValues });
const onChangeFile = async (e: any) => {
const storageRef = app.storage().ref();
const fileUploads = Array.from(e.target.files).map((file: any) => storageRef.child(file.name).put(file));
const fileURLReqs = (await Promise.all(fileUploads)).map((f: any) => f.ref.getDownloadURL());
const fileUrls = await Promise.all(fileURLReqs);
setFilesUrl(fileUrls);
};
const onSubmit = async (data: any) => {
try {
await db
.collection('collName')
.doc()
.set({ ...data, created: new Date(), uid: currentUser.uid, file: filesUrl });
reset();
} catch (error) {
console.log(error)
}
};

How to upload image save in Storage and then get Url in DB

I am uploading the image into Storage in Firebase and DB, but I have an issue that
TypeError: _config__WEBPACK_IMPORTED_MODULE_1__.default.firestore is not a function
This is my code in API for my data, it has just DB not have storage yet, how can I put both images into storage and get downloadUrl in DB.
const firebaseDb = firebaseApp.database();
const fireStore = firebaseApp.firestore();
/**
* Representation for creating new product and save to database
*/
export const doCreateProduct = (productData, image_url) => {
const productId = firebaseDb.ref().push().key;
return firebaseDb
.ref(`products/${productId}`)
.set(productData)
.then(() => {
return onceGetProducts();
})
.catch((error) => ({
error,
status: "failure",
}));
};
And this is my handleImageFile in form
const [imageFile, setImageFile] = useState();
console.log(imageFile);
const handleFileChange = (event) => {
console.log("file image", event);
const image = event.target.files[0]
setImageFile(imageFile => (image))
};
//onSave is a button save after input
const onSave = () => {
createProductRequest(values, imageFile);
};
Anybody here can help me with this problem, please? Thank you so much

Resources