Is it possible to keep a ScrollView scrolled to the bottom?
Asked Answered
M

19

150

For a chat-like app, I want to keep a ScrollView component scrolled to the bottom, because newest messages appear under the older ones. Can we adjust the scroll position of a ScrollView?

Magellan answered 27/3, 2015 at 21:32 Comment(5)
I don't know anything about react-native, but keep in mind sometimes users wan to scroll up to view the chat history. Make sure to only scroll the chat if the chat was already scrolled all the way to the bottom when a new message arrives.Scruff
This is a good idea. We're tracking this issue here: github.com/facebook/react-native/issues/107Agreeable
I just answered a similar question, please check it here – https://mcmap.net/q/160319/-how-to-scroll-to-bottom-of-react-native-listviewGreensward
Does this answer your question? How to scroll to bottom of React Native ListViewBitner
Have you considered aligning your items to the bottom?Sofko
M
238

For React Native 0.41 and later, you can do this with the built-in scrollToEnd method:

<ScrollView
    ref={ref => {this.scrollView = ref}}
    onContentSizeChange={() => this.scrollView.scrollToEnd({animated: true})}>
</ScrollView>
Morpheus answered 11/3, 2017 at 14:2 Comment(3)
From react native 0.55.4 and greater, I had to use root otherwise, scrollToEnd is undefined. i.e. this.scrollView.root.scrollToEndAllard
Currently using react native 0.58.6 and using root actually throws an undefined error.Asiatic
Should this be: ref={ref => {this.scrollView = ref}} as you don't want to return the assignmentHyo
I
111

For anyone writing function component that can use hook,

import { useRef } from 'react';
import { ScrollView } from 'react-native';

const ScreenComponent = (props) => {
  const scrollViewRef = useRef();
  return (
    <ScrollView
      ref={scrollViewRef}
      onContentSizeChange={() => scrollViewRef.current.scrollToEnd({ animated: true })}
    />
  );
};
Idealism answered 4/6, 2020 at 6:23 Comment(1)
Seems pretty straightforward but it didn't work in my very similar use case.Jericajericho
G
30

Use onContentSizeChange to keep track of the bottom Y of the scroll view (height)

https://facebook.github.io/react-native/docs/scrollview.html#oncontentsizechange

var _scrollToBottomY

<ScrollView
    onContentSizeChange={(contentWidth, contentHeight)=>{
    _scrollToBottomY = contentHeight;
    }}>
</ScrollView>

then use the ref name of the scroll view like

this.refs.scrollView.scrollTo(_scrollToBottomY);
Galang answered 16/2, 2016 at 3:54 Comment(1)
Note that this scrolls the view completely to the bottom, so the view is essentially not visible (unless you added something to it after the measurement). This makes sense since the code scrolls to the height of the scrollable area, not to the height of children overflowing the scrollable area (probably NOT what OP wanted). See https://mcmap.net/q/157151/-is-it-possible-to-keep-a-scrollview-scrolled-to-the-bottom instead.Aubert
E
24

Here is the simplest solution I could muster:

 <ListView
ref={ref => (this.listView = ref)}
onLayout={event => {
    this.listViewHeight = event.nativeEvent.layout.height;
}}
onContentSizeChange={() => {
    this.listView.scrollTo({
        y: this.listView.getMetrics().contentLength - this.listViewHeight
    });
}}
/>;
Emelia answered 22/4, 2016 at 20:43 Comment(1)
When I use this or other similar answers, it makes the items in the list disappear (it looks like it's scrolling too far, but I can't be sure). Scrolling a tiny bit makes everything reappear, and kind of looks like it's sliding back into view (hence the scrolling too far theory). Any obvious idea why that might be?Cale
K
11

The modern way of achieving this using a function component written in TypeScript:

import {useRef} from 'react';
import {ScrollView, View} from 'react-native';

function App() {
  const scrollViewRef = useRef<ScrollView>(null);

  // ...

  return (
    <View>
      {/* ... */}

      <ScrollView
        onContentSizeChange={() => scrollViewRef.current?.scrollToEnd()}
        ref={scrollViewRef}
      >
        {/* ... */}
      </ScrollView>
    </View>
  );
}

Kaslik answered 5/6, 2023 at 23:10 Comment(0)
B
8

You can invert the scroll/list view using the react-native-invertible-scroll-view module, then simply call ref.scrollTo(0) to scroll to bottom.

Install the module:

npm install --save react-native-invertible-scroll-view

Then import the component:

import InvertibleScrollView from 'react-native-invertible-scroll-view';

