How to use a callback to a setState or something similar - javascript

So this is the code I have been working on, it gets the data from API, then I assign the data to slides (using setSlides). After that I need to create a new array without the first one, but apparently slides is still null when it executes the setInterval. Which sounds not okay since then is only executed when the request is sucessful. Can anyone tell me what's wrong.
//---getting data from api
useEffect(() => {
axios
.get(DATA_URL, headers)
.then((answer) => {
setSlides(answer.data.spotlights);
let newArr = [...slides];
newArr.shift();
setTrio(newArr);
setSpolight(answer.data.spotlights[0]);
const interval = setInterval(() => {
switchSpolight();
}, 4000);
return () => clearInterval(interval);
})
.catch((answer) => console.log(answer));
}, []);

The solution to this problem is to move the code that relies on the updated value of slides into a separate function, and call that function inside the then block, after the setSlides function has been called.
For example:
useEffect(() => {
axios
.get(DATA_URL, headers)
.then((answer) => {
setSlides(answer.data.spotlights);
function updateTrio() {
let newArr = [...slides];
newArr.shift();
setTrio(newArr);
setSpolight(answer.data.spotlights[0]);
const interval = setInterval(() => {
switchSpolight();
}, 4000);
return () => clearInterval(interval);
}
updateTrio();
})
.catch((answer) => console.log(answer));
}, []);

I thing the problem is that you are trying to access the slides state right after the setSlides "which will not be available at that moment"
so I suggest you try this instead
before:
setSlides(answer.data.spotlights);
let newArr = [...slides];
after:
setSlides(answer.data.spotlights);
let newArr = [...answer.data.spotlights];

Related

How can I return the response of the Axios Post in React?

