React Firebase, Await data before do code - reactjs

I get this error with a function in react. How can I make sure the I received the data before i use .push?
Unhandled Rejection (TypeError): Cannot read property 'push' of null
const joinTheLobby = () => {
if(lobbyKey != ''){
// Add Player to DB Lobby.
const db = firebase.database().ref(`Lobby/${lobbyKey}/players`);
let allPlayers = [];
let pushPlayers = [];
let newPlayer = {'userName': userName};
db.on('value', async (snapshot) => {
allPlayers = await snapshot.val();
allPlayers.push(newPlayer);
allPlayers.map(({userName}) => {
pushPlayers.push(userName);
});
});
This is how I fire the function. Also, Will this function run immediately after state updates?
<button onClick={joinTheLobby}>Join</button>

Looking at this code, snapshot.val() is most likely returning null.
null is a valid thing for it to return according to the docs: https://firebase.google.com/docs/reference/node/firebase.database.DataSnapshot#val
So, most likely this Lobby/${lobbyKey}/players does not exist in Firebase.

Related

Firestore Async function

So I'm trying to use React Query with Firestore and usually this works pretty fine but recently I have been unable to make a function that returns a value.
const fetchProduct = async () => {
const querySnapshot = await getDocs(collection(db, "notes"));
const arr = [];
querySnapshot.forEach((doc) => {
setNotes(doc.data())
}).catch((error) => {
console.log(error);
return null;
});
return notes
}
I'm later trying to use this function with React Query like this -
const { isLoading, isError, data, error } = useQuery(['todos'], fetchProduct)
However the value of the {data} always return to undefined but however once the fetchProduct() function is called manually it all works.
Is there anything I'm missing or doing wrong?
Setting the state is an asynchronous operation in React (see docs), so the value of notes isn't set yet by the time your return notes runs.
If you want to return the value synchronously:
return querySnapshot.docs.map((doc) => doc.data());
More typically though, you'll want to put the code that depends on that return value into its own useEffect hook that depends on the notes state.
Also see:
The useState set method is not reflecting a change immediately
Why does calling react setState method not mutate the state immediately?
Is useState synchronous?
It should be like this, you should not set inside the foreach function
const fetchProduct = async () => {
const querySnapshot = await getDocs(collection(db, "notes"));
const notes = [];
querySnapshot.forEach((note) => {
notes.push({ ...note.data(), id: note.id })
}).catch((error) => {
console.log(error);
return null;
});
return notes;
}
// in the place of the calling the function
const notes = await fetchProduct();
setNotes(notes);
Note: don't use variable name as doc in foreach, instead use some other variable name like since doc is built in function of firebase you might have imported it

My firebase storage function doesnt let me take the url from the function

I'm new at programming and I'm so confused at this error because I want to have the download link into an array but it returns me empty but when I execute it with the console.log inside the .then it returns me the link but if I do it outside the the returns me nothing :(
var title = "";
var price = "";
var description = "";
var image = new Array();
firebase
.database()
.ref("/productos/" + itemId)
.on("value", (snapshot) => {
title = snapshot.val().title;
price = snapshot.val().price;
description = snapshot.val().description;
});
firebase
.storage()
.ref("/images/" + itemId)
.getDownloadURL()
.then((url) => {
image = [url];
console.log(image)
setLoading(false);
})
.catch((error) => {
console.log(error);
});
console.log(image)
You need to do some research on how promises work I think so you can understand whats going on. Your code looks ok but its a bit messy to be honest, maybe the below using async/await instead of promise chaining can help you. I just created this function to model how the logic works, not because its going to be useful.
// Global array variable image
const image = new Array();
const itemId = 'an_id';
const addItemDownloadUrlToArray = async (ref) => {
// Use async await not promise chaining
const url = await firebase.storage().ref(ref).getDownloadURL();
image.push(url);
};
// After this has run, image will have the url in it
addItemDownloadUrlToArray(`/images/${itemId}`);

web3 react contract interaction

I am having some issues when calling a contract via form with React and Web3 component.
In my contract, I have a public function called GetDomainInfo which takes the domain as a parameter. You can view the contract here: https://ropsten.etherscan.io/address/0x756ad7f7c22e7c04a845bd8a138e455a1bc95f6f
The problem is that my components gets the form value, but when use it in a contract, gives the error:
Uncaught TypeError: Cannot read property 'GetDomainInfo' of undefined
Code:
GetInfo = (event) => {
event.preventDefault();
console.log(this.state.value);
const response = this.state.rouDomains.method.GetDomainInfo(this.state.value).send({from: this.state.account})
.once('receipt', (receipt) => {
console.log("receipt: ", receipt);
console.log(response);
})
}
The data arrives in console.log(this.state.value), I can see it.
EDIT: rouDomains is the async load data from web3.
async loadData() {
const web3 = new Web3(Web3.givenProvider || "http://localhost:8545");
const network = await web3.eth.net.getNetworkType();
this.setState({network});
//Fetch account
const accounts = await web3.eth.getAccounts();
this.setState({ account: accounts[0]});
//Load the contract
const rouDomains = new web3.eth.Contract(ROU_TLD, ROU_TLD_ADDRESS);
this.setState({ rouDomains });
console.log("ROU: ", rouDomains);
}
The answer was that I forgot a 's' for method in the below:
const response = this.state.rouDomains.method.GetDomainInfo(this.state.value).send({from: this.state.account})
const response = this.state.rouDomains.methods.GetDomainInfo(this.state.value).send({from: this.state.account})
Stupid rookie mistake :)

React async method setState variable not being set in time

I'm trying to run 3 methods after each other when a user clicks a button
Steps:
1: Push a file to IPFS, get the link back and assign it to a state variable
2: Add that link (from that var) to blockchain smart contract
3: Add an entry to the firebase database
The problem is that that IPFS link is null when I try to pass it to my smart contract, however after the method runs I can see the link print to console.
So I'm guessing it is not being set in time for the next method to see the variable.
IPFS Method:
pushToIPFS = async(e) => {
// e.preventDefault()
await ipfs.add(this.state.buffer, (err, ipfsHash) => {
console.log(err, ipfsHash)
//this.setState({IPFSlink : ipfsHash[0].hash})
console.log(ipfsHash[0].hash)
return ipfsHash[0].hash
})
}
Blockchain method:
addToBlockchain = async(e) => {
//create a new key for our student
var key = this.state.StudentNumber + this.state.account[0]
key = parseInt(hash(key), 10)
this.setState({idForBlockchain: key})
console.log(key)
//get todays date
let newDate = new Date()
newDate = newDate.getTime()
var _ipfsLink = this.state.IPFSlink
var _account = this.state.account[0]
console.log(_ipfsLink)
console.log(this.state.IPFSlink)
await storehash.methods.sendDocument(_ipfsLink, newDate,
}
Firebase method:
createStudent = async(e) => {
//get student details from state variables & current user uid
var _uid = this.state.uid
var _studentName = this.state.StudentName
var _studentNumber = this.state.StudentNumber
var _courseCode = this.state.CourseCode
var _courseName = this.state.CourseName
var _idForBlockchain = this.state.idForBlockchain
// database.ref.students.uid.studentNumber
const db = firebase.database()
db.ref().child("students").child(_uid).child(_studentNumber).set(
{ studentName: _studentName,
courseCode: _courseCode,
courseName: _courseName,
blockchainKey: _idForBlockchain
}
);
alert("Student added")
}
The method that triggers when the button is clicked:
AddMyStuff = async (e) => {
e.preventDefault()
await this.pushToIPFS()
await this.addToBlockchain()
await this.createStudent()
}
This is the error returned, so what I assume is the await and setState are causing issues and the variable I need is not being set.
Unhandled Rejection (Error): invalid string value (arg="_ipfsLocation", coderType="string", value=null, version=4.0.27)
Does anyone know how this can be solved?
you can convert your pushToIPFS to be a promise instead of a callback, and just resolve it when the callback is fired.
pushToIPFS = (e) => {
return new Promise((resolve, reject) => {
ipfs.add(this.state.buffer, (err, ipfsHash) => {
resolve(ipfsHash[0].hash);
})
});
}
And since its a promise you can use async/await.
AddMyStuff = async (e) => {
e.preventDefault()
const ipfsHash = await this.pushToIPFS();
//you have your ipfsHash defined, you can pass it to your other methods
}

Get data asynchronously from firebase and set state after the data is reached

I'm using react-native and I want to get data from the firebase realtime database and set state of that data and then only then load the view, I don't want the user to see the data getting pushed and mapped on every load of the chat view.
Here is what I've tried
_getMessages = async () => {
let message_array = [];
await firebase.database().ref('User-Message').child(this.state.fromUser).child(this.state.toUser).on('child_added', async (snapshot) => {
let message_id = await snapshot.key;
let message_ref = await firebase.database().ref('Message').child(message_id).once('value', async (payload) => {
await message_array.push(payload.val())
})
await this.setState({ messages : message_array })
})
}
And in my componentWillMount is simply call the _getMessages() function like this
componentWillMount = async () => {
await this._getMessages();
}
How can I make sure to set the state of messages after getting all the messages from the firebase?
This won't work:
await firebase.database().ref('User-Message').child(this.state.fromUser).child(this.state.toUser).on('child_added', async (snapshot) => {
Firebase's on() method starts actively listening for events. It does not have a clear moment when it's done, so doesn't return a promise. Hence it can't be used with await/async.
My feeling is that you're trying to simply load all user messages, which is easiest to do by using once("value":
let ref = firebase.database().ref('User-Message').child(this.state.fromUser).child(this.state.toUser);
let message_array = await ref.once('value', async (snapshot) => {
let messagePromises = [];
snapshot.forEach(function(userMessageSnapshot) {
messagePromises.push( firebase.database().ref('Message').child(snapshot.key).once('value', async (payload) => {
return payload.val();
})
})
await messageSnapshots = Promise.all(messagePromises);
this.setState({ messages : messageSnapshots })
})
If you want to get realtime updates, you will have to use an on() listener. But that does mean you can't use async/await in the outer listener. It'd look something like this:
let ref = firebase.database().ref('User-Message').child(this.state.fromUser).child(this.state.toUser);
ref.on('value', async (snapshot) => {
let messagePromises = [];
snapshot.forEach(function(userMessageSnapshot) {
messagePromises.push( firebase.database().ref('Message').child(snapshot.key).once('value', async (payload) => {
return payload.val();
})
})
await messageSnapshots = Promise.all(messagePromises);
this.setState({ messages : messageSnapshots })
})

Resources