React Too many re-renders. useEffect + useState + axios - reactjs

I´m new to react so I guess I haven't understood properly how the use effect and useStates works to prevent this error. I tried already to set the states in the array of useEffect to render only on the change but no success as well.
I´m trying to build dynamic forms configurations from a knack app (no code database app creator). So the end result would be a config I can use to render the form.
Below is a sample of my code.
const [fields, setObject] = useState([]);
const [groups, setGroup] = useState();
const [fieldGroupConfig, setFieldGroupConfig] = useState();
const [formConfig, setFormConfig] = useState();
useEffect(() => {
axios.get(KnackUrl('entityDefinitions-ObjectBased', "object_4"), { headers: KnackHeaders() })
.then(response => {
setObject(response.data.object.fields)
})
axios.get(KnackUrl('getMultiple-ObjectBased', "object_31"), { headers: KnackHeaders() })
.then(response => {
setGroup(response.data.records)
})
axios.get(KnackUrl('getMultiple-ObjectBased', "object_32"), { headers: KnackHeaders() })
.then(response => {
setFieldGroupConfig(response.data.records)
})
}, [])
if(groups && fieldGroupConfig && fields){
groups.forEach((group) => {
group.fields = [];
fieldGroupConfig.forEach((fieldGroup) => {
if (fieldGroup.field_333_raw[0].id === group.id) {
fields.forEach((field) => {
if (fieldGroup.field_330 === field.key) {
group.fields.push(field);
}
});
}
});
});
setFormConfig(groups);
}
console.log('group prepared =>', formConfig);
Thanks in advance!

Related

Fetching an array of objects from POKEAPI using REACT.js and AXIOS {Answered}

