37

I have a FlatList inside a KeyboardAvoidingView. When the keyboard is displayed I would like to scroll to the end of the FlatList.

I am listening for the 'keyboardDidShow' event which does get fired, but it may be fired too early as the FlatList is not scrolled to the end after calling scrollToEnd.

I have looked into the onLayout event of KeyboardAvoidingView however just setting the onLayout event to trigger a function seems to stop the KeyboardAvoidingView from adjusting it's size when the Keyboard is shown.

<KeyboardAvoidingView behavior='padding' style={{ flex: 1}} onLayout={this._scrollEnd}>

Code:

import React from 'react';
import {Image, Linking, Platform, ScrollView, StyleSheet, Text, TouchableOpacity, View, Button, Alert, FlatList, TextInput, KeyboardAvoidingView, Keyboard} from 'react-native';
import { MonoText } from '../components/StyledText';

export default class HomeScreen extends React.Component {
  constructor() {
    super();
    this.state = {
      messages: getMessages()
    };

    this.keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', this._scrollEnd);
    this.keyboardDidShowListener = Keyboard.addListener('keyboardDidHide', this._scrollEnd);
  }

  _scrollEnd = (evt) => {
    this.refs.flatList1.scrollToEnd();
  }

  render() {
    return (
      <KeyboardAvoidingView behavior='padding' style={{ flex: 1}} >
        <FlatList
          style={{ flex:1}}
          ref="flatList1"
          data={this.state.messages}
          renderItem={({ item }) => <Text>{item.text}</Text>}
        />
      </KeyboardAvoidingView>
    );
  }
}
honor
  • 4,451
  • 6
  • 33
  • 55
Andrew Arthur
  • 1,455
  • 2
  • 11
  • 16
  • Did you try to add also the `getItemLayout` prop? Looks like it fixes it: http://facebook.github.io/react-native/releases/0.44/docs/flatlist.html#getitemlayout . Or you items' height is not static? – Ivan Chernykh May 27 '17 at 17:36
  • in wich platform are you? win, os, lin? – Damián Rafael Lattenero Jun 02 '17 at 15:31
  • This code works on my computer. Could you please precise more what's happening ? The scroll is never happening ? Or is it that the content is cut out ? – yachaka Jun 02 '17 at 17:31
  • 1
    What you posted works like a charm for me. Feels like you shouldn't add the listeners in the constructor though, rather in componentWillMount and then remove them again in componentWillUnmount. – Anthony De Smet Jan 01 '18 at 13:57

5 Answers5

77

I'm making a chat component and I want about the same things. Did it like this:

<FlatList
   ref={ref => this.flatList = ref}
   onContentSizeChange={() => this.flatList.scrollToEnd({animated: true})}
   onLayout={() => this.flatList.scrollToEnd({animated: true})}
   ...
/>

Keyboard popping up triggers a layout, so that's fixed. New chat messages arriving trigger content changes, so it also scrolls to the bottom (which is what I wanted for my chat window)

Anthony De Smet
  • 1,867
  • 3
  • 12
  • 21
  • 1
    Awesome answer bro! – Aditya Sonel Mar 29 '18 at 07:14
  • Anthony can you please tell me how did you achieve this "Keyboard popping up triggers a layout, so that's fixed." – Sadan A. May 31 '18 at 11:31
  • When the keyboard pops up, the screen has to re-render to adjust its size. This means that the FlatList will also get new dimensions (height) and a layout event will trigger. When this occurs, we scroll to the end of the list: onLayout={() => this.flatList.scrollToEnd({animated: true})} – Anthony De Smet Jun 05 '18 at 08:08
  • Thanx for onLayout!! I didint know about it :) When I changed height on keybord show event and changing FlatlIst wrapper height so onLayout did a trick :) – Ernestyno Jun 30 '18 at 19:29
  • 1
    thanks, works great. I recommend using also initialNumToRender={1} along with this. If you have a lot of data in the list, the list initialy loads everything, then moves back to to top and animates down again. If you set the intialNum to for example 1 it prevents the intial stutter/jump. – CosmicSeizure May 17 '19 at 14:20
  • Hi @CosmicSeizure adding intialNumtoRender={1} is not helping with the stutter. – Narayan Choudhary May 05 '20 at 16:46
  • Fantastic, dude! – C-lio Garcia May 15 '20 at 16:13
  • @AnthonyDeSmet assume i have a load more feature. In that case when the user reaches the top of the list and press the load more button, the contentchanges and it scrolls to the end. How to handle this scenario? – FortuneCookie May 18 '20 at 11:06
  • @FortuneCookie it just seems like you don't want the onContentSizeChange to do anything. In my case, for new chat messages arriving (so extra content being added, like your "load more") I felt it would be natural if the list scrolled to the bottom automatically. You don't have do to that though - simply omit that property. – Anthony De Smet May 18 '20 at 13:24
  • All the answers here are in class component. Does it mean it can't be achieved with functional component and hooks? – Nathileo Sep 02 '20 at 20:06
  • Haven't tested it, but I suppose the only thing that's different is how you get the ref. If you use the useRef hook, it should work the same: https://reactjs.org/docs/hooks-reference.html#useref – Anthony De Smet Nov 19 '20 at 16:53
  • it was laggy for me, and showed other content first and then scrolled down. – André Dec 28 '20 at 20:49
  • This does scroll to the end of the FlatList, but I can't understand how it would help to make the keyboard avoid covering the FlatList content; when I tried this out, they Keyboard was still covering the FlatList content. What am I missing? – sandre89 Feb 11 '21 at 17:57
  • Also having this problem, trying to use keyboardavoindingview – Chefk5 Feb 15 '21 at 15:32
