Updating Data in Firebase using React - reactjs

I am updating an object in firebase using React js.
I'm using this boilerplate as reference.
updateBookList: (id, data) => {
return firebaseDb.ref('NewBooks').child(id).update(data).then(() => {
return {};
}).catch(error => {
return {
errorCode: error.code,
errorMessage: error.message
}
});
},
The following updates the Books fine.
What I want to do is return the result instead of returning a blank {}. How can I return the result of what I updated?
This is how I fetch books:
fetchBooks: () => {
return new Promise((resolve, reject) => {
const bookSub = firebaseDb.ref('NewBooks').on("value", books => {
resolve(books.val());
}, error => {
reject(error);
})
})
},

If you want to return the value, you need to retrieve it. You can do that using once and the value event, which returns a promise that resolves to a Firebase snapshot:
updateBookList: (id, data) => {
let ref = firebaseDb.ref('NewBooks');
return ref
.child(id)
.update(data)
.then(() => ref.once('value'))
.then(snapshot => snapshot.val())
.catch(error => ({
errorCode: error.code,
errorMessage: error.message
}));
}
Also, you could simplify your fetchBooks by using once there, too:
fetchBooks: () => {
return firebaseDb.ref('NewBooks')
.once("value")
.then(snapshot => snapshot.val());
}
once returns a promise, so you don't have to create your own and you won't have a dangling event listener. The call to on in your implementation of fetchBooks will see a listener added with each call and multiple calls to resolve will be attempted if the database changes.

Related

Firebase promise - unable to set state, state array is empty

I have a promise to get a firebase collection and then return respective data (all according firebase documentation).
The data is returning fine, the only problem is that it seems that I am unable to set the collection data to a state array. When console.log(chats)this returns an empty array.
The data is returning fine as adding a console.log(doc.data()) in the first then logs the data ok but then it is empty.
What am I doing wrong in the promise?
const HomeScreen: React.FC<Props> = ({ navigation}) => {
const [chats, setChats] = useState<{[key: string]: any}>([]);
useEffect(() => {
const chatsArray = [{}];
db.collection("chats")
.get()
.then((querySnapshot) => {
querySnapshot.forEach((doc) => {
chatsArray.push({
id: doc.id,
data:doc.data(),
})
});
})
.then(() => {
setChats(chatsArray)
console.log(chats);
})
.catch((error) => {
console.log("Error getting documents: ", error);
});
}, []);
return (
<SafeAreaView>
<ScrollView style={styles.container}>
{chats go here}
</ScrollView>
</SafeAreaView>
)
}
export default HomeScreen;
The console.log(chats); doesn't work, since updating the state is a asynchronous operation that won't have completed by the time you log the value. Try console.log(chatsArray); to see the actual value.
In addition, consider passing the array as the result along the promise chain instead of using a variable in the context:
db.collection("chats")
.get()
.then((querySnapshot) => {
return querySnapshot.docs.map((doc) => {
return {
id: doc.id,
data:doc.data(),
}
});
})
.then((chatsArray) => {
setChats(chatsArray)
console.log(chatsArray);
})
.catch((error) => {
console.log("Error getting documents: ", error);
});
Or simpler and with the same result:
db.collection("chats")
.get()
.then((querySnapshot) => {
const chatsArray = querySnapshot.docs.map((doc) => {
chatsArray.push({
id: doc.id,
data:doc.data(),
})
});
setChats(chatsArray)
console.log(chatsArray);
})
.catch((error) => {
console.log("Error getting documents: ", error);
});

Using JS native fetch() api in React ComponentDidMount() results in a pending Promise

I was trying to load data into my project from the public folder in the componentDidMount() lifecycle method. However, I didn't get the desired FeatureCollection Object but a pending Promise.
componentDidMount = () => {
...
const data = fetch(`vcd/${this.state.monthFile}`)
.then(response => response.text())
.then(async data => {
return csv2geojson.csv2geojson(data, {
latfield: 'lat',
lonfield: 'lng',
delimiter: ','
}, (err, data) => {
if (err) console.log(err);
console.log(data); // correctly outputs a FeatureCollection, length 30277
return data;
// this.setState({ someAttribute: data }) => Also doesn't work.
})
})
.then(data => data); // If to use another Promise chaining, the result would be undefined.
console.log(data); // a pending Promise
}
My file contains 30277 rows * 3 columns, ~500Kb in size, which I think shouldn't be a problem with data loading, and after consulting the csv2geojson and fetch API, I still can't think of a solution to this problem. I am grateful for any helpful inputs.
EDIT: Using both async-await pattern and chaining another .then would result in undefined.
JS Fetch returns a promise so its because you're returning that promise.
So just change your code like this it will work;
import React, { useEffect, useState } from "react";
export default function ExampleHooks() {
const [data, setData] = useState(null);
var csv2geojson = require("csv2geojson");
useEffect(() => {
fetch("https://gw3xz.csb.app/sample.csv")
.then((response) => response.text())
.then(async (data) => {
csv2geojson.csv2geojson(
data,
{
latfield: "lat",
lonfield: "lng",
delimiter: ","
},
(err, data) => {
if (err) console.log(err);
setData(data);
}
);
});
}, []);
return <div onClick={() => console.log(data)}>show data</div>;
}
or as a Class Component:
import React from "react";
var csv2geojson = require("csv2geojson");
class ExampleClass extends React.Component {
state = {
data: null
};
componentDidMount() {
fetch(`vcd/${this.state.monthFile}`)
.then((response) => response.text())
.then(async (data) => {
csv2geojson.csv2geojson(
data,
{
latfield: "lat",
lonfield: "lng",
delimiter: ","
},
(err, data) => {
if (err) console.log(err);
this.setState({ data: data });
}
);
});
}
render() {
return <div onClick={() => console.log(this.state.data)}>show data</div>;
}
}
export default ExampleClass;
Working example over here
fetch returns a promise, and that is what you save to data. If you want to log the "data", then you have a couple options.
Log it IN the promise chain (you already do that)
Convert over to async/await and await the fetch to resolve/reject
code
componentDidMount = async () => {
...
const data = await fetch(`vcd/${this.state.monthFile}`)
.then(response => response.text())
.then(data => {
return csv2geojson.csv2geojson(data, {
latfield: 'lat',
lonfield: 'lng',
delimiter: ','
}, (err, data) => {
if (err) console.log(err);
console.log(data);
return data;
})
});
console.log(data); // a resolved/rejected Promise result
}

