From 8c095b72cd4a71bcdf9a00ed33caee643a358313 Mon Sep 17 00:00:00 2001
From: Johan-dutoit
Date: Wed, 7 Jun 2023 17:57:31 +0200
Subject: [PATCH] refactor(example): change to functional component
---
README.md | 19 +-
example/App.tsx | 422 ++++++++++++-------------
example/example-expo/CustomActions.tsx | 63 ++--
src/GiftedChat.tsx | 7 +-
4 files changed, 265 insertions(+), 246 deletions(-)
diff --git a/README.md b/README.md
index 9a5fbe5dd..f13cefe27 100644
--- a/README.md
+++ b/README.md
@@ -67,12 +67,14 @@
-## The future of GiftedChat 🎉
+## The future of GiftedChat 🎉
+
Please give us your advice: [Related PR](https://github.com/FaridSafi/react-native-gifted-chat/pull/1775)
## Please vote
**GiftedChat** depends on other packages and some needs a boost, please vote for PRs will improve it, thanks:
+
- https://github.com/watadarkstar/react-native-typing-animation/issues/18
## Features
@@ -100,23 +102,24 @@ Please give us your advice: [Related PR](https://github.com/FaridSafi/react-nati
- Use version `0.0.10` for RN `< 0.40.0`
## Testing
+
`Test_ID` is exported as constants that can be used in your testing library of choice
-Gifted Chat uses `onLayout` to determine the height of the chat container. To trigger `onLayout` during your tests, you can run the following bits of code.
+Gifted Chat uses `onLayout` to determine the height of the chat container. To trigger `onLayout` during your tests, you can run the following bits of code.
```typescript
const WIDTH = 200 // or any number
const HEIGHT = 2000 // or any number
-const loadingWrapper = getByTestId(Test_ID.LOADING.WRAPPER)
-fireEvent(loadingWrapper, "layout", {
+const loadingWrapper = getByTestId(TEST_ID.LOADING_WRAPPER)
+fireEvent(loadingWrapper, 'layout', {
nativeEvent: {
layout: {
width: WIDTH,
height: HEIGHT,
},
},
-});
+})
```
## Installation
@@ -143,7 +146,7 @@ import React, { useState, useCallback, useEffect } from 'react'
import { GiftedChat } from 'react-native-gifted-chat'
export function Example() {
- const [messages, setMessages] = useState([]);
+ const [messages, setMessages] = useState([])
useEffect(() => {
setMessages([
@@ -161,7 +164,9 @@ export function Example() {
}, [])
const onSend = useCallback((messages = []) => {
- setMessages(previousMessages => GiftedChat.append(previousMessages, messages))
+ setMessages(previousMessages =>
+ GiftedChat.append(previousMessages, messages),
+ )
}, [])
return (
diff --git a/example/App.tsx b/example/App.tsx
index ef57ad4dc..56b5df8df 100644
--- a/example/App.tsx
+++ b/example/App.tsx
@@ -1,33 +1,20 @@
-import MaterialIcons from '@expo/vector-icons/MaterialIcons'
-import * as Linking from 'expo-linking'
-import { SafeAreaView } from 'react-native-safe-area-context'
-import AppLoading from 'expo-app-loading'
-import React, { Component } from 'react'
-import { StyleSheet, View, Text, Platform, Alert } from 'react-native'
+import { MaterialIcons } from '@expo/vector-icons'
+import React, { useCallback, useReducer } from 'react'
+import { Alert, Linking, Platform, StyleSheet, Text, View } from 'react-native'
import {
- Bubble,
GiftedChat,
- SystemMessage,
IMessage,
Send,
SendProps,
+ SystemMessage,
} from 'react-native-gifted-chat'
-
+import { SafeAreaView } from 'react-native-safe-area-context'
+import { NavBar } from './components/navbar'
import AccessoryBar from './example-expo/AccessoryBar'
import CustomActions from './example-expo/CustomActions'
import CustomView from './example-expo/CustomView'
-import messagesData from './example-expo/data/messages'
import earlierMessages from './example-expo/data/earlierMessages'
-import { NavBar } from './components/navbar'
-
-const styles = StyleSheet.create({
- container: { flex: 1, backgroundColor: '#f5f5f5', },
- content: { backgroundColor: "#ffffff", flex: 1, }
-})
-
-const filterBotMessages = message =>
- !message.system && message.user && message.user._id && message.user._id === 2
-const findStep = step => message => message._id === step
+import messagesData from './example-expo/data/messages'
const user = {
_id: 1,
@@ -40,92 +27,98 @@ const otherUser = {
avatar: 'https://facebook.github.io/react/img/logo_og.png',
}
-export default class App extends Component {
- state = {
- inverted: false,
- step: 0,
- messages: [],
- loadEarlier: true,
- typingText: null,
- isLoadingEarlier: false,
- appIsReady: false,
- isTyping: false,
- }
-
- _isMounted = false
+interface IState {
+ messages: any[]
+ step: number
+ loadEarlier?: boolean
+ isLoadingEarlier?: boolean
+ isTyping: boolean
+}
- componentDidMount() {
- this._isMounted = true
- // init with only system messages
- this.setState({
- messages: messagesData, // messagesData.filter(message => message.system),
- appIsReady: true,
- isTyping: false,
- })
- }
+enum ActionKind {
+ SEND_MESSAGE = 'SEND_MESSAGE',
+ LOAD_EARLIER_MESSAGES = 'LOAD_EARLIER_MESSAGES',
+ LOAD_EARLIER_START = 'LOAD_EARLIER_START',
+ SET_IS_TYPING = 'SET_IS_TYPING',
+ // LOAD_EARLIER_END = 'LOAD_EARLIER_END',
+}
- componentWillUnmount() {
- this._isMounted = false
- }
+// An interface for our actions
+interface StateAction {
+ type: ActionKind
+ payload?: any
+}
- onLoadEarlier = () => {
- this.setState(() => {
+function reducer(state: IState, action: StateAction) {
+ switch (action.type) {
+ case ActionKind.SEND_MESSAGE: {
+ return {
+ ...state,
+ step: state.step + 1,
+ messages: action.payload,
+ }
+ }
+ case ActionKind.LOAD_EARLIER_MESSAGES: {
+ return {
+ ...state,
+ loadEarlier: true,
+ isLoadingEarlier: false,
+ messages: action.payload,
+ }
+ }
+ case ActionKind.LOAD_EARLIER_START: {
return {
+ ...state,
isLoadingEarlier: true,
}
- })
-
- setTimeout(() => {
- if (this._isMounted === true) {
- this.setState((previousState: any) => {
- return {
- messages: GiftedChat.prepend(
- previousState.messages,
- earlierMessages() as IMessage[],
- Platform.OS !== 'web',
- ),
- loadEarlier: true,
- isLoadingEarlier: false,
- }
- })
+ }
+ case ActionKind.SET_IS_TYPING: {
+ return {
+ ...state,
+ isTyping: action.payload,
}
- }, 1500) // simulating network
+ }
}
+}
- onSend = (messages = []) => {
- const step = this.state.step + 1
- this.setState((previousState: any) => {
+const App = () => {
+ const [state, dispatch] = useReducer(reducer, {
+ messages: messagesData,
+ step: 0,
+ loadEarlier: true,
+ isLoadingEarlier: false,
+ isTyping: false,
+ })
+
+ const onSend = useCallback(
+ (messages: any[]) => {
const sentMessages = [{ ...messages[0], sent: true, received: true }]
- return {
- messages: GiftedChat.append(
- previousState.messages,
- sentMessages,
- Platform.OS !== 'web',
- ),
- step,
- }
- })
- // for demo purpose
- // setTimeout(() => this.botSend(step), Math.round(Math.random() * 1000))
- }
+ const newMessages = GiftedChat.append(
+ state.messages,
+ sentMessages,
+ Platform.OS !== 'web',
+ )
- botSend = (step = 0) => {
- const newMessage = (messagesData as IMessage[])
- .reverse()
- // .filter(filterBotMessages)
- .find(findStep(step))
- if (newMessage) {
- this.setState((previousState: any) => ({
- messages: GiftedChat.append(
- previousState.messages,
- [newMessage],
- Platform.OS !== 'web',
- ),
- }))
- }
- }
+ dispatch({ type: ActionKind.SEND_MESSAGE, payload: newMessages })
+ },
+ [dispatch, state.messages],
+ )
+
+ const onLoadEarlier = useCallback(() => {
+ console.log('loading')
+ dispatch({ type: ActionKind.LOAD_EARLIER_START })
+ setTimeout(() => {
+ const newMessages = GiftedChat.prepend(
+ state.messages,
+ earlierMessages() as IMessage[],
+ Platform.OS !== 'web',
+ )
- parsePatterns = (_linkStyle: any) => {
+ dispatch({ type: ActionKind.LOAD_EARLIER_MESSAGES, payload: newMessages })
+ }, 1500) // simulating network
+ }, [dispatch, state.messages])
+
+ const parsePatterns = useCallback((_linkStyle: any) => {
return [
{
pattern: /#(\w+)/,
@@ -133,79 +126,20 @@ export default class App extends Component {
onPress: () => Linking.openURL('http://gifted.chat'),
},
]
- }
-
- renderCustomView(props) {
- return
- }
-
- onReceive = (text: string) => {
- this.setState((previousState: any) => {
- return {
- messages: GiftedChat.append(
- previousState.messages as any,
- [
- {
- _id: Math.round(Math.random() * 1000000),
- text,
- createdAt: new Date(),
- user: otherUser,
- },
- ],
- Platform.OS !== 'web',
- ),
- }
- })
- }
-
- onSendFromUser = (messages: IMessage[] = []) => {
- const createdAt = new Date()
- const messagesToUpload = messages.map(message => ({
- ...message,
- user,
- createdAt,
- _id: Math.round(Math.random() * 1000000),
- }))
- this.onSend(messagesToUpload)
- }
+ }, [])
- setIsTyping = () => {
- this.setState({
- isTyping: !this.state.isTyping,
- })
- }
+ const onLongPressAvatar = useCallback((pressedUser: any) => {
+ Alert.alert(JSON.stringify(pressedUser))
+ }, [])
- renderAccessory = () => (
-
- )
+ const onPressAvatar = useCallback(() => {
+ Alert.alert('On avatar press')
+ }, [])
- renderCustomActions = props =>
- Platform.OS === 'web' ? null : (
-
- )
-
- renderBubble = (props: any) => {
- return
- }
-
- renderSystemMessage = props => {
- return (
-
- )
- }
-
- onQuickReply = replies => {
+ const onQuickReply = useCallback((replies: any[]) => {
const createdAt = new Date()
if (replies.length === 1) {
- this.onSend([
+ onSend([
{
createdAt,
_id: Math.round(Math.random() * 1000000),
@@ -214,7 +148,7 @@ export default class App extends Component {
},
])
} else if (replies.length > 1) {
- this.onSend([
+ onSend([
{
createdAt,
_id: Math.round(Math.random() * 1000000),
@@ -225,62 +159,120 @@ export default class App extends Component {
} else {
console.warn('replies param is not set correctly')
}
- }
+ }, [])
- renderQuickReplySend = () => {' custom send =>'}
+ const renderQuickReplySend = useCallback(() => {
+ return {' custom send =>'}
+ }, [])
- renderSend = (props: SendProps) => (
-
-
-
+ const setIsTyping = useCallback(
+ (isTyping: boolean) => {
+ dispatch({ type: ActionKind.SET_IS_TYPING, payload: isTyping })
+ },
+ [dispatch],
)
- render() {
- if (!this.state.appIsReady) {
- return
- }
+ const onSendFromUser = useCallback(
+ (messages: IMessage[] = []) => {
+ const createdAt = new Date()
+ const messagesToUpload = messages.map(message => ({
+ ...message,
+ user,
+ createdAt,
+ _id: Math.round(Math.random() * 1000000),
+ }))
+
+ onSend(messagesToUpload)
+ },
+ [onSend],
+ )
+
+ const renderAccessory = useCallback(() => {
return (
-
-
-
- alert(JSON.stringify(user))}
- onPressAvatar={() => alert('short press')}
- onPress={() => {
- Alert.alert('Bubble pressed')
- }}
- onQuickReply={this.onQuickReply}
- keyboardShouldPersistTaps='never'
- renderAccessory={Platform.OS === 'web' ? null : this.renderAccessory}
- renderActions={this.renderCustomActions}
- renderBubble={this.renderBubble}
- renderSystemMessage={this.renderSystemMessage}
- renderCustomView={this.renderCustomView}
- renderSend={this.renderSend}
- quickReplyStyle={{ borderRadius: 2 }}
- quickReplyTextStyle={{
- fontWeight: '200',
- }}
- renderQuickReplySend={this.renderQuickReplySend}
- inverted={Platform.OS !== 'web'}
- timeTextStyle={{ left: { color: 'red' }, right: { color: 'yellow' } }}
- isTyping={this.state.isTyping}
- infiniteScroll
- />
-
-
+ setIsTyping(true)}
+ />
)
- }
+ }, [onSendFromUser, setIsTyping])
+
+ const renderCustomActions = useCallback(
+ props =>
+ Platform.OS === 'web' ? null : (
+
+ ),
+ [onSendFromUser],
+ )
+
+ const renderSystemMessage = useCallback(props => {
+ return (
+
+ )
+ }, [])
+
+ const renderCustomView = useCallback(props => {
+ return
+ }, [])
+
+ const renderSend = useCallback((props: SendProps) => {
+ return (
+
+
+
+ )
+ }, [])
+
+ return (
+
+
+
+
+
+
+ )
}
+
+const styles = StyleSheet.create({
+ container: { flex: 1, backgroundColor: '#f5f5f5' },
+ content: { backgroundColor: '#ffffff', flex: 1 },
+})
+
+export default App
diff --git a/example/example-expo/CustomActions.tsx b/example/example-expo/CustomActions.tsx
index b2829ad7f..baa33abc9 100644
--- a/example/example-expo/CustomActions.tsx
+++ b/example/example-expo/CustomActions.tsx
@@ -1,21 +1,41 @@
import PropTypes from 'prop-types'
-import React from 'react'
+import React, { useCallback } from 'react'
import {
+ StyleProp,
StyleSheet,
Text,
+ TextStyle,
TouchableOpacity,
View,
ViewPropTypes,
+ ViewStyle,
} from 'react-native'
+import { useActionSheet } from '@expo/react-native-action-sheet'
import {
getLocationAsync,
pickImageAsync,
takePictureAsync,
} from './mediaUtils'
-export default class CustomActions extends React.Component {
- onActionsPress = () => {
+interface Props {
+ renderIcon?: () => React.ReactNode
+ wrapperStyle?: StyleProp
+ containerStyle?: StyleProp
+ iconTextStyle?: StyleProp
+ onSend: (messages: any) => void
+}
+
+const CustomActions = ({
+ renderIcon,
+ iconTextStyle,
+ containerStyle,
+ wrapperStyle,
+ onSend,
+}: Props) => {
+ const { showActionSheetWithOptions } = useActionSheet()
+
+ const onActionsPress = useCallback(() => {
const options = [
'Choose From Library',
'Take Picture',
@@ -23,13 +43,12 @@ export default class CustomActions extends React.Component {
'Cancel',
]
const cancelButtonIndex = options.length - 1
- this.context.actionSheet().showActionSheetWithOptions(
+ showActionSheetWithOptions(
{
options,
cancelButtonIndex,
},
async buttonIndex => {
- const { onSend } = this.props
switch (buttonIndex) {
case 0:
pickImageAsync(onSend)
@@ -43,31 +62,31 @@ export default class CustomActions extends React.Component {
}
},
)
- }
+ }, [showActionSheetWithOptions])
- renderIcon = () => {
- if (this.props.renderIcon) {
- return this.props.renderIcon()
+ const renderIconComponent = useCallback(() => {
+ if (renderIcon) {
+ return renderIcon()
}
return (
-
- +
+
+ +
)
- }
+ }, [])
- render() {
- return (
-
- {this.renderIcon()}
-
- )
- }
+ return (
+
+ <>{renderIconComponent()}>
+
+ )
}
+export default CustomActions
+
const styles = StyleSheet.create({
container: {
width: 26,
diff --git a/src/GiftedChat.tsx b/src/GiftedChat.tsx
index 769739b0a..9b1623662 100644
--- a/src/GiftedChat.tsx
+++ b/src/GiftedChat.tsx
@@ -6,7 +6,7 @@ import {
import dayjs from 'dayjs'
import localizedFormat from 'dayjs/plugin/localizedFormat'
import PropTypes from 'prop-types'
-import React, { useEffect, useMemo, useRef, useState } from 'react'
+import React, { createRef, useEffect, useMemo, useRef, useState } from 'react'
import {
Animated,
FlatList,
@@ -53,6 +53,8 @@ import * as utils from './utils'
dayjs.extend(localizedFormat)
export interface GiftedChatProps {
+ /* Message container ref */
+ messageContainerRef?: React.RefObject>
/* Messages to display */
messages?: TMessage[]
/* Typing Indicator state */
@@ -256,6 +258,7 @@ function GiftedChat(
inverted = true,
minComposerHeight = MIN_COMPOSER_HEIGHT,
maxComposerHeight = MAX_COMPOSER_HEIGHT,
+ messageContainerRef = createRef>(),
} = props
const isMountedRef = useRef(false)
@@ -264,7 +267,7 @@ function GiftedChat(
const maxHeightRef = useRef(undefined)
const isFirstLayoutRef = useRef(true)
const actionSheetRef = useRef(null)
- const messageContainerRef = useRef>(null)
+
let _isTextInputWasFocused: boolean = false
let textInputRef = useRef()