32

I have a react component that I wish to populate with images using the Dropbox api. The api part works fine, but the component is rendered before the data comes through & so the array is empty. How can I delay the rendering of the component until it has the data it needs?

var fileList = [];
var images = [];
var imageSource = [];

class Foo extends React.Component {

 render(){
  dbx.filesListFolder({path: ''})
  .then(function(response) {
   fileList=response.entries;
   for(var i=0; i<fileList.length; i++){
    imageSource.push(fileList[0].path_lower);
   }
   console.log(imageSource);
   })

  for(var a=0; a<imageSource.length; a++){
   images.push(<img key={a} className='images'/>);
  }

  return (
   <div className="folioWrapper">
    {images}
   </div>
  );
 }
}

Thanks for your help!

r_cahill
  • 577
  • 3
  • 9
  • 20
  • I would only mount that component once you have the images. So whoever mounts this component (the parent), should fetch the images then mount the component. – MinusFour Jul 04 '17 at 16:59
  • You shouldn't be doing all of that work inside your `render` method. Where are you changing the state? The component renders after updating the state. – DonovanM Jul 04 '17 at 17:00

3 Answers3

40

Changes:

1. Don't do the api call inside render method, use componentDidMount lifecycle method for that.

componentDidMount:

componentDidMount() is invoked immediately after a component is mounted. Initialization that requires DOM nodes should go here. If you need to load data from a remote endpoint, this is a good place to instantiate the network request. Setting state in this method will trigger a re-rendering.

2. Define the imageSource variable in state array with initial value [], once you get the response update that using setState, it will automatically re-render the component with updated data.

3. Use the state array to generate the ui components in render method.

4. To hold the rendering until you didn't get the data, put the condition inside render method check the length of imageSource array if length is zero then return null.

Write it like this:

class Foo extends React.Component {

    constructor(){
        super();
        this.state = {
            imageSource: []
        }
    }

    componentDidMount(){
        dbx.filesListFolder({path: ''})
          .then((response) => {
              let fileList = response.entries;
              this.setState({
                  imageSource: fileList
              });
          })
    }

    render(){
        if(!this.state.imageSource.length)
            return null;

        let images = this.state.imageSource.map((el, i) => (
            <img key={i} className='images' src={el.path_lower} />
        ))

        return (
            <div className="folioWrapper">
                {images}
            </div>
        );
    }
}
Mayank Shukla
  • 100,735
  • 18
  • 158
  • 142
6

You should be using your component's state or props so that it will re-render when data is updated. The call to Dropbox should be done outside of the render method or else you'll be hitting the API every time the component re-renders. Here's an example of what you could do.

class Foo extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      imageSource: []
    }
  }

  componentDidMount() {
    dbx.filesListFolder({ path: '' }).then(function(response) {
      const fileList = response.entries;

      this.setState({
        imageSource: fileList.map(file => file.path_lower);
      })
    });
  }

  render() {
    return (
      <div className="folioWrapper">
        {this.state.imageSource.map((image, i) => <img key={i} className="images" src={image} />)}
      </div>
    );
  }
}

If there are no images yet, it'll just render an empty div this way.

DonovanM
  • 1,174
  • 1
  • 12
  • 17
0

First off, you should be using the component's state and not using globally defined variables.

So to avoid showing the component with an empty array of images, you'll need to apply a conditional "loading" class on the component and remove it when the array is no longer empty.

Cisco
  • 20,972
  • 5
  • 38
  • 60