tl;dr: a react component wrapper around img that implements the 'lowsrc' property.

React's JSX parser is a white-list based interpreter. It will only accept and pass through properties (attributes) to the DOM component that are currently part of the standard.

IMG's lowsrc attribute has been deprecated, even though all browsers still implement it and probably will never remove it. React's team decided to not allow it. This causes problems for situations where the html standard's suggestion, to use two-pass jpeg files, is actually extremely impractical. Dynamically created files may not have such a feature available in their library. You may be using png files. You may just not want to go through all that work for all of your files.

In the case of SubFire M, the situation was the dynamic one: the cover art and playlist art is generated dynamically from the cached images. That library isn't configured to write two-phase files, and probably doesn't support the feature in the first place.

So I was going to use the deprecated lowsrc to handle it, because loading across the phone networks, esp in 3G, was just not working fast enough.

And it didn't work. I got the dreaded Warning: Unknown prop `lowsrc` on <img> tag. Remove this prop from the element..

This is one way to work around that. The LowSrcImg component sets the embedded img src to lowsrc until it has detected that it has loaded. Then it replaces it with the regular (high) src. In addition, on mount it checks img.complete to see if the low version was already loaded (if it was, the callback onLoad doesn't fire) and immediately sets it to show the high version.

// copyright Joseph W Shelby. Released under the MIT License.

import React from 'react';

class LowSrcImg extends React.Component {  
  constructor(props) {
    super(props);
    this.state = {
      loaded: false
    }
    this.replaceLowHigh = this.replaceLowHigh.bind(this);
  }

  componentDidMount() {
    if (this.i.complete) {
      this.replaceLowHigh();
    }
  }

  componentWillUpdate(props) {
    this.state = {
      loaded: false
    }
  }

  replaceLowHigh() {
    var s = this.state;
    if (s.loaded) return;
    s.loaded = true;
    this.setState(s);
  }

  render() {
    const props = Object.assign({}, this.props);
    if (!this.state.loaded) {
      props.src = props.lowsrc;
    }
    delete props.lowsrc;
    return (
      <img {...props} onLoad={this.replaceLowHigh} ref={(i) => {this.i = i;}}/>
    )
  }
}

export default LowSrcImg;  

Not perfect, of course, but workable. Optimizations might include

  • A second flag to confirm the high-res version was loaded and keep showing the lowres until then.
  • Relatedly, loading the high-res in an un-appended img tag on the DOM until it is ready.
  • Detect if the high one is already loaded and use it immediately (well, as immediate as react lets you, given state and property)

I avoided the latter because of the simultaneous network connections issue. With a large number of images loading at once, even the low-res versions will take time to load.

Finally, of course, one could look at progressive page loading (paging or infinite scrolling) when one is flipping through a lot of cover images, to reduce how many are attempting to load at one time, which is something I will be looking at in another part of the app later.