Stable image component with placeholder in React

Youichi Fujimoto
ITNEXT
Published in
3 min readJun 9, 2018

--

In most case, showing placeholders during loading images is a good idea, especially for slow network/devices. To build React apps with such an image component, there are several things to consider from both React and DOM aspect of view.

In this article, I will describe how to implement such a component by showing examples written in ES6.

*Updated 2018/07/21 Switched to ES6, Updated examples using reenhance-components.

Showing a placeholder until ‘onload’ event fires

The most straightforward strategy to implement such a behavior will be:

  1. Initialize internal state with false
  2. Set an onLoad event handler which updates state to true
  3. Let the component show either placeholder or image depending on the state.

Because the placeholder must be displayed since the page loaded, it should be SVG or Data URI. We can embed SVG elements in regular JSX/TSX just by renaming attributes like view-box to viewBox.

The state can be stored in StateProvider from reenhance-components. It provides state and updater by embedding it in JSX/TSX.

The whole procedure can be coded as following:

interface OuterProps {
src: string;
}
const LoadedState = StateProvider(false);const ImageWithLoading = ({ src }) => (
<LoadedState>
{({ state: loaded, setState: setLoaded }) => (
<div>
{!loaded ? (
<svg width="100" height="100" viewBox="0 0 100 100">
<rect width="100" height="100" rx="10" ry="10" fill="#CCC" />
</svg>
) : null}
<img
src={src}
style={!loaded ? { visibility: 'hidden' } : {}}
onLoad={() => setLoaded(true)}
/>
</div>
)}
</LoadedState>
);

Did you notice that the placeholder and img are hidden in different way? The placeholder can be removed from dom once loaded became true but img have to be there from beginning to fire onLoad event.

The result will be like this.

You can also try it in this CodePen.

Looks good? Actually, this is not perfect yet.

Remounting causes image flickering

In React application, UI interaction may cause some update of application state which trigger remounting the image component. If the component was remounted, the loaded state will be reset and result in flickering like this,

What can we do for this problem?

Reflecting DOM attribute to component state

This flickering happens because our component is waiting for the load event regardless the image is *initially* loaded or not. That state is available through complete DOM attribute which indicates the current state of element.

In order to access the attribute, we need a reference to the Image element. Since React 16.3, using createRef is the recommended way. the ref object can be stored & watched by ObjectWatcher from reenhance-components.

Once complete be available, that value can be used to determine if the image is ready or not. It is described in the following code:

const { StateProvider, ObjectWatcher } = ReenhanceComponents;const LoadedState = StateProvider(false);
const ImageRefWatcher = ObjectWatcher(React.createRef());
const ImageWithLoading = ({ src }) => (
<LoadedState>
{({ state: loaded, setState: setLoaded }) => (
<ImageRefWatcher watch="current">
{(imageRef) => {
const complete = imageRef.current && imageRef.current.complete;
return (
<div>
{!complete ? (
<svg width="100" height="100" viewBox="0 0 100 100">
<rect width="100" height="100" rx="10" ry="10" fill="#CCC" />
</svg>
) : null}
<img
src={src}
style={!complete ? { visibility: 'hidden' } : {}}
ref={imageRef}
onLoad={() => setLoaded(true)}
/>
</div>
);
}}
</ImageRefWatcher>
)}
</LoadedState>
);

Notice that the loaded state is not referred anymore. It is just for revoking rendering by calling setLoaded.

The CodePen is here.

Exercise.

As we do not refer to loaded state anymore, we can remove key prop from ImageWithLoading tag in the example above. Remove the key prop and modify ImageWithLoading so that it shows placeholder when new id is given. (Hint. Change the type of LoadedState)

Conclusion

React works pretty well with stateless components but some HTML elements have hidden states behind them. You can take full control if you understand their behavior and wrap them up.

References

https://stackoverflow.com/questions/12354865/image-onload-event-and-browser-cache

--

--

Software Engineer living in Japan. Love Golang, Rust, TypeScript, React, GCP and flavour of functional programming. Twitter/Instagram: @_fp