61

I'm trying to nest ScrollViews in React Native; a horizontal scroll with nested vertical scrolls.

Here's an example:

var Test = React.createClass({
    render: function() {
        return (
            <ScrollView
                style={{width:320, height:568}}
                horizontal={true}
                pagingEnabled={true}>

                {times(3, (i) => {
                    return (
                        <View style={{width:320, height:568}}>

                            <ScrollView>
                                {times(20, (j) => {
                                    return (
                                        <View style={{width:320, height:100, backgroundColor:randomColor()}}/>
                                    );
                                })}
                            </ScrollView>

                        </View>
                    );
                })}

            </ScrollView>
        );
    },
});

AppRegistry.registerComponent('MyApp', () => Test);

The outer scroller works flawlessly, but the inner one sticks when you touch it while it's moving. What I mean is: if you scroll, lift your finger and touch it again while it's still moving with momentum, it stops and doesn't react at all to touch moves. To scroll more you have to lift your finger and touch again.

This is so reproducible it feels like something to do with the Gesture Responder.

Has anyone seen this issue?

How would I even begin to debug this? Is there a way to see what's responding to touches, granting and releasing, and when?

Thanks.

Update:

It looks like it is the responder system, by putting onResponderMove listeners on the inner and outer scrollers:

<ScrollView 
    onResponderMove={()=>{console.log('outer responding');}}
    ...

    <ScrollView
        onResponderMove={()=>{console.log('inner responding');}}>
        ...

It's clear that the outer ScrollView is grabbing control. The question, I guess, is how do I stop the outer scroller from taking control when trying to scroll vertically? And why is this only happening when you try to scroll an already moving inner ScrollView?

4
  • 1
    Do you see this behaviour in the simulator too? It's not immediately clear what you're trying to do here, practically speaking - maybe there's a simpler method. Commented May 2, 2015 at 13:59
  • 1
    Yes. Happening in the simulator as well. A good example of what I'm looking for is Gilt. You can scroll vertically through products, and horizontally through categories. Another one is Netflix, though reversed as they have multiple horizontal scrolls inside a vertical one. Commented May 3, 2015 at 17:44
  • 1
    Did you find a solution to this? I don't want to modify the core code. Commented May 20, 2015 at 14:24
  • After a little investigating, you can have a function that returns true on the outer Scrollview that responds to the following event onStartShouldSetResponder. So if onStartShouldSetResponder is set to true, the inner Scrollview will not respond to events. So we need to figure out if the user is swiping horizontally and if the user is swiping horizontally on the onStartShouldSetResponder, have the function return true otherwise return false. I am just wondering how to get the gesture on onStartShouldSetResponder. Commented May 20, 2015 at 16:13

5 Answers 5

83

If you are working with RN > 56.0, just add this prop to your scroll views:

<ScrollView nestedScrollEnabled = {true}>
 ......
  <ScrollView nestedScrollEnabled = {true}>
    .....
  </ScrollView>
</ScrollView>

That's the only one worked for me.

Sign up to request clarification or add additional context in comments.

6 Comments

Nested scroll is the default for iOS. reactnative.dev/docs/scrollview#nestedscrollenabled
I can't believe how simple this solution is, or that I only found it here. Anyway, this is amazing thank you so much!!
Holy shit. I just spend around 2 hours looking through posts, this is amazing thank you so much.
It's really simple it's crazy. Thank you so much it worrked
In this case, if I scroll the inner one, then the outer one also scrolled. Is there a solution for that?
|
10

@IlyaDoroshin and @David Nathan's answer pointed me in the right direction but I had to wrap each item in the scrollview with a touchable, rather than one touchable for everything.

<ScrollView>
  ...
  <ScrollView horizontal>
    {items.map(item => (
        <TouchableWithoutFeedback>
          { /* your scrollable content goes here */ }
        </TouchableWithoutFeedback>
    ))}
  </ScrollView>
</ScrollView>

1 Comment

Thank you. This is the only solution that worked for me. I tried all of the above ones. I have been looking for a solution like this for hours.
9

In your panresponder for the inner one, try setting this:

onPanResponderTerminationRequest: () => false

3 Comments

I'm getting this error message: "Warning: ScrollView doesn't take rejection well - scrolls anyway" . The ScrollView seems to have special privileges where it doesn't have to listen. Is there another option?
what is a panresponder and where do we put that line of code??
@Yokhen Did you figure out how to set this and where? I am facing the same problem.
9

Wrapping scrollable content of nested ScrollView with fixed this one for me on android:

<ScrollView>
  ...
  <ScrollView horizontal>
    <TouchableWithoutFeedback>
      { /* your scrollable content goes here */ }
    </TouchableWithoutFeedback>
  </ScrollView>
</ScrollView>

5 Comments

did not help to me :( using RN 0.40
on RN 0.46, still useful. I also had to wrap my inner content with a view, since TWF only takes single components.
Very nice! Works perfectly!
Any ideas on how to get this working with a nested webview? It seems to behave like a scrollview.
Absolutely, this is working, I can confirm
5

Modify node_modules/react-native/Libraries/Components/ScrollResponder.js: Line 136 (See UPDATE):

scrollResponderHandleScrollShouldSetResponder: function(): boolean {
    return this.state.isTouching;
},

UPDATE: I find if the scroll view is currently animating and wants to become the responder, then it will reject. Line 189 in ScrollResponder.js. So I modify Line 340 and it work for me:

scrollResponderIsAnimating: function(): boolean {
  // var now = Date.now();
  // var timeSinceLastMomentumScrollEnd = now - this.state.lastMomentumScrollEndTime;
  // var isAnimating = timeSinceLastMomentumScrollEnd < IS_ANIMATING_TOUCH_START_THRESHOLD_MS ||
  //   this.state.lastMomentumScrollEndTime < this.state.lastMomentumScrollBeginTime;
  // return isAnimating;
    return false;
},

You can see here: https://github.com/facebook/react-native/issues/41

1 Comment

This works like a charm for me!!!!!!! Thank you very much!!! you are my life saver!!!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.