My book on iOS interface design, Design Teardowns: Step-by-step iOS interface design walkthroughs is now available!

A better <img> tag

There's usually two solutions to embedding images into your web content:

1) Use the HTML <img> tag
This means you can set its height and width which could result in a non-uniform scaling applied to each dimension.

2) Apply background-image via CSS
You have more control over how the image is displayed but now you have to consider classes and how you would apply them to your HTML content.

With React, we can simplify this by abstracting the implementation details into a reusable component. Ideally we want to use our component as if it was regular HTML markup:

<Image src={url} width={500} height={300} mode='fit' />  

This bit of JSX suggests we want to be able to set the image src, and dimensions via width and height. We also have mode which may be specified as fit or fill. This allows us to scale proportionally and crop the image appropriately.

Conveniently, CSS also exposes a similar attribute via background-size which can be specified as contain or cover.

Let's start with a typical React component template:

import React, {Component} from 'react';

export default class Image extends Component {  
  render() {
    return <div />
  }
}

Next we'll extract the necessary props and setup the CSS attributes as inline styles on our <div />:

import React, {Component} from 'react';

export default class Image extends Component {  
  render() {
    let {mode, src, height, width, style, ...props} = this.props;
    let modes = {
      'fill': 'cover',
      'fit': 'contain'
    };
    let size = modes[mode] || 'contain';

    let important = {
      backgroundImage: `url("${src}")`,
      backgroundSize: size,
      backgroundPosition: 'center center',
      backgroundRepeat: 'no-repeat'
    };

    return <div {...props} style={{...style, ...important}} />
  }
}

Here we're doing a couple of things:

1) We're using ES6's destructuring assignment feature to extract the props we're specifically interested to handle and coalesce the remaining props into the props variable.

2) We setup the CSS styles we require (mostly background- attributes.) ES6's template strings allow us to interpolate the url from src easily.

3) We use the spread operator ... to expand props into the <div /> element and also to merge style, which contains inline styles the parent component might have specified, with important, which contains the CSS styles needed for displaying the image. The order here is crucial because we want keys from important to override the same keys if they are specified in style.

Finally to make our component a bit more robust, let's add some default styles if they are not specified by the parent component:

import React, {Component} from 'react';

export default class Image extends Component {  
  render() {
    let {mode, src, height, width, style, ...props} = this.props;
    let modes = {
      'fill': 'cover',
      'fit': 'contain'
    };
    let size = modes[mode] || 'contain';

    let defaults = {
      height: height || 100,
      width: width || 100,
      backgroundColor: 'gray'
    };

    let important = {
      backgroundImage: `url("${src}")`,
      backgroundSize: size,
      backgroundPosition: 'center center',
      backgroundRepeat: 'no-repeat'
    };

    return <div {...props} style={{...defaults, ...style, ...important}} />
  }
}

This fixes the issue whereby if no height and width (or corresponding styles) are specified, we would end up with a zero-by-zero element. And there we have a functional and complete React element!