How to make multiple Fetch calls - javascript

I have multiple API calls with fairly lengthy, yet similar, response/error handling for each call.
What is the best non-repetitive ways to make multiple independent api calls that update state using fetch?
Copying and pasting 40+ instances of fetch doesn't seem right.
I want to avoid doing this ....
fetch(url,options)
.then((response) => {
// ...
return response.json
})
.then((data) => {
setState(data)
//...
})
.catch((err) => {
//Error logic here
})
Here's what I've done so far:
I made (found and modified) a useFetch hook...
useFetch.ts
//Only calls fetch() when .load() is called.
const useFetch = (path : string, HttpMethod : string, dependencies : any = [] , body : {} | undefined = undefined) => {
const history = useHistory()
const [response, setResponse] = useState<any>({});
const [error, setError] = useState<string>("");
const [isLoading, setIsLoading] = useState<boolean>(false);
const [controller, setController] = useState(2)
const [isReady, setIsReady] = useState<any>(false)
const load = ():void => {
setError("")
//This prevents useEffect from triggering on declaration.
if (isReady) {
//Math.random() is just to get useEffect to trigger.
setController(Math.random())
}
}
const token = localStorage.getItem("token");
let requestOptions:any = {
method: HttpMethod,
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "* always",
Authorization: "Token " + token,
},
};
if (body !== undefined) {
requestOptions["body"] = {
body: JSON.stringify(body)
}
}
const URI = BASE_URI + path
useEffect(() => {
const fetchData = async () => {
if (controller !== 2) {
setIsLoading(true);
try {
const res = await fetch(URI, requestOptions);
const json = await res.json();
if (json?.action == "ENFORCE_BILLING" ) {
history.push(BILLING_CREDENTIALS_PATH, { enforceBillingPopUp: true });
}
if (json?.action == "ENFORCE_SMS_CONFIRMATION") {
// Should we log user out, as well?
history.push(CONFIRMATION_CODE_PATH)
}
if (res.ok) {
setResponse(json);
setIsLoading(false)
} else {
setError(json)
setIsLoading(false)
}
} catch (err) {
setError(err);
// Error logic here...
}
}
}
};
fetchData()
setIsReady(true)
}, [controller, ...dependencies]);
return { response, setResponse ,error, isLoading, load, isReady };
};
Component.tsx
//Inside react functional component...
// Prepares to fetch data from back-end
const data1 = useFetch(PATH1, "GET");
const data2 = useFetch(PATH2, "GET");
const data3 = useFetch(PATH3, "GET");
useEffect(() => {
// Initial on load data fetch
// .load() fetches data
data1.load();
data2.load();
data3.load();
}, [activeReservations.isReady]);
// Sort data depending on sort selection
...
Is useFetch considered bad practice? What are the advantages of using Redux, instead?
Any help would be greatly appreciated. Thanks.

Related

How can i run a function in every screen user is navigating to?

how can I run a function in the whole app? like if that function is triggered then even if a user is navigating to a different screen then also that function will do its work until that function work is done how can I do this in react native? I want to do this with my PostImageHandler once postimagehandler is triggered then that function will run and it won't stop until the image is uploaded also user can navigate through different screens during that process
const [loading1, setLoading1] = useState(false)
const [loading2, setLoading2] = useState(false)
const [photo, setPhoto] = useState(null)
const { postImage } = route.params;
const PostImageHandler = useCallback(async () => {
if (!postImage.cancelled) {
const response = await fetch(postImage);
const blob = await response.blob();
const filename = postImage.substring(postImage.lastIndexOf('/') + 1);
const ref = firebase.storage().ref().child(filename);
const snapshot = await ref.put(blob);
const url = await snapshot.ref.getDownloadURL();
setPhoto(url)
console.log(url)
}
}, [postImage])
useEffect(() => {
PostImageHandler();
}, [PostImageHandler])
const handleUpload = useCallback(() => {
if (postImage != null) {
AsyncStorage.getItem('user')
.then(data => {
setLoading2(true)
fetch('https://mybackend.com/addpost', {
method: 'post',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: JSON.parse(data).user.email,
post: postImage,
})
})
.then(res => res.json())
.then(data => {
if (data.message == 'Photo added successfully') {
alert('Photo added successfully')
setLoading2(false)
navigation.navigate('home')
} else {
alert('Something went wrong, please try again')
setLoading2(false)
}
})
})
} else {
alert('Please wait photo is processing')
}
}, [photo, navigation, setLoading2]);

