Reactjs Asynchronously load events from calendar - Google Calendar API - javascript

I am trying to load events from google calendar api which I fetch with gapi.client.request, the problem is that I can't figure a way how to use async/await properly. My events load after my presentational components. I've used async await before and it worked properly with fetch and other APIs. Is there some other way to wait for google.thenable object. Since it's promise like I thought that it would be easier to handle like I handled promises with fetch before. I'm utterly lost here, any help would be appreciated.
const [events, setEvents] = useState([]);
useEffect(() => {
getEvents();
});
async function get(){
await getEvents();
}
function getEvents(){
init()
.then(() => {
return gapi.client.request({
'path': `https://www.googleapis.com/calendar/v3/calendars/${CALENDAR_ID}/events`,
})
})
.then((res) => {
const allEvents = res.result.items;
setEvents(sortedEvents);
}, (reason) => {
console.log(reason);
});
}
Events don't load before components so they aren't being waited properly. I would like my events to be load asynchronously so that they show simultaneously with other presentational components.

It seems you have some small problems in your code.
First of all, don't forget to make your getEvents() function asynchronous.
Secondly, remember to add a second parameters on your useEffect() method to stop the function from triggering on every single update.
So, you code should look like this:
const [events, setEvents] = useState([]);
useEffect(() => {
getEvents();
}, []);
async function get(){
await getEvents();
}
async function getEvents(){
init()
.then(() => {
return gapi.client.request({
'path': `https://www.googleapis.com/calendar/v3/calendars/${CALENDAR_ID}/events`,
})
})
.then((res) => {
const allEvents = res.result.items;
setEvents(sortedEvents);
}, (reason) => {
console.log(reason);
});
}
You might want to read some more on how to handle APIs with React, here is a good resource for you.
Hope this helps.

Related

How do I mock this custom React hook with API call

I am new to React testing and I am trying to write the tests for this custom hook which basically fetches data from an API and returns it.
const useFetch = (url) => {
const [response, setResponseData] = useState();
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
fetch(url)
.then(response => {
if(response.ok === false){
throw Error('could not fetch the data for that resource');
}
return response.json();
})
.then(data => {
setResponseData(data);
setIsLoading(false);
})
.catch((err) => {
console.log(err.message);
})
}, [url]);
return {response, isLoading};
}
export default useFetch;
Since I am new to this I have tried many solutions but nothing is working and I am getting confused too. Can you provide me a simple test for this so that I can get an idea as to how it is done?
here's an example https://testing-library.com/docs/react-testing-library/example-intro/ using msw library to mock server call.
If you mock the server instead of the hook, it will be more resilient to changes.
Moreover, I suggest you to use a library like react-query or read source code of them and reimplement them if you want to learn to avoid fetching pitfalls

useEffect fetch request is pulling data twice

What I Want:
I'm pulling data from an api, then setting the data to state. I've done this inside a useEffect hook, but when I console.log the data afterwards, it's displaying the data twice, sometimes 4 times. I'm at a loss as to why this is happening.
What I've Tried:
console.log within useEffect to see data from source
disabling react developer tools within chrome.
My Code:
// make api call, assign response to data state
const [apiData, setApiData] = useState();
useEffect(() => {
async function fetchData() {
try {
await fetch('https://restcountries.com/v3.1/all')
.then(response => response.json())
.then(data => setApiData(data));
} catch (e) {
console.error('Error fetching api data', e);
};
};
fetchData();
}, []);
console.log(apiData);
Result of console.log:
As was mentioned in the other comment this is due to effects being "double invoked" in strict-mode.
A common solution and one I believe has been suggested by the React team (although am struggling to find where I read this) is to use useRef.
// make api call, assign response to data state
const [apiData, setApiData] = useState();
const hasFetchedData = useRef(false);
useEffect(() => {
async function fetchData() {
try {
await fetch('https://restcountries.com/v3.1/all')
.then(response => response.json())
.then(data => setApiData(data));
} catch (e) {
console.error('Error fetching api data', e);
};
};
if (hasFetchedData.current === false) {
fetchData();
hasFetchedData.current = true;
}
}, []);
If you are using React version 18+, StrictMode has a behavior change. Before it wasn't running effects twice, now it does to check for bugs. ( Actually, it does mount-remount, which causes initial render effects to fire twice)
https://reactjs.org/blog/2022/03/29/react-v18.html#new-strict-mode-behaviors

