ReactJs re-render on state change - javascript

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 })
}}

Related

Why is React not rendering my map function?

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.

React Native How to get multiple APIs at the same time?

For my project, I have to get several APIs in my projects, these APIs are linked to the others, i.e. they have the same data ...
Here is my code
export default class App extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
Promise.all([
getData(),
getData('?page=2'),
])
.then(([dataSource1, dataSource2]) => {
this.setState({
isLoading: false,
isLoading2: false,
dataSource1,
dataSource2,
});
})
.catch((error) => {
// handle errors
});
}
render() {
const getData = (subpath = '') => fetch(`https://api.rawg.io/api/games${subpath}`)
.then(res => res.json())
.then(result => result.results);
console.log(getData)
}
I tried with axios but without success ...
When I remove the comment, it shows me only the second fetch ...
You need two separate fetch calls for each API. To wait for both to finish, use Promise.all.
const getData = (subpath = '') => fetch(`https://api.rawg.io/api/games${subpath}`)
.then(res => res.json())
.then(result => result.results);
componentDidMount() {
Promise.all([
getData(),
getData('?page=2'),
])
.then(([dataSource1, dataSource2]) => {
this.setState({
isLoading: false,
isLoading2: false,
dataSource1,
dataSource2,
});
})
.catch((error) => {
// handle errors
});
}

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.

receiving data from api but cant setState

For some reason, I am unable to update the state of my component with the data from my fetch request.
When I console.log, I can see that I am getting the data. I'm not sure what this could be
If this is a noob issue, please bear with me. I'm still learning.
Here is my code:
import React, { Component } from "react";
class Nav extends Component {
state = {
searchTerm: "",
posts: []
};
getPost = e => {
e.preventDefault();
const val = e.target.value;
this.setState({ searchTerm: val }, () => {
if (val !== "") {
fetch(
`http://www.reddit.com/search.json?q=${val}&sort=relevance&limit=25`
)
.then(res => res.json())
.then(data => console.log(data.data))
//.then(data => this.setState({ posts: data.data }))
//.then(console.log(this.state.posts))
.catch(err => console.log(err));
}
});
};
Actually everything is right and going well, just your logging is wrong.
.then(console.log(this.state.posts))
That logs the state now and passes the result of console.log() (undefined) to the .then chain as a callback which is obviously wrong. I guess you meant:
.then(() => console.log(this.state.posts))
But that still won't work as setState does not trigger a state update immeadiately, but somewhen. After that it calls the second argument as a callback, so you should log then:
.then(data => this.setState({ posts: data.data }, () => {
console.log(this.state.posts);
}))
Altogether:
const response = fetch(
`http://www.reddit.com/search.json?q=${val}&sort=relevance&limit=25`
).then(res => res.json());
// PS: I would not build up a chain if the logic is not really "chained"
response.then(data => console.log(data.data));
response.then(data => this.setState({ posts: data.data }, () => console.log(this.state.data)));
response.catch(err => console.log(err));

React doesn't render array value from firebase and array's length is 0 despite having the value on console

I am trying to fetch the data from firebase and push the data to the array one by one in a loop. But array of the fetched data doesn't show up on the React component.
I console logged and I found that as the picture below strangely shows, console.log(array.length) is 0, but console.log(array.length) show the contents of array.
code is as follows.
const getThread = (threadID) => {
return db.ref(`threads/${threadID}`).once('value')
.then((snapshot)=>{
return snapshot.val()
})
}
const getThreadList = (id) => {
let threadList = []
return db.ref(`users/${id}/friends`).once('value')
.then((snapshot)=>{
let friends = snapshot.val()
for(let key in friends){
if(friends.hasOwnProperty(key)) {
getThread(friends[key].threadID)
.then((thread)=>{
threadList.push(thread)
})
}
}
return threadList
})
}
class SomeComponent extends Component{
componentDidMount(){
let { id } = this.props
getThreadList(id)
.then(threadList => {
this.setState(() => ({ threadList }))
})
}
render(){
//value of threadList doses't appear
I suspect this is due to the misuse of Promise so console.log shows value but React component doesn't show anything. But I also suspect the React side and couldn't locate exactly where the problem is.
It would be much appreciated if you suggest any clue on this problem.
You are not waiting for the nested promises to resolve. You could use Promise.all to accomplish the task.
const getThreadList = (id) => {
return db.ref(`users/${id}/friends`).once('value')
.then((snapshot)=>{
let friends = snapshot.val()
const threads = []
for(let key in friends){
if(friends.hasOwnProperty(key)) {
threads.push(getThread(friends[key].threadID))
}
}
return Promise.all(threads)
})
}
Or using Object.values
const getThreadList = id => {
return db
.ref(`users/${id}/friends`)
.once("value")
.then(snapshot => {
const loadingThreads = Object.values(snapshot.val()).map(friend =>
getThread(friend.threadID)
);
return Promise.all(loadingThreads);
});
};

Categories