Why is React not rendering my map function? - javascript

I have a map function to a state that I'm sure it is not empty because its states have been logged.
When i load the page it shows the div "did Load", but thats about it. Console also does not register he "running" log.
This is for CS50W course, so I am using in-browser Babel transformer. Someone suggested i completly switched my approach to compiling the code but that would mean learing and making a whole new setup, so I was hoping that is not the reason this is not working.
class Posts extends React.Component{
constructor(props){
super(props)
this.state = {
aposts: [],
isLoaded: false
}
}
componentDidMount() {
console.log('running go fetch');
let currentList = [];
fetch('/posts')
.then(response => response.json())
.then(posts => {
for (var i = 0; i < posts.length; i++) {
currentList.push(posts[i])
console.log(currentList[i])
}
});
console.log(currentList)
this.setState({ aposts: currentList, isLoaded: true }, () => {
console.log(this.state.aposts, 'aposts');
});
}
render(){
var isLoaded = this.state.isLoaded
if (isLoaded = true){
return (
<div>
<div>did load</div>
{this.state.aposts.map(function(apost) {
console.log("running")
return (<li key = {apost.id}> {apost.id} </li>)
})}
</div>
);
}
else{
<div>No load</div>
}
}

componentDidMount() {
console.log('running go fetch');
let currentList = [];
fetch('/posts')
.then(response => response.json())
.then(posts => {
for (var i = 0; i < posts.length; i++) {
currentList.push(posts[i])
console.log(currentList[i])
}
console.log(currentList)
this.setState({ aposts: currentList, isLoaded: true }, () => {
console.log(this.state.aposts, 'aposts');
});
});
When making api call it take some time so JS just run that work on some other process and your main process keep working.
Read about sync/asyn
and when you use .then it was when JS complete api call and get the response from server it should run anything which is inside .then
So you have to set state in .then so it will set value for state after it fetch values
You said you have to setup and learn to do setup. Why are you not using
[CRA][1] it will setup everything for you.

Related

How can I wait until the functions finish in Reactjs?

Hi I am new to reactjs and I am trying to build button with a function doing some calculation by Reactjs. The logic is, first I will get two lists from database by two functions. After these 2 functions return results and setState, the calculate function will continue and do its job. But somehow the state is not being updated and it will crash. How can I secure the state is being updated before to the calculate? Thanks a lot!
Code:
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
dividendList : [],
divisorList : [],
};
}
getDividend(){
var self = this;
axios.post(SERVER_NAME + 'api/getDividend', {})
.then(function(response){
let results = response.data;
console.log(results)
self.setState({ dividendList : results.data})
})
.catch(function(err){
console.log(err)
});
}
getDivisor(){
var self = this;
axios.post(SERVER_NAME + 'api/getDivisor', {})
.then(function(response){
let results = response.data;
console.log(results)
self.setState({ divisorList : results.data})
})
.catch(function(err){
console.log(err)
});
}
doCal = () => {
var self = this;
self.getDividend();
self.getDivisor();
const { dividendList , divisorList} = self.state;
# then will loop the list and do math
# but since the state is not update, both lists are empty []
}
Tried Promise;
getDivisor(){
var self = this;
return new Promise((resolve, reject) => {
axios.post(SERVER_NAME + 'api/draw/getDivisor', {})
.then(function(response){
resolve(response)
})
.catch(function(err){
resolve();
});
})
}
I think the issue here is self.getDividend(); and self.getDivisor(); are async operations. They will take some time to complete. By the time you hit the next line const { dividendList , divisorList} = self.state;, these operations are not complete and you will end up getting empty lists.
One way to address this is using moving your doCal function logic after getDividend and getDivisor are completed. You can also execute these in parallel instead of in a sequence. I used async format instead of .then(). It is just a sysntatic sugar. You can achieve the same using .then() if you prefer that way
async function doCalc() {
const prom1 = axios.get('https://..dividentList');
const prom2 = axios.get('https://..divisorList');
const results = await Promise.all([ prom1, prom2]); // wait for both promise to complete
// look inside results to get your data and set the state
// continue doCal logic
}
Using .then()
request1('/dividentList')
.then((res) => {
//setState for divident
return request2('/divisorList'); // this will return a promise to chain on
})
.then((res) => {
setState for divisor
return Promise.resolve('Success') // we send back a resolved promise to continue chaining
})
.then(() => {
doCalc logic
})
.catch((err) => {
console.log('something went wrong');
});
I looked at your code and thought it should be changed like this to be correct.
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
dividendList: [],
divisorList: [],
};
}
componentDidMount() {
// the API just need be called once, so put here
this.getDividend()
this.getDivisor()
}
componentDidUpdate(_, prevState) {
const { dividendList , divisorList } = this.state;
// Ensure that the answer is only calculated once
// the answer is only be calculated while the two list data are obtained
if (
prevState.divisorList.length === 0 &&
prevState.dividendList.length === 0 &&
divisorList.length > 0 &&
dividendList.length > 0
) {
doCal()
}
}
getDividend(){
var self = this;
axios.post(SERVER_NAME + 'api/getDividend', {})
.then(function(response){
let results = response.data;
console.log(results)
self.setState({ dividendList : results.data})
})
.catch(function(err){
console.log(err)
});
}
getDivisor(){
var self = this;
axios.post(SERVER_NAME + 'api/getDivisor', {})
.then(function(response){
let results = response.data;
console.log(results)
self.setState({ divisorList : results.data})
})
.catch(function(err){
console.log(err)
});
}
doCal = () => {
const { dividendList , divisorList } = this.state;
# then will loop the list and do math
# but since the state is not update, both lists are empty []
this.setState({ answer: 'xxx' })
}
render() {
const { dividendList, divisorList, answer } = this.state
if (dividendList.length === 0 && divisorList.length === 0) {
return <div>Loading...</div>
}
if (!answer) {
return <div>Error</div>
}
return <div>{answer}</div>
}
}
The following are just some suggestions to make the code easier to read,
you can use arrow function so that you don't need to write self.setState({...})
getDividend = () => {
axios.post(SERVER_NAME + 'api/getDivisor', {})
.then((response) => {
let results = response.data;
console.log(results)
this.setState({ divisorList : results.data})
})
.catch((err) => {
console.log(err)
});
}
and you can also use async/await instead of promise.then
getDividend = async () => {
const response = await axios.post(SERVER_NAME + 'api/getDivisor', {})
let results = response.data;
console.log(results)
this.setState({ divisorList : results.data})
}
Set 'dividendList' and 'divisorList' equals to 'null' by default. Then, when a function that uses those lists is called, make a if statement to verify if those states goes for false (if they are still null) then return inside the function, if not, it should not crash anything.

