fetch vs. axios - what is the difference in error handling? - reactjs

I have an app where I'm updating the API URL via searchbox and hence displaying certain items based on their name. The name parameter is being updated via onChange event in the input and then useEffect re-renders the list of the items as it observes the URL.
If the name parameters does not match any items, the page should show that no data was found.
Now, the thing is, that the feature works when I'm using fetch API as even if there's 404 code status, the fetchedData state seems to still get updated with the object below:
fetch
and "no data found" can be displayed.
However, when I use axios, fetchedData keeps the latest displayable list of items and I'm not able to show the communicate. Here's what I see in the console:
enter image description here
const [fetchedData, setFetchedData] = useState('');
const [search, setSearch] = useState('');
const { info, results } = fetchedData;
const url = `https://rickandmortyapi.com/api/character/?name=${search}`;
// useEffect(() => {
// (async function () {
// try {
// const response = await axios.get(url);
// console.log(response.data);
// setFetchedData(response.data);
// } catch (err) {
// console.log(err);
// }
// })();
// }, [url]);
useEffect(() => {
(async function () {
const response = await fetch(url);
const data = await response.json();
console.log(data);
setFetchedData(data);
})();
}, [url]);
Does anyone have any idea what's behind it and how that mechanism can be implemented with axios? When working the axios way, I tried to set fetcheData state to null in case of error but this did not work.

Related

react page doesnt render when getting the data back from server

react won't work when rendering the page, but when i changed code in the vscode (added a line of console or comment one line out), the page is rendered. or when page is not rendered. when i hit refresh. i can see some of my content but it won't render. the usestate doesnt seem like to successfully save the value
const ParkDetail = () => {
const [parkId, setParkId] = useState('')
const [park, setpark] = useState('')
const [areas, setAreas] = useState([])
const [ridesName, setridesName] = useState([])
const [rides, setrides] = useState([])
let { id } = useParams()
console.log(id)
useEffect(() => {
async function getPark() {
try {
await setParkId(id)
const res = await axios.get(`/parks/details/${parkId}`)
console.log(res)
const park1 = await res.data.park
console.log(park1)
await setpark(park1)
console.log(park)
await setAreas(park1.serviceAnimalRelief)
// await setridesName(park1.topRides)
// console.log(ridesName)
} catch (e) {
console.log(e.message)
}
}
getPark()
}, [parkId])
}
I believe the problem is you using the state as parameters in your get requests. Since state setter functions do not return promises, using await on them is basically of no use. You should rather use the variables that you obtain from the get requests directly. For example, use id instead of parkId and so on.

How to display custom Nextjs error page when api call fails?