Then use the following JSX instead of a ScrollView:

<InvertibleScrollView inverted
    ref={ref => { this.scrollView = ref; }}
    onContentSizeChange={() => {
        this.scrollView.scrollTo({y: 0, animated: true});
    }}
>
    { /* content */ }
</InvertibleScrollView>

This works because react-native-invertible-scroll-view flips the entire view's content. Note that the view's children will also render in the opposite order to a normal view - the first child will appear at the bottom.

Boabdil answered 27/8, 2015 at 3:11 Comment(0)
I
8

In case anyone in 2020 or later is wondering how to achieve this with functional components. This is how I did:

ref={ref => scrollView = ref }
onContentSizeChange={() => scrollView.scrollToEnd({ animated: true })}>
Invasion answered 28/5, 2020 at 17:17 Comment(2)
Note: you can mention that you are using useref. most ppl don't know how to use ref with hooks.Ween
yes. The thing is that it worked out even when not using the useRef hook. But the right way is to use it! You're rightInvasion
M
6

try this solution

xcode 10.0

RN: 56.0.0

<ScrollView ref="scrollView"
             onContentSizeChange={(width,height) => this.refs.scrollView.scrollTo({y:height})}>  </ScrollView> // OR height -  width
Milly answered 3/12, 2018 at 9:40 Comment(1)
why ref=? seems like a web dev relicTruda
M
4

Actually @Aniruddha answer doesn't worked for me because I'm using "react-native": "0.59.8" and this.scrollView.root.scrollToEnd also is undefined

but I figured it out and this works this.scrollView.scrollResponderScrollToEnd({animated: true});

If you are using 0.59.8 this will work OR if you are using later version and this works for you. please add versions in this answer so others wont get trouble.

Full Code here

<ScrollView
    ref={ref => this.scrollView = ref}
    onContentSizeChange={(contentWidth, contentHeight)=>{        
        this.scrollView.scrollResponderScrollToEnd({animated: true});
    }}>
</ScrollView>
Mauldon answered 22/11, 2019 at 10:57 Comment(0)
A
3

you can do it by:

first initialize ref object:

const scrollViewRef = React.useRef<ScrollView>();

and then use it like following:

<ScrollView
  ref={scrollViewRef}
  nestedScrollEnabled={true}
  onContentSizeChange={(contentWidth, contentHeight) => {
    scrollViewRef.current?.scrollTo({ y: contentHeight });
  }}
>
  {children}
</ScrollView>
Automatic answered 25/8, 2022 at 10:26 Comment(1)
Thanks, @Automatic +Minor addition. Better to have const scrollViewRef = React.useRef<ScrollView>(null)Oliy
F
2

This method is a little bit hacky but I couldn't find a better way

  1. First add a ref to your ScrollView.
  2. Second add a dummy View before closing your ScrollView tag
  3. Get the y position of that dummy View
  4. Whenever you want to scroll to bottom scroll to the position of the dummy View

var footerY; //Y position of the dummy view
render() {    
    return (
      <View>
          <ScrollView 
          ref='_scrollView'>
            // This is where all the things that you want to display in the scroll view goes
            
            // Below is the dummy View
            <View onLayout={(e)=> {
              footerY = e.nativeEvent.layout.y;
              }}/>
          </ScrollView>
              
          //Below is the button to scroll to the bottom    
          <TouchableHighlight
            onPress={() => { this.refs._scrollView.scrollTo(footerY); }}>
            <Text>Scroll to Bottom</Text>
          </TouchableHighlight>
        
      </View>
    );
  }
Followthrough answered 5/2, 2016 at 23:8 Comment(1)
This isn't quite what was asked for; this scrolls to the bottom on tap, rather than keeping the scrollview scrolled to the bottom as content is added. Anyway, scrollToBottom makes approaches like this obsolete in newer versions of React Native.Signorina
C
1

This answers your question: https://mcmap.net/q/160319/-how-to-scroll-to-bottom-of-react-native-listview

You need to keep constantly scrolling to the bottom of the page by calculating the height of the content minus the height of its scrollView.

Cornell answered 18/9, 2016 at 22:17 Comment(1)
This is the correct answer to the question. You can be smart about when to perform the scroll, the answer in the link shows you how to get the numbers you need. In particular, if you are adding components to the list, the content height won't include them when componentDidUpdate is called, so you want to remember that you want to scroll and scroll on onContentSizeChange instead.Aubert
S
1

I guess it is too late to answer but I got one hack! If you use FlatList you can enable inverted property which falls back to setting transform scale to -1 (which is acceptable for other list components like ScrollView ...) . When you render your FlatList with data it always will be on the bottom!

