I am working on a weather app and need to properly handle a 404 response from the server. There are 2 API requests made with the second one needing data from the first one.
I basically want to render "location does not exist" when there is a 404 error response. An attempt was made with try..catch which resulted in this issue: Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'coord').
Error happens for both success and failure responses.
Questions:
What does this error mean and how can I properly de-structure coord prop?
How can I properly setup try..catch to handling error response?
Bonus question: how can try..catch be made inside getForecastData function as well?
Here is the useForecast.js file containing logic and API calls:
try...catch attempt was made in getCoordinates function
import axios from "axios";
const BASE_URL = "https://api.openweathermap.org/data/2.5";
const API_KEY = process.env.REACT_APP_API_KEY;
const useForecast = () => {
// const [forecast, setForecast] = useState(null)
// const [isError, setError] = useState(false)
const getCoordinates = async (location) => {
try {
//try statement
const { data } = await axios(`${BASE_URL}/weather`, {
params: { q: location.value, appid: API_KEY }
});
console.log("call is successful", data);
} catch (data) {
//catch statement
if (!data.ok) {
console.log("location does not exist", data.message);
return;
}
return data;
}
};
const getForecastData = async (lat, lon) => {
const { data } = await axios(`${BASE_URL}/onecall`, {
params: { lat: lat, lon: lon, appid: API_KEY }
});
//if no data is not returned, call setError("Something went wrong") and return
return data;
};
const submitRequest = async (location) => {
const response = await getCoordinates(location);
const { lat, lon } = response.coord;
if (!response || !lat || !lon) return;
console.log("getCoordinates call will render", { response });
const data = await getForecastData(lat, lon);
if (!data) return;
console.log("getForecastData call will render", { data });
};
return {
submitRequest
};
};
export default useForecast;
Here is a stripped down version of the app(where screen shots were generated from): https://codesandbox.io/s/practical-pare-uc65ee?file=/src/useForecast.js
Note: API key has been removed for privacy reasons(sorry for the inconvenience)
Lastly, for context I am using the follow with React in app:
OpenWeather API: https://openweathermap.org/
Axios: https://github.com/axios/axios
You're catching the error successfully. The problem is that when it happens, you are not returning any value to
const response = await getCoordinates(location);
response will then be undefined, and coord will therefore trigger the error since undefined values can't hold any property.
To fix it, you can use the classic safety as below:
const response = await getCoordinates(location) || {};
Which essentially will make response always an object, successful or not
In addition to suggestions from #Houssam and #ale917k adjustments also had to be made with conditionals in submitRequest.
All adjustments made were:
placing return data inside try block
appending || {} to response
changing first if statement to if(!response.coord) then de-structure lat and lon.
Codebase with changes:
import axios from "axios";
const BASE_URL = "https://api.openweathermap.org/data/2.5";
const API_KEY = process.env.REACT_APP_API_KEY;
const useForecast = () => {
// const [forecast, setForecast] = useState(null)
// const [isError, setError] = useState(false)
const getCoordinates = async (location) => {
try {
const { data } = await axios(`${BASE_URL}/weather`, {
params: { q: location.value, appid: API_KEY }
});
console.log("call is successful", data);
//adjustment 1
return data;
} catch (data) {
if (!data.ok) {
console.log("location does not exist");
return;
}
}
};
const getForecastData = async (lat, lon) => {
try {
const { data } = await axios(`${BASE_URL}/onecall`, {
params: { lat: lat, lon: lon, appid: API_KEY }
});
return data;
} catch (data) {
if (!data.ok) {
console.log("something went wrong");
return;
}
}
};
const submitRequest = async (location) => {
const response = (await getCoordinates(location)) || {}; //adjustment 2
//adjustment 3
if (!response.coord) return;
const { lat, lon } = response.coord;
const data = await getForecastData(lat, lon);
if (!data) return;
};
return {
submitRequest
};
};
export default useForecast;
Screenshot of success and failure logs:
Related
I'm trying to update user profile using react native expo I can only update all properties except image is giving me this Error :
[Unhandled promise rejection: FirebaseError: Function DocumentReference.update() called with invalid data. Unsupported field value: undefined (found in field userImg in document users ? please help
const [image, setImage] = useState(null);
const [uploading,setUploading] = useState(false)
const [ userData, setUserData] = useState(null);
useEffect(()=>{
const getUserData = async ()=>{
db.collection("users")
.doc(auth.currentUser?.uid)
.get()
.then(snap => {
setUserData(snap.data());
});
}
getUserData();
},[])
const updateProfile = async()=>{
let imgUrl = await uploadImage();
if(imgUrl == null && userData.userImg){
imgUrl = userData.userImg
}
db.collection("users")
.doc(auth.currentUser.uid)
.update({
name: userData.name,
userName: userData.userName,
email: userData.email,
phone: userData.phone,
address: userData.address,
userImg:userData.mgUrl
})
}
I can upload the image successfully but I can't fetch it from storage to fire store
const uploadImage = async ()=>{
if(image == null){
return null;
}
const blob = await new Promise((resolve, reject)=>{
const xhr = new XMLHttpRequest();
xhr.onload = function (){
resolve(xhr.response)
};
xhr.onerror = function (){
reject( new TypeError("Network request failed"))
};
xhr.responseType = "blob"
xhr.open("GET",image,true)
xhr.send(null)
});
const ref = firebase.storage().ref().child("images/" + new Date().toISOString())
const snapshot = ref.put(blob)
snapshot.on(
firebase.storage.TaskEvent.STATE_CHANGED,
()=>{
setUploading(true)
},
(error)=>{
setUploading(false)
console.log(error)
blob.close();
return;
},
()=>{
snapshot.snapshot.ref.getDownloadURL().then((url)=>{
setUploading(false);
// Alert.alert('Profile Updated', 'You profile Updated Successfully..!')
console.log('donwload:', url)
setUserData(url)
blob.close()
return null
})
}
)
}
}
so please help me out between I'm using React Native Expo and thank you so much
To start off, the error [Unhandled promise rejection: FirebaseError: Function DocumentReference.update() called with invalid data. Unsupported field value: undefined means that one or more fields has a null value but firebase doesn't allow you to store that.
In your uploadImage function you're able to upload your image fine when the image actually does exist but in cases that it doesn't you're returning null which is where the problem is. Ideally, you can return an empty string which is safe then in cases where you read the image you can just check if the string is empty or not.
Fix
Step 1
Change this
if(image == null){
return null;
}
To this
if(image == null){
return "";
}
Step 2
After you get the download URL your setUserData is replacing all the fields with the URL so consider changing it to
`
setUserData({...userData, imgUrl : url})
Step 3
Also realize that in your update() there is a typo for imgUrl change from
userImg:userData.mgUrl to userImg:userData.imgUrl to properly set the image using the step for line
Hope that fixes It :)
`
Check if below code helps you to upload a Video or Image using firebase.
const uploadImageToFirestore = async (res, type) => {
const uri = res.assets[0].uri;
const filename = uri.substring(uri.lastIndexOf('/') + 1);
const uploadUri = Platform.OS === 'ios' ? uri.replace('file://', '') : uri;
const storage = getStorage(app);
const fileRef = ref(storage, filename);
const img = await fetch(uploadUri);
const bytes = await img.blob();
let metadata;
if (type == 'video') {
if (filename.includes("mov") || filename.includes("MOV")) {
metadata = {
contentType: 'video/quicktime'
}
} else {
metadata = {
contentType: 'video/mp4',
};
}
} else {
metadata = {
contentType: 'image/jpeg',
};
}
uploadBytes(fileRef, bytes, metadata).then(async (uploadTask) => {
console.log('task', uploadTask)
getDownloadURL(uploadTask.ref).then((url) => {
if (type == 'video') {
setVideoData(url);
} else {
setImageData(url);
}
});
}).catch((err) => {
alert('Error while uploading Image!')
console.log(err);
});
}
So i have this function that i want to run once when the app start. This function task is to create userId then i will run another function to fetch data from firebase with the userId that created before. But the fetch function didn't start or it didnt do the task well, there is no sign of error, that's what make it more confusing. If i press the fetch function by button it work correctly.
the state
const [task, setTask] = useState(); // bisa di sebut sebagai controller text input
const [taskItems, setTaskItems] = useState([]); // state untuk list task
const [userId, setUserId] = useState();
const [isLoading, setIsLoading] = useState(true);
const baseUrl =
'https://react-http-post-RANDOM_KEY-default-rtdb.firebaseio.com/task/' + userId;
this is function to create userId function on init app
const handleCreateUser = async () => {
setIsLoading(true);
try {
const value = await AsyncStorage.getItem('userId');
if (value !== null) {
setUserId(value);
} else {
const uniqueId = makeid(6);
await AsyncStorage.setItem('userId', 'user' + uniqueId);
setUserId('user' + uniqueId);
}
await fetchDatabase();
} catch (error) {
console.log('errorrr AsyncStorage' + error);
}
setIsLoading(false);
};
this is function to fetch data from firebase
const fetchDatabase = async () => {
console.log('infinite looping');
try {
const response = await fetch(baseUrl + '.json');
if (!response.ok) {
throw new Error('Something went wrong!');
}
const data = await response.json();
// looping Map/Object dengan key sebagai indexnya
const loadedTask = [];
for (var id in data) {
loadedTask.push({
key: id,
text: data[id].text,
isComplete: data[id].isComplete,
});
}
setTaskItems(loadedTask);
} catch (error) {
setError(error.message);
}
};
this is how i call the useEffect
useEffect(() => {
handleCreateUser();
}, []);
The first thing I see is that you are not using await correctly. It should be before fetchDatabase(); function that is inside handleCreateUser like so:
await fetchDatabase();
The word await is there when you have to call an asynchronous function and you have to wait for this function to be completed.
Edit
To use only one useEffect you can check if your fetch function received your data by:
// or whatever statusCode you get when the data are present
if(reponse.statusCode === 200) {
// the await is not needed because it is present for the reponse abov
const data = response.json();
// looping Map/Object dengan key sebagai indexnya
const loadedTask = [];
for (var id in data) {
loadedTask.push({
key: id,
text: data[id].text,
isComplete: data[id].isComplete,
});
}
setTaskItems(loadedTask);
}
i got the answer, by using 2 useEffect
useEffect(() => {
handleCreateUser();
}, []);
useEffect(() => {
fetchDatabase();
}, [userId]);
I'm trying to update some code, taking into account new sdk versions. I have the new api call in one file:
import { CognitoIdentityProviderClient, ListUsersCommand } from "#aws-sdk/client-cognito-identity-provider";
import awsmobile from "../../aws-exports";
import { Auth } from "aws-amplify";
export default async function ListUsers() {
await Auth.currentCredentials().then((data) => {
const client = new CognitoIdentityProviderClient({
region: awsmobile.aws_project_region,
credentials: data
});
const params = {
UserPoolId: awsmobile.aws_user_pools_id
};
const command = new ListUsersCommand(params);
client.send(command).then(
(data) => {
return data
},
(error) => {
console.log(error)
}
);
});
}
I'm trying to retrive the data in another file:
import ListUsers from "../../../API/cognito/ListUsers";
import ListUsersInGroup from "../../../API/cognito/ListUsersInGroup";
import { useState, useEffect, useRef } from "react";
import PortalUsersTable from "../../../components/tables/PortalUsersTable";
export default function ManageUsers() {
const [userDetails, setUserDetails] = useState("");
const refUsers = useRef();
const refUsersExec = useRef();
const refUsersAdmin = useRef();
const refUsersGroups = useRef();
useEffect(() => {
function getUsers() {
refUsers.current = ListUsers();
refUsersExec.current = ListUsersInGroup("usersAdmin");
refUsersAdmin.current = ListUsersInGroup("usersExec");
//setUsersTloOfficers(apiTloOfficers);
refUsersGroups.current = ListUsersInGroup("usersGroups");
let userData = [];
let arrUsersExec = [];
for (let a in refUsersExec.current.Users) {
arrUsersExec.push(refUsersExec.current.Users[a].Username);
}
let arrUsersAdmin = [];
for (let b in refUsersAdmin.current.Users) {
arrUsersAdmin.push(refUsersAdmin.current.Users[b].Username);
}
let arrUsersGroups = [];
for (let b in refUsersNtigGroups.current.Users) {
arrUsersGroups.push(refUsersGroups.current.Users[b].Username);
}
for (let i in refUsers.current.Users) {
let email = null;
for (let x in refUsers.current.Users[i].Attributes) {
if (refUsers.current.Users[i].Attributes[x].Name === "email") {
email = refUsers.current.Users[i].Attributes[x].Value;
break;
}
}
let memberExec = arrUsersExec.includes(refUsers.current.Users[i].Username);
let memberAdmin = arrUsersAdmin.includes(refUsers.current.Users[i].Username);
let memberGroups = arrUsersGroups.includes(refUsers.current.Users[i].Username);
userData.push({
id: i,
Username: refUsers.current.Users[i].Username,
AccountStatus: refUsers.current.Users[i].UserStatus,
Email: email,
Users: memberGroups,
Exec: memberExec,
Admin: memberAdmin,
});
}
setUserDetails(userData);
}
getUsers();
}, []);
return (
<>
<h2>Manage Portal Users</h2>
<PortalUsersTable userDetails={userDetails} />
</>
);
}
The logic to handle the API data is sound.
This is the old API call:
import AWS from "aws-sdk";
import awsmobile from "../../aws-exports";
import { Auth } from "aws-amplify";
export default async function ListUsers() {
let idToken = "";
await Auth.currentAuthenticatedUser().then((user) => {
idToken = user.signInUserSession.idToken.getJwtToken();
});
AWS.config.region = awsmobile.aws_cognito_region;
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: awsmobile.aws_cognito_identity_pool_id,
RoleArn: "arn:aws:iam::xxxxxxxxx:role/xxxxxxxxxxxxx",
Logins: { "xxxxxxxxxxxxxxxxxxxx": idToken }
});
let cognitoidentityserviceprovider = new AWS.CognitoIdentityServiceProvider();
let params = {
UserPoolId: awsmobile.aws_user_pools_id,
AttributesToGet: ["email"]
};
return new Promise((resolve, reject) => {
cognitoidentityserviceprovider.listUsers(params, function (err, result) {
if (err) {
console.log(err);
//onError(err);
reject(err);
return;
}
if (result) {
resolve(result);
}
});
});
}
I can see the new API call is returning the correct data in the console. I think I'm not passing the data between files correctly.
I've tried various ways of changing the API call function, reading the cognito sdk description but it's not the API call that is incorrect.
How can I use the API call data in the separate file?
Even if your API call if correct, it looks like you are not returning anything from your function ListUsers. You are mixing async/await pattern with the then. I assume you have added a console.log right before the return data. Refactoring your function using async/await would look like this :
export default async function ListUsers() {
try {
const data = await Auth.currentCredentials();
const client = new CognitoIdentityProviderClient({
region: awsmobile.aws_project_region,
credentials: data,
});
const params = {
UserPoolId: awsmobile.aws_user_pools_id,
};
const command = new ListUsersCommand(params);
const commandData = await client.send(command);
return commandData;
} catch (error) {
console.log(error);
}
}
I'm building an App with Next.js, and I need to connect to specific API routes (set up with API Platform) and populate pages with the route's responses.
The API is working fine, but no matter how I try to implement my Axios call inside the getServerSideProps, I always get the same error, ECONNREFUSED, from my Node stack.
I tried to get the data from useEffect() and it's working fine, but I would like to know if there's a way to call it directly in getServerSideProps.
I'm using a Node container for Docker, and the routes are authenticated through a JWT Token (stored in the session and the client cookies for the server-side connection)
Here are is my code:
pages/accounts.js:
export async function getServerSideProps(context) {
const cookies = new Cookies(context.req.headers.cookie)
const adminToken = cookies.get('jwtToken')
const res = await getAllAccounts(adminToken)
return {
props: {
testdata: ''
},
}
}
lib/accounts.js:
import service from '../service'
export const getAllAccounts = async (adminToken) => {
const res = service({ jwtToken : adminToken }).get(`/accounts`).then((response) => {
}).catch((error) => {
console.dir(error)
})
}
HTTP wrapper:
import axios from 'axios';
import jwt_decode from "jwt-decode";
import mockAdapter from 'axios-mock-adapter';
const service = ({ jwtToken = null, store = null, mockURL = null, mockResponse = null, multipart = false } = {}) => {
const options = {};
options.baseURL = process.env.NEXT_PUBLIC_API_URL + '/api';
if(multipart === true) {
options.headers = {
'Content-Type': 'multipart/form-data'
}
} else {
options.headers = {
'Content-Type': 'application/ld+json',
accept: 'application/ld+json'
}
}
const instance = axios.create(options);
instance.interceptors.response.use(response => {
return response;
}, error => {
return Promise.reject(error);
})
if (mockURL !== null && mockResponse !== null) {
let mock = new mockAdapter(instance);
mock.onAny(mockURL).reply(200, mockResponse)
}
return instance;
};
export default service;
Through the error dump in the node stack, I managed to see that the request headers are correct, and the JWT correctly passed through.
Do not use Axios. Just use fetch().
Next.js polyfills fetch() by default on both the client and server, so you can just use it:
In addition to fetch() on the client-side, Next.js polyfills fetch() in the Node.js environment. You can use fetch() in your server code (such as getStaticProps/getServerSideProps) without using polyfills such as isomorphic-unfetch or node-fetch.
Source.
getServerSideProps works well with axios if we return response.data
export const getServerSideProps: GetStaticProps = async ({ params }) => {
const { brandName } = params as IParams;
const brandData = await $host.get(`api/brand/${brandName}`).then(response => response.data);
return {
props: {
brand: brandData,
},
};
};
Your problem is that your async method does not return a promise.
import service from '../service'
export const getAllAccounts = async (adminToken) => {
const res = service({ jwtToken : adminToken }).get(`/accounts`);
return res;
}
In my NextJS begining I followed this tutorial , and I changed fetch to axios in this way:
export const getStaticPaths = async () => {
const res = await fetch('https://jsonplaceholder.typicode.com/users');
const data = await res.json();
const paths = data.map((ninja) => {
return {
params: { id: ninja.id.toString() },
};
});
return {
paths,
fallback: false,
};
};
export const getStaticProps = async (context) => {
const id = context.params.id;
const res = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);
const data = await res.json();
return {
props: { ninja: data },
};
};
I applied the change using useEffect()
useEffect(() => {
// const data = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);
// const res = await data.json();
// setninja(res);
const fetchData = async () => {
const result = await axios(`https://jsonplaceholder.typicode.com/users/${id}`);
setninja(result.data);
};
fetchData();
console.log(data);
}, []);
I hope this info will be useful for you.
I Used Axios in getServerSideProps without any problems.
export const getServerSideProps: GetServerSideProps = async({
params,
res
}) => {
try {
const response = await axios.get(`/api/test`);
return {
props: {
data: response.data
},
}
} catch {
res.statusCode = 404;
return {
props: {}
};
}
};
I am trying to use a http hook in another component to send a get request. The post request is working fine. But when I try a get request I just get back 'true' when I console log my result. When I send the same get request in postman I get the correct data back, so it isn't a backend problem.
The hook:
import { useState, useCallback, useRef, useEffect } from "react";
export const useHttpClient = () => {
const [isLoading, setIsLoading] = useState(false);
const [errors, setErrors] = useState();
const [success, setSuccess] = useState(false);
const activeHttpRequests = useRef([]);
const sendRequest = useCallback(
async (url, method = "GET", body = null, headers = {}) => {
setIsLoading(true);
const httpAbortController = new AbortController();
activeHttpRequests.current.push(httpAbortController);
try {
setErrors();
setSuccess(false);
const response = await fetch(url, {
method: method,
body: body,
headers: headers,
signal: httpAbortController.signal,
});
const responseData = await response.json();
activeHttpRequests.current = activeHttpRequests.current.filter(
(reqCtrl) => reqCtrl !== httpAbortController
);
if (response.status !== 200) {
setErrors(responseData);
return responseData;
} else {
setSuccess(true);
return true;
}
} catch (err) {
//setErrors(err.message);
setErrors([
"There was an error submitting your form, please try again later.",
]);
setIsLoading(false);
throw err;
}
},
[]
);
//useEffect can also be used for cleanup
useEffect(() => {
return () => {
activeHttpRequests.current.forEach((AbortController) =>
AbortController.abort()
);
};
}, []);
return { isLoading, errors, sendRequest, success };
};
The server call:
useEffect(() => {
const fetchFaq = async () => {
try {
const responseData = await sendRequest(
"http://localhost:8000/api/myEndpoint"
);
console.log(responseData);
setLoadedFaq(responseData);
} catch (err) {}
};
fetchFaq();
}, [sendRequest]);
Your hook returns true if it gets a 200 response code:
if (response.status !== 200) {
setErrors(responseData);
return responseData;
} else {
setSuccess(true);
return true;
}
It only returns responseData if it gets a non-200 code. Just return the data from the hook..