I created a custom Nextjs error page that I would like to display when the api call fails. What is currently happening is even if the api call fails, it still displays the same page as a successful route. For example, I have a route that is companies/neimans that pulls data from an api to display certain text on the page. If I type, companies/neiman I want my custom error page to show, but it is displaying the same page as if going to companies/neimans just without the data coming from the api. I do get a 404 in the console when visiting a url that is invalid but it doesn't display the custom error page or the default next js 404 page.
In my file system I have a pages directory and inside that a directory called companies with a file [companydata].tsx and one called 404.tsx. [companydata].tsx is the page that dynamically displays information about the company from the api.
This is what my api call currently looks like:
export const getCompanies = async (routeData: string): Promise<Company> => {
const client = getApiClient();
const response = await client.get<Company>(`api/companies/${routeData}`);
if (response) {
return response.data;
}
return {} as Company;
In the [companydata].tsx, I tried to do a check if the object was empty to then redirect to companies/404 but doing so makes it always display the 404 page.
if (Object.keys(company).length === 0) {
return <Redirect to="/company/404"/>;
}
If I console.log the company, it is rendering multiple times. The first 6 times, it is an empty array so that would explain why the 404 page is always showing. The data doesn't come through until after the 6th render. I am not sure why that is.
I am calling getCompanies inside another function,
export const getData = async (companyName: string): Promise<[Company, Sales]> => {
if (companyName) {
return (await Promise.all([getCompanies(companyName), getSales()])) as [
Company,
Sales
];
}
return [{} as Company, {} as Sales];
};
I am calling getData inside a useEffect within [companydata].tsx.
const Company: NextPage = (): JSX.Element => {
const [selectedCompany, setSelectedCompany] = useState<Company>({} as Company);
const [salesAvailable, setSalesAvailable] = useState<boolean>(false);
const [sales, setSales] = useState<Sales>({} as Sales);
const router = useRouter();
const {companydata} = router.query;
useEffect(() => {
const init = async (companyName: string) => {
const [companyData, salesData] = await getData(companyName);
if (companyData) {
setSelectedCompany(companyData);
}
if (salesData) {
setSalesAvailable(true);
setSales(salesData);
} else {
setSalesAvailable(false);
}
}
};
init(companydata as string);
};
}, [companydata]);
// returning company page here
You currently do not have a method to check the status of the API call. There are four possible outcomes of most API calls - data, no data, error, and loading. You should add the status checks in your API calls
Below are two examples of how this can be achieved.
get companies hook
export const useGetCompanies = async (path: string) => {
const [data, setData] = useState<Company>();
const [error, setError] = useState(false);
const [loading, setLoading] = useState(false);
try {
setError(false);
setLoading(true);
const client = getApiClient();
const response = await client.get(`api/companies/${path}`);
setData(response.data);
} catch (error) {
setError(true);
} finally {
setLoading(false);
}
return {data, error, loading};
};
Since your data isn't related you also do a generic API fetch call something like
export async function useFetchData<T>(path:string){
const [data, setData] = useState<T>();
const [error, setError] = useState(false);
const [loading, setLoading] = useState(false);
try {
setError(false);
setLoading(true);
const client = getAPIClient();
const response = await client.get<{ data: T }>(path);
if(response) setData(response.data);
} catch (error) {
setError(true);
} finally {
setLoading(false);
}
return { data, error, loading };
};
Example use.
const Company = async () =>{
const { query } = useRouter();
const company = await useFetchData<Company>(`api/companies/${query.companydata}`);
const sales = await useFetchData<Sales>(`api/companies/${query.companydata}/sales`);
if (company.loading || sales.loading) return <p>Loading...</p>;
if (company.error || sales.error) return <p>Error or could show a not found</p>;
if (!company.data || !sales.data) return <Redirect to="/company/404"/>;
return "your page";
}
It would be best to render the data independently of each other on the page and do the if checks there. This is beneficial because you don't have to wait for both calls to complete before showing the page.
I'd create two separate components (company and sales) and place the corresponding API call in each.
Typically assigning empty objects ({} as Company or {} as Sales) to defined types is bad practice because it makes TS think the object's values are defined when they are not - defeating the purpose of using TS.
They should be left undefined, and there should be a check to see if they are defined.
Lastly, I can't test the code because I don't have access to the original code base so there might be bugs, but you should get a pretty good idea of what's happening.

Handle fetch api errors in useEffect and set error

I have useEffect in one of my components that fetches data from an api.
I wanna handle two types of errors -
Api is unavailable (say server is down)
Request made is invalid (request made to incorrect endpoint)
And, call setError when any of these errors happens.
This is how I solved it -
const [error, setError] = useState(null)
useEffect(() => {
const fetchModels = async () => {
let res
try {
res = await fetch('http://localhost:8000/api/models/')
}
catch (e) {
if (e instanceof (TypeError)) {
setError('Could not fetch data. Please try again')
return
}
}
if (!res.ok) {
setError('Could not fetch data. Please try again')
return
}
const data = await res.json()
setModels(data)
setError(null)
}
fetchModels()
}, [])
Even though this works, I really believe there must be a better way to do what I am trying to accomplish.

Issue with MERN stack when data is coming back from Node

