react-native-gifted-chat icon indicating copy to clipboard operation
react-native-gifted-chat copied to clipboard

Layout shifts on android when loading chat

Open muazazhar opened this issue 8 months ago • 13 comments

I'm having this issue with gifted chat. when a chat is opened, everything moves downward including header before correcting their positions and even though chats are statically defined, it takes times to load UI and chats. What am i doing wrong? This header issue happens only on android. If i comment out giftedchat component in screen, UI doesn't move downwards

Image

Versions: RN-gifted-chats: "^2.8.0" expo: "^52.0.38"

import { View, SafeAreaView, TouchableOpacity, Text } from 'react-native';
import { useLocalSearchParams } from 'expo-router';
import { useState, useCallback, useEffect } from 'react';
import {
  GiftedChat,
  IMessage,
  InputToolbar,
  Send,
  Actions,
  Composer,
  Time,
  Bubble,
} from 'react-native-gifted-chat';
import { useUserStore } from '~/store/userStore';
import { Ionicons } from '@expo/vector-icons';
import * as DocumentPicker from 'expo-document-picker';

export default function ChatScreen() {
  const { id, chatUser } = useLocalSearchParams();
  const { user } = useUserStore();
  const [messages, setMessages] = useState<IMessage[]>([]);
  const [text, setText] = useState('');
  const [isRecording, setIsRecording] = useState(false);
  const [selectedFile, setSelectedFile] = useState<DocumentPicker.DocumentResult | null>(null);
  const otherUser = chatUser ? JSON.parse(chatUser as string) : null;

  useEffect(() => {
    setMessages([
      {
        _id: 1,
        text: "Good morning! That's great to hear. Could you share the details with me?",
        createdAt: new Date(),
        user: {
          _id: 2,
          name: 'Daniella Russel',
          avatar: otherUser?.avatar,
        },
      },
      {
        _id: 2,
        text: 'Please review this',
        createdAt: new Date(),
        user: {
          _id: 2,
          name: 'Daniella Russel',
          avatar: otherUser?.avatar,
        },
        file: {
          name: 'Portfolio.zip',
          size: '54 KB',
          type: 'application/zip',
        },
      },
      {
        _id: 3,
        text: 'Sure! For starters, I recommend reallocating funds from low-yield bonds to high-growth mutual funds.',
        createdAt: new Date(),
        user: {
          _id: 2,
          name: 'Daniella Russel',
          avatar: otherUser?.avatar,
        },
      },
    ]);
  }, []);

  const onSend = useCallback((newMessages: IMessage[] = []) => {
    setMessages((previousMessages) => GiftedChat.append(previousMessages, newMessages));
  }, []);

  const handleAttachment = async () => {
    try {
      const result = await DocumentPicker.getDocumentAsync();
      if (result.assets && result.assets.length > 0) {
        const file = result.assets[0];
        setSelectedFile(result);
        // Send file message
        const fileMessage: IMessage = {
          _id: Math.random().toString(),
          text: file.name,
          createdAt: new Date(),
          user: {
            _id: user?._id || '',
            name: user?.username,
            avatar: user?.avatar,
          },
          file: {
            name: file.name,
            size: `${Math.round(file.size / 1024)} KB`,
            type: file.mimeType,
          },
        };
        onSend([fileMessage]);
      }
    } catch (error) {
      console.error('Error picking document:', error);
    }
  };

  const renderInputToolbar = (props: any) => (
    <InputToolbar
      {...props}
      containerStyle={{
        backgroundColor: '#f8f9fa',
        borderTopWidth: 0,
        paddingVertical: 8,
        paddingHorizontal: 10,
      }}
      primaryStyle={{ alignItems: 'center' }}
    />
  );

  const renderActions = (props: any) => (
    <Actions
      {...props}
      containerStyle={{
        width: 40,
        height: 40,
        alignItems: 'center',
        justifyContent: 'center',
        marginLeft: 4,
        marginRight: 4,
      }}
      icon={() => <Ionicons name="attach" size={24} color="#666" />}
      onPressActionButton={handleAttachment}
    />
  );

  const renderSend = (props: any) => (
    <Send
      {...props}
      disabled={!props.text && !isRecording}
      containerStyle={{
        width: 40,
        height: 40,
        alignItems: 'center',
        justifyContent: 'center',
        marginHorizontal: 4,
      }}>
      <TouchableOpacity
        onPress={() => {
          if (!props.text) {
            setIsRecording(!isRecording);
          } else {
            props.onSend({ text: props.text.trim() }, true);
          }
        }}
        className="h-8 w-8 items-center justify-center rounded-full bg-primary">
        <Ionicons
          name={props.text ? 'send' : isRecording ? 'stop' : 'mic'}
          size={20}
          color="white"
        />
      </TouchableOpacity>
    </Send>
  );

  const renderComposer = (props: any) => (
    <Composer
      {...props}
      textInputStyle={{
        backgroundColor: 'white',
        borderRadius: 20,
        borderWidth: 1,
        borderColor: '#e2e8f0',
        paddingHorizontal: 12,
        paddingVertical: 8,
        maxHeight: 100,
        fontSize: 16,
      }}
      placeholderTextColor="#94a3b8"
    />
  );

  const renderBubble = (props: any) => (
    <Bubble
      {...props}
      wrapperStyle={{
        left: {
          backgroundColor: 'white',
          borderRadius: 12,
          padding: 4,
          marginBottom: 4,
        },
        right: {
          backgroundColor: '#007AFF',
          borderRadius: 12,
          padding: 4,
          marginBottom: 4,
        },
      }}
      textStyle={{
        left: {
          color: '#1a1a1a',
        },
        right: {
          color: 'white',
        },
      }}
    />
  );

  const renderTime = (props: any) => (
    <Time
      {...props}
      timeTextStyle={{
        left: {
          color: '#94a3b8',
          fontSize: 12,
        },
        right: {
          color: '#94a3b8',
          fontSize: 12,
        },
      }}
    />
  );

  const renderMessageFile = (props: any) => {
    const { currentMessage } = props;
    if (currentMessage.file) {
      return (
        <View className="mt-2 rounded-lg bg-gray-100 p-3">
          <View className="flex-row items-center">
            <Ionicons name="document-outline" size={24} color="#666" />
            <View className="ml-3">
              <Text className="font-medium text-sm text-gray-900">{currentMessage.file.name}</Text>
              <Text className="text-xs text-gray-500">{currentMessage.file.size}</Text>
            </View>
          </View>
        </View>
      );
    }
    return null;
  };

  return (
    <SafeAreaView className="flex-1 bg-gray-50">
      <View className="flex-1">
        <GiftedChat
          messages={messages}
          onSend={onSend}
          user={{
            _id: user?._id || '',
            name: user?.username,
            avatar: user?.avatar,
          }}
          text={text}
          onInputTextChanged={setText}
          renderInputToolbar={renderInputToolbar}
          renderActions={renderActions}
          renderSend={renderSend}
          renderComposer={renderComposer}
          renderBubble={renderBubble}
          renderTime={renderTime}
          renderMessageFile={renderMessageFile}
          placeholder="Write a message..."
          showAvatarForEveryMessage={false}
          alwaysShowSend
          minInputToolbarHeight={60}
          timeFormat="HH:mm"
          dateFormat="ll"
          scrollToBottom
          infiniteScroll
        />
      </View>
    </SafeAreaView>
  );
}