Stidham answered 31/8, 2019 at 19:50 Comment(0)
R
1

If you use TypeScript You need to do this

interface Props extends TextInputProps { 
     scrollRef?: any;
}

export class Input extends React.Component<Props, any> {
     scrollRef;
constructor(props) {
    this.scrollRef = React.createRef();
}
<ScrollView
    ref={ref => (this.scrollRef = ref)}
    onContentSizeChange={() => {
    this.scrollRef.scrollToEnd();
    }}
>
</ScrollView>
Ruthi answered 3/12, 2019 at 10:3 Comment(0)
E
0

I fought with this for a while and finally came up with something that worked really well and smooth for me. Like many, I tried .scrollToEnd to no avail. The solution that worked for me was a combination of onContentSizeChange, onLayout props and a little bit more thinking visually about "what scrolling to the bottom" really meant. The last part seems trivial to me now, but it took me forever to figure out.

Given that the ScrollView has a fixed height, when the content height goes over the container's height, I calculated the offset by subtracting the prevHeight (fixed height of the ScrollView) for the currHeight (the content height). If you can visually imagine it, scrolling to that difference in y-axis, slides the content height over its container by that offset. I incremented my offset and made my now current content height my previous height and kept calculating a new offset.

Here is the code. Hope it helps someone.

class ChatRoomCorrespondence extends PureComponent {
  currHeight = 0;
  prevHeight = 0;
  scrollHeight = 0;

  scrollToBottom = () => {
    this.refs.scrollView.getScrollResponder().scrollResponderScrollTo({
      x: 0,
      y: this.scrollHeight,
      animated: true
    });
  };

  render() {
    return (
      <ScrollView
        style={Styles.container}
        ref="scrollView"
        onContentSizeChange={(w, h) => {
          this.currHeight = h;

          if (
            this.prevHeight > 0 &&
            this.currHeight - this.scrollHeight > this.prevHeight
          ) {
            this.scrollHeight += this.currHeight - this.prevHeight;
            console.log("--------------------------------------------");
            console.log("Curr: ", this.currHeight);
            console.log("Prev: ", this.prevHeight);
            console.log("Scroll: ", this.scrollHeight);
            this.prevHeight = this.currHeight;
            console.log("PREV: ", this.prevHeight);
            console.log("--------------------------------------------");

            this.scrollToBottom();
          }
        }}
        onLayout={ev => {
          // Fires once
          const fixedContentHeight = ev.nativeEvent.layout.height;
          this.prevHeight = fixedContentHeight;
        }}
      >
        <FlatList
          data={this.props.messages}
          renderItem={({ item }) => (
            <ChatMessage
              text={item.text}
              sender={item.sender}
              time={item.time}
              username={this.props.username}
            />
          )}
          keyExtractor={(item, index) => index.toString()}
        />
      </ScrollView>
    );
  }
}
Ermeena answered 23/2, 2019 at 10:38 Comment(0)
M
0

For those who has bug (sometimes it scrolls to top of the list, instead of bottom, and happens randomly) when using scrollToEnd and onContentSizeChange prop, here is what I am using as an alternative.

   onContentSizeChange={(_, contentHeight) => {
     flatListRef.current?.scrollToOffset({ offset: contentHeight });
   }}
Matchbook answered 12/11, 2022 at 22:54 Comment(0)
L
-2

Try setting the contentOffset property of the scroll view to the current height of the content.

To accomplish what you need you could do this as part of the onScroll event. However this may make for a bad user experience so it may prove more user-friendly to only do this when a new message is appended.

Lcm answered 17/5, 2015 at 13:0 Comment(1)
-1; nope. Like many of the answers here, this has the effect of scrolling the ScrollView down to below all of its content, rather than just scrolling to the bottom.Signorina
K
-2

I had a similar problem, but after reading this page (which give me a headache!) I find this very nice npm package which just invert the ListView and make everything easy for a chat application!!

Klingel answered 25/4, 2017 at 18:41 Comment(1)
-1; this duplicates a previously posted answer here (and is also a bit confused; this question is about a ScrollView, not ` ListView`).Signorina
I
-4

A bit late... but look at this question. Scroll to top of ScrollView

ScrollView has a "scrollTo" method.

Interleaf answered 7/8, 2015 at 21:32 Comment(1)
Yes there is a scrollTo method on ScrollView, but the question is specifically asking about scrolling to the bottom, which isn't straightforward.Pyrophotometer

© 2022 - 2024 — McMap. All rights reserved.