setState while looping through an array of props - React ComponentDidUpdate

I am working on a project and needed to setState after componentDidMount.(The props am expecting in the child component are derived at mount. Hence i can only setState after)
Only option i was able to come up with was, using componentDidUpdate.
The props parent component is an array derived from an axios fetched data.
The goal here is to loop though the array and fetch data for each from the URL showing in the code below to then setState of the child component.
Trying what i normally do, I could not stop the infinite loop fired at componentDidUpdate.
Here is my code.
Parent
render(){
return (
<div className="App">
<EachCountry countryList= {this.state.CountriesList}/>
</div>
Child component
async componentDidUpdate(prevProps, prevState, snapshot){
if(this.state.countryList.length < this.props.countryList.length){
await this.props.countryList.map(m=>{
axios ({
method:"get",
url:`/countryupdate/${m}`
}).then(res=>{
console.log(res.data)
this.setState(crntState=>({
countryList:[...crntState.countryList, res.data]
}))
})
})
}
}
console log works perfectly fine. But when i tried to setState, i run into infinite loop with something like 5000 plus error messages.
And my other trick was
async componentDidUpdate(prevProps, prevState, snapshot){
if(this.state.countryList.length < this.props.countryList.length){
await this.props.countryList.map(m=>{
var newdata = axios ({
method:"get",
url:`/countryupdate/${m}`
})
console.log(newdata)
this.setState(crntState=>({
countryList:[...crntState.countryList, newdata.data]
}))
})
}
}
And this one returns promises and not the needed data.
Help Fam
What am i missing?
Your issue is likely caused by derived state: state that is dependent on props and is an anti pattern in react:
https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#when-to-use-derived-state
See below on a plausible workaround, though its recommended you restructure your data flow.
Try something like this to first only send 1 update to state:
async componentDidMount(){
//variable to store new data
const allNewData = [];
//an async data fetcher
const getNewData = async(m) => {
let newData = await axios({
method: "get",
url: `/countryupdate/${m}`
})
allNewData.push(newData.data);
}
//an async for loop
async function updateData() {
for (const m of countryList) {
await getNewData(m);
}
this.setState(crntState => ({
countryList: [...crntState.countryList, ...allNewData]
}))
}
await updateData();
}
If the above doesn't work (which it might not), then use getDerivedStateFromProps instead of componentDidMount and replace setState with a return obj
static getDerivedStateFromProps(props, state) {
if (this.state.countryList.length < this.props.countryList.length) {
...
return {
countryList: [...state.countryList, ...allNewData]
};
}
let newState = await updateData();
return newState;
}
If that doesn't work, then revert back to componentDidMount and use shouldComponentUpdate for the conditional
shouldComponentUpdate(nextProps, nextState) {
return this.state.countryList.length != nextState.countryList.length;
}
Incase I didn't get the syntax just right look at this code snippet
function mockAxios(m) {
return new Promise(function(resolve, reject) {
setTimeout(() => resolve({
data: `${m}'s data`
}), 1000)
});
}
function setState(arr) {
console.log(arr);
console.log("state has been set")
}
const countryList = ["A", "B", "C", "D", "E"];
///////////////////////////////////////////////
async function componentDidMount() {
const allNewData = [];
async function getNewData(m) {
let newData = await mockAxios(m);
allNewData.push(newData.data);
}
async function updateData() {
for (const m of countryList) {
await getNewData(m);
console.log("fetching data...")
}
setState(allNewData);
}
await updateData();
}
componentDidMount();

React setState for API doesn't update even thought it gets new parameter

I'm having issues with this web app im working on.
https://food-search-octavian.netlify.com/
Now, on the recipe page https://food-search-octavian.netlify.com/recipes my search doesn't actually work. It's binded to Enter key which works, the request itself works, but when I try to set the state to the new search, it doesn't update.
This is the code in the component:
this.state = {
recipes: [],
search: "chicken",
loading: false,
height: 0
};
this.getRecipes = this.getRecipes.bind(this);
this.changeActive = this.changeActive.bind(this);
}
componentDidMount() {
this.getRecipes(this.state.search);
}
componentDidUpdate(prevProps, prevState) {
if (prevState.search !== this.state.search) {
this.getRecipes(this.state.search);
}
}
changeActive(newSearch) {
this.setState({
search: newSearch
});
}
getRecipes = async e => {
this.setState({
loading: true
});
await recipesRequest(e).then(response => {
this.setState({
recipes: response,
loading: false
});
});
};
This is the code for the request:
const axios = require("axios");
const recipesRequest = param => {
let api_key = "*";
let api_id = "*";
return axios
.get(
`https://api.edamam.com/search?q=chicken&app_id=${api_id}&app_key=${api_key}`,
{
headers: {
"Content-Type": "application/json"
}
}
)
.then(function(response) {
return response.data.hits;
});
};
export default recipesRequest;
This is the component with the search that updates the active state in the first compomnent:
this.state = {
input: ""
};
this.checkInput = this.checkInput.bind(this);
this.newSearch = this.newSearch.bind(this);
this.handleKeyDown = this.handleKeyDown.bind(this);
}
checkInput(e) {
var value = e.target.value;
this.setState({
input: value
});
}
handleKeyDown = e => {
if (e.key === "Enter") {
console.log(e.key);
let choice = this.state.input;
this.newSearch(choice);
}
};
newSearch(choice) {
this.props.changeActive(choice);
this.setState({
input: ""
});
}
From what I read, setState is async but I kinda have the exact logic in the other page of my web app and it works.
I'm guessing it is your getRecipes function. React's setState is "asynchronous, but not in the javascript async/await sense. It is more like state updates during the current render cycle are queued up to be processed for the next render cycle.
getRecipes = async e => {
this.setState({
loading: true
});
await recipesRequest(e).then(response => {
this.setState({
recipes: response,
loading: false
});
});
};
Here you are awaiting the execution of the state updates you've queued up, which normally are processed after the function returns. Try not awaiting it.
getRecipes = e => {
this.setState({
loading: true
});
recipesRequest(e).then(response => {
this.setState({
recipes: response,
loading: false
});
});
};
This allows the fetch to occur, which is asynchronous, but it correctly allows the state updates to be queued. The state will update loading to be true, and returns the (likely) unresolved Promise, and when the request resolves (any number of render cycles later) it updates state again loading false and with the recipes.
codesandbox
EDIT
In the codesandbox both ways work, so perhaps you are not receiving/processing response data correctly, or you have a malformed request.

