trying to update user profile but get error with image I can't update it - reactjs

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);
});
}

Related

How to check if a certain key name exist in json object in React app

I create this custom hook in my React app. It should return a boolean.
const useFetchResponse = (url: string) => {
const [isValid, setIsValid] = useState<boolean>(false);
useEffect(() => {
const fetchResponse = async () => {
const response = await fetch(url);
console.log(response);
const obj = await response.json();
if (response.ok) {
console.log(await response.json());
setIsValid(true);
}
return response;
};
fetchResponse().then((res) => res);
}, []);
return isValid;
};
export default useFetchResponse;
When I log const obj = await response.json(); it returns: {"keyName":"some=key"}.
How do I create a condition to check if response.json() has a key named keyName?
Is that for example console.log('keyName' in obj) // true?
Do you see more things which I can improve and refactor?
Let assume you get response as follow
let response = {
a:'data1',
b:'data2',
c:'data3'
};
Then you can extract keys from object as below:
let keyOnly = Object.keys(response)); // output will be ["a","b","c"]
then you can check if your require value includes on above array or not as below: Assuming if you want to check if "b" is included or not
let checkKey = keyOnly.includes(b)
if you want to check whether an object has a certain property or not, the in operator is fine.
const obj = { a: 1 };
'a' in obj // return true
'b' in obj // return false
About improvements
it's better to save all fetch states, not only valid or not. And you should wrap request with try/catch block. For example:
const [fetchState, setFetchState] = useState('pending');
useEffect(() => {
const fetchResponse = async () => {
try {
setFetchState('loading');
const response = await fetch(url);
console.log(response);
const obj = await response.json();
if (response.ok) {
console.log(await response.json());
setFetchState('success');
}
return response;
} catch (error) {
setFetchState('failed')
}
};
fetchResponse().then((res) => res);
}, []);
return fetchState;
};
fetchResponse(); would be enough. fetchResponse().then((res) => res); is unnecessary.
[optional] You could use libraries to making requests, like an axios. That would be more convenient.
in is slower than below way.
const isValid = obj[`keyname`] !== undefined
Check more detail in here

useEffect didnt run

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]);

Handling 404 Error Response with Async/Await

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:

React - within function setstate not updating state

I have a Service inclusion form, which includes 2 file uploads, one for single file selector and one more for multiple file selector. On submit click calling a function to upload the files to firebase storage and saving the links.
I'm updating the 'fileURLs' and 'sp_License' state in MultifileuploadHandler method. But it is not updating the state. when I do console.log of newState I cannot see the updated states.
Received follwoing error on submit
'FirebaseError: Function DocumentReference.set() called with invalid data. Unsupported field value: a custom File object (found in field sp_License)'
any help appreciated.!
EDIT :-
I have updated my code.
On submit, before uploading the file,save data method is called. How can I wait till upload is done and then call SaveData Method?
handleSubmit = (e) => {
e.preventDefault();
let err = this.validate();
if (!err) {
this.setState({ loading: true,disChecked:false })
this.fileupload();
this.MultifileuploadHandler();
this.saveData();
}
}
saveData=()=>{
let uid = this.props.auth.uid;
let keysToRemove = ["loading", "checked", "disChecked", "open", "message"]
let newState = Object.entries({...this.state}).reduce((obj, [key, value]) => {
if(!keysToRemove.includes(key)){
obj[key] = value
}
return obj
}, {})
console.log(newState)
this.props.UpdateUserDetails(uid, newState,this.successMessage)
}
fileupload=()=>{
//single org file
const {fileURLs,sp_License}=this.state;
const Lfilename = this.state.sp_Name + '_' + new Date().getTime();
const uploadTask = storage.ref('License/' + Lfilename).put(sp_License);
uploadTask
.then(uploadTaskSnapshot => {
return uploadTaskSnapshot.ref.getDownloadURL();
})
.then(url => {
// orgFile.push({url});
this.setState({sp_License:url})
// orgFile=url
// console.log(orgFile)
},()=>{
console.log(sp_License)
});
}
MultifileuploadHandler = () => {
const {fileURLs,sp_License}=this.state;
let files=[];
var orgFile=[];
//multi pilots file
const storageRef = storage.ref();
this.state.sp_PilotsLicense.forEach((file) => {
storageRef
.child(`License/${file.name}`)
.put(file).then((snapshot) => {
return snapshot.ref.getDownloadURL();
}).then(url =>{
files.push({url});
console.log(url)
if(files.length===this.state.sp_PilotsLicense.length)
{
console.log('url')
this.setState({ fileURLs: files },()=>{
console.log(fileURLs)
});
}
})
});
}

