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.
Related
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])
}
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!
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])
I am trying to get suggestions on a search field anytime the input field changes. However i keep running into various errors. this is what i've got so far.
export default function AutoComplete() {
const [info, setInfo] = useState([{'username': 'add a search word'},])
const [search, setSearch] = useState()
const handleChange = (e) => {
setSearch(e.target.value)
}
useEffect(() => {
const findItem = async (searchString) => {
const response = await fetch('http://localhost:8000/' + searchString, {
method: 'GET',
headers:{
'content-type': 'application/json',
},
})
let data = await response.json()
data = JSON.stringify(data)
return data
}
if (search){
var newdata = findItem(search)
setInfo(newdata)
}
else {
setInfo([{'username': 'add a search word'}])
}
}, [search])
return(
<div>
<input onChange={(e) => handleChange(e)}></input>
{info.map((suggestion) => {
return (
<div>
{suggestion.username}
</div>
)
})}
</div>)
}
The error i am running into at the moment is 'info.map is not a function' when i try to type in the input field. I decided to do my own implementation after i spent a lot of time running into this same error when i was using the material ui implementation https://mui.com/components/autocomplete/. in their implementation, they have a variable storing the data rather than calling an api and it works well. I can see that my data is being retrieved as it pops on on console.log at the end of the findItem function. Please help!
It seems that your findItem function is returning a JSON string:
data = JSON.stringify(data)
return data
Which is then set directly to the info variable:
setInfo(newdata)
This means that "info" will contain a JSON string rather than an array of JSON objects. Map doesn't work on JSON strings.
For your code to work you need to pass "setInfo" an array of objects; eg:
[
{username: "John"},
{username: "Amy"},
{username: "Peter"}
]
Using React hooks.
I'm trying to do a simple API fetch call with some data, but I can't seem to make this work.
Here is the sandbox link
In this example, the objective is that every 5secs, it fetches to the server to get any updates to the username since the latest latestUpdate.
But for convenience, I will include the code here as well:
const SmallComponent = () => {
const { id, username, latestUpdate } = useItemState();
const dispatch = useItemDispatch();
console.log("Render id", id, "Latest", latestUpdate);
const fetchUsername = useCallback(async () => {
console.log("Getting Id", id, "Latest", latestUpdate);
const response = await fetch(
"https://jsonplaceholder.typicode.com/users/" + id
);
const user = await response.json();
dispatch({ type: "setUsername", usernameUpdated: user.name });
}, [dispatch, id]);
const updateId = useCallback(() => {
dispatch({ type: "setId", id: id + 1 });
}, [dispatch, id]);
useEffect(() => {
fetchUsername();
const refresh = setInterval(() => {
updateId();
}, 5000);
return () => clearInterval(refresh);
}, [fetchUsername, updateId]);
return (
<div>
<h4>Username from fetch:</h4>
<p>{username || "not set"}</p>
</div>
);
};
As you'll notice, my fetchUsername is missing a dependency for latestUpdate (which is used on my server to only send udpates since that date). I update latestUpdate when the fetchUsername is finished in my reducer.
What I need:
on mount: fetch username => updates state for username and latestUpdate
interval: every 5secs => fetch updates to username and update latestUpdate to new Date()
The problem is:
If I add the dependency to the useCallback for fetchUsername, I get an infinite refresh loop.
If I don't add it, my latestUpdate value is wrong (ie initial value)
What am I doing wrong?
As you're not using the fetch method anywhere else, it makes sense to put it inside the useEffect directly. No need for useCallback:
useEffect(() => {
const fetchUsername = async () => {
console.log("FETCH", latestUpdate);
const url =
"https://jsonplaceholder.typicode.com/users/" + id + "#" + latestUpdate;
const response = await fetch(url);
const user = await response.json();
dispatch({ type: "setUsername", usernameUpdated: user.name });
};
const refresh = setInterval(() => {
fetchUsername();
}, 5000);
return () => clearInterval(refresh);
}, [dispatch, id, latestUpdate]);
Here is the full CodeSandBox:
https://codesandbox.io/s/trusting-framework-hvw06?file=/src/App.js
You can find more in the official docs (look for "...to move that function inside of your effect"):
https://reactjs.org/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies
And I also recommend Robin Wieruch's hook-fetching tutorial: https://www.robinwieruch.de/react-hooks-fetch-data
In general, I would highly recommend using something like react-query, as it will also take care of caching. It is a better way to consume your server data (instead of fetching and putting the response in your context): https://github.com/tannerlinsley/react-query