Infinite Loop React - reactjs

I'm new in React, and i have problems to show the content of an array.
I have a state to store the data (initial value []). Then, I have a function to consume frmo an api with a get, then i store that data in the state, and finally, I iterate over the state to show it on the page. Everything's is ok, the function gives return the values, but in the console, the return is enter in a infinite loop, slowing down the browser.
Here I detach the code
The state where i store the values
const [projects, setProjects] = useState([]);
Function that consumes the api and save it in the state
const getData = async () => {
let allProjects = {};
const res = await axios.get(URL_BASE);
allProjects = res.data;
setProjects(allProjects);
console.log(projects);
};
useEffect to refresh the render when projects is change
useEffect(() => {
getData();
}, [projects]);
This is where i iterate
return (
<div>
<p>La cantidadd de proyectos en total son:</p>
{getData() && projects.map((proyecto) => <div>{proyecto.titulo}</div>)}
</div>
);

This useEffect basically says "any time projects changes, run getData()":
useEffect(() => {
getData();
}, [projects]);
And running getData() changes projects:
const getData = async () => {
//...
setProjects(allProjects);
//...
};
Thus the endless cycle.
It sounds like you just want to getData() once, when the component first loads. For that you can just use an empty dependency array:
useEffect(() => {
getData();
}, []);

Related

Is it ok to update state very second?

I am trying to display the count of items in a cart. I want the count to update every second so if a user adds or deletes from cart it will make that request every second and refresh, updating the state and the number will change. I just tried this method and it works fine, but I'd like to know if it's ok to do or if there is a better way of doing it.
const [update, setUpdate] = useState(0)
const [data, setData] = useState([])
let currentUser = 1
const getData = () => {
axios.get(`http://localhost:4000/api/userCart/${currentUser}`)
.then((res) => {
setData(res.data)
setUpdate(++update)
})
}
useEffect(() => {
getData()
}, [update])
useEffect(() => {
setInterval(() => {
getData()
}, 1000);
},[])
I think thats ok, you need just a way to clear this interval when you destroy the component
const timer = useRef<any>(null);
timer.current = setInterval(() => {
//your interval code
}, time);
useEffect(()=>{
return () => {
clearInterval(timer.current);
}
},[])
your first useEffect I think can be a problem, you made a interval and a effect that runs every get
It's okay when you want to give a real-time like experience. If this will be on production you need to consider how many request will be done and the time it can take to resolve and get the data.
There's a pacakge SWR from Vercel team which you can use https://swr.vercel.app/docs/revalidation , it fetches data, validates it's state and serves a cached state when available. Give it a try
If you want to continue with your own implementation then you need to take into consideration this:
Intervals will keep fetching data don't caring if previous fetch was completed. Solution: Fetch data then run a setTimeout and resolve with a new fetch
Clean up. Save each timeout in a Ref and when a component unmounts clear that timeOut
There's no correct way of doing stuff, give any idea you have a try and if it works the just polish it and avoid any side effects as the mentioned above : )
To consider in your current code
In the code you shared, the getData function is being invoked twice, one from interval which then keeps requestin data, and again when you update the update prop.
A refactor idea can be this:
// Out of component body
const UPDATE_INTERVAL = 1000
// In component body
const [update, setUpdate] = useState(0)
const [data, setData] = useState([])
const timer = useRef(null)
useEffect(() => {
const triggerUpdate = setUpdate((n) => n + 1)
const getData = () => {
return axios.get(`http://localhost:4000/api/userCart/${currentUser}`)
}
getData()
.then((res) => {
setData(res.data)
timer.current = setTimeout(triggerUpdate, UPDATE_INTERVAL)
})
.catch(console.error)
return () => {
clearTimeout(timer.current)
}
}, [update])

Why my useEffect that tries to get blockchain data is looping infinitely and my async func still returns Promise pending

I am trying to use async await inside a useEffect hook getting some data from a testnet blockchain but I am getting 2 problems:
The async function returns a Promise, why is that? Shouldn't async await automatically resolve the promise and give me the data? I tried to solve it with Promise.resolve but not working, it still tells me campaigns is a Promise in pending state.
It enters in an infinite loop and I still do not get why.
Here is the code:
useEffect(() => {
const getCampaigns = async() => {
const campaigns = await factory.methods.getDeployedCampaigns().call()
return campaigns
}
const campaigns = getCampaigns();
setCampaigns(Promise.resolve(campaigns));
console.log('campaigns: ', campaigns);
})
You have no dependencies array.
useEffect(() => {
const getCampaigns = async() => {
const campaigns = await factory.methods.getDeployedCampaigns().call()
return campaigns
}
const campaigns = getCampaigns();
setCampaigns(Promise.resolve(campaigns));
console.log('campaigns: ', campaigns);
}, [])
Try this
useEffect(() => {
const getCampaigns = async() => {
const campaigns = await factory.methods.getDeployedCampaigns().call()
setCampaigns(campaigns);
}
getCampaigns();
}, []);
The empty array in useEffect call makes it behave like component did mount and only executes once (assuming factory methods are initialized on mount) and since the getDeployedCompanigns Promise is already resolved I'm simply setting the state in the getCampaigns function.
Read this article for details: https://devtrium.com/posts/async-functions-useeffect

I want to display API data on first load in React

