how to refactor duplicate API calls into a single API call? - reactjs

I am pretty new to building full-stack applications, and I could like to avoid duplicating code in order to build the following to perform the calls in react my endpoints can be called like the following /api/v1/feeds/list/?page=${page} or api/v1/feeds/list/?search=${query} , but I would like to joing ?page=${page}&?search=${query} since search param is optional . I just want to make a single api call
async function fetchFeed(page) {
return api.get(`http://localhost:8001/api/v1/feeds/list/?page=${page}`);
}
async function searchQuery(query) {
return api.get(`http://localhost:8001/api/v1/feeds/list/?search=${query}`);
}
const Main = () => {
const [currentPage, setCurrentPage] = useState(1);
const [feed, setFeed] = useState([]);
const [feedCount, setfeedCount] = useState(0);
const [visible, setVisible] = useState(3)
const showMoreItems = () => {
setVisible(prevValue => prevValue + 3);
}
const browse = (page) => {
fetchFeed(page)
.then(function(response){
setfeedCount(response.data.count)
setFeed(response.data.results)
})
.catch(function(error){
console.log(error);
});
}
// fetches data
const fetchData = (search) => {
searchQuery(search)
.then((response) => {
setFeed(response.data.results)
})
.catch((error) => {
console.log(error);
});
};
const handleSearch = (e) =>{
fetchData(e.target.value);
}
useEffect(() => {
browse(currentPage)
fetchData(feed);
}, [currentPage]);
}

I'd pass an object with both page and query, which both default to the empty string - and if empty, don't include them in the fetched URL:
async function fetchFeed({ page = '', query = '' }) {
return api.get(`http://localhost:8001/api/v1/feeds/list/?${page ? `page=${page}&` : ''}${query ? `search=${query}` : ''}`);
}
If possible, make your API accept empty query parameters too, allowing you to simplify to
return api.get(`http://localhost:8001/api/v1/feeds/list/?page=${page}&query=${query}`);

Something like this should work for you
const fetchFeed = async (page, query) => {
let url =`http://localhost:8001/api/v1/feeds/list/?page=${page}`
if(query) url += `?search=${query}`
return api.get(url)
}
const browse = (page search) => {
await fetchFeed(page search)
.then(function(response){
!search && setfeedCount(response.data.count)
setFeed(response.data.results)
})
.catch(function(error){
console.log(error);
});
}
useEffect(() => {
browse(currentPage) // just pass page
browse(currentPage, searchQuery); // pass both page and search query
}, [currentPage]);

Related

Concatenate an array with 2 different useEffect calls react

I am trying to make two different api calls -- get the array results and then concatenate them together into 1 array, which I then present in the return.
I am having difficulty with :
a) There are two calls so the array is populated 2x or
b) The data is not persisted so I lose it between calls.
The goal is to make 2 calls to the api (each once) and concatenate the arrays together into 1 result -- which is cleared or emptied between page refresh. So it is not called more than once ... or is not continuously lengthened.
My code is as follows:
const [results, setResults] = useState([]);
useEffect(() => {
getStories();
getContributions();
}, []);
const getStories = async () => {
const { data } = await axios.get(`/api/stories/admin`); // get the data
setResults((currentArray) => [...currentArray, ...data]);
};
const getContributions = async () => {
const { data } = await axios.get(`/api/contributions/admin`);
setResults((currentArray) => [...currentArray, ...data]);
};
useEffect(() => {
async function loadBootstrap() {
const bootstrap = await import(
"../../../node_modules/bootstrap/dist/js/bootstrap"
);
var tooltipTriggerList = [].slice.call(
document.querySelectorAll('[data-bs-toggle="tooltip"]')
);
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl);
});
}
loadBootstrap();
}, []);
return (<> {JSON.stringify(results)}
</>);
You should fetch them both and then set the result once.
Something like the following:
useEffect(() => {
getStoriesAndContributions();
}, []);
const getStoriesAndContributions = async () => {
const [{ data: stories }, { data: contributions }] = await Promise.all([
axios.get(`/api/stories/admin`),
axios.get(`/api/contributions/admin`),
]);
setResults([...stories, ...contributions]);
};
The Promise.all() is not really needed, just in order to make it parallel.
I'm not sure I quite get what you mean when listing the problems, but why not simply do:
const loadArrayElements = async () => {
const { data: stories } = await axios.get(`/api/stories/admin`);
const { data: contributions } = await axios.get(`/api/contributions/admin`);
setState([...stories, ...contributions]);
}
useEffect(() => void loadArrayElements(), []);
What you can do is fetch and store the two data sources in javascript variables and call the setState one time in the useEffect :
useEffect( async () => {
fetchData()
}, []);
const fetchData = async () => {
const { data:stories } = await axios.get(`/api/stories/admin`);
const { data:contributions } = await axios.get(`/api/contributions/admin`);
setResults([...stories, ...contributions])
}