Learning Promises, Async/Await to control execution order

I have been studying promises, await and async functions. While I was just in the stage of learning promises, I realized that the following: When I would send out two requests, there was no guarantee that they would come in the order that they are written in the code. Of course, with routing and packets of a network. When I ran the code below, the requests would resolve in no specific order.
const getCountry = async country => {
await fetch(`https://restcountries.com/v2/name/${country}`)
.then(res => res.json())
.then(data => {
console.log(data[0]);
})
.catch(err => err.message);
};
getCountry('portugal');
getCountry('ecuador');
At this point, I hadn't learned about async and await. So, the following code works the exact way I want it. Each request, waits until the other one is done.
Is this the most simple way to do it? Are there any redundancies that I could remove? I don't need a ton of alternate examples; unless I am doing something wrong.
await fetch(`https://restcountries.com/v2/name/${country}`)
.then(res => res.json())
.then(data => {
console.log(data[0]);
})
.catch(err => err.message);
};
const getCountryData = async function () {
await getCountry('portugal');
await getCountry('ecuador');
};
getCountryData();
Thanks in advance,
Yes, that's the correct way to do so. Do realize though that you're blocking each request so they run one at a time, causing inefficiency. As I mentioned, the beauty of JavaScript is its asynchronism, so take advantage of it. You can run all the requests almost concurrently, causing your requests to speed up drastically. Take this example:
// get results...
const getCountry = async country => {
const res = await fetch(`https://restcountries.com/v2/name/${country}`);
const json = res.json();
return json;
};
const getCountryData = async countries => {
const proms = countries.map(getCountry); // create an array of promises
const res = await Promise.all(proms); // wait for all promises to complete
// get the first value from the returned array
return res.map(r => r[0]);
};
// demo:
getCountryData(['portugal', 'ecuador']).then(console.log);
// it orders by the countries you ordered
getCountryData(['ecuador', 'portugal']).then(console.log);
// get lots of countries with speed
getCountryData(['mexico', 'china', 'france', 'germany', 'ecaudor']).then(console.log);
Edit: I just realized that Promise.all auto-orders the promises for you, so no need to add an extra sort function. Here's the sort fn anyways for reference if you take a different appoach:
myArr.sort((a, b) =>
(countries.indexOf(a.name.toLowerCase()) > countries.indexOf(b.name.toLowerCase())) ? 1 :
(countries.indexOf(a.name.toLowerCase()) < countries.indexOf(b.name.toLowerCase()))) ? -1 :
0
);
I tried it the way #deceze recommended and it works fine: I removed all of the .then and replaced them with await. A lot cleaner this way. Now I can use normal try and catch blocks.
// GET COUNTRIES IN ORDER
const getCountry = async country => {
try {
const status = await fetch(`https://restcountries.com/v2/name/${country}`);
const data = await status.json();
renderCountry(data[0]); // Data is here. Now Render HTML
} catch (err) {
console.log(err.name, err.message);
}
};
const getCountryData = async function () {
await getCountry('portugal');
await getCountry('Ecuador');
};
btn.addEventListener('click', function () {
getCountryData();
});
Thank you all.

Axios in a firebase function returning pending promise even inside two async/await blocks