Expo FileSystem.readAsStringAsync could not read file

I have the following setup, when user take picture using ImagePicker it will save it to FileSystem.documentDirectory using the following piece of code:
saveAvatar = async (uri) => {
await Expo.FileSystem.moveAsync({
from: uri,
to: Expo.FileSystem.documentDirectory + 'avatar/profile'
})
}
_takePhoto = async () => {
const result = await ImagePicker.launchCameraAsync({
allowsEditing: false,
base64: true,
quality: 0.4
});
if (!result.cancelled) {
this.setState({ image: result.base64 });
this.saveAvatar(result.uri)
}
};
Then I tried checking retrieving it using this:
ensureDirAsync = async () => {
const props = await FileSystem.getInfoAsync(FileSystem.documentDirectory + 'avatar/');
if (props.exists && props.isDirectory) {
return props;
}
try {
await FileSystem.makeDirectoryAsync(FileSystem.documentDirectory + 'avatar/', { intermediates: true });
}
catch (e) {
console.log(e);
}
return await this.ensureDirAsync()
}
getAvatar = async () => {
let dir = await this.ensureDirAsync(),
filename = await FileSystem.readDirectoryAsync(dir.uri),
data = null;
const props = await FileSystem.getInfoAsync(dir.uri + filename[0])
console.log(props)
try {
data = await FileSystem.readAsStringAsync(FileSystem.documentDirectory + 'avatar/profile');
}
catch (e) {
console.log(e);
}
console.log(data)
return data;
}
The weird thing is, const props = await FileSystem.getInfoAsync(dir.uri + filename[0]) is printing this:
Object {
"exists": 1,
"isDirectory": false,
"modificationTime": 1532930978,
"size": 399861,
"uri": "file:///var/mobile/Containers/Data/Application/9D3661AF-8EB5-49F5-A178-3ECA0F96BEEC/Documents/ExponentExperienceData/%2540anonymous%252FWAMS-1163fc3b-4484-44a2-9076-b4b71df1e55c/avatar/profile",
}
Which should indicate that the image was saved successfully, but data = await FileSystem.readAsStringAsync(dir.uri + filename[0]); OR data = await FileSystem.readAsStringAsync(FileSystem.documentDirectory + 'avatar/profile') would give me this error:
File 'file:///var/mobile/Containers/Data/Application/9D3661AF-8EB5-49F5-A178-3ECA0F96BEEC/Documents/ExponentExperienceData/%2540anonymous%252FWAMS-1163fc3b-4484-44a2-9076-b4b71df1e55c/avatar/profile' could not be read.
Any idea how could this happen? can FileSystem.readAsStringAsync() even read the file I moved from ImagePicker? if not, what should I have used instead?
I'm trying this on IOS.
Thanks in advance for the help :smiley:
You have to specify the EncodingType. FileSystem.EncodingType.Base64 works for audio/image files.
example:
content = await FileSystem.readAsStringAsync(uri, { encoding: FileSystem.EncodingType.Base64 });
got it! turns out instead of moving the file using FileSystem.moveAsync() I just have to save the base64 representation as normal string using FileSystem.writeAsStringAsync() and it solved the problem for me

Resources