I am using the Fetch API to post data to my Node server. There is behaviour that I am seeing which I am unable to understand and need some help in resolving. When the user clicks a button a modal opens up with a form with a few fields. On hitting submit, the data is passed to a node server. First it creates a new record for Mongo and saves the data in the collection. After saving I want to fetch some data from a few other collections and then send the information back to my react app.
Here is what I am seeing.
If i only save the data and don't do the fetching part and send back a message like this
res.status(201).json({"message":"data received"})
Then after the modal closes in my console I see the message "Data Received".
However if I do the fetch part, which basically means a few more milli seconds of delay, then once the modal closes, this message flashes in the react console.
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
DOMException: The user aborted a request.
Here is my submit handler in react
const submitHandler = async(event) => {
event.preventDefault()
try {
const responseData = await sendRequest('http://localhost:5000/challenge/createChallenge', 'POST',
JSON.stringify({
location: selectedOption,
format: selectedOption1,
date: selectedDate.toString(),
opponent: props.initialValues,
month: cMonth,
year: cYear,
player: auth.profile.MemberName
}),
{
'Content-Type': 'application/json',
Authorization: 'Bearer ' + auth.token
})
console.log(responseData)
} catch (err){
console.log(err)
}
}
And here is my http-hook, which is a custom hook I am using
import { useState, useCallback, useRef, useEffect } from 'react'
export const useHttpClient = () => {
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState(false)
const activeHttpRequests = useRef([])
const sendRequest = useCallback(async (url, method = 'GET', body = null, headers = {}) => {
setIsLoading(true)
const httpAbortCtrl = new AbortController()
activeHttpRequests.current.push(httpAbortCtrl)
try {
const response = await fetch(url, {
method,
body,
headers,
signal: httpAbortCtrl.signal
})
const responseData = await response.json()
activeHttpRequests.current = activeHttpRequests.current.filter(reqCtrl => reqCtrl !== httpAbortCtrl)
if(!response.ok){
throw new Error(responseData.message)
}
setIsLoading(false)
return responseData
} catch (err){
setError(err.message)
setIsLoading(false)
throw err
}
}, [])
const clearError = () => {
setError(null)
}
useEffect(() => {
return () => {
activeHttpRequests.current.forEach(abortCtrl => abortCtrl.abort())
}
}, [])
return { isLoading, error, sendRequest, clearError}
}
Can some one please help. I am not sure how to go about resolving this. I checked the reasons for this error, while some of the content on the internet gave me some clue. But I am not clear enough to resolve this.

Get type error cannot destructure property as it is undefined in React

I have a MongoDB collection and inside this collection, there are some documents. In these documents, I stored some IDs of another collection documents. This is an image of this document.
In the frontend, I access this document and get the postId. I tried this way.
const onePostId=posts.postId
console.log(onePostId);
const type=typeof (onePostId);
console.log(type);
This code part gives me this result.
I try to pass this postId to an API const response = await axios.get(`/buyerGetOnePost/${onePostId}`) like this way. But this postId is a string type I think that's why I can't get results from this API. Then I try like this const {onePostId}=posts.postId then I get an error that says "TypeError: Cannot destructure property 'onePostId' of 'posts.postId' as it is undefined". How do I solve this problem?
This is the complete code that I tried.
function PostsLocation() {
const { offerId } = useParams();
console.log(offerId);
const [posts, setPosts] = useState({});
useEffect(()=>{
getOnePost();
}, []);
const getOnePost = async () => {
try {
const response = await axios.get(`/buyerGetOneSellerOffer/${offerId}`)
console.log(response);
const allPost=response.data.oneOffer;
setPosts(allPost);
} catch (error) {
console.error(`Error: ${error}`)
}
}
console.log(posts);
const onePostId=posts.postId
console.log(onePostId);
const type=typeof (onePostId);
console.log(type);
const [offerPosts, setOfferPosts] = useState({});
useEffect(()=>{
getOneOfferPost();
}, []);
useEffect(()=>{
if (offerPosts && offerPosts.location) {
console.log(offerPosts.location);
console.log(offerPosts.location.longitude);
console.log(offerPosts.location.latitude);
}
}, [offerPosts]);
const getOneOfferPost = async () => {
try {
const response = await axios.get(`/buyerGetOnePost/${onePostId}`)
console.log(response);
const allOfferPost=response.data.onePost;
setOfferPosts(allOfferPost);
} catch (error) {
console.error(`Error: ${error}`)
}
}
console.log(offerPosts);
const long = offerPosts?.location?.longitude;
console.log(long);
const lat=offerPosts?.location?.latitude;
console.log(lat);
const location={lat,long};
}
Below image shows the results after console.log(posts).
You are trying to destructure a value without getting the data first. So, you are getting a error.
You are running the below code for posts at starting when it doesn't have any data
const onePostId=posts.postId
and only after the call to
getOnePost();
your data gets filled but there is a time delay which you should always consider with async await syntax and you should first check if posts contain posts.postId with some value or if defined and then do the destructuring. If it doesn't then probably don't use it either wait for it or return loading or return mock value probably.
eg:
if(!posts.postId){
// posts.postId is not set and it doesn't have data
}

Resources