When I try to test this code portion by running useEffect on the info state, I notice that the info state runs 1 time less than it should be. Can I know why?
data.length = 2
const fetchData = async function (contractAddress) {
setContractAddress(contractAddress)
const loan = await readLoanAmount({"address": contractAddress});
setLoanAmount(loan)
const collected = await readCollectedAmount({"address": contractAddress});
setCollectedAmount(collected);
setInfo([...info, {"contractAddress":contractAddress,"loanAmount": loanAmount, "collectedAmount":collectedAmount}])
}
useEffect(() => {
checkWalletIsConnected();
fetch('http://localhost:5000/loanContract/getloanContracts').then((response) => response.json()).then((data) => {
for(let i =0 ; i< data.length; i++){
let ca = data[i]["contractAddress"];
fetchData(ca);
}
}).catch((err) => {
console.log(err.message);
});
}, []);
Whenever we supply an empty array to the second argument of useEffect, the effect runs only once after render. That is by definition how React built the hook.
Link to useEffect expalaination in React Docs Beta: https://beta.reactjs.org/learn/synchronizing-with-effects
Did you mean for loop inside your useEffect is running once only instead of twice (as it should for data.length = 2)?
It could be due to the way you have used setInfo setter. Instead of getting the info value directly, do the following:
setInfo(prevInfo => {
return [
...prevInfo,
{
"contractAddress":contractAddress,
"loanAmount": loanAmount,
"collectedAmount":collectedAmount
}
];
});
Receiving prevInfo this way makes sure you have the latest value of info especially when dealing with synchronous consecutive calls to the setter function of useState. This might solve the issue you're facing...
Related
i have to get some data from a complex objects json (in my case is: valor_custo_fornecedor from the detalhe object, how can i do this ?
i already tried with axios like this:
const [valorCusto, setValorCusto] = useState([]);
useEffect(() => {
axios.get('MY API LINK').then((res) => {
let valor = res.detalhe.valor_custo_fornecedor;
setValorCusto(res.valorCusto.valor_custo_fornecedor);
});
});
It is hard to tell without seeing the complete json what key/value pairs exist in the data, but from what I can see you are passing a value that does not seem to exist in the data to setValorCusto, namely: res.valorCusto.valor_custo_fornecedor
I am guessing what you want to pass to setValorCusto is the valor variable that you have defined above? However the res.data.detalheis as others have replied an array, which you would either have to iterate through or specify an index for.
Another detail, not relating to the question in itself, is that I would add a dependency array to the effect, so that this api request is not called on every component re-render. So the end result would look something like this:
const [valorCusto, setValorCusto] = useState([]);
useEffect(() => {
axios.get('MY API LINK').then((res) => {
const valor = res.data.detalhe.map((detalheItem) => {
return detalheItem.valor_custo_fornecedor;
});
setValorCusto(valor);
});
}, []);
Notice the empty array as the second parameter in the useEffect call. This means that the effect will only be run on the initial rendering of the component.
Then you can access the value by simply using valorCusto wherever you need it in the component.
After setting response in setValorCusto ; you can loop over you state variable and get the desired value
Like the other answers state. What you are returning from your API is an array of objects. Currently in your code, that array lives inside the response object. So in this snippet below:
axios.get('MY API LINK').then((res) => {
let valor = res.detalhe.valor_custo_fornecedor;
You need to specify from which item in the array you would like to get the detalhe object from (like res[0].detalhe).
One way you could do if you like to use everything from the array, is to store the entire array in your useState. Afterwards, you could loop over the array and do something with the objects inside of it.
PS. If you're not entirely sure what the state holds. You could log it to the page by doing something like <div>{JSON.stringify(valorCusto)}</div>
export default function App() {
const [valorCusto, setValorCusto] = useState(null);
useEffect(() => {
const getData = async () => {
const res = await axios.get("API");
const data = await res.data;
setValorCusto(data);
};
getData();
}, []);
return (
<main>
<div>
{valorCusto.map((item) => {
<div>{item.id}</div>;
})}
</div>
</main>
);
}
You can convert json like this:
useEffect(() => {
axios.get('MY API LINK').then((res) => {
let obj = JSON.parse(res.detalhe.valor_custo_fornecedor)
});
});
I'm creating a react js app for a project that interacts with an api. I get an array with values from the api, and those values are google maps pins. What I want to do here is: the user sets a data interval and all the pins in that data interval are shown in the map.
I have this js code to set everything:
//above is the function that receives two dates and returns the interval, not relevant here
var pinsdata = [];
useEffect(()=>{
axios.get('MYAPILINK')
.then(res=>{
var arraydatas = getDates(data1, data2)
for(var i = 0; i < res.data.data.length; i++)
{
if(arraydatas.includes(res.data.data[i].data_pg))
{
pinsdata.push(res.data.data[i])
}
}
setPorra(pinsdata)
})
}, [porra])
and for now i just have this on the return (I'm just testing this for now):
<div>
<DatePicker selected={startDate} onSelect={(date) => setStartDate(date)} dateFormat="dd-MM-y"/>
<DatePicker selected={startDate2} onSelect={(date2) => setStartDate2(date2)} dateFormat="dd-MM-y"/>
<ul>
{porra.map(pinsdata =>{
return(<li>{pinsdata.id_pg}</li>)
})}
</ul>
</div>
So... everything seems to work fine here: I set two dates and I get all the markers/pins in that data interval shown on the page.
The problem:
My page is INFINITELY re-rendering... The console.log that i have there gets spammed as soon as i refresh the page, and when i add the google map to the page, it's not even usable because the page just get's re-render every second. Now... i know that it's because of useState, but how can i get this going without use state? Because the api request is async, so i have to re-render when i have the answer from the api...
You dependencies array is incorrect you should have data1 and data2 in there instead -
var pinsdata = [];
useEffect(()=>{
axios.get('MYAPILINK')
.then(res=>{
var arraydatas = getDates(data1, data2)
for(var i = 0; i < res.data.data.length; i++)
{
if(arraydatas.includes(res.data.data[i].data_pg))
{
pinsdata.push(res.data.data[i])
}
}
setPorra(pinsdata)
})
}, [data1, data2])
providing that you want to run this effect when data1 or data2 changes, otherwise leave the array blank, and it will only run once, when the component mounts
Try removing porra from the dependancy array and add two dates(startDate and startDate2) as the dependencies.
useEffect(()=>{
axios.get('MYAPILINK')
.then(res=>{
var arraydatas = getDates(data1, data2)
for(var i = 0; i < res.data.data.length; i++)
{
if(arraydatas.includes(res.data.data[i].data_pg))
{
pinsdata.push(res.data.data[i])
}
}
setPorra(pinsdata)
})
}, [startDate, startDate2])
when you call setPorra inside useEffect, it creates an infinite loop.
const [urlList, setUrlList] = useState(0);
const callDayList = () => {
console.log(urlList);
Axios.post('http://localhost:3001/api/get', {
passNum: urlList,
});
};
useEffect(callDayList, [urlList]);
<td>
<Link to={"/study/"+item.num}
onClick={function() {
setUrlList(item.num);
// num = item.num
}}
>{item.title}</Link>
</td>
this code is sending a state, which is urlList to backend, and I have some problem.
as I studied about useEffect, it executes only if the urlList changes its value.
but when I click a url, it keep passes the initial value of urlList, which is 0.
to check urlList changes correctly, I changed Link in to "/", and saw the outcome of console.log(urlList) in callDayList, when I click, it shows correct value of state, but if set back the Link, and click, the value of the urlList is still initial value and send it to backend.
callDayList has to be execute when the urlList changes it's value, I can't figure out why it keep passes initial value of the state.
useEffect will run the first time it is set up as well as when anything changes.
If you want to avoid this initial run, you can try using a useRef variable to track whether it's the first run:
import { useState, useRef, useEffect } from 'react';
const isFirstRun = useRef(true);
const callDayList = () => {
if (isFirstRun.current) {
isFirstRun.current = false;
return;
}
// do your effect
Axios.post('http://localhost:3001/api/get', {
passNum: urlList,
});
return () => { isFirstRun.current = true; }
};
useEffect(callDayList, [urlList, isFirstRun]);
It looks cumbersome, but should get you the behaviour you require. If you end up needing to skip useEffect's initial run in more than one place in your code, it'd probably be worthwhile extracting this behaviour into a custom hook.
I come from C++, C, Python space and I'm new to react native / JS / back-end world.
I have some issues loading data from firebase. Here is what I want :
My Database :
users : uid : postids[]
posts : postids : content
I want to load the postids[] array from a user and then, load content of every postids[] in this array (according to every postids in the postids[] array).
Here is my code :
_getPostsFromDatabase() {
var docRef = firebase.firestore().collection("users").doc(firebase.auth().currentUser.uid);
return docRef.get().then(function(doc) {
if (doc.exists) {
return doc.data()["posts"];
}
}).catch(function(error) {
alert("Error getting document:", error);
});
}
_loadPosts() {
var new_posts = [];
this._getPostsFromDatabase()
.then(res => {
var i;
for (i = 0; i < res.length; i++) {
firebase.firestore().collection("posts").doc(res[i])
.onSnapshot(function(doc) {
new_posts.push(doc.data());
console.log(new_posts); --> This line print correct data
});
}
})
.catch(error => console.log(error));
console.log(new_posts); ---> This line print an empty array
}
componentDidMount() {
this._loadPosts()
}
So I want this behavior :
In componentDidMount I begine the routine --> this works
loadPosts is loading the postids[] array with _getPostsFromDatabase() function --> this works
Then, I make a for loop to push every object in an array to set the state at the end --> FAIL
At step 3, everything f... up, I made some console log to debug but there is a huge real time issue because evrything is printed randomly.
How can I get my new_posts filled array at the end of the for loop to setState. Maybe I'm wrong with this method, or if I'm not, I must have some issues with Async funtion ?
Is there an expert to help me understund better what is inside this kind of use case ?
Thanks
Basically the problem is that you are trying to perform an asynchronous code in a synchronous way.
You solution might be waiting for all promises to resolve.
_loadPosts() {
this._getPostsFromDatabase()
.then(res => {
let promises = res.map(id => {
return firebase.firestore().collection("posts").doc(id)
.get().then(doc => doc.data())
})
Promise.all(promises).then(res => {console.log(res);})
}
Your console will log before the for loop, that's the reason you are getting an empty array just include your console in the response just like this:
this._getPostsFromDatabase()
.then(res => {
var i;
for (i = 0; i < res.length; i++) {
firebase.firestore().collection("posts").doc(res[i])
.onSnapshot(function(doc) {
new_posts.push(doc.data());
console.log(new_posts); --> This line print correct data
});
}
console.log(new_posts); ---->Include here
})
Hope this helps!
Quick version:
My ultimate goal is to do something like the link below but with an async call to firebase per useEffect where the list data is composed of firebase object content.
https://codesandbox.io/s/usage-pxfy7
Problem
In the code below useEffect encapsulates code that pings firebase and gets some data back called "clients". The data is retrieved perfectly.
I then store that data using useState to two different instances of useState. The data is stored at clientList and clientListForRender.
So far so good.
Now the problem starts.
I have a third instance of useState that takes a number. I want to set a keypress event to the document so that I can use the up/down arrows to toggle the counter and access each value of the clientListForRender array.
When I set the eventListener I do not have access to the array (presumably due to the async calls not being in an order that allows for it).
I am not sure how to write my hooks in a way that gives me the result I want.
Thank you.
const clientsRef = firebase.database().ref('clients');
const [clientList,setClientListState] = useState([]);
const [clientListForRender,setClientListStateForRender] = useState([]);
const [selectedIndex, updateSelectedIndex] = useState(0);
useEffect(() => {
function handleKeyPress(event,arr){
console.log(arr)
if(event.key === "ArrowDown"){
updateSelectedIndex((prev)=>{
return prev += 1
});
}
}
clientsRef.on('child_added', snapshot => {
const client = snapshot.val();
client.key = snapshot.key; // __________________________1. get firebase data
setClientListState(function(prev){
setClientListStateForRender(()=>[client,...prev]); //_______2 store data
// document.addEventListener('keydown', handleKeyPress); <---I am not sure where to put this. I have experimented and
// I decided to omit my cluttered "experiments" to protect your eyes
return[client,...prev]
});
});
},[]);
Ok there are few issues with the code you posted:
1) You should definitely not add your keyboard listener in the child_ added listener ( this means that every time the child_added listener is called, you are going to create a new listener, leading to unexpected results and memory leak)
2) You are calling setState in a setState updater function (the callback function you provided for, setClientListState), which is an anti pattern and makes your code hard to follow and understand, and will cause unexpected effects once the component grows. If you want to update a state based on a previous state then use the useEffect callback
3) the useEffect function takes a second parameter, called array of dependencies. When you have provided it with an empty array, it means that you want your effect to run only once, which is problematic because we see that the function depends on clientsRef variable. ( from this actually comes your problem because the keyboard listener was having the old value of your clientsList which is the empty array, and so it was always returning 0, when keys where pressed, i explained more in the code sandbox)
4)You should return a callback function from the useEffect function to clean the effects you created, turning off the listeners you attached (or else you might have memory leaks depending on how much the component gets mounted/unmounted)
ok here is how the code should be to work:
const clientsRef = firebase.database().ref('clients');
const [clientList, setClientListState] = useState([]);
// I don't understand why you wanted another list, so for now i only use on list
// const [clientListForRender,setClientListStateForRender] = useState([]);
const [selectedIndex, updateSelectedIndex] = useState(0);
useEffect(() => {
function handleKeyPress(event, arr) {
if (event.key === 'ArrowDown') {
updateSelectedIndex(prev => {
if (prev >= clientList.length - 1) {
return (prev = 0);
} else {
return prev + 1;
}
});
}
}
clientsRef.on('child_added', snapshot => {
const client = snapshot.val();
client.key = snapshot.key; // __________________________1. get firebase data
setClientListState(function(prev) {
return [client, ...prev];
});
});
document.addEventListener('keydown', handleKeyPress);
// here you should return a callback to clear/clean your effects
return () => {
document.removeEventListener('keydown', handleKeyPress);
clientsRef.off();
};
// Its important to add these here, or else each time your keyboard listener runs it will have the initial value of
// clientsList ([]), and so clientsList.length = 0, and so you will always updateSelectedIndex(0)
}, [clientList, clientsRef]);
//here render based on selected list as you wish
Finally i have set up a working codesandbox that emulated data fetching based on the example you give https://codesandbox.io/s/usage-4sn92, i added some comments there to help explain what i said above.