How to set data to the state after fetching from backend? - reactjs

I want to get data from the backend and want to set those data to the state in ReactJS. Here is my source code
const [eachAsset, setEachAsset] = useState([]);
function ShowModalView(id)
{
axios.get("http://localhost:8070/assets/detail/"+id).then((res)=>{
const data = res.data
setEachAsset(data)
//console.log(eachAsset);
}).catch((err)=>{
console.log(err.message);
})
setShow2(true);
}
When I uncomment the console log, it shows an empty array. It means, setEachAsset(data) does not work properly. But I want to store data that are getting from the backend to the eachAsset state. What is the problem of this source code?

setEachAsset([...data])
I hope this would work

I would recommend using async-await which makes the code easier to read and understand the flow of the program as compared to promise chains.
const [eachAsset, setEachAsset] = useState([]);
const ShowModalView = async (id) => {
try {
const resp = await axios.get("http://localhost:8070/assets/detail/"+id);
setEachAsset(resp.data)
console.log(resp.data);
} catch (err) {
// Handle Error Here
console.error(err);
}
setShow2(true);
}

Related

How to fetch data from api when new data is added to it using UseEffect React

I'm trying to build a chat app, the issue that i'm facing is the following:
When i open the chat the useEffect render the function to fetch all the message inside that particular chat and return it to me, but if i add a new message on that particular chat the hooks is not updated and do not show the new message
Here is the code
const ChatWindow = () => {
const [loadMessage, setLoadMessage] = useState([])
const [message, setMessage] = useState({
message: ""
})
useEffect(() => {
loadMessageInChat()
},[]);
//Api call to load message
const loadMessageInChat = async() => {
try {
const attempt = await axios.get(`https://bbobras.onrender.com/api/messages/${openWindow.chatInfo._id}`,config)
if(attempt.status === 200){
//This will return an [{}]
setLoadMessage(attempt.data.data)
}
} catch (error) {
console.log(error)
}
}
}
export default ChatWindow;
I know that passing an empty array on useEffect will only call loadMessageInChat() once, so i thought to pass the hook loadMessage into it, but doing that will create an infinite loop and fetch the data over and over again because what i get from the api is an [{}] so if i'm not wrong i understand this behaviour as {} === {} --> False and that's why the infinite loop.
Sorry but i'm so junior in react and i'm trying to solve this but i can't get any solution.
Thanks!

How do I separate api / async request logic from react components when using recoil