Assigning return values from api to state in React

Hi I am trying to call an api assign the returned values to a state object in React, the API is returning values but the values are not being set to state, not understanding what's the reason thank you
handleDDLCommunityChange = event => {
let filesFromApi = []; // ["file1", "file2", "file3", "file4"];
fetch('https://localhost:44352/api/files/Community-1')
.then((response) => {
return response.json();
})
.then(data => {
filesFromApi = data.map(file => { return { value: file, display: file } });
}).catch(error => {
console.log(error);
debugger;
});
console.log(filesFromApi);
this.setState({
files: filesFromApi.map(file => {
return {
fileName: file,
checked: false
};
})
});
};
fetch is an async method. An async method dispatches an action with the callbacks and unblocks following code branch from executing. The callbacks are then used to act on completion (success or failure) of the async method execution.
As you are calling the setState outside of the callbacks of the fetch call's chain, it's not guaranteed to run after the fetch call is done. As Sudheer has pointed out in their comment, you should try to set the state in a then block of the fetch chain.
warning: untested code
handleDDLCommunityChange = event => {
let filesFromApi = []; // ["file1", "file2", "file3", "file4"];
fetch('https://localhost:44352/api/files/Community-1')
.then(response => response.json())
.then(data => {
filesFromApi = data.map(file => ({ value: file, display: file });
this.setState({
files: filesFromApi.map(file => ({
fileName: file,
checked: false
})
})
});
}).catch(error => {
console.log(error);
debugger;
});
};

This state not updating after a method returns in react

I currently have a class in react. For the sake of saving space I'm only putting the code that is relevant.
this.state = {
orgID: null,
memberID: null,
}
checkAuthUser = () => {
new Promise((resolve, reject) => {
this.props.firebase.auth.onAuthStateChanged(authUser => {
if(authUser) {
console.log(authUser);
resolve(authUser);
} else {
reject(new Error("Not authorized"));
}
})
})
.then( authDetails => {
this.props.firebase.getOrgID().on('value', snapshot => {
const setSnapshot = snapshot.val();
const getOrganizationID = Object.keys(setSnapshot)[0];
this.setState({ memberID: authDetails, orgID: getOrganizationID })
})
})
.catch(err => console.log(err))
}
render() {
if (this.state.orgID == null) {
try {
this.checkAuthUser();
console.log(this.state);
} catch (e) {
console.error(e);
}
}
In the class there's a method checkAuthUser . using firebase it checks whether a user has been signed in or not. If the user is signed in I console.log the object authUser. The console.log does log authUser details so I know it's hitting firebase and then returning the details. After I have a call to get some info from firebase.
I had added some console.logs to also confirm that it was running. However after the data comes back the console.log in render doesn't run anymore. The only console.log I get is the first initial state. What am I missing here?

react native get response from then and assign to variable

I am trying to get thumbnail path and storing to a variable to be used, But I am getting undefined
getThumbnail(filePath){
let thumbnailURL = RNThumbnail.get(filePath)
.then((response) => response.path)
.then((responseData) => {
console.warn(responseData);
return responseData;
}).catch(error => console.warn(error));
alert(thumbnailURL);
//return thumbnailURL;
}
.then doesn't work like that, it won't return a value. You could do:
let thumbnailURL;
RNThumbnail.get(filePath)
.then((response) => response.path)
.then((responseData) => {
thumbnailURL = responseData;
alert(thumbnailURL);
}).catch(error => console.warn(error));
but you have to continue computation inside the second then call because the value is only going to be reliable there
You're better off using async/await, just refactor your code to this:
async function getThumbnail(filePath){
try {
let thumbnailURL = await RNThumbnail.get(filePath)
alert(thumbnailURL)
} catch(err) {
console.warn(err)
}
read more about async / await
For React app, most likely you will want to set the response as state:
state = {
thumbnailURL: ''
}
getThumbnail = (filePath) => {
RNThumbnail.get(filePath)
.then(response => response.path)
.then(responseData => {
this.setState({
thumbnailURL: responseData
})
})
.catch(error => console.warn(error))
}
render() {
return (
<img src={this.state.thumbnailURL} />
)
}
You will need arrow function on getThumbnail for lexical binding so that you can access this.setState().
Edit:
You can't actually make getThumbnail() return thumbnailURL value right away. getThumbnail() can however return the promise, and you resolve it at the place where you want access to thumbnailURL:
getThumbnail = filePath => {
return RNThumbnail.get(filePath)
.then(response => response.path)
.then(responseData => responseData)
.catch(error => console.warn(error))
}
IWannaAccessThumbnailURLHere = () => {
this.getThumbnail('....')
.then(thumbnailURL => {
// do things with thumbnailURL
})
}
Or, use setState, re-render then you can access this.state.thumbnailURL in the next render-cycle.

Resources