kirillzyusko / react-native-keyboard-controller

Keyboard manager which works in identical way on both iOS and Android
https://kirillzyusko.github.io/react-native-keyboard-controller/
MIT License
1.38k stars 55 forks source link

Reanimated Chat Flatlist does not scroll to end properly when content size changes #480

Closed NoureldinAmer closed 1 week ago

NoureldinAmer commented 2 weeks ago

Describe the bug The reanimated chat flatlist does not scroll to end properly when content size changes, this has been observed when I modified the sample ReanimatedChatFlatlist.tsx code to enable inserting new messages.

Code snippet

import React, { useRef, useState } from "react";
import { FlatList, TextInput, View } from "react-native";
import { useKeyboardHandler } from "react-native-keyboard-controller";
import Animated, {
  useAnimatedStyle,
  useSharedValue,
} from "react-native-reanimated";

import Message from "../../../components/Message";
import { history } from "../../../components/Message/data";

import styles from "./styles";

import type { MessageProps } from "../../../components/Message/types";
import type { ListRenderItem } from "react-native";

const staticReversedMessages = [...history].reverse();

const RenderItem: ListRenderItem<MessageProps> = ({ item, index }) => {
  return <Message key={index} {...item} />;
};

const useGradualAnimation = () => {
  const height = useSharedValue(0);

  useKeyboardHandler(
    {
      onMove: (e) => {
        "worklet";

        height.value = e.height;
      },
      onEnd: (e) => {
        "worklet";

        height.value = e.height;
      },
    },
    []
  );

  return { height };
};

function ReanimatedChatFlatList() {
  const { height } = useGradualAnimation();
  const [messages, setMessages] = useState(staticReversedMessages);
  const [userInput, setUserInput] = useState<string>("");
  const flatListRef = useRef<FlatList>(null);

  const handleSend = () => {
    setUserInput("");
    setMessages((prevMessages) => [
      {
        text: userInput,
        sender: true,
      },
      ...prevMessages,
    ]);
  };

  const fakeView = useAnimatedStyle(
    () => ({
      height: Math.abs(height.value),
    }),
    []
  );

  return (
    <View style={styles.container}>
      <FlatList
        inverted
        initialNumToRender={15}
        contentContainerStyle={styles.contentContainer}
        data={messages}
        renderItem={RenderItem}
        ref={flatListRef}
        onContentSizeChange={(e) => {
          flatListRef.current?.scrollToEnd({
            animated: true,
          });
        }}
      />
      <TextInput
        style={styles.textInput}
        enterKeyHint="send"
        value={userInput}
        onChangeText={setUserInput}
        onSubmitEditing={handleSend}
      />
      <Animated.View style={fakeView} />
    </View>
  );
}

export default ReanimatedChatFlatList;

Repo for reproducing /example (ReanimatedChatFlatList/index.tsx) has been modified as shown above

To Reproduce Steps to reproduce the behavior:

  1. Go to 'Chat FlatList component'
  2. Click on 'text field' and add text
  3. Click on "Enter"
  4. See error

Expected behavior The chat-log scrolls gracefully, to the last sent message (similar to any typical chat app)

Screenshots

https://github.com/kirillzyusko/react-native-keyboard-controller/assets/84950398/1a2115c3-2f8c-4194-89c8-cdc51373883a

Smartphone (please complete the following information):

kirillzyusko commented 2 weeks ago

Hey @NoureldinAmer

I used this code and it works (though scroll is not animated):

import React, { useState } from "react";
import { Button, FlatList, TextInput, View } from "react-native";
import { useKeyboardHandler } from "react-native-keyboard-controller";
import Animated, {
  useAnimatedStyle,
  useSharedValue,
} from "react-native-reanimated";

import Message from "../../../components/Message";
import { history } from "../../../components/Message/data";

import styles from "./styles";

import type { MessageProps } from "../../../components/Message/types";
import type { ListRenderItem } from "react-native";

const reversedMessages = [...history].reverse();

const RenderItem: ListRenderItem<MessageProps> = ({ item, index }) => {
  return <Message key={index} {...item} />;
};

const useGradualAnimation = () => {
  const height = useSharedValue(0);

  useKeyboardHandler(
    {
      onMove: (e) => {
        "worklet";

        height.value = e.height;
      },
      onEnd: (e) => {
        "worklet";

        height.value = e.height;
      },
    },
    [],
  );

  return { height };
};

function ReanimatedChatFlatList() {
  const [messages, setMessages] = useState(reversedMessages);
  const [userInput, setUserInput] = useState<string>("");
  const { height } = useGradualAnimation();

  const fakeView = useAnimatedStyle(
    () => ({
      height: Math.abs(height.value),
    }),
    [],
  );

  const handleSend = (e) => {
    setUserInput("");
    setMessages((prevMessages) => [
      {
        text: userInput,
        sender: true,
      },
      ...prevMessages,
    ]);
  };

  return (
    <View style={styles.container}>
      <FlatList
        inverted
        initialNumToRender={15}
        contentContainerStyle={styles.contentContainer}
        data={messages}
        renderItem={RenderItem}
      />
      <View>
        <TextInput
          style={styles.textInput}
          value={userInput}
          onChangeText={setUserInput}
          onSubmitEditing={handleSend}
        />
        <Button title="Send" onPress={handleSend} />
      </View>
      <Animated.View style={fakeView} />
    </View>
  );
}

export default ReanimatedChatFlatList;
kirillzyusko commented 2 weeks ago

If you would like to avoid instant scrolling and have an animated variant, then you can look into - https://github.com/facebook/react-native/issues/25239 But honestly I didn't find out on how to make this animation 🤷‍♂️

kirillzyusko commented 1 week ago

I'm going to close that issue, because it's related to example app (which was crafted for demonstration purposes) and not related to the functionality of the library.