can't render fetched api data on my nextjs project

on this nextjs project i'm fetching data from an api and it's logging successfully. but i just can't seem to render the response (from the handleSubmit const) on my main jsx return. i try it as {cart.categoryTitle} and i got no error, but also no render on my app. am i doing something wrong? thanks!
import { useEffect, useState } from "react";
const Lista = () => {
const [categoryTitle, setCategoryTitle] = useState<any>();
const [cart, setCart] = useState([])
interface Data {
id: number;
title: string;
}
useEffect(() => {
handleCategoryData();
}, []);
async function handleCategoryData() {
const response = await fetch("/api/category");
const categoryTitle = await response.json();
setCategoryTitle(categoryTitle);
}
const handleSubmit = async (event: any) => {
event.preventDefault();
const categoryTitle = event.target[0].value;
const sub = event.target[1].value;
const name = [event.target[2].value];
const type = event.target[3].value == "Unidade" ? "unit" : "kg";
const price = event.target[4].value;
const counter = event.target[5].value;
//const img = event.target[6].value;
const res = await fetch("../api/list", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
products: [
{
categoryTitle,
sub,
name,
type,
price,
quantity,
},
],
}),
});
const response = await res.json();
console.log(response);
// return JSON.stringify(response);
setCart(response)
};
if (!categoryTitle) return <p>Loading</p>;
if (!sub) return <p>Loading</p>;
if (!productResponse) return <p>Loading</p>;
return (
<>
<section>
<div className="listsContainer">
<div className="cartContainer">
<div className="listText">
<p>Lista</p>
<p>{cart.categoryTitle}</p>
<p>0 categorias / 0 itens</p>
</div>
</div>
What does the response look like? Is it an object that has categoryTitle property? It may be the case that there’s no categoryTitle in the response object. Your cart state is being initialize as an array so maybe you are expecting the response to be an array as well?

Call custom React async hook within a component and se it's state again

I have a custom react hook fetching number of comments from an API that looks like this:
export async function useFetchNumberOfComments(articleId) {
const [numberOfComments, setNumbeOfComments] = useState(0);
useEffect(() => {
(async () => {
try {
const response = await axios.get(`https://example.com/${articleId}`, {
headers: {
"Content-Type": "application/json",
"X-API-KEY": "X",
Authorization: localStorage.getItem("access_token"),
},
});
const data = await response.data;
setNumbeOfComments(data);
} catch (err) {
console.log(err);
}
})();
}, []);
return numberOfComments;
}
And I want to use it in a Article component that looks like this:
import { useFetchNumberOfComments } from "../utils";
const SingleArticle = () => {
let { id } = useParams();
// Important state
const [numOfComments, setNumOfComments] = useState(0);
// Not important states
const [title, setTitle] = useState("");
const [author, setAuthor] = useState("");
const [content, setContent] = useState("");
const [comments, setComments] = useState([]);
const [commentAuthor, setCommentAuthor] = useState("");
const [commentContent, setCommentContent] = useState("");
const [imageId, setImageId] = useState("");
const [imageUrl, setImageUrl] = useState("");
const [createdAt, setCreatedAt] = useState();
const postComment = async (e) => {
e.preventDefault();
const dataToSend = {
articleId: id,
author: commentAuthor,
content: commentContent,
};
try {
await axios.post(`https://example.com/comments`, dataToSend, {
headers: {
"Content-Type": "application/json",
"X-API-KEY": "X",
Authorization: localStorage.getItem("access_token"),
},
});
// Here, fetch the number of comments from my custom hook and update numOf Comments in this component
setCommentAuthor("");
setCommentContent("");
} catch (err) {
console.log(err);
}
};
return (
<>
<form onSubmit={postComment}>
// Here are some inputs, labels and a submit button
</form>
<h4 className={styles.h1}>Comments({numOfComments})</h4>
</>
);
};
export default SingleArticle;
Now, the problem is that I have no idea how to do the mentioned activity within the Article component: Once the form data(for comment) are sent, trigger the useFetchNumberOfComments custom hook and set the numOfComments state inside article component to the newly fetched data.
I think you'd be better served refactoring the useFetchNumberOfComments hook to return a fetch function and some fetch request meta data, i.e. loading and response and error states.
Example:
export function useFetchNumberOfComments() {
const [numberOfComments, setNumbeOfComments] = useState(0);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const fetchArticleCommentCount = useCallback(async (articleId) => {
setLoading(true);
try {
const response = await axios.get(`https://example.com/${articleId}`, {
headers: {
"Content-Type": "application/json",
"X-API-KEY": "X",
Authorization: JSON.parse(localStorage.getItem("access_token")),
},
});
const data = await response.data;
setNumbeOfComments(data);
setError(null);
return data;
} catch (err) {
console.log(err);
setError(err);
} finally {
setLoading(false);
}
}, []);
return {
fetchArticleCommentCount,
numberOfComments,
loading,
error
};
};
...
import { useFetchNumberOfComments } from "../utils";
const SingleArticle = () => {
const { id } = useParams();
const {
fetchArticleCommentCount,
numberOfComments,
} = useFetchNumberOfComments();
// Important state
const [numOfComments, setNumOfComments] = useState(0);
// Not important states
...
const postComment = async (e) => {
e.preventDefault();
const dataToSend = {
articleId: id,
author: commentAuthor,
content: commentContent,
};
try {
await axios.post(`https://example.com/comments`, dataToSend, {
headers: {
"Content-Type": "application/json",
"X-API-KEY": "X",
Authorization: localStorage.getItem("access_token"),
},
});
// await returned comment count and update local state
const commentCount = await fetchArticleCommentCount(id);
setNumOfComments(commentCount);
// or use the updated numberOfComments value returned from hook
fetchArticleCommentCount(id);
// both are available, but you only need one or the other here
setCommentAuthor("");
setCommentContent("");
} catch (err) {
console.log(err);
}
};
return (
<>
<form onSubmit={postComment}>
// Here are some inputs, labels and a submit button
</form>
<h4 className={styles.h1}>Comments({numberOfComments})</h4>
</>
);
};
export default SingleArticle;

Multiple axios get request not returning the data properly

I have created a react hook to work on with multiple get request using axios
const useAxiosGetMultiple = (urls,{preventCall = false} = {}) => {
const [error, setError] = useState(false);
const [loading, setLoading] = useState(false);
const [response, setResponse] = useState(()=>{
const temp = {}
Object.keys(urls).forEach(key => temp[key] = [])
return temp
})
const [reloadToken, setReloadToken] = useState(false)
const urlObj = useRef({...urls})
const unmountedOnReload = useRef(false)
useEffect(() => {
if(preventCall === true){
return null
}
let unmounted = false;
const source = axios.CancelToken.source();
setLoading(true)
const requests = []
Object.values(urlObj.current).forEach(url => {
requests.push(
axios.get(url, {
cancelToken: source.token,
})
);
});
const result = {}
const errors = {}
console.log(requests)
Promise.allSettled(requests)
.then(resArray => {
if(!unmounted){
console.log('from promise allsettled')
console.log(resArray)
console.log(urlObj.current)
Object.keys(urlObj.current).forEach((key,i) =>{
if(resArray[i].status === 'fulfilled'){
result[key] = resArray[i].value.data.responseData
}
if(resArray[i].status === 'rejected'){
errors[key] = resArray[i].reason
result[key] = []
}
})
setError(errors)
setLoading(false)
setResponse(result)
}
})
.catch(err => {
if (!unmounted) {
setError(err);
setLoading(false);
setResponse([])
if (axios.isCancel(err)) {
console.log(`request cancelled:${err.message}`);
} else {
console.log("another error happened:" + err.message);
}
}
})
return () => {
unmounted = true;
unmountedOnReload.current = true
source.cancel("Api call cancelled on unmount");
};
}, [reloadToken,preventCall]);
const reFetchAll = () => {
setReloadToken((token) => !token);
};
const reload = (urlKey) =>{
unmountedOnReload.current = false
setLoading(true)
axios.get(urls[urlKey])
.then(res =>{
if(!unmountedOnReload.current){
setLoading(false)
setResponse({...response,[urlKey]: res.data.responseData})
}
})
.catch(err=>{
if(!unmountedOnReload.current){
setLoading(false)
setError({...error, [urlKey]: err})
setResponse({...response,[urlKey]: []})
}
})
}
return {response, loading, error, reFetchAll, reload, setLoading};
};
I call this hook as follows..
const {response,loading,setLoading,reload} = useAxiosGetMultiple({
stateCodes: StateCode.api,
countryCodes: CountryCode.api,
districts: District.api,
})
Rather than getting variable stateCodes containing state codes or countryCodes containing country codes it's returning in wrong order or returning same data in multiple variable. Every time the call happens every time it changes. I also tried axios.all method instead of Promise.all but problem remains same.
Even in chrome's network panel the response data is improper.
What's the possible cause for this error and how to fix it ?
Thanks in advance

How to stop loading page until response is received from REST endpoint in React?

I have the following function in my ReactJS app:
function MyView() {
const [mydata, setMyData] = useState({ mydata: {} });
const [mystatus, setMyStatus] = useState({ mystatus: null });
useEffect(() => {
let config = {
headers: {
'Authorization': 'Bearer ' + localStorage.getItem('my-token'),
'Content-Type': 'application/json'
}
}
const fetchData = async () => {
const result = await axios(
'http://localhost:8000/endpoint/' + some_id + "/",
config
);
setMyStatus(result.status);
setMyData(result.data);
};
fetchData();
}, []);
.... <Loading the rest of functions and HTML here>
I want to make sure that the mystatus is 200 and that mydata.view == public before I load anything here. The problem is that JS loads rest of the page before the response is received from server.
How do I stop it from doing that i.e, redirect to another page if mystatus !== 200 or mydata.view !== 'public'
You can conditionally render the react component based on the value of the components states mystatus and mydata.
const [mydata, setMyData] = useState(null);
const [mystatus, setMyStatus] = useState(null);
const history = useHistory();
useEffect(() => {
let config = {
headers: {
Authorization: 'Bearer ' + localStorage.getItem('my-token'),
'Content-Type': 'application/json',
},
};
const fetchData = async () => {
const result = await axios(
'http://localhost:8000/endpoint/' + some_id + '/',
config
);
setMyStatus(result.status);
setMyData(result.data);
};
fetchData();
}, []);
if (!mystatus || !mydata) {
return <h1>Loading....</h1>;
}
if (
(mystatus?.status !== 200) ||
(mydata?.view !== 'public')
) {
history.push('/redirect-to-your-public-route');
}
// .... <Loading the rest of functions and HTML here>
You can't stop loading, instead return null until mystatus and mydata.view will satisfy the condition.
function MyView() {
const [mydata, setMyData] = useState();
const [mystatus, setMyStatus] = useState();
const [loading, setLoading] = useState(true);
useEffect(() => {
let config = {
headers: {
'Authorization': 'Bearer ' + localStorage.getItem('my-token'),
'Content-Type': 'application/json'
}
}
const fetchData = async () => {
const result = await axios(
'http://localhost:8000/endpoint/' + some_id + "/",
config
);
if (result.status !== 200 || result.data.myview !== "public") {
window.location.href = "your_redirect_url"
}
setMyStatus(result.status);
setMyData(result.data);
setLoading(false);
};
fetchData();
}, []);
if (loading) {
return <>Loading...</>
}
if (mystatus !== 200 || mydata.myview !== "public") {
return null;
}
return // rest of your code
}
You can't at the moment, even if there is a way it will be kind of hacky.
Instead, you can consider rendering a placeholder/skeleton first similar to this:
Then, when the data is ready and verified, you replace the placeholder/skeleton with actual content.
As for error handling, you can do something like this:
const fetchData = async () => {
try {
const result = await axios(
'http://localhost:8000/endpoint/' + some_id + "/",
config
);
setMyStatus(result.status);
setMyData(result.data);
} catch (error) {
// do something
// redirect to the corresponding page
}
};
Note, the upcoming concurrent mode (still experimental and have no ETA, but this has been discussed for a long time) will change the practice diastically.
You can have a boolean stating if it can show page or not and only show if that is true.
const [canShow, setCanShow] = useState(false);
Then set it to true if all the requirements are met.
const fetchData = async () => {
const result = await axios(
'http://localhost:8000/endpoint/' + some_id + "/",
config
);
if(result.status === 200 && result.data.view === "public")
setCanShow(true)
setMyData(result.data);
};
Then in the return of your function have a check to see if you show the page or a loading screen or whatever you want.
return(
{canShow ? /* display whatever page it should show */ : /* show loading page or whatever else */}
}

Categories