Skip to content

React Native and Expo

Add the Giov chat to a React Native or Expo app as a modal or a full screen, with verified identity, attachments, unread counts and push notifications.

On this page

The React Native SDK (@giovchat/react-native, version 0.2) shows the same conversation your website visitors have as a native screen. It needs no native modules. Optional features such as file picking and push notifications use your app's own libraries through small adapters.

Colours, greeting, pre-chat fields, GiovBot's name and avatar, attachment rules and branding come from Settings → Chat widget in the dashboard. In code, tenantKey is your workspace ID, shown in Settings → Chat widget → Installation.

Install

Requires React Native 0.72 or newer and react-native-safe-area-context 4 or newer.

Terminal
npx expo install @giovchat/react-native react-native-safe-area-context

With pnpm, Yarn or Bun, use their add command instead. Wrap your app in SafeAreaProvider from react-native-safe-area-context if your navigation setup doesn't already provide one. The package is plain JavaScript (CommonJS) with TypeScript types, so Metro and Jest load it without extra configuration.

Show the chat

SupportButton.tsx
import { useState } from "react";
import { Button, View } from "react-native";
import { GiovChat } from "@giovchat/react-native";

export function SupportButton() {
  const [open, setOpen] = useState(false);
  return (
    <View>
      <Button title="Chat with us" onPress={() => setOpen(true)} />
      <GiovChat tenantKey="YOUR_WORKSPACE_ID" visible={open} onClose={() => setOpen(false)} />
    </View>
  );
}

Full screen

The chat fills its parent view, for example a dedicated screen in your navigator:

ChatScreen.tsx
import { GiovChat } from "@giovchat/react-native";

export function ChatScreen() {
  return <GiovChat tenantKey="YOUR_WORKSPACE_ID" mode="fullscreen" />;
}

A conversation starts when the visitor sends their first message or submits the pre-chat form. A stored conversation is resumed first; a closed one is forgotten, and the next message starts a new one.

Signed-in users

Pass the user's details through contact. Any field you provide skips the matching pre-chat field.

Chat.tsx
<GiovChat
  tenantKey="YOUR_WORKSPACE_ID"
  contact={{
    userId: currentUser.id,
    userHash: currentUser.giovUserHash, // computed on your server
    email: currentUser.email,
    firstName: currentUser.firstName,
  }}
  sessionKey={currentUser.id}
/>
  • userHash is the HMAC-SHA256 (lowercase hex) of userId, keyed with the identity secret from Settings → Chat widget → Visitor details. Compute it on your server and send it to the app; never ship the secret. See Identify visitors for server examples.
  • Without userHash, userId is recorded as an unverified claim and doesn't link the chat to an existing contact.
  • If the workspace requires verified identity and userHash is missing or wrong, the chat reports identity_rejected through onError.
  • Sign-out and account switching: change sessionKey, or call ref.current.reset(). The previous user's session, queued messages and unread count are dropped, and the next user starts fresh. If you registered a push token, unregister it first.

Keep the conversation across launches

By default nothing is stored. Pass sessionStorage to resume the same conversation next time, using secure storage you already have:

chatStorage.ts
import * as SecureStore from "expo-secure-store";
import type { GiovChatSessionStorage } from "@giovchat/react-native";

const key = (scope: string) => "giovchat." + scope.replace(/[^A-Za-z0-9._-]/g, "_").slice(0, 200);

export const chatStorage: GiovChatSessionStorage = {
  async load(scope) { const value = await SecureStore.getItemAsync(key(scope)); return value ? JSON.parse(value) : null; },
  async save(scope, session) { await SecureStore.setItemAsync(key(scope), JSON.stringify(session)); },
  async remove(scope) { await SecureStore.deleteItemAsync(key(scope)); },
};

The scope the SDK passes identifies the workspace, the userId and the sessionKey, so a user who edits their name or email keeps their conversation, and each user on a shared device has their own.

Warning

The session token is a private credential. Keep it in secure storage, never log it, and never share it between users.

Attachments

Attachments appear once the conversation has started, when you pass an attachmentPicker and the published GiovBot agent allows them. Visitors can pick only the allowed types and sizes, up to 5 files per message, and the server checks again.

The picker returns { uri, name, type, size?, width?, height?, thumbnail? } objects, or [] when the visitor cancels.

