Usually I do useEffect cleanups like this:
useEffect(() => {
if (!openModal) {
let controller = new AbortController();
const getEvents = async () => {
try {
const response = await fetch(`/api/groups/`, {
signal: controller.signal,
});
const jsonData = await response.json();
setGroupEvents(jsonData);
controller = null;
} catch (err) {
console.error(err.message);
}
};
getEvents();
return () => controller?.abort();
}
}, [openModal]);
But I don't know how to do in this situation:
I have useEffect in Events.js file that get events from function and function in helpers.js file that create events on given dates except holidays (holiday dates fetch from database).
Events.js
useEffect(() => {
if (groupEvents.length > 0) {
const getGroupEvents = async () => {
const passed = await passEvents(groupEvents); // function in helpers.js (return array of events)
if (passed) {
setEvents(passed.concat(userEvents));
} else {
setEvents(userEvents);
}
};
getGroupEvents();
}
}, [groupEvents, userEvents]);
helpers.js
const passEvents = async (e) => {
try {
const response = await fetch(`/api/misc/holidays`, {
credentials: 'same-origin',
});
const jsonData = await response.json();
const holidays = jsonData.map((x) => x.date.split('T')[0]); // holiday dates
return getEvents(e, holidays); // create events
} catch (err) {
console.error(err.message);
}
};
You can either not clean up, which really is also fine in many situations, but if you definitely want to be able to abort the in-flight request, you will need to create the signal from the top-level where you want to be able to abort, and pass it down to every function.
This means adding a signal parameter to passEvents.
Remove ?:
return () => controller.abort();
Like to this: https://www.youtube.com/watch?v=aKOQtGLT-Yk&list=PL4cUxeGkcC9gZD-Tvwfod2gaISzfRiP9d&index=25
Is it what you want?
Related
I have my App component defined as below;
function App() {
const [state1, setState1] = useState({});
const [state2, setState2] = useState({});
const [isApiCallDone, setIsApiCallDone] = useState(false);
const fetchFn = useMyCustomFetch();
useEffect(() => {
(async function() {
try {
let [state11] = await fetchFn('api/api1', {}, 'GET');
let [state22] = await fetchFn('api/api2', {}, 'GET');
setState1(state11); // Is there a better way to set this ?
setState2(state22);
setIsApiCallDone(true);
} catch (e) {
console.error(e);
}
})();
}, []);
useEffect(() => {
if (Object.keys(state1).length > 0 && Object.keys(state2).length > 0) {
// Set some other state variables on App
}
}, [state1, state2])
return (
<>
<MyContextProvider>
{isApiCallDone && (
<MyComponent />
)
}
</MyContextProvider>
</>
}
Also my useMyCustomFetch hook looks like below
export default function useMyCustomFetch() {
const fetchData = async (url, reqData, reqType) => {
try {
var statusObj = {
statMsg: null
};
const response = await fetch(url, reqOptions);
if (!response.ok) {
throw response;
}
statusObj.status = "success";
const json = await response.json();
return [json, statusObj];
} catch (error) {
statusObj.status = "error";
return [null, statusObj];
}
}
return fetchData;
}
My questions are;
For the lines
let [state11] = await fetchFn('api/api1', {}, 'GET');
setState1(state11);
I first assign it to a new variable state11 and then assign the same by calling setState1.
Is there a better way to set the state1 directly?
Is the usage of async function inside the useEffect fine ?
For Question 1:
You can directly setState like below without using state11.
useEffect(() => {
(async function () {
try {
setState1(
(await fetchFn("https://reqres.in/api/users/1", {}, "GET"))[0]
); // Is there a better way to set this ?
setState2(
(await fetchFn("https://reqres.in/api/users/2", {}, "GET"))[0]
);
setIsApiCallDone(true);
} catch (e) {
console.error(e);
}
})();
}, []);
For Question 2:
I don't see any problem using async & IIFE in the useEffect. In fact I like the way its done. Looks good to me.
Please find screenshot of the state being set properly in the console (I have used a dummy api url):
If you don't want to use async functions, you can use the Promise.prototype.then() method to combine your calls like this :
useEffect(() => {
fetchFn('api/api1', {}, 'GET').then(state => {
setState1(state[0]);
return fetchFn('api/api2', {}, 'GET')
}).then(state => {
setState2(state[0]);
setIsApiCallDone(true);
}).catch(console.log);
}, []);
An other way to set this with an async function but more factorised is this way :
useEffect(() => {
(async function() {
try {
await fetchFn('api/api1', {}, 'GET')
.then(tab => tab[0])
.then(setState1);
await fetchFn('api/api2', {}, 'GET');
.then(tab => tab[0])
.then(setState2);
setIsApiCallDone(true);
} catch (e) {
console.error(e);
}
})();
}, []);
Finally, the usage of the async function in an useEffect is not a problem.
In my project I have a function for downloading the files. When click the button the function onDownload will be called:
import {useOnDownload} from "../../use/useOnDownload"
setup() {
...
const loading = ref(null)
onDownload = (id) => {
loading.value = id
await useOnDownload(id)
loading.value = null
}
return {loading, onDownload}
}
I refactored the code for api in a file useOnDownload.js call because the same code is used in another components as well.
export async function useOnDownload(id) {
// make api call to server with axios
}
What I did wrong? I need to wait for the function useOnDownload ... in order the loader to work.
Here is how to make async composable functions with async await syntax
export default function useOnDownload() {
const isLoading = ref(true);
const onDownload = async () => {
isLoading.value = true;
try {
const { data } = await axios.post('/api/download', {id: id},
{responseType: 'blob'})
// handle the response
} catch (error) {
console.log(error);
} finally {
isLoading.value = false;
}
};
// invoke the function
onDownload();
return { // return your reactive data here };
}
import useOnDownload from "../../use/useOnDownload"
// no await in setup script or function
const { reactiveDataReturned } = useOnDownload();
Read more here
onDownload must be async in order to use await within it
I managed to solved another way without async and await...
I passed the reference object loader to the function parameter (as optional) and handle from there...
export function useOnDownload(id, loader) {
if(loader !== undefined) {
loader.value = id
}
axios.post('/api/download', {id: id}, {
responseType: 'blob'
}).then(response => {
// handle the response
...
if(loader !== undefined) {
loader.value = null
}
}).catch(error => {
// handle the error
...
if(loader !== undefined) {
loader.value = null
}
})
}
You are using the await keyword in your onDownlaod function, but the function is not asynchronous. Here's how you should update it.
// next line important
onDownload = async(id) => {
loading.value = id
await useOnDownload(id)
loading.value = null
}
im trying to get user location, and then use the location to return relevant data for is location.
but in the second function i get that the location is null (when i console.log(location) it prints the right location, at the second print, the first print is null) it seems like the second function is not waiting until the first one is done.
Here is some code:
from the component
const location = useSelector(state => state.locationReducer.location);
useEffect(()=> {
(async () => {
await getLocation();
// here i'm using the location from the first function
await getInfo(location);
})()
}, []);
const getLocation = async() => {
try {
await dispatch(getLocation());
console.log(location);
} catch (err) {
// TODO HANDLE ERROR;
console.log('Err:', err);
}
}
in the action
export const getLocation = locationName => {
return async dispatch => {
try {
const location = **await** locationService.getLocation(locationName);
**await** dispatch(setLocation(location));
} catch (err) {
throw err;
};
};
};
const setLocation = location => {
return {
type: types.SET_LOCATION,
location
};
};
in service
async function getLocation(locationName) {
try {]
return **await** axios.get(`${url}/${locationName}`);
} catch (err) {
throw err
};
};
The location value from the selector won't update after your first function has run and before the second function, so there you'll see the old value in your location variable.
You might need to return your location value from the reducer:
export const getLocation = locationName => {
return async dispatch => {
try {
const location = await locationService.getLocation(locationName);
await dispatch(setLocation(location));
return location;
} catch (err) {
throw err;
};
};
};
And use the returned location in your useEffect:
useEffect(()=> {
(async () => {
const location = await getLocation();
// here i'm using the location from the first function
await getInfo(location);
})()
}, []);
Or another possibility, to have an another effect, wich depends son the location value:
const location = useSelector(state => state.locationReducer.location);
useEffect(()=> {
getLocation();
}, []);
useEffect(()=> {
if(location) {
getInfo(location);
}
}, [location]);
And this would run every time location changes, and location has some value.
As Per Dan Abramov-
useEffect(() => {
async function fetchMyAPI() {
let url = 'http://something';
let config = {};
const response = await myFetch(url);
console.log(response);
}
fetchMyAPI();
}, []);
Here is the link for reference - https://github.com/facebook/react/issues/14326#issuecomment-441680293
Or You can simply use .then()
useEffect(() => {
asyncCall().then(setVal);
});
Article on how to fetch - https://www.robinwieruch.de/react-hooks-fetch-data
I have a project of a mixture of componentDidMount and react hooks(terrible i know) however within one of the componentDidMount's I setState to allow for contractData to match the params of an Id as you can see below
componentDidMount = async() => {
let tasks = [];
try {
tasks = await getTasks(this.props.match.params.id);
}
catch (err) {
if (err !== HttpStatus.NOT_FOUND) {
throw err;
}
}
this.setState({ contract: await getContractData(this.props.match.params.id), tasks: tasks });
}
I need to do something similar about the params but for my getSupplierData within my useEffect
const [suppliers, setSupplierList] = useState([]);
useEffect(() => {
getSupplierData().then(response => {
setSupplierList(response);
});
}, []);
How could i do this?
I try to fetch some object, but the problem is that I need to check first if there ist any object on cache endpoint, if not I should do normal fetching from regular endpoint.
So far I only managed to do fetching from:
Normal endpoint and set everything on state,
Cache endpoint and set everything on state
Any attempts to mix both methods ended in failure :(
How can I mix this two methods?
const getCache = async () => {
try {
const apiCall = await fetch(fetchCacheEndpoint)
const data = await apiCall.json()
return data
} catch (e) {
console.log(e);
}
}
const pageOne = getCache().then((result) => {
const convertedOBj = result.doSomeSeriousStuff()
this.setState({
desiredObj: convertedOBj
})
})
I expected to do something like this
const getCache = async () => {
try {
const apiCall = await fetch(fetchCacheEndpoint)
const data = await apiCall.json()
return data
} catch (e) {
console.log(e);
}
}
let convertedOBj
const pageOne = getCache().then((result) => {
if ((result === undefined) || (!result) || (result && !result.length > 0)) {
const makeRegularFetch = async () => {
const regularFetch = await fetch(regularEndPoint)
const data = await regularFetch.json()
}
const pageTwo = makeRegularFetch ().then((result) => {
convertedOBj = result.doSomeSeriousStuff()
this.setState({
desiredObj: convertedOBj
})
})
} else {
convertedOBj = result.doSomeSeriousStuff()
this.setState({
desiredObj: convertedOBj
})
}
})
After first fetch (getCache) is failed there is another fetch (makeRegularFetch) to second endpoint which is always fine, but only in the case when first(getCache) return empty object or just any kind of error
How can I handle this kind of action?
From what I can see in your second part of your code, you never execute your pageOne function.
Try pageOne() after your definition.
However I made a fiddle on stackblitz for your case: https://stackblitz.com/edit/js-eufm8h
If you don't understand something, feel free to ask.