Reputation: 91
I'm trying to design a component where the user can click a button which will trigger a giphy API call that will eventually render a series of gif images.
So far I'm able to successfully get everything done except the actual image rendering. Here's my code so far:
retrieveURLs() {
*bunch of stuff to make API call and return an array of URLs related
to the category of the button the user pushes*
}
renderGifs() {
this.retrieveURLs().then(function(results) {
console.log(results); //logs array of URLs
return results.map(function(url, index) {
console.log(url); //logs each url
return (<img key={index} src={url} alt="" />)
}, this);
});
}
render() {
return(
<div id="gif-div">
{this.renderGifs()}
</div>
)
}
Nothing gets rendered despite each console.log() event indicating that the URL(s) are at least being passed properly.
I do something similar for the parent component to render the buttons from an array of categories that looks like this:
btnArray = [*bunch of categories here*];
renderButtons() {
return btnArray.map(function(item, i) {
let btnID = btnArray[i].replace(/\s+/g, "+");
return <button type='button' className="btn btn-info" onClick={this.handleCategorySelect} key={i} id={btnID} value={btnID}>{btnArray[i]}</button>
}, this)
}
The buttons are rendered properly, but my images are not. Neither the renderbuttons nor the rendergifs metohds alter the state. Honestly I can't see a meaningful difference between the two, so I'd like to have some help figuring out why one works but the other doesn't.
Upvotes: 2
Views: 1215
Reputation: 36169
first of all you forgot the return statement:
renderGifs() {
return this.retrieveURLs().then(function(results) {
...
}
but this won't solve anything as it is returning a Promise
.
You need to save request results in the state and then map it:
constructor(props){
super(props);
this.state = { images: [] };
}
componentDidMount() {
this.renderGifs();
}
renderGifs() {
this.retrieveURLs().then(function(results) {
console.log(results); //logs array of URLs
this.stateState({ images: results });
});
}
render() {
return(
<div id="gif-div">
{
this.state.images.map((url, index) => (<img key={index} src={url} alt="" />);
}
</div>
)
}
Upvotes: 0
Reputation: 841
This is the nature of asynchronous functions; you can't return a value from within a callback to the original call site. If you were to write:
const res = this.retrieveURLs().then(function(results) {
return results;
});
you'd only be changing the resolution value of the promise. res
won't be assigned the value of results
, but rather it will be assigned the promise created by this.retrieveURLs()
, and the only way to retrieve the value of a resolved promise is by attaching a .then
callback.
What you could do is this:
this.retrieveURLs().then(results => {
this.setState({ images: results });
});
Now your internal state will be updated asynchronously, and your component will be told to re-render with the new data, which you can use in your render function by accessing the state.
Note: I'm using an arrow function to create the callback in the above example, because otherwise this
won't be bound to the right context. Alternatively, you could do the old that = this
trick, or use Function#bind
.
Upvotes: 3
Reputation: 1949
The problem lies with the rendering function for the images and the way React does diffing between the previous and current state. Since the fetch is asynchronous and the render is not, when the fetch is completed React doesn't know that it needs to re-render you component. There are ways to force a re-render in this case, but a better solution is to use the functionality that's already part of the shouldUpdate
check.
Your implementation might change to look something like the following:
class Images extends Component {
state = {
images: [],
error: null
}
componentDidMount() {
return this.retrieveImages()
.then(images => this.setState({images}))
.catch(error => this.setState({error}))
}
... rest of your class definition
render () {
return (
<div>
{this.state.images.map(image => <img src={image.url} />)}
</div>
)
}
}
I would also have some handling for bad results, missing key / values, etc. I hope that works for you, if not let me know! :)
Upvotes: 2