Home > Enterprise >  Why does the scrollToIndex scroll down then back to the top on initial render?
Why does the scrollToIndex scroll down then back to the top on initial render?

Time:09-08

Goal

When the data is loaded, I need the <FlatList /> to scroll to a specific index, and not the default index of 0.

Problem

When initialised, FlatList is scrolled to the 1st index, then scrolled back to the top.

enter image description here

Code

import React, { useEffect, useRef, useState } from "react";
import { Button, Image, StyleSheet, Text, View, FlatList } from "react-native";

const dataStore = [
  {
    id: 1,
    message: "Hello World!"
  },
  {
    id: 2,
    message:
      "Donec tristique metus ac mauris dapibus, in pellentesque elit elementum. Vestibulum quam mi, sagittis eget congue vulputate, commodo in odio. Etiam euismod dui libero, dignissim porttitor diam tristique id. Sed ullamcorper sollicitudin massa, nec faucibus dolor congue quis. In id eros ut magna cursus semper. Etiam suscipit leo quis neque hendrerit, sit amet tempus neque cursus. Phasellus ac fringilla quam, sed dignissim justo. Integer a ligula quis justo dignissim varius eget at nunc. Duis nec mollis odio. Suspendisse tellus mauris, laoreet vitae massa eget, sollicitudin auctor nibh."
  },
  {
    id: 3,
    message:
      "Donec tristique metus ac mauris dapibus, in pellentesque elit elementum. Vestibulum quam mi, sagittis eget congue vulputate, commodo in odio. Etiam euismod dui libero, dignissim porttitor diam tristique id. Sed ullamcorper sollicitudin massa, nec faucibus dolor congue quis. In id eros ut magna cursus semper. Etiam suscipit leo quis neque hendrerit, sit amet tempus neque cursus. Phasellus ac fringilla quam, sed dignissim justo."
  },
  {
    id: 4,
    message:
      "Donec tristique metus ac mauris dapibus, in pellentesque elit elementum. Vestibulum quam mi, sagittis eget congue vulputate, commodo in odio. Etiam euismod dui libero, dignissim porttitor diam tristique id. Sed ullamcorper sollicitudin massa, nec faucibus dolor congue quis. In id eros ut magna cursus semper. Etiam suscipit leo quis neque hendrerit, sit amet tempus neque cursus."
  },
  {
    id: 5,
    message:
      "Donec tristique metus ac mauris dapibus, in pellentesque elit elementum. Vestibulum quam mi, sagittis eget congue vulputate, commodo in odio. Etiam euismod dui libero, dignissim porttitor diam tristique id. Sed ullamcorper sollicitudin massa, nec faucibus dolor congue quis."
  },
  {
    id: 6,
    message:
      "Donec tristique metus ac mauris dapibus, in pellentesque elit elementum. Vestibulum quam mi, sagittis eget congue vulputate, commodo in odio."
  }
];

// Api to get the data
const api = () => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(dataStore);
    }, 3000);
  });
};

function App() {
  const ref = useRef();
  const [data, setData] = useState([]);
  const [index, setIndex] = useState(0);

  // When the component is rendered, call the refreshData function
  useEffect(() => {
    refreshData();
  }, []);

  // Here we fetch the data
  // 1. When the data is retrived, we assigned it to 'data'
  // 2. Then we set an index of 1, where we want the flat list to scroll to
  const refreshData = async () => {
    const response = await api();
    setData(response);
    setIndex(1);
  };

  // Here we recognise that the index has changed
  // If the index is anything other than 0, we scroll to the given index
  useEffect(() => {
    if (index !== 0) {
      ref.current?.scrollToIndex({
        index: index,
        animated: true,
        viewPosition: 0.5
      });
    }
  }, [index]);

  // If data is empty, then we display loading component (see else)
  if (data.length !== 0) {
    return (
      <FlatList
        ref={ref}
        initialScrollIndex={index} // this will equal to 1

        // --*** IMPORTANT ***--
        // Without the below I will get the following error (see error in question)
        onScrollToIndexFailed={(info) => {
          const wait = new Promise((resolve) => setTimeout(resolve, 500));
          wait.then(() => {
            ref.current?.scrollToIndex({
              index: index,
              animated: true,
              viewPosition: 0.5
            });
          });
        }}
        data={data}
        renderItem={(info) => {
          return (
            <View
              style={{ margin: 20, padding: 10, backgroundColor: "#2F4F4F" }}
            >
              <Text style={{ color: "#FFF" }}>{info.item.message}</Text>
            </View>
          );
        }}
      />
    );
  } else {
    return (
      <View style={{ marginHorizontal: "auto", maxWidth: 500 }}>
        <Text style={{ fontSize: 30 }}>Loading...</Text>
      </View>
    );
  }
}