Problem occur when use array of objects in filter function React

I try to get data from the backend and view data in the frontend. To do this I try this code.
function ViewPost() {
const { postId } = useParams();
console.log(postId);
const [posts, setPosts] = useState({});
useEffect(() => {
getOnePost();
}, []);
const getOnePost = async () => {
try {
const response = await axios.get(`/buyerGetOnePost/${postId}`);
console.log(response);
const allPost = response.data.onePost;
setPosts(allPost);
} catch (error) {
console.error(`Error: ${error}`);
}
};
console.log(posts);
console.log(posts.wasteItemList);
const [offers, getOffers] = useState([]);
useEffect(() => {
getAllOffers();
}, []);
const getAllOffers = async () => {
await axios
.get(`/viewPendingSellerOffers`)
.then((response) => {
const allNotes = response.data.existingOffers;
getOffers(allNotes);
})
.catch((error) => console.error(`Error: ${error}`));
};
console.log(offers);
const wasteItem = offers?.filter(
(wasteItem) =>
wasteItem.status === "accepted" &&
wasteItem.wasteItemsListId === posts?.wasteItemList?._id,
);
console.log(wasteItem);
}
I call the first API and get a specific post data and this post has an array of objects called wasteItemList. When I use this code console.log(posts.wasteItemList), I get length 2 array of objects. This is an image of this result.
Then I call the second API and get length 8 array of objects. This is an image of this result.
Then I try to filter data using this code const wasteItem = offers?.filter(wasteItem => wasteItem.status==='accepted' && wasteItem.wasteItemsListId===posts?.wasteItemList?._id). But this filter function give an empty array. What is the reason for this problem? How do I solve this?
As I mentioned in the comments, your naming seems a little off. A function that supposedly gets one post assigns to a state atom that's plural, and your setter for offers is getOffers.
Here's a simplification/rewrite of your component that assumes post is supposed to be singular and offers is in plural. Also, you were missing the data dependency postId for the useEffect.
In addition, since wasteItem is singular, I assume you want the first matching offer, not all of them, so .find() is the thing.
function ViewPost() {
const { postId } = useParams();
const [post, setPost] = useState(undefined);
const [offers, setOffers] = useState(undefined);
useEffect(() => {
setPost(undefined);
axios
.get(`/buyerGetOnePost/${postId}`)
.then((resp) => setPost(resp.data.onePost))
.catch((err) => console.error(err));
}, [postId]);
useEffect(() => {
axios
.get(`/viewPendingSellerOffers`)
.then((response) => setOffers(response.data.existingOffers))
.catch((err) => console.error(err));
}, []);
if (post === undefined || offers === undefined) {
return <>Loading...</>;
}
const wasteItem = offers.find(
(wasteItem) =>
wasteItem.status === "accepted" &&
wasteItem.wasteItemsListId === post.wasteItemList?._id,
);
return (
<div>
<div>Post: {JSON.stringify(post)}</div>
<div>Offers: {JSON.stringify(offers)}</div>
<div>Waste Item: {JSON.stringify(wasteItem)}</div>
</div>
);
}

