Rendering React components with promises inside the render method

戏子无情 提交于 2019-11-26 08:04:42

问题


I have a component which gets a collection of items as props and maps 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.


回答1:


render() method should render UI from this.props and this.state, so to async 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

Here is a quick sample code:
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() {
    var self = this;
    this.props.items.map((item) => {
        ImageStore.getImageById(item.imageId).then(image => {
            var mapping = {id: item.imageId, url: image.url};
            var newUrls = self.state.imageUrls.slice();
            newUrls.push(mapping);

            self.setState({
                imageUrls: newUrls
            });
        })
    });
}

render() {
    return (<div>
        this.state.imageUrls.forEach(mapping => {
            <div>id: {mapping.id}, url: {mapping.url}`</div>
        })
        </div>
    );
}



回答2:


Or you can use react-promise :

Install the package :

npm i react-promise

And your code will look something like so :

import Async from 'react-promise'

var items = this.props.items.map(function (item) {
    var imageSrc = Utils.getImageUrlById(item.get('ImageId')); // <-- this contains an async call
    return (
        <Async promise={imageSrc} then={(val) => <MenuItem text={item.get('ItemTitle')} imageUrl={val}/>} />    
    );
});

Edit: Oct 2019

The last build of react-promise provide a hook called usePomise:

import usePromise from 'react-promise';

const ExampleWithAsync = (props) => {
  const {value, loading} = usePromise<string>(prom)
  if (loading) return null
  return <div>{value}</div>}
}

Full docs: react-promise



来源:https://stackoverflow.com/questions/33242378/rendering-react-components-with-promises-inside-the-render-method

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!