I chose to start learning API handling with POKEAPI. I am at a step where I need to get the flavor_text of each pokemon (the description let's say) but I can't for some reason.
Here is the JSON structure for one specific pokemon: https://pokeapi.co/api/v2/pokemon-species/bulbasaur.
And here is my useEffect trying to get it. The line fetching the habitat works and displays on my website so I guess my issue comes from my map in setDescription but I can't be sure.
export default function Card({ pokemon }, { key }) {
const src = url + `${pokemon.id}` + ".png";
const [habitat, setHabitat] = useState(null);
const [descriptions, setDescriptions] = useState([]);
useEffect(() => {
const controller = new AbortController();
axios
.get(url2 + `${pokemon.name}`, { signal: controller.signal })
.then((res) => setHabitat(res.data.habitat.name))
.then((res) =>
setDescriptions(
res.data.flavor_text_entries.map((ob) => ob.flavor_text)
)
)
.catch((err) => {
if (axios.isCancel(err)) {
} else {
console.log("warning your useEffect is behaving");
}
});
return () => {
// cancel the request before component unmounts
controller.abort();
};
}, [pokemon]);
I tried console logging descriptions or descriptions[0] but that doesn't work.
Since you only setting up the state from those data and it doesn't looks like the second result need to wait the result from the first to perform you can do both on the same response/promise :
useEffect(() => {
const controller = new AbortController();
axios
.get(url2 + `${pokemon.name}`, { signal: controller.signal })
.then((res) => {
setHabitat(res.data.habitat.name))
const flavorTextEntrieList = res.data.flavor_text_entries;
setDescriptions(flavorTextEntrieList.map((ob) => ob.flavor_text))
})
.catch((err) => {
if (axios.isCancel(err)) {
} else {
console.log("warning your useEffect is behaving");
}
});
return () => {
// cancel the request before component unmounts
controller.abort();
};
}, [pokemon]);
Each then need to return something to be handled in next chainable then. Replace .then((res) => setHabitat(res.data.habitat.name)) with .then((res) => { setHabitat(res.data.habitat.name); return res; })

building dinamically array for react-simple-image-slider

React simple slider needs array of images at the star and some code inside html to initiate the slider
const images = [
{ url: "images/1.jpg" },
{ url: "images/2.jpg" },
{ url: "images/3.jpg" },
{ url: "images/4.jpg" },
{ url: "images/5.jpg" },
{ url: "images/6.jpg" },
{ url: "images/7.jpg" },
];
for images array I made
const [images, setImages] = useState([]);
maybe different way is better correct me pls.
Then I have useEffect where I fetch the data
useEffect(() => {
const getData= async () => {
try {
const response = await fetch(`url`, {
fetch request....
const ImagesArray = imagesArrayfromFetch.map((image) => ({
url: `/img/${image}`,
}));
console.log(ImagesArray);
setImages(ImagesArray);
console.log(images);
When I console.log ImagesArray - it gives me correctly filled array object.
console.log(images) gives me undefined. Here is an error probably
Then inside html I build the slider object
const App = () => {
return (
<div>
<SimpleImageSlider
width={896}
height={504}
images={images}
showBullets={true}
showNavs={true}
/>
</div>
);
}
So because setImages do not put array inside images slider object creates without images.
Is there a way to fix this?
It seems like you have a race condition.
Setting new state must happen after resolve.
Could be done in fetch().then( /* set state */ )
Cleaner way would be with await/async:
const fetchImages = async () => {
try {
const data = await fetch(...);
if (data) {
const ImagesArray = data.images.map((image) => ({
url: `/img/${image}`,
}));
console.log(ImagesArray);
setImages(ImagesArray);
}
} catch(error) {
console.error(error);
}
}
useEffect(() => fetchImages(), [])

why is my state not updated in useEffect?

const user = useSelector(state => state.user)
const [gioHangChiTiet, setGioHangChiTiet] = useState([])
const [gioHangSanPham, setGioHangSanPham] = useState([])
useEffect(() => {
const dataGioHang = async () => {
try {
const res = await axios.get(`${apiUrl}api/giohangs`, {
headers: {
token: `Bearer ${user.user?.accessToken}`
}
})
console.log(res.data.data.sach)
setGioHangChiTiet(res.data.data)
console.log(gioHangChiTiet "it is empty")
} catch (error) {
console.log(error)
}
}
if (user.user) {
dataGioHang()
// console.log(gioHangChiTiet)
}
}, [user])
That is my code. I trying to save gioHangChiTiet with new data but it's always is an empty array. I try console.log this and I think it will work but it's not. But if I change any thing in this code, gioHangChiTiet will update new data and console.log this. Can anyone help me and explain why? Thank you so much. I spent a lot of time figuring out how to solve it :(( . UPDATED : I fixed it. Thanks a lots ( console.log not run because it in useEffect , if i console after useEffect, i will have true value)
const user = useSelector(state => state.user)
const [gioHangChiTiet, setGioHangChiTiet] = useState([])
const [gioHangSanPham, setGioHangSanPham] = useState([])
useEffect(() => {
const dataGioHang = async () => {
try {
const res = await axios.get(`${apiUrl}api/giohangs`, {
headers: {
token: `Bearer ${user.user?.accessToken}`
}
})
console.log(res.data.data.sach)
// setGioHangChiTiet(res.data.data.sach)
setGioHangChiTiet(res.data.data)
console.log(gioHangChiTiet)
} catch (error) {
console.log(error)
}
}
if (user.user) {
dataGioHang()
// console.log(gioHangChiTiet)
}
}, [user])
Add user to your dependency array. Otherwise the useEffect wont be able to check your if statement. If you're using CRA you should get a warning in your terminal.
useEffect takes two arguments first one is callback function and second one is dependency array.
useEffect(() => {
// this is callback function
},[ /* this is dependency array */ ])
If you want to trigger the callback function every time a state changes you need to pass that state in dependency array.
useEffect(() => {
console.log(someState)
},[someState])
In above code someState will get logged each time it's value changes.
If your dependency array is empty you useEffect callback function will trigger ONLY ONCE.
In your case if you want trigger callback function on change of user state or any other state simply pass it in dependency array.
Can you give this a try:
const user = useSelector(state => state.user)
const [gioHangChiTiet, setGioHangChiTiet] = useState([])
const [gioHangSanPham, setGioHangSanPham] = useState([])
useEffect(() => {
const dataGioHang = new Promise((resolve,reject) => {
( async() => {
try {
const res = await axios.get(`${apiUrl}api/giohangs`, {
headers: {
token: `Bearer ${user.user?.accessToken}`
}
})
console.log(res.data.data.sach)
setGioHangChiTiet(res.data.data.sach)
setGioHangChiTiet(res.data.data)
console.log(gioHangChiTiet)
resolve();
} catch (error) {
console.log(error)
reject()
}})();
})
if (user.user) {
dataGioHang().then(()={ console.log(gioHangChiTiet);
})
.catch(() => console.log("Error executing dataGioHang"))
}
}, [user])

How to implement each item without using for loop inside the useEffect?

What I'm trying to do is to add some attributes to the clients that I call from the backend.
I have done it use it the for loop, but the Axios function is inside the useEffect hook, and it caused me a lot of errors.
ClientList.js (errors)
export default function ClientList() {
const [clinetlist, setClinetlist] = useState([]);
const [clientlist, setClientlist] = useState(0);
useEffect(() => {
const clientParams =
"?userName=" + currentClient "&clientId="
setClientlist([]);
axios
.get(process.env.REACT_APP_API_BACKEND_URL + "clients" + clientParams)
.then((response) => {
response.data.forEach(function (item) {
let newListclient = {
id: item.id,
previewurl: item.clinet_preview,
name: item.clinet_name,
type: "type of client",
description: "client description",
status: "Ready",
};
setClientlist((oldClientlist) => [...oldClientlist, neClientitem]);
});
})
.catch((error) => {
console.log(error);
});
}, [count]);
Like this, every data that comes from the backend, I gave them the attributes by creating a forEach loop, but this caused me errors, bc of the useEffect render.
At this moment the code is like this which doesn't have any errors:
ClientList.js (no errors)
export default function ClientList() {
const [clinetlist, setClinetlist] = useState([]);
const [clientlist, setClientlist] = useState(0);
useEffect(() => {
const clientParams =
"?userName=" + currentClient "&clientId="
setClientlist([]);
axios
.get(process.env.REACT_APP_API_BACKEND_URL + "clients" + clientParams)
.then((response) => {
const {data} = response;
setClientlist(data)
});
})
.catch((error) => console.log(error));
}, [count]);
What I'm trying to do is to make the for loop like in the (ListClients(with errors)), but not inside the useEffect hook. Want to assign every data that comes from backend with those attributes newListclient.
How can I do it?
You have added dependency in useEffect for count.
Make another useEffect with no dependencies and place your code inside it and use the logic below as your logic has some issues.
export default function ClientList() {
const [clinetlist, setClinetlist] = useState([]);
const [clientlist, setClientlist] = useState(0);
useEffect(()=>{
const clientParams =
"?userName=" + currentClient "&clientId="
setClientlist([]);
axios
.get(process.env.REACT_APP_API_BACKEND_URL + "clients" + clientParams)
.then((response) => {
let finalCustom=response.data.map(function (item) {
return {...item, id: item.id,
previewurl: item.clinet_preview,
name: item.clinet_name,
type: "type of client",
description: "client description",
status: "Ready",}
});
setClientlist(finalCustom)
})
.catch((error) => console.log(error));
},[])
useEffect(() => {
}, [count]);

ts/react - fetch in useEffect gets called multiple times

in my functional component I want to fetch data once the component mounts. But unfortunately, the request gets fired three times, until it stops. Can you tell me why?
const [rows, setRows] = useState<any[]>([]);
const [tableReady, setTableReady] = useState<boolean>(false);
const [data, setData] = useState<any[]>([]);
const getData = async () => {
const user = await Amplify.Auth.currentAuthenticatedUser();
const token = user.signInUserSession.idToken.jwtToken;
const apiurl = 'xxx';
fetch(apiurl, {
method: 'GET',
headers: {
'Authorization': token
}
})
.then(res => res.json())
.then((result) => {
setData(result);
})
.catch(console.log)
}
useEffect(() => {
if (!tableReady) {
getData();
if (data.length > 0) {
data.forEach((element, i) => {
const convertedId: number = +element.id;
setRows(rows => [...rows, (createData(convertedId, element.user))]);
});
setTableReady(true);
}
}
}, []);
return (
<div className={classes.root}>
<MUIDataTable
title={""}
data={rows}
columns={columns}
/>
</div>
);
I updated my question due to the comment.
The useEffect is missing a dependency array, so its callback is invoked every time the component renders.
Solution
Add a dependency array.
useEffect(() => {
if (!tableReady) {
getData();
if (data.length > 0) {
data.forEach((element, i) => {
const convertedId: number = +element.id;
rows.push(convertedId);
});
setTableReady(true);
}
}
}, []); // <-- dependency array
An empty dependency array will run the effect once when the component mounts. If you want it to ran when any specific value(s) update then add these to the dependency array.
See Conditionally firing an effect
Edit
It doesn't appear there is any need to store a data state since it's used to populate the rows state. Since React state updates are asynchronously processed, and useEffect callbacks are 100% synchronous, when you call getData and don't wait for the data to populate, then the rest of the effect callback is using the initially empty data array.
I suggest returning the fetch request from getData and just process the response data directly into your rows state.
const getData = async () => {
const user = await Amplify.Auth.currentAuthenticatedUser();
const token = user.signInUserSession.idToken.jwtToken;
const apiurl = 'xxx';
return fetch(apiurl, {
method: 'GET',
headers: {
'Authorization': token
}
});
}
useEffect(() => {
if (!tableReady) {
getData()
.then(res => res.json())
.then(data => {
if (data.length) {
setRows(data.map(element => createData(+element.id, element.user)))
}
})
.catch(console.error)
.finally(() => setTableReady(true));
}
}, []);

Resources