Skip to content

Commit b5f654b

Browse files
committed
Initial commit
0 parents  commit b5f654b

31 files changed

+3179
-0
lines changed

.eslintrc.cjs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/* eslint-env node */
2+
3+
module.exports = {
4+
root: true,
5+
env: { browser: true, es2020: true },
6+
extends: [
7+
//"eslint:recommended",
8+
//"plugin:@typescript-eslint/recommended",
9+
// "plugin:@typescript-eslint/recommended-requiring-type-checking",
10+
"plugin:react-hooks/recommended",
11+
],
12+
parser: "@typescript-eslint/parser",
13+
parserOptions: {
14+
ecmaVersion: "latest",
15+
sourceType: "module",
16+
project: true,
17+
tsconfigRootDir: __dirname,
18+
},
19+
//ignorePatterns: ["lib"],
20+
plugins: ["react-refresh"],
21+
rules: {
22+
"react-refresh/only-export-components": [
23+
"warn",
24+
{ allowConstantExport: true },
25+
],
26+
"@typescript-eslint/ban-ts-comment": "off",
27+
"@typescript-eslint/no-floating-promises": "off",
28+
"@typescript-eslint/no-non-null-assertion": "off",
29+
},
30+
};

.gitignore

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
yarn-debug.log*
6+
yarn-error.log*
7+
pnpm-debug.log*
8+
lerna-debug.log*
9+
10+
node_modules
11+
dist
12+
dist-ssr
13+
*.local
14+
15+
# Editor directories and files
16+
.vscode/*
17+
!.vscode/extensions.json
18+
.idea
19+
.DS_Store
20+
*.suo
21+
*.ntvs*
22+
*.njsproj
23+
*.sln
24+
*.sw?

.prettierrc.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"printWidth": 80,
3+
"tabWidth": 2,
4+
"semi": true,
5+
"bracketSpacing": true,
6+
"bracketSameLine": false,
7+
"singleQuote": false,
8+
"trailingComma": "all",
9+
"arrowParens": "always",
10+
"useTabs": false
11+
}

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2023 TalkJS (Klets B.V.)
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 304 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,304 @@
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

Comments
 (0)