export default App;

Error mentioned from comments in the code

scrollToIndex should be used in conjunction with getItemLayout or onScrollToIndexFailed, otherwise there is no way to know the location of offscreen indices or handle failures.

CodePudding user response:

getItemLayout is required for initialScrollIndex. https://reactnative.dev/docs/flatlist#initialscrollindex

CodePudding user response:

The following way worked for me by using onScrollToIndexFailed.

import React, { useEffect, useRef, useState } from "react";
import { Button, Image, StyleSheet, Text, View, FlatList } from "react-native";

const dataStore = [
  {
    id: 1,
    message: "Hello World!"
  },
  {
    id: 2,
    message:
      "Donec tristique metus ac mauris dapibus, in pellentesque elit elementum. Vestibulum quam mi, sagittis eget congue vulputate, commodo in odio. Etiam euismod dui libero, dignissim porttitor diam tristique id. Sed ullamcorper sollicitudin massa, nec faucibus dolor congue quis. In id eros ut magna cursus semper. Etiam suscipit leo quis neque hendrerit, sit amet tempus neque cursus. Phasellus ac fringilla quam, sed dignissim justo. Integer a ligula quis justo dignissim varius eget at nunc. Duis nec mollis odio. Suspendisse tellus mauris, laoreet vitae massa eget, sollicitudin auctor nibh."
  },
  {
    id: 3,
    message:
      "Donec tristique metus ac mauris dapibus, in pellentesque elit elementum. Vestibulum quam mi, sagittis eget congue vulputate, commodo in odio. Etiam euismod dui libero, dignissim porttitor diam tristique id. Sed ullamcorper sollicitudin massa, nec faucibus dolor congue quis. In id eros ut magna cursus semper. Etiam suscipit leo quis neque hendrerit, sit amet tempus neque cursus. Phasellus ac fringilla quam, sed dignissim justo."
  },
  {
    id: 4,
    message:
      "Donec tristique metus ac mauris dapibus, in pellentesque elit elementum. Vestibulum quam mi, sagittis eget congue vulputate, commodo in odio. Etiam euismod dui libero, dignissim porttitor diam tristique id. Sed ullamcorper sollicitudin massa, nec faucibus dolor congue quis. In id eros ut magna cursus semper. Etiam suscipit leo quis neque hendrerit, sit amet tempus neque cursus."
  },
  {
    id: 5,
    message:
      "Donec tristique metus ac mauris dapibus, in pellentesque elit elementum. Vestibulum quam mi, sagittis eget congue vulputate, commodo in odio. Etiam euismod dui libero, dignissim porttitor diam tristique id. Sed ullamcorper sollicitudin massa, nec faucibus dolor congue quis."
  },
  {
    id: 6,
    message:
      "Donec tristique metus ac mauris dapibus, in pellentesque elit elementum. Vestibulum quam mi, sagittis eget congue vulputate, commodo in odio."
  }
];

// Api to get the data
const api = () => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(dataStore);
    }, 3000);
  });
};

function App() {
  const [ref, setRef] = useState(null);
  const [data, setData] = useState([]);
  const [index, setIndex] = useState(0);

  useEffect(() => {
    refreshData();
  }, []);

  const refreshData = async () => {
    const response = await api();
    setData(response);
  };

  useEffect(() => {
    if(data.length !== 0) {
      setIndex(1);
    }
  }, [data])

  useEffect(() => {
    if (ref !== null) {
      ref.scrollToIndex({ index: index, animated: false });
    }
  }, [index]);

  if (data.length !== 0) {
    return (
      <FlatList
        ref={(ref) => setRef(ref)}
        onScrollToIndexFailed={(error) => {          
          const offset = error.averageItemLength * error.index;
          ref.scrollToOffset({ offset });
          setTimeout(() => ref.scrollToIndex({ index: error.index, animated: false }, 100));
        }}
        data={data}
        renderItem={(info) => {
          return (
            <View
              style={{ margin: 20, padding: 10, backgroundColor: "#2F4F4F" }}
            >
              <Text style={{ color: "#FFF" }}>{info.item.message}</Text>
            </View>
          );
        }}
      />
    );
  } else {
    return (
      <View style={{ marginHorizontal: "auto", maxWidth: 500 }}>
        <Text style={{ fontSize: 30 }}>Loading...</Text>
      </View>
    );
  }
}

export default App;
  • Related