ReactJs re-render on state change

I am trying to do a sequence of fetching some API data and manipulating it as follows:
Fetch list of string addresses via API
Convert each of this string addresses to a geocoded location via API
Displaying these geocoded addresses as markers on a map
I am having some trouble with getting the timing with all these asynchronous activities right (I am pretty new to Javascript).
Here's what I have so far:
class Map extends Component {
constructor(props) {
super(props);
this.state = {
addresses = [],
geocodedAddresses = []
}
}
componentDidMount() {
const geocodedAddresses = []
fetch('.....')
.then(result => result.json())
.then(addresses => this.setState({ addresses }, function() {
this.state.addresses.forEach(address => {
Geocode.fromAddress(address).then(geocodedAddress => {
geocodedAddresses.push(geocodedAddress['results'][0]['geometry']['location'])
})
})
}))
console.log(geocodedAddresses) //Correctly contains the geocoded addresses
this.setState({ geocodedAddresses })
}
}
render() {
this.state.addresses.map(a => console.log(a)) //Evaluates correctly
this.state.geocodedAddresses.map(ga => console.log(ga)) //Yields nothing....
.....
}
}
So I don't quite understand why React does not re render when I do this.setState({ geocodedAddresses }) - Shouldn't react re render when I do setState?
There are a couple errors with your code. In the first place, the state object is being created with equals instead of colons:
this.state = {
addresses: [],
geocodedAddresses: []
}
In the second place, you should take into account that your code is asynchronous. Only when the promises generated by the call to Geocode.fromAddress resolve you have the data for your geocodedAddresses.
In the componentDidMount, you are console logging the geocodedAdresses and you report that you are seeing the right values. This is only because the log is update after the promises resolve. But when you do the console.log the value at that moment for geocodedAdresses is an empty array. And that is the value that is being inserted in the component state.
In order to set the correct value for the state, you should call setState when all your Geocode.fromAddress promises have resolved. In order to do that you can use Promise.all method.
So, your code would look like:
class Map extends Component {
constructor(props) {
super(props);
this.state = {
addresses: [],
geocodedAddresses: []
}
}
componentDidMount() {
fetch('.....')
.then(result => result.json())
.then(addresses => {
Promise.all(addresses.map(address => Geocode.fromAddress(address)))
.then(geocodedAddresses => {
this.setState({
addresses,
geocodedAddresses
})
});
}))
}
}
render() {
this.state.addresses.map(a => console.log(a)) //Evaluates correctly
this.state.geocodedAddresses.map(ga => console.log(ga)) //Yields nothing....
.....
}
}
Note that with this solution setState is only being called once.
Since all your state refers to addresses information, it could make sense to merge that information into a single key in the state. You could initialize your state in the constructor as:
this.state = {
addresses: []
};
And then, you could populate the state once all the promises resolve:
componentDidMount() {
fetch('.....')
.then(result => result.json())
.then(addresses => {
Promise.all(addresses.map(address => Geocode.fromAddress(address)))
.then(geocodedAddresses => {
const addressesState = addresses.map((address, i) => {
return {
address,
geocodedAddress: geocodedAddresses[i]
};
});
this.setState({ addresses: addressesState })
});
}))
}
}
You're right it's the async is slightly off. The console.log will populate after it "appears" in the console, but when setState has been called (and also when console.log has been called) its value is still [].
if you're using async/await you can wait for the fetch chain to completely finish, or put the setState within the then. With the setState callback, it's probably better to do the latter.
componentDidMount() {
const geocodedAddresses = []
fetch('.....')
.then(result => result.json())
.then(addresses => this.setState({ addresses }, function() {
this.state.addresses.forEach(address => {
Geocode.fromAddress(address).then(geocodedAddress => {
geocodedAddresses.push(geocodedAddress['results'][0]['geometry']['location'])
})
})
console.log(geocodedAddresses) //Correctly contains the geocoded addresses
this.setState({ geocodedAddresses })
}))
}}
or
componentDidMount = async () => {
const geocodedAddresses = []
let addresses = await fetch('.....').then(result => result.json())
addresses.forEach(address => { // assuming this is sync
Geocode.fromAddress(address).then(geocodedAddress => {
geocodedAddresses.push(geocodedAddress['results'][0]['geometry']['location'])
})
})
this.setState({ geocodedAddresses,addresses })
}}

