Reputation: 191
Edit: Moved the getMetadata() call out of render, as suggested by @Robin Zigmond. Still having trouble getting the proper value from getMetadata() returned, but that's different from my original question. Updating code just for reference.
Quick background: I have a fair amount of experience with shell scripting and stuff like Perl and PHP, but javascript and especially the libraries on top of it like React feel very foreign to me. Forcing myself to develop this with React to teach myself something new, so if you see any bad practices, feel free to suggest improvements!
That said, I'm trying to write a simple app that:
I have 1, 2, and 4 generally worked out, but struggling with 3. Here's what I have so far, with less important code snipped out:
class VGSC_Search extends React.Component {
constructor() {
super();
this.state = {
submitted: false,
platform: '',
games: [],
metadata: [],
files: [],
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({platform: event.target.value});
}
handleSubmit(event) {
this.setState({submitted: true});
<SNIP - vars>
fetch(encodeURI(searchurl + query + fields + sort + rows))
.then(result => result.json())
.then(data => this.setState({
games: data.response.docs.map(game => ({
identifier: game.identifier,
title: game.title,
creator: game.creator,
year: game.year,
uploader: this.getMetadata(game.identifier),
}))
}));
event.preventDefault();
}
getMetadata(id) {
<SNIP - vars>
fetch(encodeURI(metadataurl + id + metadatainfo))
.then(result => result.json())
.then(data => this.setState({metadata: data.response}));
}
renderResults() {
const {games} = this.state;
const {metadata} = this.state;
return (
<SNIP - table header>
<tbody>{games.map(game =>
<tr key={game.identifier}>
<td><a href={'https://archive.org/details/' + game.identifier}>{game.title}</a></td>
<td>{game.creator}</td>
<td>{game.year}</td>
<td>{game.uploader}</td>
</tr>
)}</tbody>
</table>
);
}
render(){
return (
<div>
<SNIP - form>
<br/>
{this.state.submitted && this.renderResults()}
</div>
);
}
}
My problem is with that Uploader field, which should run getMetadata() on the given identifier and return the name of the uploader. I'm having trouble figuring out how to reference the result, but my biggest problem is actually that my browser keeps running getMetadata() on all displayed items in an endless loop. Eg, from the Developer Tools log:
XHR GET https://archive.org/metadata/4WheelThunderEuropePromoDiscLabel/metadata/uploader [HTTP/1.1 200 OK 105ms]
XHR GET https://archive.org/metadata/AirJapanCompleteArtScans/metadata/uploader [HTTP/1.1 200 OK 279ms]
XHR GET https://archive.org/metadata/AeroWings-CompleteScans/metadata/uploader [HTTP/1.1 200 OK 287ms]
XHR GET https://archive.org/metadata/BioCodeVeronicaLEDCT1210MNTSCJ/metadata/uploader [HTTP/1.1 200 OK 279ms]
XHR GET https://archive.org/metadata/Biohazard2ValuePlusDreamcastT1214MNTSCJ/metadata/uploader [HTTP/1.1 200 OK 282ms]
XHR GET https://archive.org/metadata/4WheelThunderEuropePromoDiscLabel/metadata/uploader [HTTP/1.1 200 OK 120ms]
XHR GET https://archive.org/metadata/AirJapanCompleteArtScans/metadata/uploader [HTTP/1.1 200 OK 120ms]
<SNIP>
The first search returns 5 results, and getMetadata() is run correctly on those five results, but note that it starts repeating. It'll do that endlessly until I reload the page.
I'm guessing that has something to do with running getMetadata() inside a function that's being rendered, but I'm not sure why, and having trouble thinking of a good alternate way to do that.
Can anyone explain why I'm seeing this behavior, and (hopefully) offer a suggestion on how to properly implement this?
Thanks!
Upvotes: 1
Views: 277
Reputation: 18249
The infinite loop happens because you're running getMetadata
inside render
(or rather inside a function that's called from render
) - and that results in changing state, which causes a rerender, and so on in an endless loop.
It's bad practice in any situation to call any function which cause any "side effects" from within render
- render
should simply determine the output given the component's props and state. Typically data-fetching like you're doing is done inside componentDidMount
and/or componentDidUpdate
. However in this case, where you appear to need to fetch additional data based on the first response in handleSubmit
, it seems that you need to call getMetadata
from within the final .then
callback of that function.
After your latest edit, I can see the problem with the approach you tried here. this.getMetadata
doesn't actually return anything. To fix it, you can return
the Promise returned by fetch
:
getMetadata(id) {
<SNIP - vars>
return fetch(encodeURI(metadataurl + id + metadatainfo))
.then(result => result.json())
.then(data => this.setState({metadata: data.response}));
}
and then use asyc/await
inside handleSubmit
, with Promise.all
to manage the array:
fetch(encodeURI(searchurl + query + fields + sort + rows))
.then(result => result.json())
.then(async data => {
const games = await Promise.all(data.response.docs.map(async game => ({
identifier: game.identifier,
title: game.title,
creator: game.creator,
year: game.year,
uploader: await this.getMetadata(game.identifier),
})));
this.setState({ games });
});
Upvotes: 1