npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@sujeetdotkumar/react-native-gifted-chat-performant

v1.0.1

Published

Performant chat UI for React Native — LegendList v3, native Intl, KeyboardChatLegendList

Readme


What's different from the original

This fork replaces the core rendering stack with purpose-built, higher-performance primitives while keeping the full public API intact (except the date/time format props — see Breaking Changes).

List renderer — LegendList v3

The AnimatedFlatList (a react-native-gesture-handler FlatList wrapped in Animated.createAnimatedComponent) has been replaced with KeyboardChatLegendList from @legendapp/list.

LegendList is purpose-built for chat and large lists in React Native. Key advantages over FlatList:

  • Separate recycling pools per item type ('message' vs 'day') — prevents layout thrash between different-height items
  • maintainScrollAtEnd, alignItemsAtEnd, initialScrollAtEnd — purpose-built chat scroll semantics without an inverted prop hack
  • sharedValues prop syncs scroll offset directly to a Reanimated SharedValue — no useAnimatedScrollHandler needed
  • Pure JS, no native code, no extra pod install

Day separator tracking — eliminated

The original implementation tracked day separator positions using a CellRendererComponent that fired a layout worklet on every message item on every render. Those positions were stored in a daysPositions SharedValue, sorted (O(n log n)), and interpolated on every scroll frame. All of that is gone.

Day separators are now interleaved directly in the data array (displayData) alongside messages. An onViewableItemsChanged callback tracks the topmost visible day for the animated overlay — zero per-item overhead.

Keyboard handling — KeyboardStickyView

KeyboardAvoidingView (which can cause layout jumps on keyboard open) has been replaced with:

  • KeyboardChatLegendList handles keyboard-aware scroll internally
  • KeyboardStickyView from react-native-keyboard-controller keeps the input toolbar pinned above the keyboard

dayjs removed — native Intl APIs

dayjs (~5 KB gzipped) has been removed entirely. All date and time formatting now uses the built-in Intl.DateTimeFormat API, which is zero-cost (already part of the JS engine), supports the same locale strings, and produces identical output.

lodash.isequal removed

lodash.isequal was listed as a dependency in the original but was not imported anywhere in src/. Removed.

React.memo on leaf components

Day, Time, and GiftedAvatar are now wrapped with React.memo to prevent unnecessary re-renders when parent state changes don't affect their props.

Performance summary

| Bottleneck | Original | This fork | |---|---|---| | List virtualization | RNGH FlatList + Animated wrapper | LegendList v3 with separate recycling pools | | Day position tracking | O(n log n) sort + worklet per item layout | Eliminated — data-driven via displayData | | Per-item CellRendererComponent | Fires worklet on every item layout | Removed | | daysPositions SharedValue | Modified per-item, read per scroll frame | Removed | | dayjs bundle | ~5 KB gzipped | 0 (native Intl) | | lodash.isequal | ~4 KB gzipped | 0 (removed) |


Breaking Changes

Date & time format props

The original dayjs-based format props have been replaced with Intl.DateTimeFormatOptions:

| Original prop | Type | Replacement | Type | |---|---|---|---| | timeFormat | string (dayjs format, e.g. 'LT') | timeFormatOptions | Intl.DateTimeFormatOptions | | dateFormat | string (dayjs format, e.g. 'D MMMM') | dateFormatOptions | Intl.DateTimeFormatOptions | | dateFormatCalendar | object (dayjs calendar options) | removed — use dateFormatOptions | — |

Migration example:

// Before
<GiftedChat timeFormat='HH:mm' dateFormat='D MMMM' />

// After
<GiftedChat
  timeFormatOptions={{ hour: '2-digit', minute: '2-digit', hour12: false }}
  dateFormatOptions={{ day: 'numeric', month: 'long' }}
/>

The locale prop continues to work — it is passed directly to Intl.DateTimeFormat as the locale string.


Features

  • Fully customizable — override any component with your own implementation
  • Composer actions — attach photos, files, or trigger custom actions
  • Reply to messages — swipe-to-reply with reply preview and message threading
  • Load earlier messages — infinite scroll with pagination support
  • Copy to clipboard — long-press messages to copy text
  • Smart link parsing — auto-detect URLs, emails, phone numbers, hashtags, mentions
  • Avatars — user initials or custom avatar images
  • Localized dates — full i18n support via native Intl.DateTimeFormat
  • Keyboard handling — KeyboardChatLegendList + KeyboardStickyView for all platforms
  • System messages — display system notifications in chat
  • Quick replies — bot-style quick reply buttons
  • Typing indicator — show when users are typing
  • Message status — tick indicators for sent/delivered/read states
  • Scroll to bottom — quick navigation button
  • Web support — works with react-native-web
  • Expo support — easy integration with Expo projects
  • TypeScript — complete TypeScript definitions included