I have an async/await problems (I know, I know) that makes no sense to me. I'm declaring both functions (child and HOF) as async, and awaiting the returned results before trying to console log them. Surprise surprise, I get pending. The function hangs for 60s and times out (so it seems even my runWith timeout method isn't working. Also tried logging a simple "here" right before declaring const fetchData, but also that didn't log. And yet console logging after actually calling the fn does...
exports.getBitcoinPrice = functions
.region("europe-west1")
.runWith({ timeoutSeconds: 5 })
.https.onRequest(async (req, res) => {
const fetchData = async () => {
return await axios
.get("https://api.coindesk.com/v1/bpi/currentprice.json", {
timeout: 2000,
})
.then((res) => res.json())
.catch((error) => console.log(error));
};
const data = await fetchData();
console.log(await data);
return null;
});
I wanted to use fetch but apparently node-fetch doesn't work well with firebase.
I will try to provide a list of the many SO posts and articles I've read about async/await. I've done the research and tried all of their implementations, but still can't resolve it.
Stack overflow formatting is not working, so:
Axios returning pending promise
async/await return Promise { <pending> }
Why is my asynchronous function returning Promise { <pending> } instead of a value?
Async/await return Promise<pending>
https://github.com/Keyang/node-csvtojson/issues/278
https://www.reddit.com/r/Firebase/comments/h90s0u/call_external_api_from_cloud_function/
You are using too many await in your code.
If you want to use await on the fetchData function you should return a Promise and handle it outside.
Try to change your code like this:
exports.getBitcoinPrice = functions
.region("europe-west1")
.runWith({ timeoutSeconds: 5 })
.https.onRequest(async (req, res) => {
const fetchData = () => {
return axios
.get("https://api.coindesk.com/v1/bpi/currentprice.json", {
timeout: 2000,
})
};
try {
const { data } = await fetchData();
console.log(data);
} catch (err) {
console.log(err)
}
return null;
});

Testing async `componentDidMount()` with Jest + react-testing-library

I have a component that fetches data asynchronously in componentDidMount()
componentDidMount() {
const self = this;
const url = "/some/path";
const data = {}
const config = {
headers: { "Content-Type": "application/json", "Accept": "application/json" }
};
axios.get(url, data, config)
.then(function(response) {
// Do success stuff
self.setState({ .... });
})
.catch(function(error) {
// Do failure stuff
self.setState({ .... });
})
;
}
My test for the component looks like this -
it("renders the component correctly", async () => {
// Have API return some random data;
let data = { some: { random: ["data to be returned"] } };
axios.get.mockResolvedValue({ data: data });
const rendered = render(<MyComponent />);
// Not sure what I should be awaiting here
await ???
// Test that certain elements render
const toggleContainer = rendered.getByTestId("some-test-id");
expect(toggleContainer).not.toBeNull();
});
Since rendering and loading data is async, my expect() statements go ahead and execute before componentDidMount() and the fake async call finish executing, so the expect() statements always fail.
I guess I could introduce some sort of delay, but that feels wrong and of course increases my runtime of my tests.
This similar question and this gist snippet both show how I can test this with Enzyme. Essentially they rely on async/await to call componentDidMount() manually.
However react-testing-library doesn't seem to allow direct access to the component to call its methods directly (probably by design). So I'm not sure "what" to wait on, or whether that's even the right approach.
Thanks!
It depends on what your component is doing. Imagine your component shows a loading message and then a welcome message. You would wait for the welcome message to appear:
const { getByText, findByText } = render(<MyComponent />)
expect(getByText('Loading...')).toBeInTheDocument()
expect(await findByText('Welcome back!')).toBeInTheDocument()
The best way to think about it is to open the browser to look at your component. When do you know that it is loaded? Try to reproduce that in your test.
You need to wrap render with act to solve warning message causes React state updates should be wrapped into act.
e.g:
it("renders the component correctly", async () => {
// Have API return some random data;
let data = { some: { random: ["data to be returned"] } };
axios.get.mockResolvedValue({ data: data });
const rendered = await act(() => render(<MyComponent />));
// Test that certain elements render
const toggleContainer = rendered.getByTestId("some-test-id");
expect(toggleContainer).not.toBeNull();
});
Also same goes for react-testing-library.

Categories