'How to keep scroll position using flatlist when navigating back in react native ?

I am new to react native. I am facing problem in maintaining the position of flatlist when navigating forward and then come back to that screen.

Current behavior
When navigating forward and then back, the scroll position is lost.

Expected behavior
When navigating on an extensive list of items, the user scrolls down the list and click on some item. The app goes to the next page which shows the product details. If the user decides do navigate back, the page does not scroll to the previous point.



Solution 1:[1]

Try handling scroll position changing on a state:

<FlatList onScroll={this.handleScroll} />

handleScroll method:

handleScroll: function(event: Object) {
 this.setState({ scrollPosition: event.nativeEvent.contentOffset.y });
}

Then you can use scrollToOffset(this.state.scrollPosition) when you navigate back.

If you use react-navigation and want to keep the scroll position after navigation, try this hack:

componentDidMount() {
  this.props.navigation.addListener('willBlur', () => {
    const offset = this.state.scrollPosition
    // To prevent FlatList scrolls to top automatically,
    // we have to delay scroll to the original position 
    setTimeout(() => {
      this.flatList.scrollToOffset({ offset, animated: false })
    }, 500)
  })
}

Solution 2:[2]

I'm using react-navigation to navigate between a FlatList and a details view. I'm also using react-redux to track state. Here's the process I've followed.

  • Basically listen for changes to the navigator and save in redux the name of the current route
  • When you select an item in your FlatList remember the index of the item you selected
  • When you return to your FlatList calculate the offset for that index and scroll

That's all a little easier said then done and in my own Component the rows in my FlatList are variable so that complicates calculating the offset but heres an example assuming your rows are the same

My App component which renders the navigator

handleNavigationStateChange = (prevState, newState) => {
    this._getCurrentRouteName(newState)
}

_getCurrentRouteName = (navState) => {
    if (navState.hasOwnProperty('index')) {
        this._getCurrentRouteName(navState.routes[navState.index])
    } else {
        // This is my redux action to save route name
        this.props.updateCurrentRoute( navState.routeName )
    }
}

<AppNavigator onNavigationStateChange={this.handleNavigationStateChange} />

My component with the FlatList looks like this

// Renders a row for the FlatList with TouchableHighlight to call viewCreation
renderItem = ( row ) => {
    let creation = row.item
    return (
        <CreationCard onPress={this.viewCreation} index={row.index} creation={creation} />
    )
}

viewCreation = ( index ) => {
    // Get the creation
    let currentCreation = this.props.creations[index]
    // Another reducer saves both my item and index that was selected
    this.props.setSelectedIndex(currentCreation, index)
    // Navigate to the details screen
    this.props.navigation.navigate('CreationDetailScreen')
}

// Assuming all rows are 50 height
getItemLayout = (data, index) => {
    return {length: 50, offset: 50 * index, index}
}

// We mapped the 'currentRoute' property to this component so check
// the props when the component receives new ones.
componentWillReceiveProps( nextProps ){
    // If the currentRoute matches the route for this screen
    if( nextProps.currentRoute === 'CreationListScreen' ){
        // If we also know the last index that was selected on this page
        if( this.props.lastCreationIndex ){
            // Calculate the offset for the last index selected
            let y = this.props.lastCreationIndex * 50
            // and finally scroll to the offset
            this._flatList.scrollToOffset({
                offset: y,
                animated: true
            })
        }
    }
}


// IMPORTANT to include getItemLayout
render(){
    return(
    <FlatList
       ref={(fl) => this._flatList = fl}
       data={this.props.creations}
       renderItem={this.renderItem}
       getItemLayout={this.getItemLayout}
       keyExtractor={creation => creation.id}
       />
    )
}

Solution 3:[3]

For anyone using functional component this will be helpful.

Step 1: Create a constant using hooks to maintain the scroll position with initial value zero.

  const [scrollPosition,setScrollPosition]=React.useState(0)

Step 2: Create a method for updating the scroll postion. When the user scrolled.

const handleScroll=(event)=>{
  let yOffset=event.nativeEvent.contentOffset.y / heightOfYourListItem;
  setScrollPosition(yOffset)
}

Step 3: Call the handleScroll() method when the user scrolls the flat list.

    <FlatList
            enableEmptySections={true}
            onScroll={(event)=>handleScroll(event)}
            initialScrollIndex={scrollPosition}
            numColumns={2}
            keyExtractor={(item) => item.Id}
            data={Items}
            renderItem={({item}) =>renderListItems(item}
/>

Solution 4:[4]

The current behaviour with react-navigation is actually that the scroll position is being remembered when you switch back and forward between screens. It does reset though when you switch from a screen that has headerTransparent set to a different setting than the state you navigate there from.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Kazuya Gosho
Solution 2 Kerkness
Solution 3 AlienDev
Solution 4 Sascha Reuter