How to simplify an async call inside a React useEffect Hook - javascript

I'm trying to create the simplest React Hook useEffect call I can and hoping not to have to create a function call inside useEffect. My code is below and it requires me to call fetchData() to make it work. Is there a simpler version of this?
Maybe with a strategically placed async keyword or something like that?
useEffect(() => {
const fetchData = async () => {
let result = await axios.get('http://localhost:4000/');
console.log(result.data.length);
};
fetchData();
}, []);

What you can do is make it an IIFE:
useEffect(() => {
const promise = (async () => {
let result = await axios.get('http://localhost:4000/');
console.log(result.data.length);
})();
return (() => {
promise.then(() => cleanup());
});
}, []);
Or use plain promises:
useEffect(() => {
const promise = axios.get('http://localhost:4000/').then((result) => {
console.log(result.data.length);
});
return (() => {
promise.then(() => cleanup());
})
}, []);
But that is about as much as you can do since useEffect cannot be async directly. From ESLint:
Effect callbacks are synchronous to prevent race conditions.

Related

Define Async Function outside useEffect and don't include it in the dependency array

Hi please I need help for clarifying the following:
It is about doing an API call only one time, when the component is mounted.
I know that we must never do something like this in React:
Case 1:
useEffect(async () => {
const response = await callAPI();
setData(response.data);
}, []);
I know that the problem of Case 1 is that useEffect is expecting a function and not a promise (since async functions are always of type Promise), also I know that the return callback of useEffects is not going to work properly.
I also know that these 2 possible solutions work:
Case 2:
useEffect(()=> {
const fetchData = async () => {
const response = await callAPI();
setData(response.data);
}
fetchData();
}, []);
Case 3:
const fetchData = useCallback(async () => {
const response = await callAPI();
setData(response.data);
},[]);
useEffect(()=> {
fetchData();
}, [fetchData]);
My main question here is what about Case 4 and Case 5, what is wrong with them?, I have many projects where I'm using it in that way. Thanks.
Case 4:
const fetchData = async () => {
const response = await callAPI();
setData(response.data);
}
useEffect(()=> {
fetchData();
}, []);
Case 5:
const fetchData = useCallback(async () => {
const response = await callAPI();
setData(response.data);
},[]);
useEffect(()=> {
fetchData();
}, []);
By the way I know that in Case 4 fetchData function is going to be re-defined in every re-render, sometimes that is not a big problem, specially if the function is not added in the dependency array of useEffect, since that avoids calling fetchData multiple times.
Nothing is wrong with them, other than perhaps the superfluous useCallback in case 5. Also a linter might might not like that you're calling function in an effect that is not in the dependency array, but that's a failure of the linting heuristic not an actual problem.
There is another pattern that avoids both, though: Define the function outside of the component
async function fetchData() {
const response = await callAPI();
return response.data;
}
and then just use
useEffect(() => {
fetchData().then(setData);
}, [])
Case 4 and also Case 5
In short, their return value is a promise
They basically create the same result as Case 1
I personally like to use iife like this :
useEffect(() => {
(async () => {
const res = await fetch(`${URL}people`)
const data = await res.json()
setPepoleArr(data)
})()
}, [])
Think of it this way once async is added
The function returns a promise,
The question is where she returns him
When you want to get a request from the server and use async & await, it takes a few seconds for the data to be given to you. When you setState at the same time, it doesn't give you data
const fetchData = async () => {
const response = await callAPI();
if (response) setData(response.data);
//enter code here
}
useEffect(()=> {
fetchData();
}, []);

React - call consecutive fetch operations (mtutally dependent)

