Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add create chat client hook for easy usage #2660

Merged
merged 2 commits into from
Sep 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 36 additions & 12 deletions docusaurus/docs/reactnative/core-components/chat.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,45 @@ We recommend using only one instance of `Chat` provider per application unless a

```tsx
import { StreamChat } from 'stream-chat';
import { ChannelList, Chat, OverlayProvider } from 'stream-chat-react-native';

const client = StreamChat.getInstance('api_key');

export const App = () => (
<OverlayProvider>
// highlight-next-line
<Chat client={client}>
<ChannelList />
import { ChannelList, Chat, OverlayProvider, useCreateChatClient } from 'stream-chat-react-native';

// highlight-start
const chatApiKey = 'REPLACE_WITH_API_KEY';
const chatUserId = 'REPLACE_WITH_USER_ID';
const chatUserName = 'REPLACE_WITH_USER_NAME';
const chatUserToken = 'REPLACE_WITH_USER_TOKEN';
// highlight-end

const user = {
id: chatUserId,
name: chatUserName,
};

export const App = () => {
// highlight-start
const chatClient = useCreateChatClient({
apiKey: chatApiKey,
userData: user,
tokenOrProvider: chatUserToken,
});
// highlight-end

return (
<OverlayProvider>
// highlight-next-line
</Chat>
</OverlayProvider>
);
<Chat client={chatClient}>
<ChannelList />
// highlight-next-line
</Chat>
</OverlayProvider>
);
};
```

:::tip
You can use the `useCreateChatClient` hook from `stream-chat-react-native`/`stream-chat-expo` to create a client instance and automatically connect/disconnect a user as per the example above, for simplicity.
:::

## Context Providers

