Lazy Loading With React-Native

Lazy Loading With React-Native
Lazy Loading in React-Native by CosX

There are enough blogs on Lazy Loading using FlatList but how do we implement lazy loading when we are not using FlatList. This blog will help you do that. Sometimes scrollView may contain images and other components to render, in that specific case we may not be able to use FlatList. This will require different logic to be used to lazy load images.

Currently, there are no open-source libraries available for RN to implement lazy loading. I have used a simple algorithm to make this work with React-Native. Below are the steps I used to write the solution for it.

There are multiple ways to approach this problem.

  1. Set the initial location of the Images in the ScrollView: Set reference to all the image components using React.useRef(). marker.current.measure function is used to calculate (x, y, width, height, pageX, pageY) values for the targeted component.
  2. Track the current location of the page offset: While scrolling we have to pass the current y offset from parent component to child component. This can be done using “container.current.onScroll()” inside onScroll props of ScrollView. Container reference is passed to the children component to detect its scroll position.
  3. Change state when page offset comes closer: This is the biggest challenge when we need to pass the value of scroll position to the child component without re-rendering. React provides React.forwardRef method to pass scroll position to the children without any state updates or re-rendering. React.useImperativeHandle works as a listener and allows to trigger multiple functions, this is where we will change the states to enable Image loading. We are using FastImage to render images, a great library to work with images.
class LazyLoadParent extends Component {
    const container = React.createRef(null);
    return (
      <>
        <ScrollView
          showsVerticalScrollIndicator={false}
          scrollEventThrottle={10}
          onScroll={(event) => {
            container.current.onScroll();
          }}
          bounces={false}
          scrollEnabled={true}
        >
          <View>
            <ImageLazyLoad
              ref={container}
            />
          </View>
        </ScrollView>
      </>
    );
  }
}

LazyLoadParent.js

// lazy loading for three images
const ImageLazyLoad = React.forwardRef((props, ref) => {
  const marker1 = React.useRef(null);
  const marker2 = React.useRef(null);
  const marker3 = React.useRef(null);
  const [markerVisible, setmarkerVisible] = useState(0);

  // marker Visible set with value -> 1,2,3
  React.useImperativeHandle(ref, () => ({
    onScroll: () => {
      marker1
        && marker1.current
        && marker1.current.measure((x, y, width, height, pageX, pageY) => {
          if (pageY < heightWindow + 100 && markerVisible === 0) {
            setmarkerVisible(1);
          }
        });
      marker2
        && marker2.current
        && marker2.current.measure((x, y, width, height, pageX, pageY) => {
          if (pageY < heightWindow + 100 && markerVisible === 1) {
            setmarkerVisible(2);
          }
        });
      marker3
        && marker3.current
        && marker3.current.measure((x, y, width, height, pageX, pageY) => {
          if (pageY < heightWindow + 100 && markerVisible === 2) {
            setmarkerVisible(3);
          }
        });
    },
  }));

  const renderComponent = (
    <>
    // your own image list
      {[1,2,3].map((item) => {
            return (
              <View
                ref={
                    item.key === 1
                      ? marker1
                      : item.key === 2
                        ? marker2
                        : item.key === 3
                          ? marker3
                              : null
                  }
                key={`${item.key}`}
                onLayout={(event) => event.target.measure(() => {})}
              >
                <If condition={markerVisible >= item.key}>
                  <FastImage
                    source={{
                      uri, // setup your own uri
                      priority: FastImage.priority.low,
                    }}
                    resizeMode={FastImage.resizeMode.cover}
                  />
                </If>
                <If condition={markerVisible < item.key}>
                  <FastImage
                    resizeMode={FastImage.resizeMode.cover}
                  />
                </If>
              </View>
            );
        })}
    </>
  );
  return <>{renderComponent}</>;
});

ImageLazyLoad.js

This piece of code has worked great for me and I hope it will work for you as well. Do let me know in comments, how it helped. Also, checkout my other blog on React-Native standards to be used while starting a new project.