Using async to get API data does not render - javascript

I'm pulling data from Firebase in getSubscriptions() which returns an array:
[
{
"price": 2000.01,
"product_name": "Awesome product"
},
{
"active": true,
"product_name": "Other product",
"Price": 599.99
}
]
I'm iterating through the array and I'm able to get a result but I'm having trouble getting that result to render.
I know the issue has something to do with async and waiting for the result to return but I'm stuck on how to apply those concepts to my issue here.
When I uncomment the line useEffect(() => setActiveNotifications(active), [active]), introducing the useState, the calls are put in an infinite loop calling the Firebase API ad infinitum.
export default function UserAccount(props) {
const [activeNotifications, setActiveNotifications] = useState()
function buildSubscriptions(userId) {
getSubscriptions(userId).then(active => {
console.log(JSON.stringify(active, null, 4)) // This returns output above
// useEffect(() => setActiveNotifications(active), [active]) // This will cause infinite loop
return (
active.map(product =>
<p key={product.product_name}>{product.product_name}</p>) // key defined some value to not throw warning
)
})
}
async function getSubscriptions(userId) {
const subPtrCollection = await getUserSubs(userId)
let a = []
await Promise.all(subPtrCollection.map(async docId => { // Promise.all used to execute commands in series
const docData = await getDocData(docId.product)
a.push(docData)
}))
return a
}
return (
...
<Box>{buildSubscriptions(user.uid)}</Box> // Not important where `user.uid` comes from
...
)
}

Infinite loop is because the method invoked in render method setting the state and that causes render. Try some thing like below 1) On change of uid, request for build 2) in when you receive the results from api save to state, this cause render 3) in render method used the state data.
export default function UserAccount(props) {
const [activeNotifications, setActiveNotifications] = useState();
// when ever uid changes, do the build subscriptions
useEffect(() => buildSubscriptions(user.uid), [user.uid]);
// get subscriptions and save to state
function buildSubscriptions(userId) {
getSubscriptions(userId).then((active) => setActiveNotifications(active));
}
async function getSubscriptions(userId) {
const subPtrCollection = await getUserSubs(userId);
let a = [];
await Promise.all(
subPtrCollection.map(async (docId) => {
// Promise.all used to execute commands in series
const docData = await getDocData(docId.product);
a.push(docData);
})
);
return a;
}
return (
<Box>
{activeNotifications.map((product) => (
<p key={product.product_name}>{product.product_name}</p>
))}
</Box>
);
}

Related

How to concat multiple responses and set all response in an array [React JS]

I am doing an API call which is returning IDs and based on number of ids I am doing another call and trying to combine the responses but I am stuck with async issues.
const SearchUser = async () => {
try {
const response = await getSearchUsers();
const ids = response.data?.map((user) => user.userId);
await ids.forEach(async (id) => {
const result = await getUserInfo(id);
setRNOUsers(...result);
// combine result in one state
});
} catch (error) {
setSearching(false);
}
};
useEffect(() => {
SearchUser();
console.log('RNOUsers', RNOUsers); // this is empty and runs even before callng api
}, []);
How can handle this?
You can use Promise.all to wait for all responses, and then set them together with setRNOUsers
const SearchUser = async () => {
try {
const response = await getSearchUsers();
const ids = response.data?.map((user) => user.userId);
const responses = await Promise.all(ids.map(id => getUserInfo(id)))
setRNOUsers(...responses.flatMap(x => x));
} catch (error) {
setSearching(false);
}
};
useEffect(() => {
SearchUser();
console.log('RNOUsers', RNOUsers);
}, []);
Side note, the problem with console.log('RNOUsers', RNOUsers) is setRNOUsers (initialized by useState) is asynchronous. Besides that, your API calls are also asynchronous, so you cannot get values from RNOUsers immediately in useEffect. If you want to see data in that log, you should wait until the state is updated and your component gets re-rendered with your latest data.

Rendering a promise return value with React.useEffect

