Skip to content

Commit

Permalink
Merge pull request #178 from xmtp/user/alexrisch/hook-setup
Browse files Browse the repository at this point in the history
Hook Setup
  • Loading branch information
alexrisch authored Dec 15, 2023
2 parents ad6055b + 0ac8158 commit 0a69bf5
Show file tree
Hide file tree
Showing 13 changed files with 187 additions and 77 deletions.
7 changes: 3 additions & 4 deletions example/App.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
import { NavigationContainer } from '@react-navigation/native'
import React from 'react'
import { Button, Platform } from 'react-native'
import { QueryClient, QueryClientProvider } from 'react-query'
import { XmtpProvider } from 'xmtp-react-native-sdk'

import ConversationCreateScreen from './src/ConversationCreateScreen'
import ConversationScreen from './src/ConversationScreen'
import HomeScreen from './src/HomeScreen'
import LaunchScreen from './src/LaunchScreen'
import { Navigator } from './src/Navigation'
import TestScreen from './src/TestScreen'
import { XmtpContextProvider } from './src/XmtpContext'

const queryClient = new QueryClient()
export default function App() {
return (
<QueryClientProvider client={queryClient}>
<XmtpContextProvider>
<XmtpProvider>
<NavigationContainer>
<Navigator.Navigator>
<Navigator.Screen
Expand Down Expand Up @@ -71,7 +70,7 @@ export default function App() {
/>
</Navigator.Navigator>
</NavigationContainer>
</XmtpContextProvider>
</XmtpProvider>
</QueryClientProvider>
)
}
2 changes: 1 addition & 1 deletion example/src/ConversationCreateScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { NativeStackScreenProps } from '@react-navigation/native-stack'
import React, { useState } from 'react'
import { Button, ScrollView, Text, TextInput } from 'react-native'
import { useXmtp } from 'xmtp-react-native-sdk'

import { NavigationParamList } from './Navigation'
import { useXmtp } from './XmtpContext'

export default function ConversationCreateScreen({
route,
Expand Down
58 changes: 34 additions & 24 deletions example/src/ConversationScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import * as ImagePicker from 'expo-image-picker'
import type { ImagePickerAsset } from 'expo-image-picker'
import { PermissionStatus } from 'expo-modules-core'
import moment from 'moment'
import React, { useRef, useState } from 'react'
import React, { useCallback, useMemo, useRef, useState } from 'react'
import {
Button,
FlatList,
Expand All @@ -30,11 +30,10 @@ import {
DecodedMessage,
StaticAttachmentContent,
ReplyContent,
Client,
useClient,
} from 'xmtp-react-native-sdk'

import { NavigationParamList } from './Navigation'
import { useXmtp } from './XmtpContext'
import {
useConversation,
useMessage,
Expand All @@ -57,7 +56,7 @@ export default function ConversationScreen({
}: NativeStackScreenProps<NavigationParamList, 'conversation'>) {
const { topic } = route.params
const messageListRef = useRef<FlatList>(null)
let {
const {
data: messages,
refetch: refreshMessages,
isFetching,
Expand All @@ -74,10 +73,15 @@ export default function ConversationScreen({
fileUri: attachment?.image?.uri || attachment?.file?.uri,
mimeType: attachment?.file?.mimeType,
})
messages = (messages || []).filter(
(message) => !hiddenMessageTypes.includes(message.contentTypeId)

const filteredMessages = useMemo(
() =>
(messages ?? [])?.filter(
(message) => !hiddenMessageTypes.includes(message.contentTypeId)
),
[messages]
)
// console.log("messages", JSON.stringify(messages, null, 2));

const sendMessage = async (content: any) => {
setSending(true)
console.log('Sending message', content)
Expand All @@ -102,16 +106,22 @@ export default function ConversationScreen({
const sendRemoteAttachmentMessage = () =>
sendMessage({ remoteAttachment }).then(() => setAttachment(null))
const sendTextMessage = () => sendMessage({ text }).then(() => setText(''))
const scrollToMessageId = (messageId: string) => {
const index = (messages || []).findIndex((m) => m.id === messageId)
if (index === -1) {
return
}
return messageListRef.current?.scrollToIndex({
index,
animated: true,
})
}
const scrollToMessageId = useCallback(
(messageId: string) => {
const index = (filteredMessages || []).findIndex(
(m) => m.id === messageId
)
if (index === -1) {
return
}
return messageListRef.current?.scrollToIndex({
index,
animated: true,
})
},
[filteredMessages]
)

return (
<SafeAreaView style={{ flex: 1 }}>
<KeyboardAvoidingView
Expand Down Expand Up @@ -142,7 +152,7 @@ export default function ConversationScreen({
contentContainerStyle={{ paddingBottom: 100 }}
refreshing={isFetching || isRefetching}
onRefresh={refreshMessages}
data={messages}
data={filteredMessages}
inverted
keyboardDismissMode="none"
keyExtractor={(message) => message.id}
Expand All @@ -154,9 +164,9 @@ export default function ConversationScreen({
onReply={() => setReplyingTo(message.id)}
onMessageReferencePress={scrollToMessageId}
showSender={
index === (messages || []).length - 1 ||
(index + 1 < (messages || []).length &&
messages![index + 1].senderAddress !==
index === (filteredMessages || []).length - 1 ||
(index + 1 < (filteredMessages || []).length &&
filteredMessages![index + 1].senderAddress !==
message.senderAddress)
}
/>
Expand Down Expand Up @@ -1046,7 +1056,7 @@ function MessageContents({
contentTypeId: string
content: any
}) {
const { client }: { client: Client<any> } = useXmtp()
const { client } = useClient()

if (contentTypeId === 'xmtp.org/text:1.0') {
const text: string = content
Expand Down Expand Up @@ -1080,8 +1090,8 @@ function MessageContents({
if (contentTypeId === 'xmtp.org/reply:1.0') {
const replyContent: ReplyContent = content
const replyContentType = replyContent.contentType
const codec = client.codecRegistry[replyContentType]
const actualReplyContent = codec.decode(replyContent.content)
const codec = client?.codecRegistry[replyContentType]
const actualReplyContent = codec?.decode(replyContent.content)

return (
<View>
Expand Down
3 changes: 1 addition & 2 deletions example/src/HomeScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@ import {
Text,
View,
} from 'react-native'
import { Conversation, Client } from 'xmtp-react-native-sdk'
import { Conversation, Client, useXmtp } from 'xmtp-react-native-sdk'

import { useXmtp } from './XmtpContext'
import { useConversationList, useMessages } from './hooks'

/// Show the user's list of conversations.
Expand Down
40 changes: 21 additions & 19 deletions example/src/LaunchScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { NativeStackScreenProps } from '@react-navigation/native-stack'
import React from 'react'
import React, { useCallback } from 'react'
import { Button, ScrollView, StyleSheet, Text, View } from 'react-native'
import * as XMTP from 'xmtp-react-native-sdk'
import { useXmtp } from 'xmtp-react-native-sdk'

import { NavigationParamList } from './Navigation'
import { useXmtp } from './XmtpContext'
import { useSavedKeys } from './hooks'

const appVersion = 'XMTP_RN_EX/0.0.1'
Expand All @@ -22,24 +22,26 @@ export default function LaunchScreen({
}: NativeStackScreenProps<NavigationParamList, 'launch'>) {
const { setClient } = useXmtp()
const savedKeys = useSavedKeys()
const configureWallet = (
label: string,
configuring: Promise<XMTP.Client>
) => {
console.log('Connecting XMTP client', label)
configuring
.then(async (client) => {
console.log('Connected XMTP client', label, {
address: client.address,
const configureWallet = useCallback(
(label: string, configuring: Promise<XMTP.Client>) => {
console.log('Connecting XMTP client', label)
configuring
.then(async (client) => {
console.log('Connected XMTP client', label, {
address: client.address,
})
setClient(client)
navigation.navigate('home')
// Save the configured client keys for use in later sessions.
const keyBundle = await client.exportKeyBundle()
await savedKeys.save(keyBundle)
})
setClient(client)
navigation.navigate('home')
// Save the configured client keys for use in later sessions.
const keyBundle = await client.exportKeyBundle()
await savedKeys.save(keyBundle)
})
.catch((err) => console.log('Unable to connect XMTP client', label, err))
}
.catch((err) =>
console.log('Unable to connect XMTP client', label, err)
)
},
[]
)
return (
<ScrollView>
<Text
Expand Down
26 changes: 0 additions & 26 deletions example/src/XmtpContext.tsx

This file was deleted.

2 changes: 1 addition & 1 deletion example/src/hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import {
EncryptedLocalAttachment,
ReactionContent,
RemoteAttachmentContent,
useXmtp,
} from 'xmtp-react-native-sdk'

import { useXmtp } from './XmtpContext'
import { downloadFile, uploadFile } from './storage'

/**
Expand Down
36 changes: 36 additions & 0 deletions src/context/XmtpContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import * as React from 'react'

import { Client } from '../lib/Client'

export interface XmtpContextValue {
/**
* The XMTP client instance
*/
client: Client<any> | null
/**
* Set the XMTP client instance
*/
setClient: React.Dispatch<React.SetStateAction<Client<any> | null>>
}

export const XmtpContext = React.createContext<XmtpContextValue>({
client: null,
setClient: () => {},
})
interface Props {
children: React.ReactNode
client?: Client<any>
}
export const XmtpProvider: React.FC<Props> = ({
children,
client: initialClient,
}) => {
const [client, setClient] = React.useState<Client<any> | null>(
initialClient ?? null
)
const context = React.useMemo(
() => ({ client, setClient }),
[client, setClient]
)
return <XmtpContext.Provider value={context}>{children}</XmtpContext.Provider>
}
1 change: 1 addition & 0 deletions src/context/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './XmtpContext'
2 changes: 2 additions & 0 deletions src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './useXmtp'
export * from './useClient'
80 changes: 80 additions & 0 deletions src/hooks/useClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { Signer } from 'ethers'
import { useCallback, useRef, useState } from 'react'

import { useXmtp } from './useXmtp'
import { Client, ClientOptions } from '../lib/Client'

interface InitializeClientOptions {
signer: Signer
options?: ClientOptions
}

export const useClient = (onError?: (e: Error) => void) => {
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState<Error | null>(null)
// client is initializing
const initializingRef = useRef(false)

const { client, setClient } = useXmtp()
/**
* Initialize an XMTP client
*/
const initialize = useCallback(
async ({ options, signer }: InitializeClientOptions) => {
// only initialize a client if one doesn't already exist
if (!client && signer) {
// if the client is already initializing, don't do anything
if (initializingRef.current) {
return undefined
}

// flag the client as initializing
initializingRef.current = true

// reset error state
setError(null)
// reset loading state
setIsLoading(true)

let xmtpClient: Client<any>

try {
// create a new XMTP client with the provided keys, or a wallet
xmtpClient = await Client.create(signer ?? null, {
...options,
})
setClient(xmtpClient)
} catch (e) {
setClient(null)
setError(e as Error)
onError?.(e as Error)
// re-throw error for upstream consumption
throw e
}

setIsLoading(false)

return xmtpClient
}
return client
},
[client, onError, setClient]
)

/**
* Disconnect the XMTP client
*/
const disconnect = useCallback(async () => {
if (client) {
setClient(null)
}
}, [client, setClient])

return {
client,
error,
initialize,
disconnect,
isLoading,
}
}
Loading

0 comments on commit 0a69bf5

Please sign in to comment.