Requirements

| Requirement | Version | |-------------|---------| | React Native | >= 0.70.0 | | iOS | >= 13.4 | | Android | API 21+ (Android 5.0) | | Expo | SDK 50+ | | react-native-keyboard-controller | >= 1.21.0 | | TypeScript | >= 5.0 (optional) |


Installation

Expo Projects

npx expo install @sujeetdotkumar/react-native-gifted-chat-performant @legendapp/list react-native-reanimated react-native-gesture-handler react-native-safe-area-context react-native-keyboard-controller

Bare React Native Projects

yarn add @sujeetdotkumar/react-native-gifted-chat-performant @legendapp/list react-native-reanimated react-native-gesture-handler react-native-safe-area-context react-native-keyboard-controller

Then install iOS pods:

npx pod-install

And follow the react-native-reanimated installation guide to add the Babel plugin.


Usage

import React, { useState, useCallback, useEffect } from 'react'
import { GiftedChat } from '@sujeetdotkumar/react-native-gifted-chat-performant'
import { useHeaderHeight } from '@react-navigation/elements'

export function Example() {
  const [messages, setMessages] = useState([])
  const headerHeight = useHeaderHeight()

  useEffect(() => {
    setMessages([
      {
        _id: 1,
        text: 'Hello developer',
        createdAt: new Date(),
        user: {
          _id: 2,
          name: 'John Doe',
          avatar: 'https://placeimg.com/140/140/any',
        },
      },
    ])
  }, [])

  const onSend = useCallback((messages = []) => {
    setMessages(previousMessages =>
      GiftedChat.append(previousMessages, messages),
    )
  }, [])

  return (
    <GiftedChat
      messages={messages}
      onSend={messages => onSend(messages)}
      user={{ _id: 1 }}
      keyboardAvoidingViewProps={{ keyboardVerticalOffset: headerHeight }}
    />
  )
}

Props Reference

Core Configuration

  • messages (Array) - Messages to display
  • user (Object) - User sending the messages: { _id, name, avatar }
  • onSend (Function) - Callback when sending a message
  • messageIdGenerator (Function) - Generate an id for new messages. Defaults to a simple random string generator.
  • locale (String) - Locale string passed to Intl.DateTimeFormat (e.g. 'fr', 'de', 'ja')
  • colorScheme ('light' | 'dark') - Force color scheme. When undefined, uses the system color scheme.

Refs

  • messagesContainerRef (LegendList ref) - Ref to the list
  • textInputRef (TextInput ref) - Ref to the text input

Keyboard & Layout

  • keyboardProviderProps (Object) - Props for KeyboardProvider
  • keyboardAvoidingViewProps (Object) - Props including keyboardVerticalOffset (distance from screen top to GiftedChat container — use useHeaderHeight() when inside a navigation stack)
  • isAlignedTop (Boolean) - Align bubbles to top instead of bottom; default false
  • isInverted (Bool) - Reverses display order of messages; default true

Text Input & Composer

  • text (String) - Controlled input text
  • initialText (String) - Initial text in the input field
  • isSendButtonAlwaysVisible (Bool) - Always show send button; default false
  • minComposerHeight / maxComposerHeight (Number) - Composer height bounds
  • minInputToolbarHeight (Integer) - Minimum toolbar height; default 44
  • renderInputToolbar (Component | Function) - Custom input toolbar
  • renderComposer (Component | Function) - Custom text input
  • renderSend (Component | Function) - Custom send button
  • renderActions (Component | Function) - Custom action button (left of composer)
  • renderAccessory (Component | Function) - Custom second line below composer
  • textInputProps (Object) - Props passed to <TextInput>

Messages & Message Container

  • messagesContainerStyle (Object) - Custom style for messages container
  • renderMessage (Component | Function) - Custom message container
  • renderLoading (Component | Function) - Loading view while initializing
  • renderChatEmpty (Component | Function) - Component when messages are empty
  • renderChatFooter (Component | Function) - Component below the message list
  • listProps (Object) - Extra props passed to the underlying LegendList

