React: async and await not working with fetch - javascript

I have API on Node server returning JSON like this when called:
{"result":[{"ProductID":1,"ProductName":"iPhone10","ProductDescription":"Latest smartphone from Apple","ProductQuantity":100}]}
I'm trying to display all of that information to user using fetch API with React but no matter what my call returns undefined. Here is my React code:
const [products, setProducts] = useState({})
async function getProducts() {
await fetch(`http://127.0.0.1:5000/listProducts`)
.then(response => response.json())
.then(response=>{
setProducts({products:response.result})
console.log(response.result);
products.map(products =>
<h1>{products.ProductName}</h1>
<h1>{products.ProductDescription}</h1>
)
})
.catch(err=>console.error(err))
}
Function getProducts() is called once when page is loaded. What I'm doing wrong? Thanks in advance.

Try this it will work
const handleFetchData = async () => {
const response = await fetch(`https://api.unsplash.com/photos/random?client_id=${process.env.NEXT_PUBLIC_UNSPLASH_API_ACCESS_KEY}`);
const data = await response.json();
console.log(data);
}
useEffect(() => {
handleFetchData();
},[])

Your function is doing it wrong :
The name should be getAndSetProducts or even setProducts / initProducts because it returns a Promise<void> since you don't actually return anything ;
You're setting inside products an object { products: Product[] }, I think you want only Product[] (an array of Products) else you'll have to get products by doing products.products ;
The map is useless, since you don't do anything with the map response, plus the variable products in the map overwrite the one imported (may cause some errors later).
Try to do :
const [products, setProducts] = useState([]); // Array instead of object
async function initProducts() {
await fetch(`http://127.0.0.1:5000/listProducts`)
.then(response => response.json())
.then(response => {
setProducts(response.result);
console.log(response.result);
)
.catch(err => console.error(err));
}
function getProductsHtml() {
return products.map(product =>
<h1>{product.ProductName}</h1>
<h1>{product.ProductDescription}</h1>
);
}
You can call initProducts when component initialize and return getProductsHtml inside your jsx render.

Try this...
const [products, setProducts] = useState({})
React.useEffect(() => {
const fetchData = async () => {
const result = await fetch('http://127.0.0.1:5000/listProducts')
// console log here to determine how to set products
console.log(result)
setProducts(result)
}
fetchData()
}, [])
React.useEffect(() => {
if (!!products) {
// here you could access products!
}
}, [products])
if (!products) return null
return products.map((product) => (
<>
<h1>{products.ProductName}</h1>
<h1>{products.ProductDescription}</h1>
</>
))

If you are using Async, then you can use response.status as shown below
const response = await fetch("URL", {
body:BODY_DATA,
method:'POST',
headers: { "Content-Type": "application/json"
});
if(response.status === 200){
// Complete your action
} else {
// Show error
}

you can't use async await with .then() you should only use async await or .then() only

Related

Return value firestore onSnapshot in react

i have a onSnapshot query in a function:
//firebaseutil.js
export async function getShorts(uid) {
const q = query(collection(db, 'shorted'), where('owner', '==', uid));
const unsubscribe = onSnapshot(q, (querySnapshot) => {
const urls = [];
querySnapshot.forEach((doc) => {
urls.push({
url: doc.data().url,
shorturl: doc.data().shorturl,
hits: doc.data().hits,
});
});
console.log(urls);
return urls;
});
}
Which correctly logs the data, and relog it if i change it on the firestore collection (as expected)
i am trying to access these data from a user dashboard this way:
//dashboard.js
import { getShorts } from '../lib/fbutils';
import { useEffect, useState } from 'react';
export default function Dashboard() {
const [myurls, setUrls] = useState([]);
useEffect(() => {
const fetchShorts = async () => {
if (user) {
const urls = await getShorts(user.uid);
setUrls(urls);
console.log(myurls);
console.log(urls);
}
};
fetchShorts();
}, []);
user.id is correctly set, but both urls and myurls are logged as undefined (i was thinking at least for a Promise pending)
what am i doing wrong? i usually use this pattern to retrieve data, but it's my first time i get data from a firestore subscription
The onSnapshot() does not return a promise and your getShorts() function returns before the data is received. You can return a promise from that function as shown below:
let fetched = false;
export function getShorts(uid) {
return new Promise((resolve, reject) => {
const q = query(collection(db, 'shorted'), where('owner', '==', uid));
const unsubscribe = onSnapshot(q, (querySnapshot) => {
const urls = querySnapstho.docs.map((d) => ({ id: d.id, ...d.data() }))
if (!fetched) {
// URLs fetched for first time, return value
fetched = true;
resolve(urls);
} else {
// URLs fetched already, an update received.
// TODO: Update in state directly
}
})
})
}
This should return all the URLs when you call the function await getShorts(user.uid); but for the updates received later, you'll have to update them in the state directly because the promise has resolved now.

React Native memory leak error after updating to Firebase version 9 onValue

I'm going through to update my code for the Firebase version 9 modular form. I am now using onValue. From what I'm reading it returns a function that removes the listener. But I'm still not doing it right because although it functions well at first, when I change the database on the backend with the app open I get the "can't perform a react state update on an unmounted component" error when I'm in a different app screen. See old and new code below please.
OLD CODE:
useEffect(() => {
loadListings();
},[]);
const loadListings = async () => {
setLoading(true);
updateInput('');
let testData = [];
let searchData = [];
db.ref('deals').once('value', (snapshot) =>{
snapshot.forEach((child)=>{
testData.push({
id: child.key,
title: child.val().hasOwnProperty('title') ? child.val().title : 'NA',
})
searchData.push(
child.val().title
)
})
})
.then(()=>{
checkMessages(testData);
setLoading(false);
})
.catch((error) => Sentry.Native.captureException('Error MessagesScreen function loadListings 1 ' + error));
}
NEW CODE:
useEffect(() => {
loadListings();
},[]);
const loadListings = async () => {
setLoading(true);
updateInput('');
const dbRef = ref(db, 'deals');
return onValue(dbRef , (snapshot) => {
let testData = [];
let searchData = [];
let storeData = filterStores;
snapshot.forEach((childSnapshot)=>{
testData.push({
id: childSnapshot.key,
title: childSnapshot.val().hasOwnProperty('title') ? childSnapshot.val().title : 'NA',
})
})
checkMessages(testData);
setLoading(false);
})
}
After receiving answer below I changed the useEffect to this instead and now it works:
useFocusEffect(
React.useCallback( () => {
async function fetchData() {
// You can await here
const response = await loadListings();
// ...
return () => response();
}
fetchData();
}, [])
);
You mentioned the unsubscribe function returned from onValue. In order to call it, I think you'll want to grab it from the invocation and then call it on some navigation state change.
Assuming you're using React Navigation, it might look something like this (using the useFocusEffect
import { useFocusEffect } from '#react-navigation/native';
function YourComponent() {
const [loading, setLoading] = React.useState(false)
useFocusEffect(
React.useCallback(async () => {
const unsubscribe = await loadListings();
return () => unsubscribe();
}, [])
);
const loadListings = async () => {
setLoading(true);
updateInput('');
const dbRef = ref(db, 'deals');
return onValue(dbRef , (snapshot) => {
let testData = [];
let searchData = [];
let storeData = filterStores;
snapshot.forEach((childSnapshot)=>{
testData.push({
id: childSnapshot.key,
title: childSnapshot.val().hasOwnProperty('title') ? childSnapshot.val().title : 'NA',
})
})
checkMessages(testData);
setLoading(false);
})
}
return <View />;
}
Also don't forget to either use async/await for your asynchronous loadListings function, or use .then(). Otherwise you'll be working with an unresolved promise.
I also found a related StackOverflow question that helped me get to this answer. Maybe that'll be of some use to you.

How to run the second fetch function when the first one is finished with async, Javascript?

So i have two functions that fetch data from the API. One of them updates the todo completion, and the other one fetches the list of todos. Currently the UpdateAndFetch() function lags behind, and sometimes returns the not updated list.
How can i fix this?
Functions that make API calls
let base = import.meta.env.VITE_API_BASE
// fetch the todos that belong to groupId
export async function TESTFetchTodosFromGroup(groupId, todos, groupName) {
let url = `${base}/todos/group/${groupId}`
fetch(url).then(async (response) => {
const json = await response.json()
todos.value = json.data
groupName.value = json.message
if (!response.ok) {
return Promise.reject(error)
}
})
}
//
export async function TESTupdateCompletion(todo) {
//
let url = `${base}/todos/completion/${todo.todoId}`
var requestOptions = {
method: 'PATCH',
redirect: 'follow',
}
fetch(url, requestOptions)
.then((response) => response.json())
.then((result) => console.log(result))
.catch((error) => console.log('error', error))
}
Function inside the Component
async function UpdateAndFetch(todo, groupId) {
const updateTodoCompletion = await TESTupdateCompletion(todo)
const updateTodoList = await TESTFetchTodosFromGroup(groupId, todos, groupName)
}
You always have to return a fetch function otherwise it will not pass the value to the next async call.
And in order to have the ability to execute the function one by one you can do this where you call your functions.
async function UpdateAndFetch(todo, groupId) {
Promise.resolve(() => updateTodoCompletiong(todo)).then(response => fetchTodosFromGroup(groupId,todos,groupName))
}
after that, you can catch errors.
You can read the documentation here it is really very helpful what javascript provides. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve
also if any questions leave a comment.
Nevermind, found how to fix this.
Wrap the fetch functions to be a return value
// fetch the todos that belong to groupId
export async function fetchTodosFromGroup(groupId, todos, groupName) {
let url = `${base}/todos/group/${groupId}`
// you need to wrap the fetch into return so that the awaits would work !
return fetch(url).then(async (response) => {
const json = await response.json()
todos.value = json.data
groupName.value = json.message
if (!response.ok) {
return Promise.reject(error)
}
})
// Promise.resolve('done')
}
//
export async function updateTodoCompletion(todo) {
//
let url = `${base}/todos/completion/${todo.todoId}`
var requestOptions = {
method: 'PATCH',
redirect: 'follow',
}
// you need to wrap the fetch into return so that the awaits would work !
return (
fetch(url, requestOptions)
.then((response) => response.json())
.then((result) => console.log(result))
// .then(() => TESTFetchTodosFromGroup())
.catch((error) => console.log('error', error))
)
}
Refactor the function that executes the functions
// delete the todo and fetch the list
async function UpdateAndFetch(todo, groupId) {
await updateTodoCompletion(todo)
await fetchTodosFromGroup(groupId, todos, groupName)
}

How to get the result of an async fetch request into my layout in gatsby js and react

I have the following fetchData async function returning a message froma lambda function I want to take that response and dump it onto my page I am using the react-hooks-async package, with a useEffect inside of it. However when I start the function isnide the useAsyncTask it just contiunally runs and never gets the result. I could do it if I hooked up a button to the start() function and it would display correct, but I want it to run on load.
I am using Gatsby JS and react
var fetchData = async function run() {
const response = await fetch(fetchUrl, {
method: "post",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
customer_id: parsed.session_id,
}),
})
.then(res => {
return res.json()
})
.catch(error => console.log(error))
console.log(response)
return response
}
const Customer = () => {
const { start, started, result } = useAsyncTask(fetchData)
useEffect(() => {
console.log("result")
console.log(result)
console.log("result ends")
start()
}, [result])
return (
<div>
{started && "Fetching..."}
<div>Name: {result && result.message.customer_id}</div>
</div>
)
}
I was over engineering it. All that was required was the following
fetchData().then(value => console.log(value))
const Test = () => {
const [data, setData] = useState("")
useEffect(() => {
fetchData().then(test => {
setData(test)
})
}, [])
return data && <p>{data.message.customer_id}</p>
}

how to save promise chaining event in javaScript

I'm making a promise chained fetch call to an API and using it to get the desired data from it. I can't figure out how to save the resulting array which I am logging to the console and then export it to use it another javaScript file.
fetch('http://api.waqi.info/feed/delhi/?token=66cc9b64ec97aff8a78266ca41b082edf3e9a65a')
.then(res => res.json())
.then(response_body => Object.values(response_body.data.iaqi).map(({v}) => v))
.then(console.log)
You can just export your fetch call in a function and return your Promise.
Here is a minimal example:
// == api.js ==
export const getData = () => {
return fetch('http://api.waqi.info/feed/delhi/?token=66cc9b64ec97aff8a78266ca41b082edf3e9a65a')
.then(res => res.json())
.then(response_body => Object.values(response_body.data.iaqi).map(({v}) => v))
.then(console.log)
}
// == otherfile.js ==
import { getData } from './api.js'
const myFunction = () => {
getData().then(res => {
// do your stuff
})
}
// Async/await style
const myFunction = async () => {
const res = await getData()
}

Categories

Resources