I have a component which gets a collection of items as props and map
s them to a collection of components which are rendered as children of a parent component. We use images stored in WebSQL
as byte arrays. Within the map
function I get an image Id from the item and make an async call to the DAL
in order to get the byte array for the image. My problem is that I cannot propagate the promise in to React, since it was not designed to deal with promises in rendering (not as far as I can tell anyway). I come from a C#
background, so I guess I'm looking for something like the await
keyword for resync-ing branched code.
The map
function looks something like this (simplified):
var items = this.props.items.map(function (item) {
var imageSrc = Utils.getImageUrlById(item.get('ImageId')); // <-- this contains an async call
return (
<MenuItem text={item.get('ItemTitle')}
imageUrl={imageSrc} />
);
});
and the getImageUrlById
method looks like this:
getImageUrlById(imageId) {
return ImageStore.getImageById(imageId).then(function (imageObject) { //<-- getImageById returns a promise
var completeUrl = getLocalImageUrl(imageObject.StandardConImage);
return completeUrl;
});
}
This doesn't work, but I don't know what I need to modify to make this work. I tried adding another promise to the chain, but then I get an error because my render function return a promise instead of legal JSX. I was thinking that maybe I need to leverage one of the React
life-cycle methods to fetch the data, but since I need the props
to already be there, I can't figure out where I can do this.
render()
method should render UI from this.props
and this.state
, so to asynchronously load data, you can use this.state
to store imageId: imageUrl
mapping.
Then in your componentDidMount()
method, you can populate imageUrl
from imageId
. Then the render()
method should be pure and simple by rendering the this.state
object
Note that the this.state.imageUrls
is populated asynchronously, so the rendered image list item will appear one by one after its url is fetched. You can also initialize the this.state.imageUrls
with all image id or index (without urls), this way you can show a loader when that image is being loaded.
constructor(props) {
super(props)
this.state = {
imageUrls: []
};
}
componentDidMount() {
this.props.items.map((item) => {
ImageStore.getImageById(item.imageId).then(image => {
const mapping = {id: item.imageId, url: image.url};
const newUrls = this.state.imageUrls.slice();
newUrls.push(mapping);
this.setState({ imageUrls: newUrls });
})
});
}
render() {
return (
<div>
{this.state.imageUrls.map(mapping => (
<div>id: {mapping.id}, url: {mapping.url}</div>
))}
</div>
);
}