How to call 2 fetch operations where second fetch requires id returned from first call?
My code:
useEffect(() => {
const fetchData = async () => {
const aaa=
await service.getDefaultTemplate();
const bbb=
await service.getAnnouncementTemplate({
templateId: aaa.id,
});
setAaa(aaa); //useState
setBbb(bbb); //useState
};
fetchData().catch(noop)
}, []);
Only first result aaa is returned, bbb is null
UPDATE
after first answer:
useEffect(() => {
service
.getDefaultAnnouncementTemplate()
.then((aaa) => {
setAAA(aaa);
service
.getAnnouncementTemplate({
templateId: aaa.id,
})
.then((bbb) => {
setBbb(bbb);
})
.catch(noop);
})
.catch(noop);
}, []);
BUt I get warning: Eslint: avoid nesting promises
to avoid nesting promises warning:
useEffect(() => {
service.getDefaultAnnouncementTemplate()
.then((aaa) => {
setAAA(aaa);
return service.getAnnouncementTemplate({templateId: aaa.id})
})
.then((bbb) => setBbb(bbb))
.catch(noop);
}, []);
But your snippet with async/await looks good. Check input and output of service.getAnnouncementTemplate method
you can make them dependent with a return, inside the first call you can return any status, maybe an object or something so the code will look like this:
useEffect(() => {
const fetchData = async () => {
const aaa=
await service.getDefaultTemplate();
if(aaa.status !== "success") return;
const bbb=
await service.getAnnouncementTemplate({
templateId: aaa.id,
});
setAaa(aaa); //useState
setBbb(bbb); //useState
};
fetchData().catch(noop)
}, []);
that way if the first call is not a success you kill the execution, what you need to do and the vital part is to return any value from the fetch that will tell you its a success or an error

How do I destructure a promise returned by axios?