pickers.ts
import * as DocumentPicker from "expo-document-picker";
import type { GiovChatAttachmentPicker } from "@giovchat/react-native";

export const documentPicker: GiovChatAttachmentPicker = {
  async pick({ allowedTypes }) {
    const result = await DocumentPicker.getDocumentAsync({ type: allowedTypes, multiple: true, copyToCacheDirectory: true });
    if (result.canceled) return [];
    return result.assets.map((asset) => ({
      uri: asset.uri, name: asset.name, type: asset.mimeType ?? "application/octet-stream", size: asset.size,
    }));
  },
};

Then pass it: <GiovChat tenantKey="YOUR_WORKSPACE_ID" attachmentPicker={documentPicker} />. Uploads show progress in the composer.

To let everyone, including your team, see an image's shape before it loads, add a small thumbnail: resize the image to about 24 px wide, save it as JPEG in base64, and pass "data:image/jpeg;base64," + base64. It must be under 6,000 characters; otherwise it's left out and the image loads in place.

Unread replies and badges

By default the chat connects only while it's visible. To count replies that arrive while it's closed, keep it mounted with keepConnected. It only resumes an existing conversation and never starts one in the background.

ChatTab.tsx
import { GiovChat, useGiovChatUnreadCount } from "@giovchat/react-native";

function ChatTabIcon() {
  const unread = useGiovChatUnreadCount("YOUR_WORKSPACE_ID");
  return <Icon name="chat" badge={unread || undefined} />;
}

<GiovChat tenantKey="YOUR_WORKSPACE_ID" visible={open} keepConnected sessionStorage={chatStorage}
  onUnreadCountChange={(count) => console.log(count)} />;

Opening the chat clears the count.

Push notifications

  1. Add your Apple push key and/or Firebase service account in Settings → Chat widget → Push notifications. Notifications are sent from your app, with your keys. See Push credentials.
  2. Register each device token with the SDK:
push.ts
const chat = useRef<GiovChatHandle>(null);
// After your app has an APNs or FCM token (for example from expo-notifications getDevicePushTokenAsync):
const result = await chat.current?.registerPushToken(deviceToken, Platform.OS === "ios" ? "ios" : "android", {
  environment: __DEV__ ? "sandbox" : "production", // APNs only: development builds get sandbox tokens
});
  • Pass the raw device token: the APNs token on iOS (or the FCM token if your iOS app uses Firebase Messaging), and the FCM token on Android.
  • It registers straight away when connected, otherwise as soon as the conversation connects, and never throws. Called before the visitor has written anything, it waits for the first message to start the conversation. It resolves { registered: false, reason: "no_conversation" } if the chat unmounts or sessionKey changes first, on reset(), or when the workspace refuses the visitor's identity.
  • The token is remembered. When your team closes a conversation and the visitor starts a new one, the device is registered for the new one automatically.
  • The device language (or the locale prop) goes with the token, so You have a new message, used when the workspace hides message text, arrives in the visitor's language. Pass { locale } to choose another.
  • A tapped notification carries { type: "giov.message", tenantId, conversationId, messageId }, under giov on APNs and in data on FCM. Open the chat and it catches up from history.

When the visitor signs out or turns notifications off, unregister the token before resetting the chat. This works while the chat is closed too:

signOut.ts
await chat.current?.unregisterPushToken(deviceToken);
await chat.current?.reset();

Connection and app lifecycle

  • The chat stays connected for 5 seconds after your app goes to the background, so a file picker or a glance at the app switcher doesn't reload it. After that it disconnects, and replies arrive as push notifications.
  • When the app returns, or a dropped connection comes back, it reconnects and fetches what it missed. Messages typed offline are sent then, in order. Connection lost. Reconnecting… shows meanwhile.
  • If the first connection fails, for example on a flaky network, the chat keeps retrying on its own with increasing delays. Try again retries at once.

Language

The interface follows the device language: English, French, Spanish, Portuguese or German, with English for others. Text you write in the dashboard (greeting, placeholder, bot name) is shown as written.

Chat.tsx
<GiovChat tenantKey="YOUR_WORKSPACE_ID" locale="pt-BR" strings={{ sendMessage: "Enviar", startChat: "Começar" }} />

Replace any string through strings; GiovChatStrings lists the keys and GIOVCHAT_STRINGS exports the built-in tables.

