React Native - how to scroll a ScrollView to a given location after navigation from another screen
Asked Answered
M

4

13

Is it possible to tell a ScrollView to scroll to a specific position when we just navigated to the current screen via StackNavigator?

I have two screens; Menu and Items. The Menu is a list of Buttons, one for each item. The Items screen contain a Carousel built using ScrollView with the picture and detailed description of each Item.

When I click on a button in the Menu screen, I want to navigate to the Items screen, and automatically scroll to the Item that the button represent.

I read that you can pass in parameters when using the StackNavigator like so: but I don't know how to read out that parameter in my Items screen.

navigate('Items', { id: '1' })

So is this something that is possible in React Native and how do I do it? Or perhaps I'm using the wrong navigator?

Here's a dumbed down version of my two screens:

App.js:

const SimpleApp = StackNavigator({
    Menu: { screen: MenuScreen},
    Items: { screen: ItemScreen }
  }
);

export default class App extends React.Component {
  render() {
    return <SimpleApp />;
  }
}

Menu.js

export default class Menu extends React.Component {
    constructor(props){
        super(props)
        this.seeDetail = this.seeDetail.bind(this)
    }

    seeDetail(){
        const { navigate } = this.props.navigation;
        navigate('Items')
    }

    render(){
        <Button onPress={this.seeDetail} title='1'/>
        <Button onPress={this.seeDetail} title='2'/>
    }
}

Items.js

export default class Items extends React.Component {
  render(){
    let scrollItems = [] //Somecode that generates and array of items
    return (
      <View>
        <View style={styles.scrollViewContainer}>
          <ScrollView 
          horizontal
          pagingEnabled
          ref={(ref) => this.myScroll = ref}>
            {scrollItems}
          </ScrollView>
        </View>
      </View>
    )
  }  
}

P.S I am specifically targeting Android at the moment, but ideally there could be a cross-platform solution.

Mancini answered 11/10, 2017 at 6:4 Comment(0)
M
12

I read that you can pass in parameters when using the StackNavigator like so: but I don't know how to read out that parameter in my Items screen.

That is achieved by accessing this.props.navigation.state.params inside your child component.

I think the best time to call scrollTo on your scrollview reference is when it first gets assigned. You're already giving it a reference and running a callback function - I would just tweak it so that it also calls scrollTo at the same time:

export default class Items extends React.Component {
  render(){
    let scrollItems = [] //Somecode that generates and array of items
    const {id} = this.props.navigation.state.params;

    return (
      <View>
        <View style={styles.scrollViewContainer}>
          <ScrollView 
          horizontal
          pagingEnabled
          ref={(ref) => {
            this.myScroll = ref
            this.myScroll.scrollTo() // !!
          }>
            {scrollItems}
          </ScrollView>
        </View>
      </View>
    )
  }  
}

And this is why I use FlatLists or SectionLists (which inherit from VirtualizedList) instead of ScrollViews. VirtualizedList has a scrollToIndex function which is much more intuitive. ScrollView's scrollTo expects x and y parameters meaning that you would have to calculate the exact spot to scroll to - multiplying width of each scroll item by the index of the item you're scrolling to. And if there is padding involved for each item it becomes more of a pain.

Matz answered 12/10, 2017 at 0:56 Comment(0)
E
3

Here is an example of scroll to the props with id.

import React, { Component } from 'react';
import { StyleSheet, Text, View, ScrollView, TouchableOpacity, Dimensions, Alert, findNodeHandle, Image } from 'react-native';
class MyCustomScrollToElement extends Component {

constructor(props) {
  super(props)
  this.state = {
  }
  this._nodes = new Map();
}
componentDidMount() {
  const data = ['First Element', 'Second Element', 'Third Element', 'Fourth Element', 'Fifth Element' ];
  data.filter((el, idx) => {
    if(el===this.props.id){
      this.scrollToElement(idx);
    }
  })

}
scrollToElement =(indexOf)=>{
  const node = this._nodes.get(indexOf);
  const position = findNodeHandle(node);
  this.myScroll.scrollTo({ x: 0, y: position, animated: true });
}
render(){
  const data = ['First Element', 'Second Element', 'Third Element', 'Fourth Element', 'Fifth Element' ];
  return (
  <View style={styles.container}>
    <ScrollView ref={(ref) => this.myScroll = ref} style={[styles.container, {flex:0.9}]} keyboardShouldPersistTaps={'handled'}>
      <View style={styles.container}>
          {data.map((elm, idx) => <View ref={ref => this._nodes.set(idx, ref)} style={{styles.element}}><Text>{elm}</Text></View>)}
      </View>
    </ScrollView>
  </View>
);

}

const styles = StyleSheet.create({
container: {
  flexGrow: 1,
  backgroundColor:"#d7eff9"
},
element:{
  width: 200,
  height: 200,
  backgroundColor: 'red'
}
});
export default MyCustomScrollToElement;
Etty answered 21/10, 2019 at 12:57 Comment(1)
It returns _nativeTag property instead of actual position. This does not solve the problem.Hardesty
O
2

Yes, this is possible by utilising the scrollTo method - see the docs. You can call this method in componentDidMount. You just need a ref to call it like: this.myScroll.scrollTo(...). Note that if you have an array of items which are all of the same type, you should use FlatList instead.

Ossa answered 12/10, 2017 at 0:13 Comment(3)
But I need to know which Item was selected to know where to scroll to. How do I let my Items screen know which item was selected in the previous Menu screen?Mancini
ideally you should know the index of the item that was selected and send it as a prop to the items screen. then you can use the scrollToIndex method, provided your items are all the same height.Ossa
Hello, yes I know the index of the item that was selected. What I'm not sure is how to pass the index as a prop from one screen to another via StackNavigator, or any other navigatorsMancini
K
1

For iOS - the best way to use ScrollView's contentOffset property. In this way it will be initially rendered in a right position. Using scrollTo will add additional excess render after the first one.

For Android - there is no other option rather then scrollTo

Kalin answered 16/12, 2019 at 22:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.