muazazhar avatar Mar 18 '25 08:03 muazazhar

Hi @muazazhar, I have the same problem, did you found a solution?

evilimkova avatar Mar 28 '25 07:03 evilimkova

No

muazazhar avatar Mar 28 '25 17:03 muazazhar

Facing the same problem only on android device. No solution found yet

aalekh0695 avatar Apr 01 '25 15:04 aalekh0695

I added a comment about our setup here: https://github.com/FaridSafi/react-native-gifted-chat/issues/2613#issuecomment-2786460043 might be helpful

Kjagd avatar Apr 08 '25 13:04 Kjagd

same issue here. @Kjagd 's approach didn't help

younseoryu avatar Apr 09 '25 20:04 younseoryu

@Kjagd Getting the same issue. Has anyone found a solution for this?

hamzazaheer avatar Apr 17 '25 16:04 hamzazaheer

And you are positively using the edge-to-edge library? And updated your code to use it for the Status bar? It just looks related to that, but it would make sense to have a small example app in the repo here where is verified works.

Kjagd avatar Apr 17 '25 16:04 Kjagd

Getting the same issue. Has anyone found a solution for this?

infoeneseren avatar May 07 '25 21:05 infoeneseren

Getting the same issue. Has anyone found a solution for this?

Updated the library to the latest version fixed my issue

younseoryu avatar May 07 '25 22:05 younseoryu

Updated to latest (2.8.1). Didn't worked for me

On Thu, May 8, 2025, 3:07 AM younseoryu @.***> wrote:

younseoryu left a comment (FaridSafi/react-native-gifted-chat#2602) https://github.com/FaridSafi/react-native-gifted-chat/issues/2602#issuecomment-2860512129

Getting the same issue. Has anyone found a solution for this?

Updated the library to the latest version fixed my issue

— Reply to this email directly, view it on GitHub https://github.com/FaridSafi/react-native-gifted-chat/issues/2602#issuecomment-2860512129, or unsubscribe https://github.com/notifications/unsubscribe-auth/ALWRHVO25N7B2ZAEMPY4FKT25J7YXAVCNFSM6AAAAABZHO4SJOVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDQNRQGUYTEMJSHE . You are receiving this because you were mentioned.Message ID: @.***>

muazazhar avatar May 11 '25 09:05 muazazhar

I was using expo-navigation-bar to fix some edge-to-edge issues in my RN app and was seeing layout shifts on new devices when loading chat messages as well. I was able to pin it to an exact line in my code:

NavigationBar.setPositionAsync("relative");

My _layout.tsx:

const AppRootLayout = () => {
    return <Stack screenOptions={{
     contentStyle: {  backgroundColor: "red" }
    }}>
        <Stack.Screen name="index" />
    </Stack>
}
export default AppRootLayout;

index.tsx (working):

export default function IndexPage() {
    return <View style={{ flex: 1, backgroundColor: 'red' }}>
        <GiftedChat messages={[]} />
    </View>
}

index.tsx (not working):

export default function IndexPage() {
    useEffect(() => {
        NavigationBar.setPositionAsync('relative');
    }, []);
    return <View style={{ flex: 1, backgroundColor: 'red' }}>
        <GiftedChat messages={[]} />
    </View>
}

I'm able to temporarily work around my issues by using https://docs.expo.dev/versions/latest/sdk/navigation-bar/#navigationbarsetpositionasyncposition this "hack":

import * as NavigationBar from 'expo-navigation-bar';

useEfect(() => {
  NavigationBar.setPositionAsync("absolute");
}, []);

Maybe that points someone in the right direction.

posixpascal avatar May 16 '25 10:05 posixpascal

Same issue

creix avatar May 20 '25 10:05 creix

For now you can use version 2.4.0 as it helps me to remove this issue

Ritikraushan1 avatar May 23 '25 11:05 Ritikraushan1

I implemented the following solution, and it works well for me:

File path: node_modules/react-native-gifted-chat/lib/GiftedChat/index.js

function GiftedChatWrapper(props) { return ( <> {Platform.OS === 'ios' ? ( <KeyboardProvider> <GiftedChat {...props} /> </KeyboardProvider> ) : ( <GiftedChat {...props} /> )} </> ); }

mmud393 avatar Jul 09 '25 13:07 mmud393

I implemented the following solution, and it works well for me:

File path: node_modules/react-native-gifted-chat/lib/GiftedChat/index.js

function GiftedChatWrapper(props) { return ( <> {Platform.OS === 'ios' ? ( <KeyboardProvider> <GiftedChat {...props} /> </KeyboardProvider> ) : ( <GiftedChat {...props} /> )} </> ); }

Thanks for sharing this! Your solution fixed the issue with "react-native-gifted-chat" v2.8.1 on React Native 0.80.1. Much appreciated!

muku534 avatar Jul 12 '25 13:07 muku534

I implemented the following solution, and it works well for me:

File path: node_modules/react-native-gifted-chat/lib/GiftedChat/index.js

function GiftedChatWrapper(props) { return ( <> {Platform.OS === 'ios' ? ( <KeyboardProvider> <GiftedChat {...props} /> </KeyboardProvider> ) : ( <GiftedChat {...props} /> )} </> ); }

this onef

I implemented the following solution, and it works well for me:

File path: node_modules/react-native-gifted-chat/lib/GiftedChat/index.js

function GiftedChatWrapper(props) { return ( <> {Platform.OS === 'ios' ? ( <KeyboardProvider> <GiftedChat {...props} /> </KeyboardProvider> ) : ( <GiftedChat {...props} /> )} </> ); }

This one fixed my issue .. my issue was when i land on the chatting screen my status bar color changed ..etc

MAsif99 avatar Jul 31 '25 12:07 MAsif99

I have applied this patch Add disableKeyboardController prop to fix react-native-navigation compatibility #2650, 🚀🚀 which works fine in react-native 0.79.2 and react-native-gifted-chats: "2.8.1"

When can we expect the release update?

Thanks

Tring-Rajaguhan avatar Sep 19 '25 07:09 Tring-Rajaguhan

Try code from master or version 3.0.0-alpha.1 Should be fixed there

kesha-antonov avatar Nov 23 '25 16:11 kesha-antonov