40

actually if you always want to scroll to the end, mean you always want see latest message, am I right?

then use new version of react-native. and add inverted to change upside down of the flat list.

<FlatList
      inverted
      style={{ flex:1}}
      ref="flatList1"
      data={this.state.messages}
      renderItem={({ item }) => <Text>{item.text}</Text>}
    />

then rearrange your this.state.messages upside-down. then your latest message will always shown at the bottom of flatlist

For my case, I doesn't need to use KeyboardAvoidingView

Kyo Kurosagi
  • 1,495
  • 13
  • 14
1

Some of the users (@Nathileo) asked for a hooks-based approach to scrolling to the end of a FlatList.

  1. First, you need to implement React's useRef hook:

    import {useRef} from 'react';

    const yourRef = useRef(null);

  2. Second, the FlatList tag must be equipped with a reference and the desired functions:

    <FlatList
      ref={yourRef}
      onContentSizeChange={() => yourRef.current.scrollToEnd() }
      onLayout={() => yourRef.current.scrollToEnd() }
    />
    
EricSchaefer
  • 22,338
  • 20
  • 63
  • 99
0

As stated in above comment, getItemLayout should resolve your issue.

According to Reactive FlatList documentation:

getItemLayout is an optional optimizations that let us skip measurement of dynamic content if you know the height of items a priori. getItemLayout is the most efficient, and is easy to use if you have fixed height items, for example:

getItemLayout={(data, index) => (
  {length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index}
)}

If you use ItemSeparatorComponent, don't forget to incorporate the separator height or width when calculating the resulting offset.

lax1089
  • 3,063
  • 3
  • 13
  • 34
0

I've been using this little component I made for managing the flatlist height with the keyboard. This use the renderProps pattern so you can reuse it :)

import { PureComponent } from 'react';
import { Keyboard, Dimensions, Animated } from 'react-native';

const DURATION = 200;

class ListSpacer extends PureComponent {
  state = {
    screenHeight: Dimensions.get('window').height,
    flatListHeight: new Animated.Value(Dimensions.get('window').height),
  };

  componentDidMount() {
    this._keyboardDidShowListener = Keyboard.addListener(
      'keyboardDidShow',
      this._keyBoardDidShow,
    );
    this._keyboardDidHideListener = Keyboard.addListener(
      'keyboardDidHide',
      this._keyBoardDidHide,
    );
  }

  componentWillUnmount() {
    this._keyboardDidShowListener.remove();
    this._keyboardDidHideListener.remove();
  }

  _keyBoardDidShow = e => {
    Animated.timing(this.state.flatListHeight, {
      toValue: Dimensions.get('window').height - e.endCoordinates.height,
      duration: DURATION,
    }).start();
  };

  _keyBoardDidHide = () => {
    Animated.timing(this.state.flatListHeight, {
      toValue: Dimensions.get('window').height,
      duration: DURATION,
    }).start();
  };

  render() {
    const renderProps = {
      flatListHeight: this.state.flatListHeight,
    };

    if (this.props.children) {
      return this.props.children(renderProps);
    }

    return this.props.render(renderProps);
  }
}

export default ListSpacer;

Here we listen for keyboard event, and the endcoordinates give you the keyboard height. This way you can make the flatlist height with it.

import {
  FlatList,
  KeyboardAvoidingView,
  Animated,
} from 'react-native';

const AnimatedFlatList = Animated.createAnimatedComponent(FlatList);

return (
  <ListSpacer>
    {({ flatListHeight }) => (
      <KeyboardAvoidingView
        behavior="padding"
        keyboardVerticalOffset={INPUT_HEIGHT}
      >
        <AnimatedFlatList
          inverted
          style={{ height: flatListHeight }}
          data={data.comments}
          keyExtractor={this._keyExtractor}
          renderItem={this._renderItem}
          contentContainerStyle={styles.contentList}
        />
      </KeyboardAvoidingView>
    )}
  </ListSpacer>
);

Here I have this tutorials where I show what that does if you are more visual :)

https://youtu.be/2QnPZXCIN44?t=28m43s

EQuimper
  • 4,734
  • 7
  • 24
  • 39