Below is the code to fetch some data
useEffect(async (id) => {
// const data = await redditFetch(id);
async function redditFetch(id){
try {
const {data:{data}} = await axios.get(`https://www.reddit.com/search.json?q=${id}&limit=50`);
const {children} = data;
return children.map(data=> data.data);
} catch (error) {
console.log(error);
}
}
const data = redditFetch(id);
setData(data)
},[setData])
console.log(Data);
And this is the promise returned:
Promise {<pending>}
__proto__: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: Array(50)
What I want is the Array inside the PromiseResult.
I've also tried another away using async await like this which returns a proper array.
const PostPage = () => {
const {id} = useParams();
const [Data, setData] = useState([])
useEffect(async (id) => {
// const data = await redditFetch(id);
async function redditFetch(id){
try {
const {data:{data}} = await axios.get(`https://www.reddit.com/search.json?q=${id}&limit=50`);
const {children} = data;
return children.map(data=> data.data);
} catch (error) {
console.log(error);
}
}
const data = await redditFetch(id);
setData(data)
},[setData])
console.log(Data);
But with this comes a bloody warning that I can't get rid of:
index.js:1 Warning: An effect function must not return anything
besides a function, which is used for clean-up.
It looks like you wrote useEffect(async () => ...) or returned a
Promise. Instead, write the async function inside your effect and call
it immediately:
useEffect(() => { async function fetchData() {
// You can await here
const response = await MyAPI.getData(someId);
// ... } fetchData(); }, [someId]); // Or [] if effect doesn't need props or state
So, my dear coders, tell me what to do???
I did a sample demo with the post code:
CODESANDBOX: https://codesandbox.io/s/flamboyant-nobel-p87mt9?file=/src/App.js:257-265
In your code const data = redditFetch(id); the async functions will return a Promise. You have to use await, but React will warn you to not transform the useEffect callback in async function
You can remove the API function of your useEffect.
Don't put the setDate in your useEffect dependency array. In this case, you can leave this empty to run just one time. Also, don't put the date because will re-render in an infinite loop. If you need some conditional just remove the dependency array and put some conditional to run setDate.
I had to mock the id in the query parameter to be possible to work with API
CODE:
import axios from "axios";
import { useEffect, useState } from "react";
import "./styles.css";
async function redditFetch() {
try {
const {
data: { data }
} = await axios.get(`https://www.reddit.com/search.json?q=01&limit=50`);
const { children } = data;
return children.map((data) => data.data);
} catch (error) {
console.log(error);
}
}
export default function App() {
const [data, setData] = useState([]);
const Data = data.map((redditContent) => {
return (
<div key={redditContent.name}>
<h2>{redditContent.title}</h2>
<hr />
</div>
);
});
useEffect(() => {
(async function () {
const redditResponse = await redditFetch();
console.log(redditResponse);
setData(redditResponse);
})();
}, []);
return <div className="App">{Data}</div>;
}
ALTERNATIVE: CLEANER APPROACH (No anonymous function)
If you don't like of anonymous function approach, you can use this one:
async function updateRedditPosts() {
const redditResponse = await redditFetch();
console.log(redditResponse);
setData(redditResponse);
}
useEffect(() => {
updateRedditPosts();
}, []);
Anything return from useEffect supposed to be clean up while component unmount. Better you may invoke setData function there instead of return.
On the other hand, you may use immediate invoke function (IIF) then you do not need to resolve the promise that return from redditFetch function.

how can i use watchPositionAsync in useFocusEffect

i tried to use this code
useFocusEffect(
useCallback(() => {
const getLocation = async() => {
await getPermissionsAsync()
const subscribe = await watchPositionAsync({
accuracy:Accuracy.BestForNavigation,
timeInterval:5000
},(location) => {console.log(1)})
return () => subscribe.remove()
}
return getLocation()
}, [])
);
but i get this error
An effect function must not return anything besides a function, which is used for clean-up. It looks like you wrote 'useFocusEffect(async () => ...)' or returned a Promise. Instead, write the
async function inside your effect and call it immediately:
useFocusEffect(
React.useCallback() => {
async function fetchData() {
// You can await here
const response = await MyAPI.getData(someId);
// ...
}
fetchData();
}, [someId])
};
TL;DR
useFocusEffect(
useCallback(() => {
let subcription;
const getLocation = async() => {
await getPermissionsAsync()
subcription = await watchPositionAsync({
accuracy:Accuracy.BestForNavigation,
timeInterval:5000
}, (location) => {console.log(1)})
}
getLocation();
return () => { subscription && subscription.remove(); }
}, [])
);
The error actually says what needs to occur. that is the return value must be a simple function used for cleanup.
The subscription is obtained as part of an async call in the getLocation function, but for it to be used it must be pulled out and have it set asynchronously.
Then the cleanup function just needs to check if it is already set and if it is invoke the remove method.

How do I run one function after another one in React?

In my code, I have loadData() and useFilter(). I tried to run loadData() first as productsData need to be set for useFilter() to process productsData. However, I tried .then() but it still gives me undefined when I console log useFilter() How do I fix this?
const [productsData, setProductsData] = useState(null);
const loadData = async () => {
const { data } = await getAxios().get('/market/list').then(response => response.data)
setProductsData(data)
}
const useFilter = () => {
return productsData.map(data => (...))
//filters productsData
};
useEffect(()=>{
loadData().then(()=>useFilter());
},[])
Changing state asynchronous. So using state immediately after changing that may not be updated one.
You can use useEffect to call your function after state changed.
You can do this:
const [productsData, setProductsData] = useState(null);
const useFilter = () => {
return productsData.map(data => (...))
//filters productsData
};
useEffect(()=>{
const loadData = async () => {
const { data } = await getAxios().get('/market/list').then(response => response.data)
setProductsData(data)
}
loadData()
},[])
useEffect(()=>{
useFilter();
},[productsData])
Hello just by making a function async doesn't make it return a promise in order to use .then on function calls you must make sure they return promises
So one implementation you could do is :
const [productsData, setProductsData] = useState(null);
const loadData = () => {
return getAxios().get('/market/list').then(response => response.data)
}
const useFilter = () => {
return productsData.map(data => (...))
//filters productsData
};
useEffect(()=>{
loadData().then((data)=>{
setProductsData(data);
useFilter();
}
);
},[])
In the above implementation I return the promise that axios returns you can also return your own promise by creating a promise using new Promise() constructor
Do let me know if you face any issues

Categories

Resources