question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Masonry component with async measurements

See original GitHub issue

The doc says

Support for this may be added in the future but for now the use of the CellMeasurer render callback’s async measure parameter is not supported.

Following is my way to use async measurements by modifying the cellRenderer with measure parameter.

  • suppose there is a components can lazy load image and has a callback method once it is loaded.
<LazyLoadImage loadedCallback={callbackFn} src={some url} />
  • use a function instead of a plain component as the child of CellMeasurer
const cellRenderer = ({ index, key, parent, style }) => {
  const material = materials[index]
  return (
    <CellMeasurer
      cache={this.cache}
      index={index}
      key={key}
      parent={parent}
    >
      {
        ({ measure }) => {
          const updateFn = () => {
            measure()
            debouncedRecompute()
          }
          return (
            <LazyLoadImage
              loadedCallback={updateFn} width={(width - 40) / 5} src={material.url_128} />
          )
        }
      }

    </CellMeasurer>
  )
}
  • before that
const {
  materials, width
} = this.props
  • and
const debouncedRecompute = debounce(this.shouldRecompute, 250) // lodash.debounce
  • at last
shouldRecompute () {
  const { width } = this.props
  if (this.masRef) { // masRef is the ref of this Masonry component
  // <Masonry ref={ele => (this.masRef = ele)} ... />
    this.cellPositioner.reset({
      columnCount: 5,
      columnWidth: (width - 40) / 5,
      spacer: 10
    })
    this.masRef.recomputeCellPositions()
  }
}
  • about this.cache and this.cellPositioner
constructor (props) {
  super(props)
  const {
    width
  } = this.props
  const cache = new CellMeasurerCache({
    defaultHeight: 250,
    defaultWidth: (width - 240) / 5,
    fixedWidth: true
  })
  this.cache = cache
  this.cellPositioner = createMasonryCellPositioner({
    cellMeasurerCache: cache,
    columnCount: 5,
    columnWidth: (width - 40) / 5,
    spacer: 10
  })
  this.shouldRecompute = this.shouldRecompute.bind(this)
}

hope it helps

Issue Analytics

  • State:closed
  • Created 6 years ago
  • Comments:8 (2 by maintainers)

github_iconTop GitHub Comments

1reaction
dylanfpaulcommented, Nov 17, 2017

@PabloMessina You can also get the image sizes using an Image() instance’s .onload event. In this example I have an array of json objects which represents my image feed items (the title, description, some other information, as well as the image urls for the images to be used in the feed). You can see here that I process through every item in my feedData array, and then I load the image using javascript ( var img = new Image(); img.src = feedItem.thumbnailUrl;).

I then define a function in this image instance’s onload event. When the onload event fires, it means the image has been downloaded, meaning its properties have been populated with information (.height, .width, etc.) You can use this information to prepare your data for the grid (or if you are using an array of image urls, create an array of objects containing all of this data). In this onload function I see what the width and the height of the images are and inject this information as a new property into each feed item explicitly, so when I go to use them in my Masonry grid I know exactly what the widths and heights are. I also am storing the height to width ratio as heightRatio, so if I have responsive column sizes in the grid I can calculate what the height should be for each image at any given column width. (I determine the column widths through querying for the screensize, etc.). You can see I also set this data into state once the last feed item has been processed, and thus once I have all of the information I need.

For example:

      var forEachIndex = 0;

      feedData.forEach((feedItem,index) => {
          var img  = new Image();
          img.src = feedItem.thumbnailUrl;

          img.onload = () => {
              ++forEachIndex;
              var heightRatio = img.height / img.width;

              feedItem.height = img.height;
              feedItem.width = img.width;
              feedItem.ratio = heightRatio;

              if(forEachIndex == feedData.length){
                  me.setState({
                      currentDataset: feedData,
                      feedIncrements: 1,
                      feedLoading: false
                   });
               }
          }
    });`
0reactions
PabloMessinacommented, Apr 19, 2018

@davidwu220, I ended up implementing a scrapper that would fetch all image dimensions off-line and save the dimensions in a .json file. The code I used is more or less like this:

const requestImageSize = require('request-image-size');
const queue = require('queue');
const fs = require('fs');
const path = require('path');

const urlsArray = require('./urlsArray.json');
const imageSizeCache = require('./imageSizeCache.json');

const q = queue({ concurrency: 30 });
const failedUrls = [];

function tryGetJob(url) {
    if (imageSizeCache[url])
        return null;

    return () => requestImageSize(url)
    .then(res => {
        imageSizeCache[url] = { width: res.width, height: res.height };
    })
    .catch(err => {
        console.error(err);
        failedUrls.push(url);
    });
}


for (const url of urlsArray) {
    job = tryGetJob(url);
    if (job) q.push(job);
}

function dump2jsonFile({_data, _path}) {
    fs.writeFile(_path, _data, (err) => {
        if (err) throw err;
    });
}

q.start(err => {
    console.log('all done!');
    console.log('dumping to json files ...');

    dump2jsonFile({
        _data: JSON.stringify(imageSizeCache),
        _path: path.join(__dirname, './imageSizeCache.json'),
    });

    dump2jsonFile({
        _data: JSON.stringify(failedUrls),
        _path: path.join(__dirname, './failedUrls.json'),
    });

    if (err) throw err;
});

Then my backend server would read the dimensions from the .json file and send the info to the React app before the masonry component is loaded.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Async loading of components in react-virtualized?
When the masonry is scrolling you can render a place holder instead of the cell content: if (isScrolling) return ( <div>placeholder</div> );.
Read more >
Writing a React Native Image Masonry Component from Scratch
Checkout this blog on writing a pure JS React / React Native image masonry component without any third party libraries.
Read more >
Element: <oj-masonry-layout> - Oracle
Masonry tiles can be of pre-defined sizes spanning 1 to 3 columns and rows. There is a specific style class for each of...
Read more >
React Masonry Layout Component Powered By CSS - Morioh
A Masonry component leveraging CSS & native React rendering, for fast, respon. ... Optional, different gutter size on mobile */ @media (max-width: 800px) ......
Read more >
How to use Masonry in react-virtualized - Javascript - Tabnine
After measurement is complete (componentDidMount or componentDidUpdate) this component evaluates positioned cells in order to determine if another measurement ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found