I have a little problem.
I have a post function in ReactJS (totally working). I wanna have the response of it outside this function, right where I called it. How can I do that?
async function confereInicial(idAluno,idDisciplina){
return await Axios.post("http://localhost:3001/api/check",{
idAluno: idAluno,
idDisciplina: idDisciplina}).then((response)=> {
});
}
//please ignore the way I call and read my function. it's confusing but I just wanna console.log the data outside my function. ignore the map etc
return (
<div>
{
dados.state.nodesPadrao.map(p => {
confereInicial(1,p.id).then(data => console.log(data)); //how should I do that?
app.post("/api/check",(req,res) => {
const idAluno = req.body.idAluno;
const idDisciplina = req.body.idDisciplina;
const sqlSelect = "SELECT * FROM `disciplinas_feitas` WHERE id_aluno = ? AND id_disciplina = ?;"
db.query(sqlSelect,[idAluno,idDisciplina],(err,result) => {
res.send(result);
});
});
may someone please help me?
this should work for you and you can put it into a variable, you don't need to put it into a map function when you are not returning a valid JSX element from it.
const dataArray = [];
async function confereInicial(idAluno, idDisciplina) {
return axios.post("http://localhost:3001/api/check", {
idAluno: idAluno,
idDisciplina: idDisciplina
});
}
const getData = () => {
dados.state.nodesPadrao.forEach(async (p) => {
const i = await confereInicial(1, p.id);
console.log("ITEM", i);
dataArray.push(i);
});
};
useEffect(() => {
getData(); // can be called onClick or anywhere you want (make sure don't call on each re-render)
}, []);
console.log(dataArray);
I think you want
function confereInicial(idAluno,idDisciplina) {
return Axios.post("http://localhost:3001/api/check",{
idAluno: idAluno,
idDisciplina: idDisciplina
});
}
This will return a Promise, so confereInicial(1, p.id).then(response => console.log(response.data)) should work.

How to print the number of all commented list for every project in reactjs?

I have the list of topic(post). I want to print the number of total commented on every topic id. below what I have tried
function Component() {
const [comments, setComments] = useState([]);
useEffect(() => {
fetch(ApiUrlConstant.getApiFullUrl("https://jsonplaceholder.typicode.com/posts/"))
.then(res => res.json())
.then(json => setComments(json))
.catch(() => setComments([]));
}, []);
//
return <div>Comments: {comments.length}</div>;
}
below is the original link https://jsonplaceholder.typicode.com/posts/1/comments
I think the issue is here:
function Component() {
here you have used the function name as Component, which is a reserved keyword in React. So change it to some other name like:
function MyComponent() {
and try to run it again.
An effect function must not return anything besides a function, which is used for clean-up. You should write the async function inside your effect and call it immediately
You should use async function inside instead:
useEffect(() => {
async function fetchData() {
const response = await fetch(ApiUrlConstant.getApiFullUrl("https://jsonplaceholder.typicode.com/posts/"))
setComments(response)
}
fetchData();
}, []);

State does not update in async function

Could you please help me understand why my state was not updated when I called two async functions in the first useEffect? and what is the best way to handle async data in the case that I don't know which one comes first (API1 or API2)?
Thank you!
const MyClass = () => {
const [myState, setMyState] = useState([]);
useEffect(() => {
callApi1();
callApi2();
}, []);
const callApi1 = () => {
fetch(...).then(result => {
// the result of API 1 always comes first and result is not empty
setMyState(result);
)};
}
const callApi2 = () => {
fetch(...).then(result => {
// the result of API 2 always comes 5 - 10 seconds after the API 1
console.log(myState) => [], WHY?
});
}
}
(1.) "... why my state was not updated ..."
Your state was updated, but the callback function captures the old state of myState (as a closure). That means myState inside the callback function will always stay the same as it was when the function was created. (And it is created only when callApi2() is invoked.)
You can not access the current up-to-date state inside an asynchronous callback.
(2.) "...best way to handle async data in the case that I don't know which one comes first"
It depends on your use case.
Generally, you would set some states from your callbacks (e.g. your setMyState(result)), and a different part of your program will do something else dependent on these states, e.g. useEffect(()=>{ /* do something */ }, [ myState ]).
e.g.:
const MyClass = () => {
const [myState, setMyState] = useState([]);
const [myState2, setMyState2] = useState([]);
const [allDone, setAllDone] = useState(false);
useEffect(() => {
callApi1();
callApi2();
}, []);
useEffect(() => {
console.log( 'myState/myState2:', myState, myState2);
if( myState.length && myState2.length ){
setAllDone(true);
}
}, [ myState, myState2 ]);
const callApi1 = () => {
fetch(...).then(result => {
setMyState(result);
)};
}
const callApi2 = () => {
fetch(...).then(result => {
setMyState2(result);
});
}
}

React.js useEffect with nested async functions

I have the common warning displaying upon loading of my web app but never again...
Warning: Can't perform a React state update on an unmounted component.
This is a no-op, but it indicates a memory leak in your application.
To fix, cancel all subscriptions and asynchronous tasks in a useEffect
cleanup function.
EDIT****
It is caused by this chunk of code. I have narrowed it down to one function. It blows up when I try to setMoisture state. I am not sure why.
function getData (){
Axios.get("http://localhost:3001/api/get-value").then((response) => {
const recievedData = response.data;
const dataValue = recievedData.map((val) => {
return [val.value]
})
if (loading === true){
setLoading(false);
}
return parseInt(dataValue);
}).then((resp)=>setMoisture(resp))
}
React.useEffect(() => {
if (moisture === "initialState"){
getData();
}
}, []);
Posting the answer here (based from the comments) for completeness.
Basically, use local variables and cleanup function towards the end of useEffect(). Using this as reference:
Similar situation here
You should declare the function inside the useEffect or add it as a dependency. one way to do it's just moving your function inside the hook.
// I assumed that your useState hooks looks something similar.
const [moisture, setMoisture] = React.useState('initialState')
const [loading, setLoading] = React.useState(true)
React.useEffect(() => {
function getData() {
Axios.get("http://localhost:3001/api/get-value").then((response) => {
const recievedData = response.data;
const dataValue = recievedData.map((val) => {
return [val.value]
})
if(loading === true) {
setLoading(false);
}
return parseInt(dataValue);
}).then((resp => setMoisture(resp)))
}
if (moisture === "initialState"){
getData();
}
}, [])
You also probably want to first set your data to the state and then change your loading state to false, this is gonna prevent some bugs. This is another way to do it and manage the loading state and the promises
React.useEffect(() => {
function getData() {
setLoading(true)
Axios.get("http://localhost:3001/api/get-value")
.then((response) => {
const dataValue = response.data.map((val) => {
return [val.value]
})
// This is going to pass 0 when can't parse the data
setMoisture(parseInt(dataValue) || 0)
setLoading(false)
})
}
getData()
}, [])

JS setInterval is being called multiple times

I'm having following code
if (input.isValidLink()) {
store()
.then(db => db.update(message.from, text, json))
.then(value => value.selectAllFromDB())
.then(users => makeRequest(users));
}
Amd makeRequest function
makeRequest(user) {
setInterval(() => {
users.forEach(user => {
httpRequest(user.json);
});
}, 10000);
}
What I'm trying to do, is selectAllFromDB function returns array of users from db, and it passed as argument to makeRequest function, which is looping thru each user, send request to receive json data and do it each 10 seconds, so it will not miss any changes. Each time when any user send the link, it should also start watching for this link. Problem is that on each received link it calls makeRequest function which creates another interval and if two links were received, I'm having two intervals. First looping thru this array
[{
id: 1234,
json: 'https://helloworld.com.json',
}]
And second thru this
[{
id: 1234,
json: 'https://helloworld.com.json',
}, {
id: 5678,
json: 'https://anotherlink.com.json',
}]
Is there any way this can be fixed so only one interval is going to be created?
You need to do something like this to make sure you only create one interval and are always using the latest users list:
let latestUsers;
let intervalId;
const makeRequest = (users) => {
latestUsers = users;
if (!intervalId) {
intervalId = setInterval(() => {
latestUsers.forEach(user => {
httpRequest(user.json);
});
}, 10000);
}
}
If you don't want variables floating around you can use a self-invoking function to keep things nicely packaged:
const makeRequest = (() => {
let latestUsers;
let intervalId;
return (users) => {
latestUsers = users;
if (!intervalId) {
intervalId = setInterval(() => {
latestUsers.forEach(user => {
httpRequest(user.json);
});
}, 10000);
}
}
})();

Categories