Multiple nested axios calls don't resolve as expected

As described in comments between my code snippet, the asynchronicity is not working as expected. For each id, an object/item should return but it only returns one item since my async await isn't implemented properly. What could be a possible workaround?
Thanks in advance
useEffect(() => {
axios.get('url-here').then((res) => {
res.data.favProperties?.map((el) => {
console.log(el) // this returns multitple id's of saved/liked items
axios.get('url-here').then(async (r) => {
if (r.data) {
console.log(r.data) // Problem starts here
// This returns the full object of the liked items
// But only one object is returned, not every object for which an id was stored
await storageRef
.child(r.data.firebaseRef + '/' + r.data.images[0])
.getDownloadURL()
.then((url) => {
// Here i need to fetch the image for each object
console.log(url)
})
.catch((err) => console.log(err))
}
})
})
})
}, [])
I think breaking down your operations into functions will prevent this Promise Hell. I would recommend using async await for these kinda operations. Also I was confused about the last part of console logging the download URL, by my guess you're trying to save all the download URLs for these liked items in an array.
useEffect(() => {
firstFunction();
}, []);
const firstFunction = async () => {
const { data } = await axios.get("url-here");
const favProperties = data.favProperties;
const fetchedUrls = await Promise.all(
favProperties?.map(async (el) => (
await secondFunction(el.id) /** use el to pass some ID */
))
);
};
const secondFunction = async (someId) => {
/** your second URL must point to some ID (or some parameters) specific API otherwise
running same op in a loop without variations doesn't make any sense */
const { data } = await axios.get(`some-other-url/${someId}`);
if (data) {
console.log(data);
const fetchedUrl = await storageThing(data);
return fetchedUrl;
}
};
const storageThing = async ({ firebaseRef, images }) => {
try {
const downloadURL = await storageRef
.child(firebaseRef + "/" + images[0])
.getDownloadURL();
console.log(downloadURL);
return downloadURL;
} catch (error) {
console.log(error);
return '';
}
};

how do I perform conditial query params in axios?

I am trying to build a conditional dynamic react component where makes an API call based on the user interaction, but if the user types something in the search bar. I want to add search= param the otherwise use /list endpoint without query params. I am using currently Axios , and I would like to know some approach to do the following
const FeedsList = () => {
const [feed, setFeed] = useState([]);
const [currentPageUrl, setCurrentPageUrl] = useState("http://localhost:8001/api/v1/feeds/list/")
const performSearch = () => {
//setLoading(true)
api.get(currentPageUrl).then(res => { // axios call
setLoading(false)
setFeed(res.data.results)
}).catch(function(error){
console.log(error);
});
}
const handleSearch = (e) =>{
console.log(e.target.value)
//performSearch();
}
useEffect(() => {
performSearch()
}, [currentPageUrl]);
if (loading) return "Loading..."
}
export const api = axios.create(
{baseURL : 'http://localhost:8001/api/v1/feeds/list/'}
)
user input
<input type="text" placeholder="Enter keyword" onChange={event => handleSearch(event)}/>
Store user input to state, not URL, and then construct your URL from initial value (list) and user input, if any:
const FeedsList = () => {
const [loading, setLoading] = useState(false);
const [feed, setFeed] = useState([]);
const [searchString, setSearchString] = useState("");
const performSearch = (searchString) => {
setLoading(true);
let url = "http://localhost:8001/api/v1/feeds/list/";
// you might want to escape this value, and sanitize input on the server
if (searchString) url = `${url}?search=${searchString}`;
const cancelTokenSource = axios.CancelToken.source();
return api
.get(url, { cancelToken: cancelTokenSource.token })
.then((res) => {
setLoading(false);
setFeed(res.data.results);
})
.catch(function (error) {
console.log(error);
});
return cancelTokenSource;
};
const handleSearch = (event) => {
setSearchString(event.target.value);
};
useEffect(() => {
let token = performSearch(searchString);
return () => token.cancel();
}, [searchString]);
if (loading) return "Loading...";
};
You might want to debounce or throttle requests, so you will not bombard your server with requests on each keystroke
The Axios api allows for passing a second parameter to the get method which is the config for the request being sent. That config object takes a params property that would be parsed and appended onto the url as a query string. The nice thing is if the params object is empty it will be like not passing anything at all. A fuller example here on their GitHub page.
Passing an empty params object is the same as passing no params object at all in terms of what is requested.
// both lines request url looks like this => https://jsonplaceholder.typicode.com/users
axios.get('https://jsonplaceholder.typicode.com/users')
axios.get('https://jsonplaceholder.typicode.com/users', { params: {} })
To answer your question you could conditionally just create the params based on whether there is a value from the search input, something like the following:
const performSearch = () => {
const config = search === '' ? {
params: {}
} : {
params: { search } // same as { search: search }
}
api.get(currentPageUrl, config).then(res => {
// do something with response
}).catch(function(error){
console.log(error);
});
}
An assumption in the above would be that you stored the search value in state somewhere and add it to your useEffect dependencies list, referencing it in performSearch.

