React - call consecutive fetch operations (mtutally dependent) - javascript

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

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();
}, []);

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.

How to simplify an async call inside a React useEffect Hook

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.

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 make a second API call based on the first response?

I need to call 2 APIs for displaying data on my component but the second api needs headers from the response of first API.
I am using React Hooks. I update the state from the first response so i can use it later for the second call, but it goes undefined. What am i doing wrong?
P.s a better way of doing this calls (maybe using async/await) would be much appriciated
const [casesHeaderFields, setCasesHeaderFields] = useState([]);
const [casesFields, setCasesFields] = useState([]);
useEffect(() => {
const fetchData = () => {
const result1 = axios
.get(`firstUrl`)
.then(response => {
//I need this as headers for my second API
setCasesHeaderFields(
response.data.result.fields
);
});
const result2 = axios
.get(`url${casesHeaderFields} //here i want to pass params from
//first response)
.then(response => {
setCasesFields(response.data.result.record_set);
});
};
fetchData();
}, []);
You can chain the results as they are regular promises:
Ref
axios.get(...)
.then((response) => {
return axios.get(...); // using response.data
})
.then((response) => {
console.log('Response', response);
});
make the second call inside a .then chained to the end of the first promise chain ... in simple terms, chain your promises
Something like
useEffect(() => axios.get(`firstUrl`)
.then(response => {
setCasesHeaderFields(response.data.result.fields);
return response.data.result.fields;
})
.then(casesHeaderFields => axios.get(`url${casesHeaderFields}`))
.then(response => {
setCasesFields(response.data.result.record_set);
}), []);
The queries result1 and result2 look sequential but there are simultaneous. In other words, result2 doesn't wait for result1 to finish before being executed.
Here is a working example to chain promises using async/await:
useEffect(async () => {
try {
// Make a first request
const result1 = await axios.get(`firstUrl`);
setCasesHeaderFields(result1.data.result.fields);
// Use resutlt in the second request
const result2 = await axios.get(`url${"some params from result1"}`);
setCasesFields(result2.data.result.record_set);
} catch (e) {
// Handle error here
}
}, []);
EDIT
Based on comments, here is a best way to use async with useEffect without warning in the console:
useEffect(() => {
const fetchData = async () => {
try {
// Make a first request
const result1 = await axios.get(`firstUrl`);
setCasesHeaderFields(result1.data.result.fields);
// Use resutlt in the second request
const result2 = await axios.get(`url${casesHeaderFields}`);
setCasesFields(result2.data.result.record_set);
} catch (e) {
// Handle error here
}
};
fetchData();
}, []);
You can use async/await to relieve the mess.
useEffect(async () => {
const response = await axios.get(`firstUrl`)
const result = await axios.get(`url${response.data.result.fields}`)
console.log(result.data)
})
You can write something like this:
useEffect(() => {
return axios
.get(`firstUrl`)
.then(response => {
return response.data.result.field
}).then(result => {
return axios.get(`url${result}`)
});
});
With async / await :
useEffect(async () => {
const result1 = await axios
.get(`firstUrl`)
.then(response => {
return response.data.result.field
});
const result2 = await axios.get(`url${result1}`)
return result2
});

Categories

Resources