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
}
Related
I'm fetching data from a json. And i want to display that data in my React component.But every time I try to pass the objects that I return from the json to my state, it returns only one and delete the previous, instead of the entire elements inside the json.
This is my code.
const [state, setState] = useState({});
const connection = new Connection("devnet");
const { publicKey } = useWallet();
useEffect(() => {
(async () => {
//if not public key, close
if(!publicKey) return;
//get tokens
let response = await connection.getTokenAccountsByOwner(
publicKey!, // owner here
{
programId: TOKEN_PROGRAM_ID,
}
);
response.value.forEach((e) => {
const accountInfo = SPLToken.AccountLayout.decode(e.account.data);
//get only tokens with value of 1
if ( parseInt(`${SPLToken.u64.fromBuffer(accountInfo.amount)}`) === 1 ) {
const tokenPublicKey = `${new PublicKey(accountInfo.mint)}`
//get the metadata of each NFT
const run = async () => {
const ownedMetadata = await programs.metadata.Metadata.load(connection, await programs.metadata.Metadata.getPDA(tokenPublicKey));
//get only tokens of the collection ...
if (ownedMetadata.data.updateAuthority === "Address_authority") {
//show the json data from arweave
let url= ownedMetadata.data.data.uri;
fetch(url)
.then(res => res.json())
.then((out) => {
setState(prevState => {
// THIS IS NOT WORKING FOR ME :(
return {...prevState, ...out};
});
})
.catch(err => { throw err });
}
};
run();
}
});
})()
}, [connection, publicKey]);
console.log(state)
{...prevState, ...out}; creates a new object, puts all of prevState's own properties on the new object, then puts all of out's own properties on the new object (overwriting the values from prevState if prevState also had properties with those names).
It sounds like you want an array, not a single object:
const [state, setState] = useState([]);
Then setting:
setState(prevState => [...prevState, out]);
Possibly unrelated, but that's potentially a bunch of distinct state changes (one for each element in response.value). Because the work is asynchronous, that could also result in a number of interim re-renders. Maybe you want that, but if you don't, you can do all the fetching and then update state once. Also, any time you're doing async work in a useEffect, you should allow for the possibility the effect's dependencies have changed in the meantime or the component has unmounted. Something like this (see *** comments):
const [state, setState] = useState({});
const connection = new Connection("devnet");
const { publicKey } = useWallet();
useEffect(() => {
// *** Use a controller to stop when the component unmounts, etc.
const controller = new AbortContoller();
const {signal} = controller;
(async () => {
if (signal.aborted) return; // ***
// If not public key, stop
if (!publicKey) return;
// Get tokens
let response = await connection.getTokenAccountsByOwner(
publicKey!, // owner here
{
programId: TOKEN_PROGRAM_ID,
}
);
// *** Build up the new data in this array (since we skip some elements,
// so we won't have a simple 1:1 mapping of `response.value` elements
// to result elements.
const newState = [];
// *** Wait for all the operations to finish and add their elements to `newState`
await Promise.all(
response.value.map(async (e) => {
const accountInfo = SPLToken.AccountLayout.decode(e.account.data);
// Skip tokens with value other than 1
if (parseInt(`${SPLToken.u64.fromBuffer(accountInfo.amount)}`) !== 1) {
return;
}
const tokenPublicKey = `${new PublicKey(accountInfo.mint)}`;
const ownedMetadata = await programs.metadata.Metadata.load(connection, await programs.metadata.Metadata.getPDA(tokenPublicKey));
// Get only tokens of the collection ...
if (ownedMetadata.data.updateAuthority !== "Address_authority") {
return;
}
// Show the data from arweave
let url = ownedMetadata.data.data.uri;
const response = await fetch(url, {signal}); // *** Pass the signal to `fetch`
if (!response.ok) { // *** This check was missing
throw new Error(`HTTP error ${response.status}`); // Or ignore if you prefer
}
const out = await response.json();
newState.push(out);
})
);
// *** Now we have all of them, do one state update
setState(prevState = [...prevState, ...newState]);
})();
return () => {
// Stop if our dependencies change or the component unmounts
controller.abort();
};
}, [connection, publicKey]);
I am new to reactJS , my problem is that I want to assign my varaiable a value that has been returned from a axios function.When I do this, I get undefined value for u1.
function getUsername(userid){
var user = ''
axios.post('/api/getuserdata' , {_id:userid}).then(res=>{
console.log(res.data)
const userdata = res.data[0]
user = userdata.username
}).catch(err=>{
console.log(err)
})
return user
}
const u1 = getUsername(id)
The reason your code doesn't work is that the POST request handled by axios is async, your getUsername function won't wait for that request to resolve and return user immediately after it's called.
The simplest solution is that make your getUsername async and call it in another async function, or if you want to use the result returned from POST request for some other stuff, you can store it as state in React, it would be better.
You can use something like this:
const getUsername = async (userid) => {
try {
const response = await axios.post("/api/getuserdata", { _id: userid });
const userdata = response?.data?.[0]?.username || "";
return userdata;
} catch (error) {
console.log(`error`, error);
}
};
// in another function
const doSomethingWithUsername = async (id) => {
const username = await getUsername(id);
// other stuff..
}
async function getUsername(userid) {
try {
const result = await axios.post('/api/getuserdata' , {_id:userid});
const user = result.data;
}
catch (error) {
console.log(error)
}
}
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}`);
const [activities, setActivities] = useState([""]);
const [contact, setContact] = useState();
const currentActivity = activities[0]
let contactId = currentActivity.contacts
useEffect(() => {
API.getActivities()
.then(res =>
setActivities(res.data)
).catch((err) => console.log(err))
API.getContact(contactId)
.then(res =>
setActivities(res.data)
).catch((err) => console.log(err))
}, []);
console.log(currentActivity)
console.log(contactId)
console.log(contact)
The first call sets the activity state which includes a contact id. I require the id to run the second call for the contact information. I believe I need to setup a promise but am getting stuck. When I run the code, the contact id does not return in time to pull the contact. Another solution could be to call all contacts and loop through to match the id returned by the contact. When I have tried that, the "contact" state returns undefined as well.
You are running both api calls "at the same time", so we can assume that you will never have the contactId defined at the moment of making the contacts call.
So basically you want to run the contacts call once you have a contactId, not before. For doing so, you can add an extra effect that will be run when contactId value changes.
Also note that in your snippet you are using setActivities instead of setContact after the contacts call.
This will fix said issues
const [activities, setActivities] = useState([""]);
const [contact, setContact] = useState();
const currentActivity = activities[0]
let contactId = currentActivity.contacts
useEffect(() => {
API.getActivities()
.then(res =>
setActivities(res.data)
).catch((err) => console.log(err))
}, []);
useEffect(() => {
// Do nothing when contactId is not defined
if(!contactId) return;
API.getContact(contactId)
.then(res =>
setContact(res.data) // I also modified this line. It was updating the activities in your snippet
).catch((err) => console.log(err))
}, [contactId]); // This effect will be run every time the contactId changes
console.log(currentActivity)
console.log(contactId)
console.log(contact)
let me know if this helps. I have not tested it, may be minor changes required.
useEffect(async() => {
const resActivities=await API.getActivities()
setActivities(resActivities.data)
const promises=resActivities.data.map(item=>API.getContact(item.contactId)) // assuming contactId property exists
const resp=await Promises.all(promises)
setContact(resp.flat())
}, []);
I created a couple of APIs on Wirespec to generate a response that you can test. You can use Wirespec to create your own APIs for free.
getRandomUser API:
https://wirespec.dev/Wirespec/projects/apis/Stackoverflow/apis/getRandomUserId
getRandomUser Response:
https://api.wirespec.dev/wirespec/stackoverflow/getrandomuserid
getUserDetails API:
https://wirespec.dev/Wirespec/projects/apis/Stackoverflow/apis/getUserDetails
getUserDetails Response:
https://api.wirespec.dev/wirespec/stackoverflow/getuserdetails?id=3
Here is the test code. Two solutions are provided:
let userDetails;
new Promise(function (resolve) {
let user = JSON.parse(httpGet("https://api.wirespec.dev/wirespec/stackoverflow/getrandomuserid"));
resolve(user);
}).then(function (user) {
userDetails = JSON.parse(httpGet("https://api.wirespec.dev/wirespec/stackoverflow/getuserdetails?id=" + user.id));
console.log("firstName: " + userDetails.firstName + ", lastName: " + userDetails.lastName);
});
// A shorter way and cleaner approach...
getUserDetails()
.then (function (userDetails) {
console.log("firstName: " + userDetails.firstName + ", lastName: " + userDetails.lastName);
});
async function getUserDetails() {
let user = await JSON.parse(httpGet("https://api.wirespec.dev/wirespec/stackoverflow/getrandomuserid"));
let userDetails = await JSON.parse(httpGet("https://api.wirespec.dev/wirespec/stackoverflow/getuserdetails?id=" + user.id));
return userDetails;
}
function httpGet(url) {
var xmlHttp = new XMLHttpRequest();
xmlHttp.open("GET", url, false);
xmlHttp.send(null);
return xmlHttp.responseText;
}
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)
});
}
})
});
}