I'm trying to request JSON data from two different URLs using fetch and format it with JSX before loading it into the state object. It's only retrieving the data from one of the URLs.
I have the URLs in an object like this:
var urls = {
'recent': 'https://fcctop100.herokuapp.com/api/fccusers/top/recent',
'alltime': 'https://fcctop100.herokuapp.com/api/fccusers/top/alltime',
};
I want to fetch the data by iterating over the object containing the URLs, turn it into an array of JSX objects, then store the result in my main object's state.
for (var key in urls) {
var url = urls[key];
fetch(url).then((response) => {
response.json().then((json) => {
var userNumber = 0;
var html = json.map((user) => {
return (
<tr>
<td>{++userNumber}</td>
<td>
<img src={user.img} alt={user.username} />
{user.username}
</td>
<td>{user.recent}</td>
<td>{user.alltime}</td>
</tr>
);
}); // Close json.map block.
this.setState({
'data': { [key]: html }
});
console.log(key);
}); // Close response.json block.
}); // Close fetch block.
}
It should fetch the data from the recent
URL and store the formatted result in this.state.data.recent
. Then it should do the same thing for alltime
, storing the result in this.state.data.alltime
. What it actually seems to do is fetch alltime
twice.
This makes no sense to me at all. I don't even have a guess as to what is happening.
I have noticed that if I put log statements into fetch's arrow function it shows that it's hitting both keys and both URLs, but if I put log statements at the end of the response.json()
arrow function, after json.map
, it shows alltime
twice.
It's processing the second URL twice. I know that the order isn't guaranteed, but it does tend to process them in the order in which I defined them and I think that's significant here. Even if it is a problem with asynchronous requests happening at the same time, though, they're two different requests ultimately storing data in two different places, aren't they?
A version of this code is also at CodePen, but I'm likely to change that as I try to figure out what's going on here.
Full code:
var urls = {
'recent': 'https://fcctop100.herokuapp.com/api/fccusers/top/recent',
'alltime': 'https://fcctop100.herokuapp.com/api/fccusers/top/alltime',
};
function View(props) {
return (
<form className="view">
<label>View: </label>
<label><input type="radio" name="view" value="recent" checked={props.view == 'recent'} />Past 30 Days</label>
<label><input type="radio" name="view" value="alltime" checked={props.view == 'alltime'} />All Time</label>
</form>
);
}
class Main extends React.Component {
constructor() {
super();
this.state = {
'view': 'recent',
'data': {
'recent': null,
'alltime': null,
},
};
}
componentWillMount() {
for (var key in urls) {
var url = urls[key];
console.log(key);
console.log(url);
fetch(url).then((response) => {
response.json().then((json) => {
var userNumber = 0;
var html = json.map((user) => {
return (
<tr>
<td>{++userNumber}</td>
<td>
<img src={user.img} alt={user.username} />
{user.username}
</td>
<td>{user.recent}</td>
<td>{user.alltime}</td>
</tr>
);
}); // Close json.map block.
console.log('Setting state for ' + key);
this.setState({
'data': { [key]: html }
});
}); // Close response.json block.
}); // Close fetch block.
}
/*
fetch(urls.recent).then((response) => {
response.json().then((json) => {
var view = 'recent';
var html = json.map((user) => {
return (
<tr>
<td>
<img src={user.img} alt={user.username} />
{user.username}
</td>
<td>{user.recent}</td>
<td>{user.alltime}</td>
</tr>
);
}); // Close json.map block.
this.setState({
'data': { [view]: html },
});
}); // Close response.json block.
}); // Close fetch block.
*/
}
render() {
return (
<div>
<View view={this.state.view} />
<table>
{this.state.data[this.state.view]}
</table>
</div>
);
}
}
ReactDOM.render(<Main />, document.getElementById('app'));
img {
width: 2em;
height: 2em;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<main id="app"></main>