Setting state inside a promise inside a useEffect hook in React - reactjs

I've been trying to figure out why my code doesn't work for an hour now. So basically I want to fetch some data from a MySQL database, my serverside code is working as expected but whenever I try to fetch it in the client with the following code setting the state fails:
const [data, setData] = useState(null);
useEffect(() => {
const loadData = () => {
fetch("http://localhost:5000/getusers")
.then((response) => response.json())
.then((data) => {
setData(data); // data is undefined but when consoled-out it's in proper form
});
};
loadData();
console.log(data);
}, [data]);
data is an array of objects. I assume I can't pass setState in a promise because I've added a conditional for rendering the data so even if it's null it just won't render but I receive a TypeError: data.map is not a function (it would be great if someone could explain how this happens).

Related

React setState inside useEffect async

I am attempting to perform a series of Axios requests inside the useEffect() of a react component. I am aware that these requests are asynchronous, and I should maintain a piece of "loading" state that specifies if series of requests have been completed.
const [state, updateState] = useState([])
const [loading, setLoading] = useState(true)
useEffect(() => {
let innerstate = []
allRespData.map(single_response => {
axios.post("<URL>", {
raw_narrative: single_response[index].response
})
.then((response) => {
innerstate.push(response.data)
});
})
updateState(innerstate)
setLoading(false)
}, []);
if (loading)
return (<h3> Loading </h3>)
else {
console.log(state)
return (<h3> Done </h3>)
}
I would expect the output from the above code to be a list containing the data of each response. Unfortunately, I think that data only arrives midway through the console.log() statement, as initially an empty list [] is logged, however the list is expandable- therein my expected content is visible.
I am having a hard time doing anything with my state at the top, because the list length is constantly 0, even if the response has already loaded (loading == false).
How can I assert that state has been updated? I assume the problem is that the loading variable only ensures that a call to the updateState() has been made, and does not ensure that the state has actually been updated immediately thereafter. How can I ensure that my state contains a list of response data so that I can continue doing operations on the response data, for example, state.forEach().
You're not awaiting any of the requests, so updateState will get called before any of the responses have had time to come back. You'll be setting the state as [] every time. You also need to return your axios.post or the data won't get passed to .then
There are lot of nicer ways to handle this (I'd recommend looking at the react-query library, for example). However, to make this work as it is, you could just use Promise.all(). Something like:
useEffect(() => {
Promise.all(
allRespData.map(single_response =>
axios
.post('<URL>', { raw_narrative: single_response[index].response })
.then(response => response.data)
.catch(error => {
// A single error occurred
console.error(error);
// you can throw the error here if you want Promise.all to fail (or just remove this catch)
})
)
)
// `then` will only be called when all promises are resolved
.then(responses => updateState(responses))
// add a `.catch` if you want to handle errors
.finally(() => setLoading(false));
}, []);

React useEffect causing infinite re-render despite passing argument to dependency array

The 'myPosts' has an object with multiple posts inside it.. I wanted the user profile to immediately show the post after it is uploaded so I passed 'myposts' in the dependency array.
But the problem is that the component is re-rendering infinitely. How can I make it so that it re-renders once, only when a new post is uploaded? I can't understand why passing 'myposts' in the array is causing infinite renders instead of only once.
const [myposts, setPosts] = useState([]);
useEffect(() => {
fetch('/mypost', {
headers: {
cookie: 'access_key',
},
})
.then((res) => res.json())
.then((data) => {
// console.log(data);
setPosts(data.myposts);
});
}, [myposts]);
When fetch resolves, it modifies myposts, which triggers a fetch because it is listed as dependency of useEffect, which modifies myposts, and so it continues...
It seems that myposts depends on the result of the fetch, not the other way around. So I would suggest removing myposts from the dependency list.
The useEffect hook is called when myposts gets updated. In the final .then of your fetch, you're updating it via setPosts. The best way to fix this is by making the dependency array an empty array.
But this won't solve the issue of updating posts from the server, but this can also be done in a periodic function with setInterval. This would result in something like the code below.
const [myposts, setPosts] = useState([]);
const update = fetch('/mypost', {
headers: {
cookie: 'access_key',
},
})
.then((res) => res.json())
.then((data) => {
// console.log(data);
setPosts(data.myposts);
});
useEffect(() => {
update()
const interval = setInterval(update, 30000)
return () => clearInterval(interval)
}, []);

react promise in functional component with UseEffect and UseState doesn't work

I'm having issue fetching data and setting them to state in a functional component using useEffect and useState.
My problem is that I would like to keep the data fetching done with axios async/await in a separate file for improving application scalability but then I don't understand how to update the state in case the promise is resolved (not rejected).
In particular I'm trying to retrieve from the promise an array of table rows called data in state, but I can't figure out how to set the result of the responce in the state
Here's the code in the component file:
const [data, setData] = React.useState([]);
useEffect(() => {
const { id } = props.match.params;
props.getTableRows(id).then((res) => {
setData(res);
});
//or is it better:
//props.getTableRows(id).then(setData); ?
}, []);
and my action.js:
export const getTableRows = (id, history) => async (dispatch) => {
try {
const res = await axios.get(`/api/test/${id}`);
dispatch({
type: GET_TEST,
payload: res.data.rows,
});
} catch (error) {
history.push("/test");
}
};
In the above picture it can be seen that the rows array inside the promise response called in action.js is present.
This code unfortunately doesn't work, error: Uncaught (in promise) TypeError: Cannot read property 'forEach' of undefined
I've found out another solution which is the define the promise in the useEffect method like this:
useEffect(() => {
const { id } = props.match.params;
const fetchData = async () => {
const result = await axios.get(`/api/test/${id}`);
setData(result.data.rows);
};
fetchData();
}, []);
this code is working in my app but as I said I don't like having the promises in the components files I would like instead to have them all the promise in action.js for app scalability (in case url change I don't have to change all files) but in that case I don't know where to put the setData(result.data.rows); which seems the right choise in this last example
Any suggestions?
Thanks
You still need to use async/await. The .then() is executed when the value is returned, however your function will continue rendering and won't wait for it. (causing it to error our by trying to access forEach on a null state). After it errors the promise via .then() will update the values and that is why you can see them in the console.
useEffect(() => {
async function getData() {
const { id } = props.match.params;
await props.getTableRows(id).then((res) => {
setData(res);
});
}
getData()
}, []);
Additionally, before you access a state you can check for null values (good practice in general).
if (this.state.somestate != null) {
//Run code using this.state.somestate
}
I don't see you return anything from getTableRows. You just dispatch the result, but hadn't return the res for the function call.
And it will be helpful if you provided error trace.