I can see that the data is being console logged but in my return statement it doesn't render and the data is null. I am well aware that before I get the data the page is being rendered first. How can I make it render after it has received the data?
const [bookingData, setBookingData] = useState(null);
const url = 'http://127.0.0.1:8000/api/test/api'
useEffect(() => {
fetchData();
console.log(bookingData)
}, []);
function fetchData() {
axios.get(url).then((response) => {
console.log(response.data.data[0])
setBookingData(response.data.data[0])
});
}
return (
<div> {bookingData.tracking_id} </div>
);
Since fetching data from an API is an asyncronous function, you'll have to use an async function.
async function fetchData () {
axios.get(url).then((response) => {
console.log(response.data.data[0])
setBookingData(response.data.data[0])
});
}

State not updating until external event

I am using useEffect to run a function that grabs data from firebase.
The data is grabbed fine, however the setState function does not seem to take effect until after another state has changed.... I thought using useEffect would run before first render.
function App() {
const [expenses, setExpenses] = useState([]);
const roster = [];
React.useEffect(() => {
const getRoster = async () => {
var db = firebase.firestore();
db.collection("Roster")
.get()
.then((querySnapshot) => {
querySnapshot.forEach((doc) => {
// doc.data() is never undefined for query doc snapshots
var data = doc.data();
data.ID = doc.id;
roster.push(data);
});
});
console.log("setting roster");
setExpenses(roster);
console.log("roster", roster);
console.log("expenses", expenses);
};
getRoster();
}, []);
the console returns the following
setting roster
roster [all data from firebase here]
expenses [blank], **expenses is the state variable**
The expenses state only updates after I change some other state in the application. I've tried to work around this by changing some other states in the use effect function, no dice. Also I've tried passing the state as a dependency to the use effect. Nothing...
I must doing something wrong but I'm not sure what that is.
My goal is to have the expenses state updated on first page load.
setExpenses(roster); should be called inside .then as it .get is an async call and it takes some time so within this time your setExpenses(roster); gets called and its has the initial value of roster. So you should use your setExpenses(roster); as bellow
React.useEffect(() => {
const getRoster = async () => {
var db = firebase.firestore();
db.collection("Roster")
.get()
.then((querySnapshot) => {
querySnapshot.forEach((doc) => {
// doc.data() is never undefined for query doc snapshots
var data = doc.data();
data.ID = doc.id;
roster.push(data);
});
console.log("setting roster");
setExpenses(roster);
console.log("roster", roster);
console.log("expenses", expenses);
});
};
getRoster();
}, []);
The setExpenses call should be placed directly after the querySnapshot.forEach call, but still within the .then((querySnapshot) => { ... } handler. Because it's currently placed after that handler, it is executed immediately on first render, not when the Firebase data is obtained.

Axios only fetches Azure API response ONCE

I am trying to fetch my blobs with axios inside my React app using the Azure REST API (and parse it to JSON as it comes in as a XML response). Problem here is that it fetches correctly once, and when the page is refreshed it does not fetch anything else, showing undefined whenever I try to access any of the data, and returning empty array in console... what am I doing wrong?
const [images, setImgs] = useState([])
useEffect(() => {
const fetchData = async () => {
const response = await axios.get(`https://${process.env.AZURE_SPACE}.blob.core.windows.net/${process.env.AZURE_CONTAINER}?restype=container&comp=list&${process.env.AZURE_TOKEN}`);
const newJson = JSON.parse(convert.xml2json(response.data, {compact: true, spaces: 4}))
setImgs(newJson);
}
fetchData();
console.log(images)
}, []);
Try
await fetchData();
Otherwise, your console log is called before the answer coming back from azure
So, if you checking console.log(images) why this is still blank :
setImgs(newJson); // is also async
fetchData();
console.log(images) //<-- So it won't reflected immediately right after fetchData()
But your DOM will get updated, you can confirm it.
You can run the below snippet and check HTML and conosle.log both :
setUsers([...users, "Vivan" , "Darshita"]); // is async
console.log(users); // Due to setUsers's async behaviour it still console.log old values
const { useState , useEffect } = React;
const App = () => {
const [users,setUsers] = useState(['Vivek' , 'Darsh']);
useEffect(() => {
setTimeout(() => {
setUsers([...users, "Vivan" , "Darshita"]);
console.log(users);
},2000);
},[]);
return (
<div>
{ users.map(user => <p>{user}</p>) }
</div>
);
}
ReactDOM.render(<App />, document.getElementById('react-root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react-root"></div>
So it sounds like two separate questions.
How to solve the await issue:
const [images, setImgs] = useState([])
useEffect(() => {
const wrapperFunc = async () => {
const fetchData = async () => {
const response = await axios.get(`https://${process.env.AZURE_SPACE}.blob.core.windows.net/${process.env.AZURE_CONTAINER}?restype=container&comp=list&${process.env.AZURE_TOKEN}`);
const newJson = JSON.parse(convert.xml2json(response.data, {compact: true, spaces: 4}))
setImgs(newJson);
}
await fetchData();
console.log(images)
};
wrapperFunc();
}, []);
The code inside the wrapperFunc can now wait for fetchData to return before writing to the console.
The second issue where it is only getting called once. It could be fixed as a result of this new code. However, I ran into something similar with React Hot Loading where the gating state was already set, so it would rerun my new code. I had to just change the state manually and then the code ran.

Resources