I'm trying to render the value returned from a promise that calls two external APIs into the JSX.
It seems to work the first time, but then I get undefined.
Am I using useEffect the wrong way?
export default function Dashboard(props) {
const [pageInfo, setPageInfo] = React.useState();
async function getPageInfo(props) {
try {
const userId = localStorage.getItem('userId');
let res = await axios.get('http://localhost:8000/user/'+userId);
let lastPageId = res.data.pages[res.data.pages.length - 1];
let pageInfoObj = await axios.get('http://localhost:8000/page/'+lastPageId);
return pageInfoObj.data;
} catch (err) {
console.log(err);
}
};
React.useEffect(() => {
getPageInfo(props)
.then(data => {
setPageInfo(data);
})
}, [props]);
return(
<Typography>
{pageInfo.pageTitle}
</Typography>
);
}
use optional chaining or initialize your data as per the response structure and put conditionals
If response is an array, initialize with an empty array (so you can map over and get no error's) and also you can check it's length before rendering like data.length > 0 && data.map()
If response is an object, initialize with null so you can check as data && data.blah
If it's a string then may be an empty string 😛
depends on response structure and it's not mandatory to have exactly same
why ? because, by the first render still the data i.e., pageInfo is not available yet and it will be undefined as it is not initialized with any value so by default is undefined
return(
<Typography>
{pageInfo?.pageTitle}
</Typography>
);
Looks like there is a backend fetch error in the second time and also initialise your state.
In your code, when there is an error in the fetch call, it simply returns undefined; So, handle the error in the .catch chain.
export default function Dashboard(props) {
const [pageInfo, setPageInfo] = React.useState({});
async function getPageInfo(props) {
const userId = localStorage.getItem('userId');
let res = await axios.get('http://localhost:8000/user/' + userId);
let lastPageId = res.data.pages[res.data.pages.length - 1];
let pageInfoObj = await axios.get('http://localhost:8000/page/' + lastPageId);
return pageInfoObj.data;
};
React.useEffect(() => {
getPageInfo(props)
.then(data => {
setPageInfo(data);
}).catch(err => {
console.log("Some Error: " + err.message)
})
}, [props]);
return (
<Typography>
{pageInfo.pageTitle}
</Typography>
);
}

How do I conditionally render data returned from a useEffect that calls an API route and adds that data to a useState variable

I have a React component that's main purpose is to display a list of profile names. The profile names are stored in a useState variable called profiles.
I have a useEffect in place on the component that effectively calls our API route to return the profile data we need on the frontend and place that data in the state variable for profiles.
If the profiles state variable has a length of zero, then we don't have the data and a logo will appear to load, otherwise the profiles should be mapped through and displayed as h1 tags.
While a console.log shows to me I am returning the data I need, I am getting the following error in my console "Uncaught TypeError: profiles.map is not a function".
Here is my code:
function ProfileListComponent() {
const fetchProfiles = async (payload) => {
const token = localStorage.getItem("token")
const response = await axios.get("http://127.0.0.1:5000/profiles", {headers:{
"Authorization": `Bearer ${token}`
}})
if (response.data) {
let profileData = []
for (let key in response.data) {
let profile = [
response.data[key].profile_id,
response.data[key].profile_name,
response.data[key].flow_rate,
response.data[key].hv,
response.data[key].lv,
response.data[key].duty_cycle,
response.data[key].frequency,
]
profileData.push(profile)
}
console.log(profileData)
return profileData
}
}
const [profiles, setProfiles] = useState([])
const compileProfileData = () => {
return ""
}
useEffect(() => {
try {
const profileData = fetchProfiles()
setProfiles(profileData)
} catch (error) {
console.log(error)
}
}, [])
return (
<div>
{profiles.length === 0 ?
<img src={logo} className="App-logo" alt="logo" />
: (
profiles.map(profile => <h1 className="profileBox" key={profile[0]} onClick={() => {compileProfileData(profile)}}>{profile[1]}</h1>
)
)}
</div>
)
}
I have tried different methods to conditionally render this data, though I always seem to error out with the same message indicating that the state variable isn't even an array (which is interesting considering its default value is an empty array).
Does anyone have some guidance to offer on how to correctly get this data rendered?
This happens because inside useEffect hook try-catch block executes both fetchProfiles and setProfiles synchronously. So setProfiles sets a promise which has not resulted yet and below map function means "Give me array, not a promise".You should put your setState inside fetchProfiles.
From this;
const fetchProfiles = async () => {
// const profileData = await axios ...
return profileData;
};
useEffect(() => {
try {
const data = fetchProfiles();
setProfiles(data); // SETS HERE
} catch (error) {
console.log(error);
}
}, []);
To this;
const fetchProfiles = async () => {
// const profileData = await axios ...
setProfiles(profileData); // SETS HERE
};
useEffect(() => {
try {
const data = fetchProfiles();
} catch (error) {
console.log(error);
}
}, []);
Imagine profileData is constant mock data. And you can try this at
Stackblitz link

async/await function not always behave correctly

I'm developing a react-native/nodeJS project and I'm experiencing issues with the Axios API call to my backend using async/await functions.
Here's the code:
const TimeTable = () => {
const [attendedCourses, setAttendedCourses] = useState([]);
const [courseSchedules, setCourseSchedules] = useState([]);
useEffect(() => {
getUserCourses();
getCourseSchedule();
console.log(courseSchedules);
}, []);
const getCourseSchedule = async () => {
for (const item of attendedCourses) {
try {
const res = await axios.get(`/api/lesson/findById/${item.courseId}`);
setCourseSchedules((prev) => [
...prev,
{
id: res.data._id,
name: res.data.name,
schedule: [...res.data.schedule],
},
]);
} catch (err) {
const error = err.response.data.msg;
console.log(error);
}
}
};
const getUserCourses = async () => {
const userId = "12345678"; //hardcoded for testing purpose
try {
const res = await axios.get(`/api/users/lessons/${userId}`);
setAttendedCourses(res.data);
} catch (err) {
const error = err.response.data.msg;
console.log(error);
}
};
return (...); //not important
};
export default TimeTable;
The method getUserCourses() behave correctly and returns always an array of objects which is saved in the attendedCourses state. The second method getCourseSchedule() doesn't work correctly. The console.log() in the useEffect() prints most of the time an empty array.
Can someone please explain to me why? Thank you!
While the method is async, the actual useEffect is not dealing it in asynchronous manner and won't await till you reach the console.log in the useEffect. If you put the console.log inside the getCourseSchedule method and log the result after the await, it'll show you correct result every time.
You are confusing the async nature of each method. Your code does not await in the useEffect, it awaits in the actual method while the rest of the useEffect keeps executing.
If you really want to see the result in useEffect, try doing:
useEffect(() => {
const apiCalls = async () => {
await getUserCourses();
await getCourseSchedule();
console.log(courseSchedules);
}
apiCalls()
})
Your useEffect has an empty array as dependencies that means it is run only onetime in before initial render when the courseSchedules has initial value (empty array)
To see the courseSchedules when it change you should add another useEffect like this:
useEffect(() => {
console.log(courseSchedules);
}, [courseSchedules]);

How to get fetch api results in execution order with async/await?

After an input change in my input element, I run an empty string check(if (debouncedSearchInput === "")) to determine whether I fetch one api or the other.
The main problem is the correct promise returned faster than the other one, resulting incorrect data on render.
//In my react useEffect hook
useEffect(() => {
//when input empty case
if (debouncedSearchInput === "") autoFetch();
//search
else searchvalueFetch();
}, [debouncedSearchInput]);
searchvalueFetch() returned slower than autoFetch() when I emptied the input. I get the delayed searchvalueFetch() data instead of the correct autoFetch() data.
What are the ways to tackle this? How do I queue returns from a promises?
I read Reactjs and redux - How to prevent excessive api calls from a live-search component? but
1) The promise parts are confusing for me
2) I think I don't have to use a class
3) I would like to learn more async/await
Edit: added searchvalueFetch, autoFetch, fetcharticles code
const autoFetch = () => {
const url = A_URL
fetchArticles(url);
};
const searchNYT = () => {
const url = A_DIFFERENT_URL_ACCORDING_TO_INPUT
fetchArticles(url);
};
const fetchArticles = async url => {
try{
const response = await fetch(url);
const data = await response.json();
//set my state
}catch(e){...}
}
This is an idea how it could looks like. You can use promises to reach this. First autoFetch will be called and then searchvalueFetch:
useEffect(() => {
const fetchData = async () => {
await autoFetch();
await searchvalueFetch();
};
fetchData();
}, []);
You can also use a function in any lifecycle depends on your project.
lifecycle(){
const fetchData = async () => {
try{
await autoFetch();
await searchvalueFetch();
} catch(e){
console.log(e)
}
};
fetchData();
}
}

Categories

Resources