Message Bubbles & Content

  • renderBubble (Component | Function) - Custom message bubble
  • renderMessageText (Component | Function) - Custom message text
  • renderMessageImage (Component | Function) - Custom message image
  • renderMessageVideo (Component | Function) - Custom message video
  • renderMessageAudio (Component | Function) - Custom message audio
  • renderCustomView (Component | Function) - Custom view inside the bubble
  • isCustomViewBottom (Bool) - Render custom view below text/image; default false
  • onPressMessage / onLongPressMessage (Function) - Message tap/long-press callbacks
  • imageProps / imageStyle / videoProps - Image and video customization
  • messageTextProps (Object) - Props for MessageText (link parsing, matchers, styles)

Avatars

  • renderAvatar (Component | Function | null) - Custom avatar; null to hide
  • isUserAvatarVisible (Bool) - Show avatar for current user; default false
  • isAvatarVisibleForEveryMessage (Bool) - Show avatar on every message; default false
  • onPressAvatar / onLongPressAvatar (Function) - Avatar tap callbacks
  • isAvatarOnTop (Bool) - Show avatar at top of consecutive messages; default false

Username

  • isUsernameVisible (Bool) - Show username in bubble; default false
  • renderUsername (Component | Function) - Custom username component

Date & Time

  • timeFormatOptions (Intl.DateTimeFormatOptions) - Format for message times; default { hour: '2-digit', minute: '2-digit' }
  • dateFormatOptions (Intl.DateTimeFormatOptions) - Format for day separators; default { day: 'numeric', month: 'long' }
  • renderDay (Component | Function) - Custom day separator
  • renderTime (Component | Function) - Custom time component
  • timeTextStyle (Object) - Custom time text style (supports left/right)
  • isDayAnimationEnabled (Bool) - Animated day label on scroll; default true

System Messages

  • renderSystemMessage (Component | Function) - Custom system message

Load Earlier Messages

  • loadEarlierMessagesProps (Object)
    • isAvailable - Show/hide button
    • onPress - Load callback
    • isLoading - Show spinner
    • isInfiniteScrollEnabled - Auto-trigger onPress at top of list
    • label, containerStyle, wrapperStyle, textStyle - Customization
  • renderLoadEarlier (Component | Function) - Custom load-earlier button

Typing Indicator

  • isTyping (Bool) - Show typing indicator; default false
  • renderTypingIndicator (Component | Function) - Custom typing indicator
  • renderFooter (Component | Function) - Custom footer (overrides typing indicator)

Quick Replies

  • onQuickReply (Function) - Callback when quick reply is sent
  • renderQuickReplies / renderQuickReplySend (Function) - Custom renderers
  • quickReplyStyle / quickReplyTextStyle / quickReplyContainerStyle - Styles

Reply to Messages

<GiftedChat
  reply={{
    swipe: {
      isEnabled: true,
      direction: 'left',
      onSwipe: (message) => setReplyMessage(message),
    },
    message: replyMessage,
    onClear: () => setReplyMessage(null),
  }}
/>

Full reply prop shape:

interface ReplyProps<TMessage> {
  swipe?: {
    isEnabled?: boolean
    direction?: 'left' | 'right'
    onSwipe?: (message: TMessage) => void
    renderAction?: (progress, translation, position) => React.ReactNode
    actionContainerStyle?: StyleProp<ViewStyle>
  }
  previewStyle?: { containerStyle?, textStyle?, imageStyle? }
  messageStyle?: { containerStyle?, textStyle?, imageStyle?, ...left/right variants }
  message?: ReplyMessage
  onClear?: () => void
  onPress?: (message: TMessage) => void
  renderPreview?: (props: ReplyPreviewProps) => React.ReactNode
  renderMessageReply?: (props: MessageReplyProps) => React.ReactNode
}

Scroll to Bottom

  • isScrollToBottomEnabled (Bool) - Show scroll-to-bottom button; default false
  • scrollToBottomComponent (Function) - Custom button content
  • scrollToBottomStyle / scrollToBottomContentStyle - Styles

Platform Notes

Android

Add android:windowSoftInputMode="adjustResize" to your AndroidManifest.xml:

<activity
  android:name=".MainActivity"
  android:windowSoftInputMode="adjustResize"
  android:configChanges="keyboard|keyboardHidden|orientation|screenSize">

Web (react-native-web)

@legendapp/list is pure JS and works with react-native-web. Follow the standard react-native-web webpack config to alias react-native imports.


Contributing

yarn install
yarn build       # outputs to lib/
yarn test        # runs all tests
yarn lint        # check for lint errors
yarn lint:fix    # auto-fix lint errors
yarn prepublishOnly  # full validation: lint + test + build

Credits

Based on react-native-gifted-chat by Farid Safi and contributors.

Performance improvements by Sujeet Kumar.


License

MIT