I'm having an issue with useEffect and useState. I'm trying to fill a state with data from an api, but it results in an infinite loop, even if I use an array with dependencies. It works when I try to get a name. The problem occurs when I try to get an array or an object.
Here the code:
const id = props.match.params.id;
const [pokemon, setPokemon] = useState({});
useEffect(() => {
let cancelRequest;
axios
.get(`https://pokeapi.co/api/v2/pokemon/${id}`, {
cancelToken: new axios.CancelToken(
(cancel) => (cancelRequest = cancel)
),
})
.then((res) => {
setPokemon(res.data);
console.log(pokemon);
})
.catch((err) => {
console.log(`ERROR:: ${err.message}`);
});
return () => {
cancelRequest();
};
}, [id, pokemon]);
Here a sample of data from the console:
{abilities: Array(2), base_experience: 64, forms: Array(1), game_indices: Array(20), height: 7, …}
Thank you.
Do not use the axios request inside the useEffect.
Create another function for this and use useCallback. For example:
const fetchPokemon = useCallback(() => {
axios.get(`https://pokeapi.co/api/v2/pokemon/${id}`)
.then((res) => {
setPokemon(res.data);
})
.catch(() => {}
}, [id])
useEffect(() => {
fetchPokemon()
}, [fetchPokemon])
If you pass in pokemon into the dependency array, it will update every single time you call setPokemon since the pokemon update aka, you have an infinte loop.
Related
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; })
I'm trying to add an object array in the state series. With this code the useEffect function get stuck in an infinite loop. How can I solve this? Without adding the series const as parameter I get the error about a missing dependency and the code will only run on startup.
import React, { useState, useEffect } from "react";
const LineChart = () => {
const [series, setSeries] = useState([]);
useEffect(() => {
const url = "http://localhost:4000";
const fetchData = async () => {
try {
fetch(url, {
method: "GET",
})
.then((response) => response.json())
.then((data) => {
let chartData = data.testRunSummaries
.map(function (testrun) {
return {
duration: testrun.endTime - testrun.startTime,
label: testrun.testSetName + "#" + testrun.userFacingId,
testrun: testrun.testRunId,
status: testrun.status,
};
});
setSeries(chartData, ...series);
console.log(series);
});
} catch (error) {
console.log(error);
}
};
fetchData();
}, [series]);
return (
...
);
};
export default LineChart;
series is in your useEffect dependency array. And your useEffect is changing series. So obviously you'll be stuck in a infinite loop.
You don't need your series to be set as a dependency for useEffect.
As your useEffect will only be trigger once on mount, you can just have
setSeries(chartData);
And if you really need to have former values of series, you should use
setSeries(series => [...chartData, ...series]);
Moreover, seeing your
setSeries(chartData, ...series);
console.log(series);
Let me remind you that setState is async there is no way this will log your updated state :)
So I'm creating a simple MERN App, backend is working properly, but when working with useState hook in frontend is causing issues.
what im trying to do is to fetch "users" data(an array of object with field username) from backend endpoints, and updating the users array which is a hook, but it only updates with the last itm of the incoming username and not list of all usernames!!
code for fetching and updating the hook:
const [users, setUsers] = useState([]);
const getUsers = () => {
fetch("http://localhost:5000/users")
.then(res => res.json())
.then(data => {
console.log(data); //line 17
data.map((itm) => {
console.log([itm.username]) //line 19
setUsers([...users, itm.username])
})
})
.catch(err => console.log(err))
}
useEffect(() => {
getUsers();
}, [])
console.log(users); //line 30
what I want is to get a list of usernames in the "users" state!
something like this:
users = ["spidey", "thor", "ironman", "captain america"]
console.log is also not showing any errors...
console window
pls help, can't figure out where it's getting wrong?
The issue is two-fold, first you are using Array.prototype.map to iterate an array but are issuing unintentional side-effects (the state updates), and second, you are enqueueing state updates in a loop but using standard updates, each subsequent update overwrites the previous so only the last enqueued update is what you see in the next render.
Use either a .forEach to loop over the data and use a functional state update to correctly update from the previous state.
const getUsers = () => {
fetch("http://localhost:5000/users")
.then(res => res.json())
.then(data => {
console.log(data);
data.forEach((itm) => {
console.log([itm.username]);
setUsers(users => [...users, itm.username]);
})
})
.catch(err => console.log(err));
}
Or use the .map and just map data to the array you want to append to the users state.
const getUsers = () => {
fetch("http://localhost:5000/users")
.then(res => res.json())
.then(data => {
console.log(data);
setUsers(users => users.concat(data.map(itm => itm.username)));
})
.catch(err => console.log(err));
}
you can set the map result in a variable after that you can call the useState on it.
const [users, setUsers] = useState([]);
const getUsers = () => {
fetch("http://localhost:5000/users")
.then(res => res.json())
.then(data => {
console.log(data); //line 17
const userNameData = data.map(itm => itm.username)
setUsers(...users, userNameData)
})
.catch(err => console.log(err))
}
useEffect(() => {
getUsers();
}, [])
console.log(users);
I have problem with loop on axis GET request, and I can't understood why.
const [ state, setState ] = useState<any[]>([]);
ids.forEach((id) => {
getData(id)
.then((smth: Map<string, any>[]) => getNeededData(smth, id));
});
console.log(JSON.stringify(state));
and getData (getNeededData is only choose parameters):
export const getData= async (id: string) => {
const response = await Axios.get(`/rest/${id}`)
.then((res: { data: any; }) => res.data);
return response;
};
I should have 2 response (it's 2 id in variable "ids"), but I have first, second, first, second, first, and this in a loop.
Why it's been working like this?
What I can change for fix this?
By putting that forEach at the top level of your component function, you're running it every time the function is called by React to render its contents, which React does when state changes. The code you've shown doesn't set state, but I'm assuming your real code does.
To do it only when the component first mounts, wrap it in a useEffect callback with an empty dependency array:
const [ state, setState ] = useState<any[]>([]);
useEffect(() => {
ids.forEach((id) => {
getData(id)
.then(/*...*/);
});
}, []);
If all of the results are going in the state array, you probably want to use map and Promise.all to gether them all up and do a single state change with them, for instance:
const [ state, setState ] = useState<any[]>([]);
useEffect(() => {
Promise.all(
ids.map((id) => {
return getData(id).then(/*...*/);
})
)
.then(allResults => {
// Use `allResults` to set state; it will be an array in the same order
// that the `id` array was in
})
.catch(error => {
// handle/report error
});
}, []);
I have two useEffect-s. One is used to fetch data from api and save it in the state and second is called only once and it starts listening to websocket event.
In the websocket event handler I log the fetched data but it always has the default value.
Even though fetching data completes successfully and the list is drawn on UI, the value of list is always empty - [].
const [list, setList] = useState([]);
useEffect(() => {
axios.get("https://sample.api.com/get/list")
.then(res => {
setList(res.data);
});
}, [window.location.pathname.split('/')[2]]);
useEffect(() => {
webSocket.on('messageRecieved', (message) => {
console.log(list);
});
}, []);
Your second effect is referencing the initial list value (an empty array) due to closure. This is why useEffect should reference all of its dependencies in its second argument.
But in this case, where you don't want to subscribe to the webSocket event each time the list is updated, you could use React's refs on the list.
const listValue = useRef([]);
const [list, setList] = useState(listValue.current);
When setting the value:
res => {
listValue.current = res.data
setList(listValue.current);
}
And when retrieving the list in a one time fired useEffect:
useEffect(() => {
webSocket.on('messageRecieved', (message) => {
console.log(listValue.current);
});
}, []);
try changing
.then(res => {
to
.then((res) => {
Would clarify if you added console logs to each hook or said if the values are preset in them:
useEffect(() => {
axios.get("https://sample.api.com/get/list")
.then((res) => {
console.log(res.data)
setList(res.data);
});
}, [window.location.pathname.split('/')[2]]);
useEffect(() => {
webSocket.on('messageRecieved', (message) => {
console.log(list);
console.log(message);
});
}, []);
You could also add error catch, just in case:
.catch((error) => {
console.log(error.response)
})