React: how can I process the data I get from an API before I render it out?

I have no issues fetching the data from an API using useEffect. That works fine.
The problem is that I need to apply some processing to the data before I actually render it out (in this case, I need to shuffle the array that I receive).
I tried a million different ways, but I just can't find the right place to write that logic. Basically, it won't work anywhere.
What is the right way of going about this?
you can do everything with data before setState.
is useEffect when you fetched data from Api, shuffle it and then do setState.
little example:
useEffect(() => {
axios.get("http://example.com/data").then(response => {
const data = shuffle(response.data);
setState(data);
})
});
useEffect(() => {
const fetchData = async () => {
await axios.get("http://example.com/data").then(response => {
const data = shuffle(response.data);
setState(data);
});
};
fetchData();
return () => {
// Clean up func
}
}, []); //[] will prevent infinite API calling.

I cannot collect data from API using Axios + React

I'm beginner with React. I have 2 different cases where I'm using React Hooks which I cannot receive the data from my local API properly.
Case 1:
export const RegisterT = () => {
const [test, setTest] = useState()
const addrState = {}
axios.get('http://127.0.0.1:3333/states', { addrState })
.then(res => {
setTest(res.data)
console.log(test)
})
...
}
It works with the state test displaying correctly the content from the API but I don't know why/how the Axios continues calling the API infinity - endless. (Ps: the very first call it returns undefined, then the next ones it works) What am I doing wrong?
To fix this I've tried to use useEffect like this (Case 2):
export const RegisterT = () => {
const [test, setTest] = useState()
const addrState = {}
useEffect(() => {
axios.get('http://127.0.0.1:3333/states', { addrState })
.then(res => {
setTest(res.data)
console.log(test);
})
}, [])
...
}
Now the Axios works only once but no data is coming from the API. Maybe I should use async/wait for this case but I cannot make it work. Does anyone know how to fix that (Case 1 or/and Case 2)?
Thanks.
Updating the state is an asynchronous operation. So the state is not really updated until the next time the component gets rendered. If you want to capture the correct state, you can either console.log(res.data) or wrap that inside the useEffect hook with test as dependency.
export const RegisterT = () => {
const [test, setTest] = useState()
const addrState = {}
// effect only runs when component is mounted
useEffect(() => {
axios.get('http://127.0.0.1:3333/states', { addrState })
.then(res => {
setTest(res.data);
});
}, []);
// effect runs whenever value of test changes
useEffect(() => {
console.log(test);
}, [test]);
}
That way it is guaranteed that the console.log runs when the value of test is updated.
Also the reason the API request is invoked once is you have not mentioned anything in the dependency array. [] empty dependency array runs the effect when the component is mounted for the first time.
async/await is just a wrapper around Promise object. So they would behave similarly.
The solution with useEffect is good. If you don't use it each render will call the request. This is the same if you put there console.log with any information. The reason why you don't see the data in the useEffect is that the value of the state is not updated in current render but in the next which is called by setter of the state. Move the console.log(test); after useEffect to see the data. On init it will be undefined but in the next render, it should contain the data from the request.

Resources