|
| 1 | +# React components for TalkJS |
| 2 | + |
| 3 | +This library makes it easy to use the [TalkJS](https://talkjs.com) pre-built chat UIs inside a React web application. |
| 4 | + |
| 5 | +`talkjs-react` encapsulates `talkjs`, the framework-independent ("vanilla") [TalkJS JavaScript SDK](https://talkjs.com/docs/Reference/JavaScript_Chat_SDK). It only provides React components for UI-related matters: For anything related to data manipulation, such as synchronizing user data, or creating and joining conversations, use the vanilla SDK. |
| 6 | + |
| 7 | +TypeScript bindings are included. |
| 8 | + |
| 9 | +## Experimental note |
| 10 | +This library is hot off the press. It provides a substantially faster way to get TalkJS going with React, but it may still have a few childhood quirks. |
| 11 | + |
| 12 | +If you encounter any problems with `talkjs-react`, please open an issue. If you have a problem with TalkJS itself, or if you're not sure where the problem lies, it's better to open a chat with our support on https://talkjs.com/ (just open the chat widget). TalkJS support is staffed by engineers. |
| 13 | + |
| 14 | +## Getting Started |
| 15 | + |
| 16 | +Install both `talkjs-react` and the vanilla [TalkJS NPM package](https://www.npmjs.com/package/talkjs): |
| 17 | + |
| 18 | +```sh |
| 19 | +yarn add talkjs talkjs-react |
| 20 | +``` |
| 21 | + |
| 22 | +`talkjs-react` is just a thin wrapper around `talkjs`. Notably, it is designed such that when you update to a new version of `talkjs` that exposes new options, methods or events, you can use these right away from the React components, without having to update `talkjs-react` (or wait for such an update to be published) |
| 23 | + |
| 24 | +## Load a UI for existing chat data |
| 25 | + |
| 26 | +### 1. Create a session |
| 27 | + |
| 28 | +Wrap your main app components in a session: |
| 29 | + |
| 30 | +```tsx |
| 31 | +import { Session } from "talkjs-react"; |
| 32 | + |
| 33 | +//... |
| 34 | + |
| 35 | +<Session appId={/* your app ID, find it in the dashboard */} userId="pete"> |
| 36 | + {/* your main app here here */} |
| 37 | +</Session>; |
| 38 | +``` |
| 39 | + |
| 40 | +This assumes user `pete` exists in TalkJS. |
| 41 | + |
| 42 | +The session encapsulates a connection to the TalkJS realtime infrastructure, and this connection will live as long as the session component is mounted. If you want to listen for events or show desktop notifications for new messages, we recommend putting the session at the top of your component hierarchy, so it's active even when no chat UI is being shown. |
| 43 | + |
| 44 | +### 2. Create a chatbox |
| 45 | + |
| 46 | +Anywhere inside the children of `<Session>` you can create a `<Chatbox>`, an `<Inbox>` or a `<Popup>`: |
| 47 | + |
| 48 | +```tsx |
| 49 | +import { Chatbox } from "talkjs-react"; |
| 50 | + |
| 51 | +//... |
| 52 | + |
| 53 | +<Chatbox |
| 54 | + conversation="welcome" |
| 55 | + style={{ width: 400, height: 600 }} |
| 56 | + className="chat-container" |
| 57 | +/>; |
| 58 | +``` |
| 59 | + |
| 60 | +This assumes conversation `welcome` exists in TalkJS, with `pete` as a participant. |
| 61 | + |
| 62 | +The code above will render a `div` with the `style` and `className` passed through, and put a TalkJS Chatbox inside it. If you change the value of the `conversation` prop, the chatbox will switch to another conversation. |
| 63 | + |
| 64 | +## Manipulating data |
| 65 | + |
| 66 | +If your security settings allow, TalkJS lets you create users and join conversations from the client side. This lets you synchronize user data with TalkJS in a just-in-time fashion. |
| 67 | + |
| 68 | +You manipulate data exactly as you would if you directly used the [vanilla JavaScript SDK](https://talkjs.com/docs/Reference/JavaScript_Chat_SDK). |
| 69 | + |
| 70 | +### 1. Synchronize user data |
| 71 | + |
| 72 | +To create or update user data, just replace the `userId` prop by `syncUser`. This prop expects a callback that uses the vanilla TalkJS SDK to create a `Talk.User` object: |
| 73 | + |
| 74 | +```tsx |
| 75 | +import { useCallback } from "react"; |
| 76 | +import { Session } from "talkjs-react"; |
| 77 | +import Talk from "talkjs"; |
| 78 | + |
| 79 | +//... |
| 80 | + |
| 81 | +const syncUser = useCallback( |
| 82 | + () => |
| 83 | + // regular vanilla TalkJS code here |
| 84 | + new Talk.User({ |
| 85 | + id: "pete", |
| 86 | + name: "Pete", |
| 87 | + photoUrl: "https://example.com/pete.jpg", |
| 88 | + //... |
| 89 | + }), |
| 90 | + [] |
| 91 | +); |
| 92 | + |
| 93 | +<Session |
| 94 | + appId={/* your app ID, find it in the dashboard */} |
| 95 | + syncUser={syncUser} |
| 96 | +> |
| 97 | + {/* your main app here here */} |
| 98 | +</Session>; |
| 99 | +``` |
| 100 | + |
| 101 | +Note: you can't create `Talk.User` object before the TalkJS SDK has initialized. In vanilla JS you'd have to await `Talk.ready` promise for this, but `talkjs-react` ensures that you `syncUser` callback is only called after TalkJS is ready. |
| 102 | + |
| 103 | +### 2. Create and join a conversation |
| 104 | + |
| 105 | +Similarly, `<Chatbox>`, `<Inbox>` and `<Popup>` let you lazily create or update conversations with the `syncConversation` prop. |
| 106 | + |
| 107 | +Just replace the `conversation` prop by a `syncConversation` callback, which will be invoked just before the chatbox is created. Then, use the provided [`session`](https://talkjs.com/docs/Reference/JavaScript_Chat_SDK/Session) object to create a [`ConversationBuilder`](https://talkjs.com/docs/Reference/JavaScript_Chat_SDK/ConversationBuilder/#ConversationBuilder) using [`getOrCreateConversation`](https://talkjs.com/docs/Reference/JavaScript_Chat_SDK/Session/#Session__getOrCreateConversation): |
| 108 | + |
| 109 | +```tsx |
| 110 | +import { useCallback } from "react"; |
| 111 | +import { Chatbox } from "talkjs-react"; |
| 112 | +import Talk from "talkjs"; |
| 113 | + |
| 114 | +//... |
| 115 | + |
| 116 | +const syncConversation = useCallback((session: Talk.Session) => { |
| 117 | + // regular vanilla TalkJS code here |
| 118 | + const conversation = session.getOrCreateConversation("welcome"); |
| 119 | + conversation.setParticipant(session.me); |
| 120 | + return conversation; |
| 121 | +}, []); |
| 122 | + |
| 123 | +<Chatbox |
| 124 | + syncConversation={syncConversation} |
| 125 | + style={{ width: 400, height: 600 }} |
| 126 | + className="chat-container" |
| 127 | +/>; |
| 128 | +``` |
| 129 | + |
| 130 | +## Events |
| 131 | + |
| 132 | +All events supported by the [TalkJS JavaScript SDK](https://talkjs.com/docs/Reference/JavaScript_Chat_SDK) are exposed on the React components as props. For example: |
| 133 | + |
| 134 | +```tsx |
| 135 | +<Session |
| 136 | + appId={/*...*/} |
| 137 | + userId={/*...*/} |
| 138 | + onMessage={message => console.log(message.body)} |
| 139 | + onUnreadsChange={unreads => console.log(unreads.length)} |
| 140 | +> |
| 141 | +``` |
| 142 | + |
| 143 | +All events supported by `<Session>` are listed in [the `Talk.Session` reference](https://talkjs.com/docs/Reference/JavaScript_Chat_SDK/Session/#Session__methods): Any method that starts with `on` is exposed as an event prop in React. |
| 144 | + |
| 145 | +Note that [`Session.unreads.onChange`](https://talkjs.com/docs/Reference/JavaScript_Chat_SDK/Session/#Unreads__onChange) is exposed directly on the `<Session>` as a prop named `onUnreadsChange`. |
| 146 | + |
| 147 | +Similarly, for `<Chatbox>` ([vanilla sdk](https://talkjs.com/docs/Reference/JavaScript_Chat_SDK/Chatbox/#Chatbox__methods)), `<Inbox>` ([vanilla sdk](https://talkjs.com/docs/Reference/JavaScript_Chat_SDK/Chatbox/#Chatbox__methods)) or `<Popup>` ([vanilla sdk](https://talkjs.com/docs/Reference/JavaScript_Chat_SDK/Chatbox/#Chatbox__methods)): |
| 148 | + |
| 149 | +```tsx |
| 150 | +<Chatbox |
| 151 | + conversationId={/*...*/} |
| 152 | + onSendMessage={(event) => console.log(event.message.text)} |
| 153 | + onCustomMessageAction={(event) => console.log(event.action)} |
| 154 | + onSomethingSomething={/* callback */} |
| 155 | +/> |
| 156 | +``` |
| 157 | + |
| 158 | +Again, any method that starts with `on` is exposed as a prop. |
| 159 | + |
| 160 | +## Options |
| 161 | + |
| 162 | +TalkJS supports a lot of options that let you finetune the behavior of the chat UI. Any option that can be passed to [`createChatbox`](https://talkjs.com/docs/Reference/JavaScript_Chat_SDK/Session/#Session__createChatbox), [`createInbox`](https://talkjs.com/docs/Reference/JavaScript_Chat_SDK/Session/#Session__createInbox) and [`createPopup`](https://talkjs.com/docs/Reference/JavaScript_Chat_SDK/Session/#Session__createPopup) can be passed as a prop: |
| 163 | + |
| 164 | +```tsx |
| 165 | +<Inbox |
| 166 | + // only show conversations where `custom.state != 'hidden'` |
| 167 | + feedFilter={{ custom: { state: ["!=", "hidden"] } }} |
| 168 | + showMobileBackButton={false} |
| 169 | + messageField={{ placeholder: "Write a message.." }} |
| 170 | + //... |
| 171 | +/> |
| 172 | +``` |
| 173 | + |
| 174 | +If any of these props change, `<Inbox>` will apply it directly through a setter such as [`setFeedFilter`](https://talkjs.com/docs/Reference/JavaScript_Chat_SDK/Inbox/#Inbox__setFeedFilter). If no such setter exists, it will recreate the `Talk.Inbox`. |
| 175 | + |
| 176 | +## Loading |
| 177 | + |
| 178 | +TalkJS loads quickly but not always instantaneously. If you want to display something while TalkJS is loading, pass a react node to the `loadingComponent` prop: |
| 179 | + |
| 180 | +```tsx |
| 181 | +<Inbox |
| 182 | + //... |
| 183 | + loadingComponent={<h1>Loading..</h1>} |
| 184 | +/> |
| 185 | +``` |
| 186 | + |
| 187 | +## Direct access to TalkJS internals |
| 188 | + |
| 189 | +If you need to do things with your |
| 190 | +`Talk.Session`, `Talk.Chatbox`, `Talk.Inbox` or `Talk.Popup` instances that the react bindings to not let you do, you can get a direct reference to it. |
| 191 | + |
| 192 | +### Using hooks |
| 193 | + |
| 194 | +In any child component of `<Session>` you can call the `useSession` hook to get the `Talk.Session` object: |
| 195 | + |
| 196 | +```tsx |
| 197 | +import { useSession } from "talkjs-react"; |
| 198 | + |
| 199 | +function MyComponent(props) { |
| 200 | + const session = useSession(); // Talk.Session | undefined |
| 201 | + |
| 202 | + useEffect(() => { |
| 203 | + if (session?.isAlive) { |
| 204 | + session.getOrCreateConversation("welcome").sendMessage("hi"); |
| 205 | + } |
| 206 | + }, []); |
| 207 | +} |
| 208 | +``` |
| 209 | + |
| 210 | +Note that the value may be undefined if the session has not yet loaded or has |
| 211 | +since been destroyed. See the section on liveness below. |
| 212 | + |
| 213 | +### Using refs |
| 214 | + |
| 215 | + |
| 216 | +All `talkjs-react` components let you assign the underlying TalkJS object to a |
| 217 | +ref: |
| 218 | + |
| 219 | +```tsx |
| 220 | +const session = useRef<Talk.Session>(); |
| 221 | +const chatbox = useRef<Talk.Chatbox>(); |
| 222 | + |
| 223 | +//... |
| 224 | + |
| 225 | +onSomeEvent = useCallback(async () => { |
| 226 | + // do something with the chatbox |
| 227 | + if (chatbox.current?.isAlive) { |
| 228 | + chatbox.current.sendLocation(); |
| 229 | + } |
| 230 | + // do something with the session. |
| 231 | + if (session.current?.isAlive) { |
| 232 | + const inboxes = await session.current.getInboxes(); |
| 233 | + } |
| 234 | +}, []); |
| 235 | + |
| 236 | +<Session /*...*/ sessionRef={session}> |
| 237 | + <Chatbox /*...*/ chatboxRef={chatbox} /> |
| 238 | +</Session>; |
| 239 | +``` |
| 240 | + |
| 241 | +The ref will be set once the Talk.Chatbox object has been created (resp. when the Talk.Session has initialized), and it will be set back to `undefined` once it has been destroyed. |
| 242 | + |
| 243 | +### Liveness |
| 244 | + |
| 245 | +Make sure you always check the `isAlive` property to ensure that the object is |
| 246 | +not destroyed because React is prone to trigger race conditions here (especially |
| 247 | +when React.StrictMode is enabled or when using a development setup with Hot |
| 248 | +Module Reloading, both of which cause a lot of destroying). |
| 249 | + |
| 250 | +## Reference |
| 251 | + |
| 252 | +### `<Session>` |
| 253 | + |
| 254 | +Accepted props: |
| 255 | + |
| 256 | +- `appId: string` - your app ID from the TalkJS dashboard |
| 257 | +- `userId: string` - required unless `syncUser` is given |
| 258 | +- `syncUser: Talk.User | () => Talk.User` - required unless `userId` is given |
| 259 | +- `sessionRef: React.MutableRefObject<Talk.Session>` - See [above](#using-refs) |
| 260 | + |
| 261 | +Accepted events (props that start with "on"): |
| 262 | + |
| 263 | +- all events accepted by [`Talk.Session`](https://talkjs.com/docs/Reference/JavaScript_Chat_SDK/Session/#Session__methods) |
| 264 | +- `onUnreadsChange` - as in [`Unreads.onChange`](https://talkjs.com/docs/Reference/JavaScript_Chat_SDK/Session/#Unreads__onChange) |
| 265 | + |
| 266 | +### `<Chatbox>`, `<Inbox>` and `<Popup>` |
| 267 | + |
| 268 | +Accepted props: |
| 269 | + |
| 270 | +- `conversationId: string` - Selects this conversation. If the conversation does not exist, the "Not found" screen is shown. |
| 271 | +- `syncConversation: Talk.ConversationBuilder | () => Talk.ConversationBuilder` - Creates or updates the given conversation using the supplied ConversationBuilder, then selects it. |
| 272 | +- `style: CSSProperties` - Optional. Passed through to the `div` that will contain the chatbox |
| 273 | +- `className: string` - Optional. Passed through to the `div` that will contain the chatbox |
| 274 | +- `loadingComponent: ReactNode` - Optional. A react node which will be shown while the chatbox is |
| 275 | + loading |
| 276 | +- `chatboxRef` (resp. `inboxRef`, `popupRef`) - Pass a ref (created with `useRef`) and it'll be set to the vanilla JS [Chatbox](https://talkjs.com/docs/Reference/JavaScript_Chat_SDK/Chatbox/) (resp. [Inbox](https://talkjs.com/docs/Reference/JavaScript_Chat_SDK/Inbox/), [Popup](https://talkjs.com/docs/Reference/JavaScript_Chat_SDK/Popup/)) instance. See [above](#using-refs) for an example. |
| 277 | +- All [Talk.ChatboxOptions](https://talkjs.com/docs/Reference/JavaScript_Chat_SDK/Session/#ChatboxOptions) |
| 278 | + |
| 279 | +Accepted events (props that start with "on"): |
| 280 | + |
| 281 | +- All events accepted by [`Talk.Chatbox`](https://talkjs.com/docs/Reference/JavaScript_Chat_SDK/Chatbox/#Chatbox__methods) (resp. [Inbox](https://talkjs.com/docs/Reference/JavaScript_Chat_SDK/Inbox/#Inbox__methods), [Popup](https://talkjs.com/docs/Reference/JavaScript_Chat_SDK/Popup/#Popup__methods)) |
| 282 | + |
| 283 | +Note: For `<Chatbox>` and `<Popup>`, you must provide exactly one of `conversationId` and `syncConversation`. For `<Inbox>`, leaving both unset selects the latest conversation this user participates in (if any). See [Inbox.select](https://talkjs.com/docs/Reference/JavaScript_Chat_SDK/Inbox/#Inbox__select) for more information. |
| 284 | + |
| 285 | + |
| 286 | +## Contributing |
| 287 | + |
| 288 | +This library is open-source and permissively licensed (MIT). |
| 289 | + |
| 290 | +To contribute a PR, we require that you fill out a contributor license agreement (CLA) so that we (TalkJS) retain ownership over this repository. Pick the [Corporate CLA](corporate_contributor_license_agreement.md) or the [individual CLA](individual_contributor_license_agreement.md). Note that you do not need to sign anything to be able to fork this repository and make changes for your own use. |
| 291 | + |
| 292 | +Should you want to contribute, please take note of the design notes below. |
| 293 | + |
| 294 | +## Design notes |
| 295 | + |
| 296 | +This library has been designed to be maximally forward-compatible with future TalkJS features. The `talkjs` package is a peer dependency, not a direct dependency, which means you can control which TalkJS version you want to be on. It also means you won't need to wait for a new version of `talkjs-react` to be published before you can get access to new TalkJS features. |
| 297 | + |
| 298 | +From our (TalkJS) perspective, it means we have a lower maintenance burden: we can ship new JS features without having to update (and test and verify) `talkjs-react`. |
| 299 | + |
| 300 | +This works because vanilla TalkJS is fully backward compatible and has a very consistent design: all UI components are instantiated and mutated in the same way. The React components simply treats any prop that looks like an event (name starts with "on") like an event. Also, barring some props unique to the react components (such as `syncConversation`, `style` and `loadingComponent`), all remaining props are simply assumed to be valid options to pass to `createChatbox` and its sister methods. |
| 301 | + |
| 302 | +This way, if TalkJS eg adds support for a new event or a new option, this will Just Work without updating `talkjs-react`. Modern TypeScript features (notably, [template literal types](https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html)) let us do this in a fully type-safe manner: If you upgrade `talkjs`, then your invocations to `talkjs-react` components will allow new props. |
| 303 | + |
| 304 | +Any future changes should follow this same design: they should be maximally forward compatible. |
0 commit comments