Better option for image upload (cloud) in NextJS - reactjs

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])

Related

How do I assign a value from response to body for another post data?

so I have 2 post data functions, the first is to upload photo/file, another one is for add a document.
in the API for add document, there are 3 body that need to be filled. name(string), location(string), and photo(string).
I already created the function for upload photo, and I get the return link of the url path for photo.
My question is, how do I assign the link from url path to body add document for photo(string)?
json for add document :
json for uploaded photo:
code for upload photo:
try {
const postFileReq = await axios.post(
"https://spda-api.onrender.com/api/file/upload",
formData,
options
);
const postFileRes = await postFileReq.data.image;
if (postFileReq.status === 200) {
setPhotoUrl(postFileRes);
console.log("postFileRes", postFileRes);
// console.log("photoUrl", photoUrl);
}
} catch (error) {
const err = error as AxiosError;
console.log(err.response?.data);
}
code for add document:
try {
const postDocReq = await axios.post(
"https://spda-api.onrender.com/api/admin/documents",
{
name: field.name,
location: field.location,
photo: photoUrl,
},
{
headers: {
"Content-Type": "application/json",
authorization: `Bearer ${token}`,
},
}
);
const postDocRes = await postDocReq.data;
if (postDocReq.status === 200) {
setShowSnackbar(true);
router.reload();
console.log(postDocRes);
}
} catch (error) {
const err = error as AxiosError;
console.log(err.response?.data);
}
i already tried using useState to assign but it still not working, anyone have an idea?
const [photoUrl, setPhotoUrl] = useState("");
My complete code: https://pastebin.com/Rb5xX08z
Alright so what you need you to do is call the second api in the success method of the first call. I will jump to the code directly with async/await. You can do it with then as well.
You can handle it inside your if condition 200 success. So like once it is successfull call the second function then. But I have shown with async/await like mentioned
import axios from 'axios'
const MyComponent = () => {
const [data, setData] = useState(null);
const [secondData, setSecondData] = useState(null);
const fetchData = async () => {
try {
const response = await axios.get('https://first-api.com');
setData(response.data);
//if first api was successful,call the second api
const secondResponse = await axios.get(`https://second-api.com/${response.data.id}`);
setSecondData(secondResponse.data);
} catch (error) {
console.error(error);
}
};
useEffect(() => {
fetchData();
}, []);
return (
<div>
<p>Data from first API: {JSON.stringify(data)}</p>
<p>Data from second API: {JSON.stringify(secondData)}</p>
</div>
);
};

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.

Unable to update hook after image is uploaded to firebase

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!

Can't increasing the value in front end by react. Data comes from mongodb

I can't update the value in the front end. I want to decrease the value of quantity when I click delivered.
Here is my code.
const handleDelivered = () => {
const newQuantity = parseInt(inventory.quantity) + 1;
const makeQuantity = newQuantity;
console.log(makeQuantity);
fetch(`http://localhost:4000/inventory/${id}`, {
method: "PUT",
headers: {
"content-type": "application/json",
},
body: JSON.stringify({ makeQuantity }),
})
.then((res) => res.json())
.then((data) => {
console.log(data);
});
};
Here is the update operation on the server-side.
app.put("/inventory/:id", async (req, res) => {
const id = req.params.id;
const data = req.body;
const filter = { _id: ObjectId(id) };
const options = { upsert: true };
const updateDoc = {
$set: {
...data,
},
};
const result = await fruitsCollection.updateOne(
filter,
updateDoc,
options
);
res.send(result);
});
Here is the code for button:
<Card.Link
onClick={handleDelivered}
className="btn btn-danger">
Delivered
</Card.Link>
Here is the screenshot:
If I click the delivered button, the console says it is 13, but UI doesn't update. Also, Restock Item button doesn't increase the quantity
How can I solve this?
Thank you
React doesn't automatically handle model updates for you. You need to handle this yourself or use a library which handles it for you.
Without seeing more code, it's hard to give specific details on how you should update your local models, but it might look like this:
const [inventory, setInventory] = useState(defaultInventory)
function handleDelivered() {
fetch(...).then(res => res.json()).then(setInventory)
}
assuming the response from the fetch is the entire fruit object. If it's a partial, you'll need to merge in the response with the local model.

Unable to update state variables after fetch in ReactJS

On form submit, a new event is added in the Database and the details of this new event are returned back via JSON. I want to concatenate this new JSON in the 'events' data so that the 'events' data gets updated automatically. But it is not working. Here is my code. Please see the line commented as //Here. It is where I am trying to update the event state variable but it is not getting updated.
import React, { useState,useEffect } from 'react';
async function getAllEvents() {
return fetch('http://localhost:8080/get-all-events/', {
method: 'GET',
//body: JSON.stringify(credentials)
})
.then(
data => data.json()
)
}
export default function Dashboard() {
const [events, setEvents ] = useState({});
const [eventName, setEventName] = useState();
useEffect(() => {
getEventsWithFetch();
}, []);
const getEventsWithFetch = async () => {
const response = await fetch("http://localhost:8080/get-all-events/");
const jsonData = await response.json();
setEvents(jsonData);
};
const addEvent = async (name) => {
const response = await fetch("http://localhost:8080/create-event/",
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
"title": name,
"day": "1",
"month": "2"
})
}
).then((response) => response.json())
.then((json) => {
events.push(json);
var events_array = events;
setEvents(events_array); //Here
})
};
const handleSubmit = async e => {
e.preventDefault();
var data = new FormData(e.target)
}
return(
<div>
<p>EVENTS DASHBOARD</p>
{JSON.stringify(events)}
<form onSubmit={handleSubmit}>
<label>
<p><b>Event Name</b></p>
<input type="text" name="event_name" onChange={e => setEventName(e.target.value)} />
</label>
<div>
<button type="submit">Submit</button>
</div>
</form>
</div>
);
}
You're modifying the read-only state events, instead of using setEvents, which is already a mistake.
Furthermore, the diff-check React does is by reference for objects, so you have to clone events, instead of setting the state to itself:
setEvents([...events, json]); // Using spread to clone the array and add a new element in the end
Otherwise, React sees the same reference being set and ignores it.

Resources