So at the moment I am having to put my request / api logic directly into my components because what I need to do a lot of the time is set state based on the response I get from the back end.
Below is a function that I have on my settings page that I use to save the settings to recoil after the user hits save on the form:
const setUserConfig = useSetRecoilState(userAtoms.userConfig);
const submitSettings = async (values: UserConfigInterface) => {
try {
const { data: {data} } = await updateUser(values);
setUserConfig({
...data
});
} catch (error) {
console.log('settings form error: ', error);
}
}
This works perfectly...I just dont want the function in my component as most of my components are getting way bigger than they need to be.
I have tried making a separate file to do this but I can only use the recoil hooks (in this instance useSetRecoilState) inside of components and it just complains when I try and do this outside of a react component.
I have tried implementing this with recoils selector and selectorFamily functions but it gets kind of complicated. Here is how I have tried it inside of a file that has atoms / selectors only:
export const languageProgress = atom<LanguageProgress>({
key: "LanguageProgress",
default: {
level: 1,
xp: 0,
max_xp: 0
}
})
export const languageProgressUpdate = selectorFamily<LanguageProgress>({
key: "LanguageProgress",
get: () => async () => {
try {
const { data: { data } } = await getLanguageProgress();
return data;
} catch (error) {
console.log('get language progress error');
}
},
set: (params:object) => async ({set}) => {
try {
const { data: { data } } = await updateLanguageProgress(params);
set(languageProgress, {
level: data.level,
xp: data.xp,
max_xp: data.max_xp
});
} catch (error) {
console.log('language progress update error: ', error);
}
}
});
What I want to do here is get the values I need from the back end and display it in the front which I can do in the selector function get but now I have 2 points of truth for this...my languageProgress atom will initially be incorrect as its not getting anything from the database so I have to use useGetRevoilValue on the languageProgressUpdate selector I have made but then when I want to update I am updating the atom and not the actual value.
I cannot find a good example anywhere that does what I am trying to here (very suprisingly as I would have thought it is quite a common way to do things...get data from back end and set it in state.) and I can't figure out a way to do it without doing it in the component (as in the first example). Ideally I would like something like the first example but outside of a component because that solution is super simple and works for me.
So I dont know if this is the best answer but it does work and ultimately what I wanted to do was seperate the logic from the screen component.
The answer in my situation is a bit long winded but this is what I used to solve the problem: https://medium.com/geekculture/crud-with-recoiljs-and-remote-api-e36581b77168
Essentially the answer is to put all the logic into a hook and get state from the api and set it there.
get data from back end and set it in state
You may be looking for useRecoilValueLoadable:
"This hook is intended to be used for reading the value of asynchronous selectors. This hook will subscribe the component to the given state."
Here's a quick demonstration of how I've previously used it. To quickly summarise: you pass useRecoilValueLoadable a selector (that you've defined somewhere outside the logic of the component), that selector grabs the data from your API, and that all gets fed back via useRecoilValueLoadable as an array of 1) the current state of the value returned, and 2) the content of that API call.
Note: in this example I'm passing an array of values to the selector each of which makes a separate API call.
App.js
const { state, contents } = useRecoilValueLoadable(myQuery(arr));
if (state.hasValue && contents.length) {
// `map` over the contents
}
selector.js
import { selectorFamily } from 'recoil';
export const myQuery = selectorFamily({
key: 'myQuery',
get: arr => async () => {
const promises = arr.map(async item => {
try {
const response = await fetch(`/endpoint/${item.id}`);
if (response.ok) return response.json();
throw Error('API request not fulfilled');
} catch (err) {
console.log(err);
}
});
const items = await Promise.all(promises);
return items;
}
});

RTK Query response state

I'm trying to convert some Axio code to RTK query and having some trouble. The 'data' response from RTK query doesn't seem to act like useState as I thought.
Original axio code:
const [ importantData, setImportantData ] = useState('');
useEffect(() => {
async function axiosCallToFetchData() {
const response = await axiosAPI.post('/endpoint', { payload });
const { importantData } = await response.data;
setImportantData(importantData);
}
axiosCallToFetchData()
.then((res) => res)
.catch((error) => console.log(error));
}, []);
const objectThatNeedsData.data = importantData;
New RTK Query code
const { data, isSuccess } = useGetImportantDataQuery({ payload });
if(isSuccess){
setImportantData(data.importantData);
}
const objectThatNeedsData.data = importantData;
This however is giving me an infinite render loop. Also if I try to treat the 'data' object as a state object and just throw it into my component as:
const objectThatNeedsData.data = data.importantData;
Then I get an undefined error because it's trying to load the importantData before it's completed. I feel like this should be a simple fix but I'm getting stuck. I've gone through the docs but most examples just use the if statement block to check the status. The API calls are being made atleast with RTK and getting proper responses. Any advice?
Your first problem is that you always call setImportantData during render, without checking if it is necessary - and that will always cause a rerender. If you want to do that you need to check if it is even necessary:
if(isSuccess && importantData != data.importantData){
setImportantData(data.importantData);
}
But as you noticed, that is actually not necessary - there is hardly ever any need to copy stuff into local state when you already have access to it in your component.
But if accessing data.importantData, you need to check if data is there in the first place - you forgot to check for isSuccess here.
if (isSuccess) {
objectThatNeedsData.data = data.importantData;
}
All that said, if objectThatNeedsData is not a new local variable that you are declaring during this render, you probably should not just modify that during the render in general.

RTK query send data, is coming back manipulated

At work, I need to make an infinite scroll pagination with RTK-query. I am using Firestore as a DB, and I made a very basic version with just async functions super quickly. Now I need to convert what I have made to RTK query.
As I was doing this I noticed I was not longer able to fetch more data from Firestore because my query with Firestore was not responding. After doing some digging, I found out that RTK query is somehow changing my data. I will explain below with my code. This very well would be a Firestore problem as well.
async queryFn() {
try {
const { prod, lastDoc } = await fetchInitialData();
return { data: { prod, lastDoc } };
} catch (err) {
return { error: err };
}
This is how I am sending my data with RTK-query, I left a lot of code out since it is not needed. Down below is my react file, that calls my RTK query hook, as well as checks to see if the data being sent from RTK is correct
const [products, setProducts] = useState<BasicProductData[] | null>();
const [lastDocSaved, setLastDocSaved] = useState<any>();
const { data, isLoading, isError, error, isSuccess, refetch } = useFetchProductsQuery(null);
useEffect(() => {
getPost();
if (data && lastDocSaved) {
console.log(data.lastDoc === lastDocSaved);
}
}, []);
const getPost = async () => {
const { prod, lastDoc } = await fetchInitialData();
setProducts(prod);
setLastDocSaved(lastDoc);
};
As you can tell both of these ways of getting the initial data is pretty much exactly the same. Finally here is my pagination function, to fetch more data from firebase to keep the infinite scroll going.
const getMorePosts = async () => {
const postData = await fetchMoreData(lastDocSaved);
setLastDocSaved(postData.lastDoc);
setProducts([...products, ...postData.prod]);
};
I know that 'data' I get back from the RTK hook is not the same since I manually have checked if is by using that console.log in the useEffect. I also know it does not work since if I setLastDocSaved(data.lastDoc) the pagination does not work.
This is strange to me, since both data.lastDoc and const { lastDoc } = await fetchInitialData() come back as pretty much identical in the console, however lastDoc properly queries from firebase and data.lastDoc does not.

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.

Resources