Uxmaan Ali
Uxmaan Ali

Reputation: 349

Render images from files array on client with Reactjs

I am new to React and I'm implementing a multi-file upload system. I current have implemented user driven file selection via a click event handler on a button which adds the selected file into an array.

For some reason when ever I select a file, the following error is shown in the console which causes the application to stop working:

Uncaught Invariant Violation: Objects are not valid as a React child (found: [object Promise]). If you meant to render a collection of children, use an array instead

For reference, my interface for this functionality looks like: enter image description here

And here are the relevant parts of my component implementation:

state = {
  selectedPortifolio: {},
  portflioFiles: []
}

And this method is called on the file onchange event

loadPortfolio = (e) => {
        if (e.target.files) {
            this.setState({
                selectedPortifolio: {
                    file: e.target.files[0],
                    filename: e.target.files[0].name
                }
            });
        }
    };

And when add button is clicked then this method is called.

addPortfolio = () => {
        if (
            this.state.selectedPortifolio &&
            this.state.selectedPortifolio.file
        ) {
            let items = this.state.portflioFiles;
            items.push(this.state.selectedPortifolio);
            this.setState({ selectedPortifolio: {}, portflioFiles: items });
            console.log(this.state.portflioFiles);
        }
    };

After all this i load this array into the div

{this.state.portflioFiles.length > 0 ? (
    this.state.portflioFiles.map((item, index) => {
        return (
            <div className="portfolio-file" key={index}>
                {getDataURL(item.file).then((data) => {
                    return (
                        <image
                            src={data}
                            alt={item.filename}
                        />
                    );
                })}
            </div>
        );
    })
) : (
    <div></fdv>
)}

And getDataURL method is defined in another file.

export const getDataURL = (file) => {
    return new Promise((resolve, reject) => {
        const reader = new FileReader();
        reader.readAsDataURL(file);
        reader.onload = () => resolve(reader.result);
        reader.onerror = (error) => reject(error);
    });
};

Upvotes: 2

Views: 266

Answers (1)

Dacre Denny
Dacre Denny

Reputation: 30360

Currently, your code is attempting to directly render promise objects by mapping portfolioItems via the call to getDataURL() function. At the moment, React is based on synchronous rendering which means "promise-based rendering" is not currently possbile with React.

To achieve what you require, consider resolving the promises returned by getDataURL() outside of your rendering logic (ie in addPortfolio()).

This would require that your component state is extended to hold the image data resolved by the promise returned by getDataURL(). You'd then update the rendering logic of your component so that images a rendered from image data now present in your component state:

Update state shape

state = {
  selectedPortifolio: {},
  portflioFiles: [],
  portflioImages : [] // Extend state with arrage for image data
}

Update addPortfolio()

addPortfolio = () => {
        if (
            this.state.selectedPortifolio &&
            this.state.selectedPortifolio.file
        ) {
            let items = this.state.portflioFiles;
            let images = this.state.portflioImages;
            items.push(this.state.selectedPortifolio);

            // Resolve promise outside of render-logic. Once data 
            // required for rendering is available, update state
            // to trigger a re-render
            getDataURL(this.state.selectedPortifolio).then((image) => {
                this.setState({ 
                  selectedPortifolio: {}, 
                  portflioFiles: items,
                  // Add resolved image to portflioImages images array
                  portflioImages : images.concat([image]) 
            });
          });
        }
    };

Update render logic

{this.state.portflioImages.length > 0 ? (
    /* map portflioImages directly to image without promise */
    this.state.portflioImages.map((imageSrc, index) => {
        return (
            <div className="portfolio-file" key={index}>
                <img src={imageSrc} alt={item.filename} />
            </div>
        );
    })
) : (
    <div></fdv>
)}

Hope that helps!

Upvotes: 1

Related Questions