The state changed but the render method from react does not trigger

I am currently self teaching myself code and at the moment diving into React.js. I am building a simple app which displays beer information pulled through the punkAPI (https://punkapi.com/documentation/v2).
In my app I wanted to add some kind of "infinite" scroll which would pull more data from the API when the user reaches the bottom of the page.
At the moment, the handleScroll method works, and updates the state of the component but it does not trigger the render method and I was wondering why.
I realize that there are a lot of things wrong with my code and I plan to restructure it, add some booleans in this.state to check if there is still more data to load, to handle errors and to not trigger the handleScroll method so intensively.
However, I am still wondering why there is no rendering happening even though the state is updated.
class BeerList extends Component {
constructor() {
super()
this.state = {
loading: false,
beers: []
}
this.handleScroll = this.handleScroll.bind(this)
}
componentDidMount() {
window.addEventListener('scroll', this.handleScroll, true);
this.setState({
loading: true
})
fetch("https://api.punkapi.com/v2/beers?per_page=12")
.then(response => response.json())
.then(data => {
this.setState({
loading: false,
beers: data
})
})
}
handleScroll() {
const checkForNewBeers = () => {
let lastBeerCard = document.querySelector(".beer-list > div:last-child");
let lastBeerCardOffset = lastBeerCard.offsetTop + lastBeerCard.clientHeight;
let pageOffset = window.pageYOffset + window.innerHeight;
if (pageOffset <= lastBeerCardOffset - 10) {
return;
}
this.setState(prevState => {
const beers = prevState.beers;
const page = (prevState.beers.length / 12) + 1;
fetch(`https://api.punkapi.com/v2/beers?per_page=12&page=${page}`)
.then(response => response.json())
.then(data => {
for (let item of data) {
beers.push(item);
}
console.log({
beers: beers
});
return {
beers: beers
}
});
});
}
document.addEventListener("scroll", function (event) {
checkForNewBeers();
});
}
render() {
let beerCards = []
if (this.state.beers.length > 0) {
beerCards = this.state.beers.map(beer => {
return <BeerCard
key = {
beer.id
}
img = {
beer.image_url
}
title = {
beer.name
}
description = {
beer.description
}
/>
})
}
return ( <
div className = "container" >
<
div className = "row beer-list" > {
beerCards
} <
/div> < /
div >
)
}
}
export default BeerList
So BeerCards are correctly appended when the page is loading then when you scroll the console shows you that the state is updated (way too much but still). I would expect the page loading a shit ton of BeerCards but nothing is happening. Why is that?
Instead of returning an object from your async fetch, call setState inside the .then()
let beers = this.state.beers;
const page = (this.state.beers.length / 12) + 1;
fetch(`https://api.punkapi.com/v2/beers?per_page=12&page=${page}`)
.then(response => response.json())
.then(data => {
for (let item of data) {
beers.push(item);
}
this.setState({ beers });
});
As soon as you invoke fetch which is the last statement inside setState, an undefined would be returned. Try transforming the setState parameter to an async function:
this.setState(async prevState => {
const beers = prevState.beers;
const page = prevState.beers.length / 12 + 1;
let response = await fetch(
`https://api.punkapi.com/v2/beers?per_page=12&page=${page}`
);
response = await response.json();
for (const item of response) {
beers.push(item);
}
console.log({
beers: beers
});
return {
beers: beers
};
});
You are so close! By simply adding the keyword "async" in front of "prevState" in "this.setState" and "return await" in front of "fetch", the state of your app should be updated and trigger a rerender as expected.

Categories