React Native - state is returning null after setting state

I'm very much new to react native currently i'm building small app for just getting an idea about this. I'm facing an issue in mapping the data from API. This is the json response returning from the api
{"data":[{"digit":300,"countsum":"52"},{"digit":301,"countsum":"102"},{"digit":302,"countsum":"27"},{"digit":303,"countsum":"201"},{"digit":500,"countsum":"101"}]}
When i tried to map this data i'm facing some issues. I stored the response from API to the state and when i tried to display the state data using map function it's showing the state value is null. This the code i tried till now
const [listdata, setListData] = useState(null)
useEffect(() => {
// Run! Like go get some data from an API.
getListData();
}, []);
const getListData = async () => {
const token = await AsyncStorage.getItem("#userToken")
axios
.get(constants.BASE_URL + "getlist?token=" +token)
.then(response => setListData(response.data))
.catch(error => {
console.log(error);
});
listdata.map(item => <Text>{item.digit}</Text>)
}
Do it like this,
export default function ComponentName () {
const [listdata, setListData] = useState([])
useEffect(() => {
// Run! Like go get some data from an API.
getListData();
}, []);
const getListData = async () => {
const token = await AsyncStorage.getItem("#userToken")
axios
.get(constants.BASE_URL + "getlist?token=" +token)
.then(response => setListData(response.data))
.catch(error => {
console.log(error);
});
}
return (<>
listdata.map(item => <Text>{item.digit}</Text>)
</>
);
}
You have to wait the fetch execution and later do the list map.
// wait for it
await axios
.get(constants.BASE_URL + "getlist?token=" +token)
.then(response => setListData(response.data))
.catch(error => {
console.log(error);
});
listdata.map(item => <Text>{item.digit}</Text>)
If you want to map the data then do that inside return statement of your code ,like:
return(
{listData?listdata.map(item => return <Text>{item.digit}</Text>):""}
);
This is a sample of a meant in my comment above:
Try console.log listdata at this stage, you will find that it is still
null, in other words, the value of the updated value of the
listdata:useSate will be ready after the render take place. You can
make another function outside of the current one. then use useEffect
with listdata to update your text views
const [listdata, setListData] = useState(null)
useEffect(() => makeRemoteRequest(), [listdata])
makeRemoteRequest = () => {
const url = `your-url-of-data-here`;
fetch(url)
.then(res => res.json())
.then(res => {
setListData(res.data);
})
.catch(error => {
console.log(error)
});
};
You could try the following:
const [listdata, setListData] = useState([])
useEffect(() => {
// Run! Like go get some data from an API.
getListData();
}, []);
const getListData = async () => {
const token = await AsyncStorage.getItem("#userToken")
try {
const dataResponse = await axios.get(constants.BASE_URL + "getlist?token=" +token);
setListData(dataResponse.data || [] );
} catch(error) {
console.log(error);
}
}
return (<>
listdata.map(item => <Text>{item.digit}</Text>)
</>);

Resources