Appearance

  • Theme: the dashboard setting (light, dark, auto) applies, and auto follows the device. Override it with theme.
  • Colour: the dashboard's main colour applies. Override it with primaryColor. Text on that colour turns black or white, whichever is easier to read.
  • Fonts and surface colours: styleOverrides.
  • Privacy link: the header menu links to the Privacy policy URL from Settings → Chat widget → Appearance, as the web widget does.
chatStyles.ts
import { GiovChat, type GiovChatStyleOverrides } from "@giovchat/react-native";

const chatStyles = {
  fontFamily: "YourAppFont", // Load/register this font in your app first.
  container: { backgroundColor: "#f8fafc" },
  header: { backgroundColor: "#0f172a" },
  headerTitle: { color: "#ffffff", fontWeight: "700" },
  headerSubtitle: { color: "#cbd5e1" },
  agentBubble: { backgroundColor: "#e2e8f0" },
  agentText: { color: "#0f172a" },
  visitorBubble: { backgroundColor: "#2563eb" },
  visitorText: { color: "#ffffff" },
  composer: { backgroundColor: "#f8fafc" },
  input: { backgroundColor: "#ffffff", color: "#0f172a" },
  button: { backgroundColor: "#2563eb" },
  buttonText: { color: "#ffffff" },
} satisfies GiovChatStyleOverrides;

<GiovChat tenantKey="YOUR_WORKSPACE_ID" styleOverrides={chatStyles} />;
SlotsAllowed propertiesApplies to
container, header, composer, formbackgroundColorChat surface, header, composer row, pre-chat form
agentBubble, visitorBubblebackgroundColorAgent, greeting and typing bubbles; visitor bubbles
buttonbackgroundColorSend, start-chat and retry buttons
headerTitle, headerSubtitlefontFamily, fontWeight, fontStyle, colorHeader text; the title colour also applies to the close icon
agentText, visitorTextSame text propertiesMessage content, sender labels and timestamps; agent text also covers greeting and typing
formTitle, label, buttonText, hint, errorTextSame text propertiesForm heading and labels, button text, loading hint, attachment chips, errors; the hint colour also sets input placeholders
inputText properties plus backgroundColorComposer and all pre-chat inputs

A slot's fontFamily takes precedence over the top-level one. Unsupported slots and properties are ignored. Font sizes, spacing, borders, positioning and the branding footer stay under the SDK's control.

Branding: Starter and Pro always show Powered by GiovChat. Business and Advanced can turn it off in Settings → Chat widget → Appearance. The footer's look isn't affected by style overrides.

Props

PropTypeDefaultDescription
tenantKeystringrequiredYour workspace ID
mode"modal" | "fullscreen""modal"Modal sheet, or fill the parent view
visiblebooleantrueShow or hide the chat
onClose() => void—Called when the visitor taps close
contactGiovChatContact—Signed-in user's details
sessionKeystring | number—Change it to isolate or reset the session (sign-out, account switch)
sessionStorageGiovChatSessionStoragenoneYour secure storage for the session
baseUrlstringhttps://api.giovchat.appAPI base URL override
primaryColorstringFrom dashboardBrand colour override
theme"light" | "dark" | "auto"From dashboardTheme override; auto follows the device
styleOverridesGiovChatStyleOverridesnoneAllowed font and colour overrides
localestringDevice languageInterface language
stringsPartial<GiovChatStrings>noneReplace any interface string
attachmentPickerGiovChatAttachmentPickernoneEnables attachments, when the agent allows them
keepConnectedbooleanfalseStay connected while hidden to count unread replies
onUnreadCountChange(count: number) => void—Unread replies while hidden
onConversationStart(id: string) => void—Called once with the conversation ID
onError(error: GiovChatError) => void—Every failure, with a code
onConnectionStatusChange(status) => void—"connecting", "connected" or "disconnected"

The ref (GiovChatHandle) has reset(), which clears this user's session and starts fresh, registerPushToken(token, platform, options?) and unregisterPushToken(token).

onError codes: network, timeout, invalid_response, invalid_session (the stored session is gone), conversation_closed, connection_failed, disconnected, send_failed, upload_failed, workspace_busy, identity_rejected (the workspace requires a verified identity and userHash is wrong or missing), storage, server and rate_limited (with retryAfterSeconds).

Loading…