`Chat` contains providers for the `ChatContext`, `ThemeContext`, and `TranslationContext`.
Expand Down
4 changes: 4 additions & 0 deletions docusaurus/docs/reactnative/ui-components/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ To disconnect a user you can call `disconnectUser` on the client.
await client.disconnectUser();
```

:::tip
Alternatively, you can also use the `useCreateChatClient` hook from `stream-chat-react-native`/`stream-chat-expo` to create a client instance and automatically connect/disconnect a user.
:::

## Creating a Channel

Channels are at the core of Stream Chat, they are where messages are contained, sent, and interacted with.
Expand Down
7 changes: 3 additions & 4 deletions examples/ExpoMessaging/components/ChatWrapper.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React, { PropsWithChildren } from 'react';
import { Chat, OverlayProvider, Streami18n } from 'stream-chat-expo';
import { useChatClient } from '../hooks/useChatClient';
import { Chat, OverlayProvider, Streami18n, useCreateChatClient } from 'stream-chat-expo';
import { AuthProgressLoader } from './AuthProgressLoader';
import { StreamChatGenerics } from '../types';
import { STREAM_API_KEY, user, userToken } from '../constants';
Expand All @@ -12,7 +11,7 @@ const streami18n = new Streami18n({

export const ChatWrapper = ({ children }: PropsWithChildren<{}>) => {
const { bottom } = useSafeAreaInsets();
const chatClient = useChatClient({
const chatClient = useCreateChatClient({
apiKey: STREAM_API_KEY,
userData: user,
tokenOrProvider: userToken,
Expand All @@ -24,7 +23,7 @@ export const ChatWrapper = ({ children }: PropsWithChildren<{}>) => {

return (
<OverlayProvider<StreamChatGenerics> bottomInset={bottom} i18nInstance={streami18n}>
<Chat client={chatClient} i18nInstance={streami18n} enableOfflineSupport={true}>
<Chat client={chatClient} i18nInstance={streami18n}>
{children}
</Chat>
</OverlayProvider>
Expand Down
46 changes: 0 additions & 46 deletions examples/ExpoMessaging/hooks/useChatClient.tsx

This file was deleted.

133 changes: 66 additions & 67 deletions examples/TypeScriptMessaging/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { DarkTheme, DefaultTheme, NavigationContainer, RouteProp } from '@react-
import { createStackNavigator, StackNavigationProp } from '@react-navigation/stack';
import { useHeaderHeight } from '@react-navigation/elements';
import { SafeAreaProvider, useSafeAreaInsets } from 'react-native-safe-area-context';
import { Channel as ChannelType, ChannelSort, StreamChat } from 'stream-chat';
import { Channel as ChannelType, ChannelSort } from 'stream-chat';
import {
Channel,
ChannelList,
Expand All @@ -18,12 +18,14 @@ import {
Thread,
ThreadContextValue,
useAttachmentPickerContext,
useCreateChatClient,
useOverlayContext,
} from 'stream-chat-react-native';

import { useStreamChatTheme } from './useStreamChatTheme';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { useFlipper } from 'stream-chat-react-native-devtools';
import { AuthProgressLoader } from './AuthProgressLoader';

LogBox.ignoreAllLogs(true);

Expand Down Expand Up @@ -62,7 +64,7 @@ QuickSqliteClient.logger = (level, message, extraData) => {
console.log(level, `QuickSqliteClient: ${message}`, extraData);
};

const chatClient = StreamChat.getInstance<StreamChatGenerics>('q95x9hkbyd6p');
const apiKey = 'q95x9hkbyd6p';
const userToken =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoicm9uIn0.eRVjxLvd4aqCEHY_JRa97g6k7WpHEhxL7Z4K4yTot1c';

Expand Down Expand Up @@ -219,85 +221,82 @@ type AppContextType = {
const AppContext = React.createContext({} as AppContextType);

const App = () => {
const colorScheme = useColorScheme();
const { bottom } = useSafeAreaInsets();
const theme = useStreamChatTheme();
const { channel } = useContext(AppContext);

const [channel, setChannel] = useState<ChannelType<StreamChatGenerics>>();
const [clientReady, setClientReady] = useState(false);
const [thread, setThread] = useState<ThreadContextValue<StreamChatGenerics>['thread']>();

useEffect(() => {
const setupClient = async () => {
const connectPromise = chatClient.connectUser(user, userToken);
setClientReady(true);
await connectPromise;
};
const chatClient = useCreateChatClient({
apiKey,
userData: user,
tokenOrProvider: userToken,
});

setupClient();
}, []);
if (!chatClient) {
return <AuthProgressLoader />;
}

return (
<DebugContextProvider useFlipper={useFlipper}>
<NavigationContainer
theme={{
colors: {
...(colorScheme === 'dark' ? DarkTheme : DefaultTheme).colors,
},
dark: colorScheme === 'dark',
}}
>
<AppContext.Provider value={{ channel, setChannel, setThread, thread }}>
<GestureHandlerRootView style={{ flex: 1 }}>
<OverlayProvider<StreamChatGenerics>
bottomInset={bottom}
i18nInstance={streami18n}
value={{ style: theme }}
>
<Chat client={chatClient} i18nInstance={streami18n} enableOfflineSupport>
{clientReady && (
<Stack.Navigator
initialRouteName='ChannelList'
screenOptions={{
headerTitleStyle: { alignSelf: 'center', fontWeight: 'bold' },
}}
>
<Stack.Screen
component={ChannelScreen}
name='Channel'
options={() => ({
headerBackTitle: 'Back',
headerRight: EmptyHeader,
headerTitle: channel?.data?.name,
})}
/>
<Stack.Screen
component={ChannelListScreen}
name='ChannelList'
options={{ headerTitle: 'Channel List' }}
/>
<Stack.Screen
component={ThreadScreen}
name='Thread'
options={() => ({ headerLeft: EmptyHeader })}
/>
</Stack.Navigator>
)}
</Chat>
</OverlayProvider>
</GestureHandlerRootView>
</AppContext.Provider>
</NavigationContainer>
</DebugContextProvider>
<OverlayProvider<StreamChatGenerics>
bottomInset={bottom}
i18nInstance={streami18n}
value={{ style: theme }}
>
<Chat client={chatClient} i18nInstance={streami18n} enableOfflineSupport>
<Stack.Navigator
initialRouteName='ChannelList'
screenOptions={{
headerTitleStyle: { alignSelf: 'center', fontWeight: 'bold' },
}}
>
<Stack.Screen
component={ChannelScreen}
name='Channel'
options={() => ({
headerBackTitle: 'Back',
headerRight: EmptyHeader,
headerTitle: channel?.data?.name,
})}
/>
<Stack.Screen
component={ChannelListScreen}
name='ChannelList'
options={{ headerTitle: 'Channel List' }}
/>
<Stack.Screen
component={ThreadScreen}
name='Thread'
options={() => ({ headerLeft: EmptyHeader })}
/>
</Stack.Navigator>
</Chat>
</OverlayProvider>
);
};

export default () => {
const [channel, setChannel] = useState<ChannelType<StreamChatGenerics>>();
const [thread, setThread] = useState<ThreadContextValue<StreamChatGenerics>['thread']>();
const theme = useStreamChatTheme();
const colorScheme = useColorScheme();

return (
<SafeAreaProvider style={{ backgroundColor: theme.colors?.white_snow || '#FCFCFC' }}>
<App />
<DebugContextProvider useFlipper={useFlipper}>
<NavigationContainer
theme={{
colors: {
...(colorScheme === 'dark' ? DarkTheme : DefaultTheme).colors,
},
dark: colorScheme === 'dark',
}}
>
<AppContext.Provider value={{ channel, setChannel, setThread, thread }}>
<GestureHandlerRootView style={{ flex: 1 }}>
<App />
</GestureHandlerRootView>
</AppContext.Provider>
</NavigationContainer>
</DebugContextProvider>
</SafeAreaProvider>
);
};
17 changes: 17 additions & 0 deletions examples/TypeScriptMessaging/AuthProgressLoader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React from 'react';
import { ActivityIndicator, StyleSheet } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';

export const AuthProgressLoader = () => {
return (
<SafeAreaView style={styles.container}>
<ActivityIndicator size={'large'} style={StyleSheet.absoluteFill} />
</SafeAreaView>
);
};

const styles = StyleSheet.create({
container: {
flex: 1,
},
});
57 changes: 57 additions & 0 deletions package/src/components/Chat/hooks/useCreateChatClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { useEffect, useState } from 'react';

import { StreamChat } from 'stream-chat';

import type {
DefaultGenerics,
ExtendableGenerics,
OwnUserResponse,
StreamChatOptions,
TokenOrProvider,
UserResponse,
} from 'stream-chat';

/**
* React hook to create, connect and return `StreamChat` client.
*/
export const useCreateChatClient = <SCG extends ExtendableGenerics = DefaultGenerics>({
apiKey,
options,
tokenOrProvider,
userData,
}: {
apiKey: string;
tokenOrProvider: TokenOrProvider;
userData: OwnUserResponse<SCG> | UserResponse<SCG>;
options?: StreamChatOptions;
}) => {
const [chatClient, setChatClient] = useState<StreamChat<SCG> | null>(null);
const [cachedUserData, setCachedUserData] = useState(userData);

if (userData.id !== cachedUserData.id) {
setCachedUserData(userData);
}

const [cachedOptions] = useState(options);

useEffect(() => {
const client = new StreamChat<SCG>(apiKey, undefined, cachedOptions);
let didUserConnectInterrupt = false;

const connectionPromise = client.connectUser(cachedUserData, tokenOrProvider).then(() => {
if (!didUserConnectInterrupt) setChatClient(client);
});

return () => {
didUserConnectInterrupt = true;
setChatClient(null);
connectionPromise
.then(() => client.disconnectUser())
.then(() => {
console.log(`Connection for user "${cachedUserData.id}" has been closed`);
});
};
}, [apiKey, cachedUserData, cachedOptions, tokenOrProvider]);

return chatClient;
};
Loading
Loading