Glith on rendering new item
Open
Nobody has claimed this yet.
bug
- Dominant language
- TypeScript
- Stars
- 7.2k
- Forks
- 393
- Avg merge
- 1d 3h
- Merged PRs (30d)
- 1
Description
Current behavior
Currently when I added item to top then never rendered first time that with glitch on scrolling
Expected behavior
smooth scrolling while rendering new item never rendered before
To Reproduce
Already add keyExtractor and more
Platform:
- iOS
- Android
Environment
"dependencies": {
"@lodev09/react-native-true-sheet": "^2.0.6",
"@react-native-vector-icons/icomoon": "^12.2.0",
"@react-native/new-app-screen": "0.80.2",
"@react-navigation/bottom-tabs": "^7.4.6",
"@react-navigation/elements": "^2.6.3",
"@react-navigation/native": "^7.1.17",
"@react-navigation/native-stack": "^7.3.25",
"@shopify/flash-list": "^2.0.3",
"clsx": "^2.1.1",
"formik": "^2.4.6",
"nativewind": "^4.1.23",
"react": "19.1.0",
"react-native": "0.80.2",
"react-native-edge-to-edge": "^1.6.2",
"react-native-flash-message": "^0.4.2",
"react-native-image-picker": "^8.2.1",
"react-native-keyboard-controller": "^1.18.5",
"react-native-reanimated": "^3",
"react-native-safe-area-context": "^5.6.1",
"react-native-screens": "^4.15.2",
"yup": "^1.7.0"
},
List
import { ChatListing } from "@/src/features/chat/components/molecule";
import { ChatMessage } from "@/src/features/chat/types";
import { faker } from "@faker-js/faker";
import { FlashList, FlashListRef, ListRenderItem } from "@shopify/flash-list";
import React, { useState } from "react";
import { useSafeAreaInsets } from "react-native-safe-area-context";
// Generate initial 50 dummy items
const generateItems = (start: number, count: number) => {
const timestamp = faker.date.recent({ days: 7 });
return Array.from({ length: count }, (_, i) => ({
id: String(start + i),
text: faker.lorem.sentence(),
sender: Math.random() > 0.5 ? "u1" : "u2",
timestamp: timestamp.toISOString(),
}));
};
const CURRENT_USER = "u1";
interface Props {
ref?: React.RefObject<FlashListRef<ChatMessage> | null>;
}
export const ChatList: React.FC<Props> = ({ ref }) => {
const [items, setItems] = useState<ChatMessage[]>(generateItems(1, 50));
const { bottom } = useSafeAreaInsets();
const timestamp = faker.date.recent({ days: 7 });
const handleStartReached = async () => {
// Generate 15 new messages
const newMessages = Array.from({ length: 115 }, (_, i) => ({
id: `${Date.now()}-${i}`, // unique ID
text: faker.lorem.sentence(),
sender: Math.random() > 0.5 ? "u1" : "u2",
timestamp: timestamp.toISOString(),
}));
// Prepend them to the existing items
setItems((prev) => [...newMessages, ...prev]);
};
const renderItem: ListRenderItem<ChatMessage> = ({ index, item }) => {
const prevUser = index > 0 ? items[index - 1].sender : null;
const nextUser = index < items.length - 1 ? items[index + 1].sender : null;
const prevIsMe = prevUser === item.sender;
const nextIsMe = nextUser === item.sender;
return (
<ChatListing
prevIsMe={prevIsMe}
nextIsMe={nextIsMe}
isMe={item.sender === CURRENT_USER}
message={item}
/>
);
};
return (
<FlashList
onStartReachedThreshold={0.6}
onStartReached={handleStartReached}
ref={ref}
data={items}
showsVerticalScrollIndicator
renderItem={renderItem}
keyExtractor={(item) => item.id}
contentContainerStyle={{ paddingTop: 16, paddingBottom: bottom + 16 }}
maintainVisibleContentPosition={{
autoscrollToBottomThreshold: 0.2,
startRenderingFromBottom: true, // keep chat starting from bottom
}}
getItemType={(item) => {
return item.sender;
}}
drawDistance={600}
/>
);
};
- Card
import { ChatMessage } from "@/src/features/chat/types";
import colors from "@/src/theme/colors";
import { Image } from "expo-image";
import React, { useState } from "react";
import { Text, View } from "react-native";
import { StyleSheet } from "react-native-unistyles";
interface Props {
message: ChatMessage;
isMe: boolean;
prevIsMe?: boolean;
nextIsMe?: boolean;
}
const formatTime = (isoTimestamp: string): string => {
return new Date(isoTimestamp).toLocaleTimeString([], {
hour: "2-digit",
minute: "2-digit",
hour12: true,
});
};
const MESSAGE_AVATAR_SIZE = 25;
const GAP_BETWEEN_AVATAR_AND_CARD = 8;
const GAP_BETWEEN_CARD_AND_TIME = 4;
export const ChatListing: React.FC<Props> = ({
message,
isMe,
prevIsMe,
nextIsMe,
}) => {
const [showTime, setShowTime] = useState(false);
styles.useVariants({
isMe: isMe ? "true" : "false",
prevIsMe: prevIsMe ? "true" : "false",
nextIsMe: nextIsMe ? "true" : "false",
});
return (
<View style={[styles.container]}>
{!isMe && !nextIsMe && (
<Image
style={{ width: 25, height: 25 }}
source={require("@/assets/images/avatar.png")}
cachePolicy={"memory-disk"}
/>
)}
<View style={styles.inner}>
<View style={[styles.card]}>
<Text style={styles.text}>{message.text}</Text>
</View>
{showTime ||
(!nextIsMe && (
<Text style={styles.time}>{formatTime(message.timestamp)}</Text>
))}
</View>
</View>
);
};
const styles = StyleSheet.create(() => ({
container: {
flex: 1,
flexDirection: "row",
gap: GAP_BETWEEN_AVATAR_AND_CARD,
paddingHorizontal: 16,
variants: {
isMe: {
true: {
maxWidth: "65%",
alignSelf: "flex-end",
},
false: {
maxWidth: "70%",
alignSelf: "flex-start",
alignItems: "flex-end",
},
},
prevIsMe: {
true: {
paddingTop: 4,
},
false: {
paddingTop: 16,
},
},
},
},
inner: {
gap: GAP_BETWEEN_CARD_AND_TIME,
variants: {
nextIsMe: {
true: {
paddingLeft: MESSAGE_AVATAR_SIZE + GAP_BETWEEN_AVATAR_AND_CARD,
},
false: {},
},
},
},
card: {
paddingHorizontal: 12,
paddingVertical: 8,
borderTopLeftRadius: 12,
borderTopRightRadius: 12,
variants: {
isMe: {
true: {
backgroundColor: colors.blue[600],
borderBottomLeftRadius: 12,
},
false: {
backgroundColor: colors.gray[200],
borderBottomRightRadius: 12,
},
},
},
},
text: {
fontSize: 15,
lineHeight: 20,
variants: {
isMe: {
true: {
color: colors.white,
},
false: {
color: colors.black,
},
},
},
},
time: {
fontSize: 12,
color: colors.gray[500],
variants: {
isMe: {
true: {
alignSelf: "flex-end",
},
false: {
alignSelf: "flex-start",
},
},
},
},
}));
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the supplied ChatList entry point and reproduce the iOS behavior using FlashList 2.0.3 with the shown prepend logic and maintainVisibleContentPosition settings. Compare the first render and scrolling after new items are prepended; done means newly added items render on the first attempt without the reported scrolling glitch.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- react-native, typescript
- Domain
- mobile
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Needs clarification
- Newbie friendliness
- 32/100