Skip to content

Commit

Permalink
Merge pull request #499 from GetStream/ts-chat-fixes-for-upgrade
Browse files Browse the repository at this point in the history
Ts chat fixes for upgrade to stream-chat@2.x.x
  • Loading branch information
vishalnarkhede committed Sep 10, 2020
2 parents 9985037 + 84823ca commit 3077169
Show file tree
Hide file tree
Showing 63 changed files with 1,316 additions and 1,105 deletions.
3 changes: 2 additions & 1 deletion .eslintrc.json
Expand Up @@ -39,7 +39,8 @@
"jest/no-hooks": 0,
"sonarjs/cognitive-complexity": ["error", 30],
"no-unused-expressions": "off",
"babel/no-unused-expressions": "error"
"babel/no-unused-expressions": "error",
"jest/no-if": "off"
},
"env": {
"es6": true,
Expand Down
28 changes: 16 additions & 12 deletions README.md
Expand Up @@ -54,6 +54,10 @@ The [styleguidist docs for stream-chat-react](https://getstream.github.io/stream

The React components are created using the [stream-chat-js](https://github.com/getstream/stream-chat-js) library. If you're customizing the components, it's important to learn how the Chat Server API works. You'll want to review our [JS chat API docs](https://getstream.io/chat/docs/js/).

## Typescript

**Note:** The [stream-chat-js](https://github.com/getstream/stream-chat-js) library allows for fully typed responses using generics, currently the React SDK does not allow for user defined types via generics, so custom fields will be returned with type `unknown` and need to be ignored using `@ts-ignore` when using custom components in typescript.

## Commands

- yarn docs-server
Expand All @@ -62,9 +66,9 @@ The React components are created using the [stream-chat-js](https://github.com/g

## Component Reusability

1. If a component implements a ton of logic, it's helpful if you split it out into two parts: The top-level component, which handles all the logic, and a lower level component, which handles rendering. That makes it easy to change the rendering without having to touch the other stuff. Have a look at Message and MessageTeam to see how this approach works.
1. If a component implements a ton of logic, it's helpful if you split it out into two parts: The top-level component, which handles all the logic, and a lower level component, which handles rendering. That makes it easy to change the rendering without having to touch the other stuff. Have a look at Message and MessageTeam to see how this approach works.

2. Make things configurable via the props where possible. Sometimes an even better approach is to use the props.children. Have a look at how flexible the channel layout is due to this approach:
2. Make things configurable via the props where possible. Sometimes an even better approach is to use the props.children. Have a look at how flexible the channel layout is due to this approach:

```jsx
<Channel>
Expand Down Expand Up @@ -131,7 +135,7 @@ Note that the PureComponent uses a shallow diff to determine if a component shou
The regular component simply always rerenders when there is a state change.

You can read more about PureComponents and common gotchas here:
https://codeburst.io/when-to-use-component-or-purecomponent-a60cfad01a81
<https://codeburst.io/when-to-use-component-or-purecomponent-a60cfad01a81>

You want the shallow diff only to be true if something changed.
Common mistakes that hurt performance are:
Expand All @@ -146,13 +150,13 @@ Common mistakes that hurt performance are:
Instance of class `Streami18n` should be provided to the Chat component to handle translations.
Stream provides the following list of in-built translations for components:

1. English (en)
2. Dutch (nl)
3. Russian (ru)
4. Turkish (tr)
5. French (fr)
6. Italian (it)
7. Hindi (hi)
1. English (en)
2. Dutch (nl)
3. Russian (ru)
4. Turkish (tr)
5. French (fr)
6. Italian (it)
7. Hindi (hi)

The default language is English. The simplest way to start using chat components in one of the in-built languages would be the following:

Expand All @@ -178,7 +182,7 @@ const i18n = new Streami18n({
});
```

You can find all the available keys here: https://github.com/GetStream/stream-chat-react/tree/master/src/i18n
You can find all the available keys here: <https://github.com/GetStream/stream-chat-react/tree/master/src/i18n>

They are also exported as a JSON object from the library.

Expand All @@ -195,7 +199,7 @@ import {
} from 'stream-chat-react';
```

Please read this docs on i18n for more details and further customizations - https://getstream.github.io/stream-chat-react/#section-streami18n
Please read this docs on i18n for more details and further customizations - <https://getstream.github.io/stream-chat-react/#section-streami18n>

## Contributing

Expand Down
1 change: 1 addition & 0 deletions examples/typescript-app/package.json
Expand Up @@ -13,6 +13,7 @@
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-scripts": "3.4.1",
"stream-chat": "2.1.3",
"stream-chat-react": "^2.0.3",
"typescript": "~3.7.2"
},
Expand Down
21 changes: 18 additions & 3 deletions examples/typescript-app/yarn.lock
Expand Up @@ -2861,9 +2861,9 @@ caniuse-api@^3.0.0:
lodash.uniq "^4.5.0"

caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001020, caniuse-lite@^1.0.30001035:
version "1.0.30001035"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001035.tgz#2bb53b8aa4716b2ed08e088d4dc816a5fe089a1e"
integrity sha512-C1ZxgkuA4/bUEdMbU5WrGY4+UhMFFiXrgNAfxiMIqWgFTWfv/xsZCS2xEHT2LMq7xAZfuAnu6mcqyDl0ZR6wLQ==
version "1.0.30001125"
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001125.tgz"
integrity sha512-9f+r7BW8Qli917mU3j0fUaTweT3f3vnX/Lcs+1C73V+RADmFme+Ih0Br8vONQi3X0lseOe6ZHfsZLCA8MSjxUA==

capture-exit@^2.0.0:
version "2.0.0"
Expand Down Expand Up @@ -10475,6 +10475,21 @@ stream-chat-react@^2.0.3:
uuid "^7.0.3"
visibilityjs "^2.0.2"

stream-chat@2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-2.1.3.tgz#ddaa7095a49471d9c3c279494d7147e36e24671e"
integrity sha512-o7eArbbfHiMdJCiaaZwzhYWLuNvkN3CVUmvxBk+cGtCATxNDQESgD+T+4G9pcEXGSpLavh/18oP/WgYMBMnKZw==
dependencies:
"@babel/runtime" "^7.3.1"
axios "^0.18.1"
base64-js "^1.3.1"
form-data "^2.3.3"
isomorphic-ws "^4.0.1"
jsonwebtoken "^8.3.0"
seamless-immutable "^7.1.4"
uuid "^3.3.2"
ws "^6.1.3"

stream-chat@^1.13.0:
version "1.13.2"
resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-1.13.2.tgz#d158e5f35dc75915772d0466d43969f874f53ac9"
Expand Down
4 changes: 2 additions & 2 deletions package.json
Expand Up @@ -41,7 +41,7 @@
"react-player": "^2.5.0",
"react-textarea-autosize": "^8.2.0",
"seamless-immutable": "^7.1.4",
"stream-chat": "^1.14.1",
"stream-chat": "2.1.3",
"textarea-caret": "^3.1.0",
"uuid": "^7.0.3"
},
Expand Down Expand Up @@ -75,7 +75,7 @@
"@types/lodash.throttle": "^4.1.6",
"@types/lodash.uniqby": "^4.7.6",
"@types/react-dom": "^16.9.8",
"@types/seamless-immutable": "7.1.12",
"@types/seamless-immutable": "7.1.13",
"@types/uuid": "^8.0.0",
"autoprefixer": "^9.8.4",
"babel-eslint": "^10.1.0",
Expand Down
3 changes: 2 additions & 1 deletion src/components/Attachment/Attachment.js
Expand Up @@ -281,7 +281,8 @@ Attachment.propTypes = {
* @see See [Attachment structure](https://getstream.io/chat/docs/#message_format)
*
* */
attachment: PropTypes.object.isRequired,
attachment: /** @type {PropTypes.Validator<ExtendedAttachment>} */ (PropTypes
.object.isRequired),
/**
*
* @param name {string} Name of action
Expand Down
2 changes: 1 addition & 1 deletion src/components/Attachment/__tests__/Card.test.js
Expand Up @@ -207,6 +207,6 @@ describe('Card', () => {

it('should return null if no og_scrape_url && no title_link', () => {
const { container } = render(<Card title="test card" />);
expect(container).toBeEmpty();
expect(container).toBeEmptyDOMElement();
});
});
17 changes: 11 additions & 6 deletions src/components/Channel/Channel.js
Expand Up @@ -151,7 +151,7 @@ const ChannelInner = ({
);

const markRead = useCallback(() => {
if (channel.disconnected || !channel.getConfig().read_events) {
if (channel.disconnected || !channel.getConfig()?.read_events) {
return;
}
lastRead.current = new Date();
Expand Down Expand Up @@ -187,7 +187,7 @@ const ChannelInner = ({
) {
if (!document.hidden) {
markReadThrottled();
} else if (channel.getConfig().read_events) {
} else if (channel.getConfig()?.read_events) {
const unread = channel.countUnread(lastRead.current);
document.title = `(${unread}) ${originalTitle.current}`;
}
Expand Down Expand Up @@ -260,7 +260,7 @@ const ChannelInner = ({
debounce(
/**
* @param {boolean} hasMore
* @param {import('seamless-immutable').ImmutableArray<import('stream-chat').MessageResponse>} messages
* @param {import('stream-chat').ChannelState['messages']} messages
*/
(hasMore, messages) => {
dispatch({ type: 'loadMoreFinished', hasMore, messages });
Expand All @@ -281,7 +281,7 @@ const ChannelInner = ({
if (state.loadingMore || oldestMessage?.status !== 'received') return;
dispatch({ type: 'setLoadingMore', loadingMore: true });

const oldestID = oldestMessage?.id || null;
const oldestID = oldestMessage?.id;

const perPage = limit;
let queryResponse;
Expand Down Expand Up @@ -443,7 +443,7 @@ const ChannelInner = ({
debounce(
/**
* @param {boolean} threadHasMore
* @param {import('seamless-immutable').ImmutableArray<import('stream-chat').MessageResponse>} threadMessages
* @param {import('seamless-immutable').ImmutableArray<ReturnType<import('stream-chat').ChannelState['messageToImmutable']>>} threadMessages
*/
(threadHasMore, threadMessages) => {
dispatch({
Expand All @@ -463,8 +463,13 @@ const ChannelInner = ({
if (state.threadLoadingMore || !state.thread) return;
dispatch({ type: 'startLoadingThread' });
const parentID = state.thread.id;

if (!parentID) {
dispatch({ type: 'closeThread' });
return;
}
const oldMessages = channel.state.threads[parentID] || [];
const oldestMessageID = oldMessages[0] ? oldMessages[0].id : null;
const oldestMessageID = oldMessages[0]?.id;
const limit = 50;
const queryResponse = await channel.getReplies(parentID, {
limit,
Expand Down
10 changes: 7 additions & 3 deletions src/components/Channel/channelState.js
Expand Up @@ -50,7 +50,7 @@ export const channelReducer = (state, action) => {
...state,
messages: channel.state.messages,
threadMessages: parentId
? channel.state.threads[parentId] || []
? channel.state.threads[parentId] || Immutable([])
: state.threadMessages,
};
}
Expand All @@ -59,7 +59,9 @@ export const channelReducer = (state, action) => {
if (!state.thread) return state;
return {
...state,
threadMessages: channel.state.threads[state.thread.id],
threadMessages: state.thread?.id
? channel.state.threads[state.thread.id] || Immutable([])
: Immutable([]),
thread:
message?.id === state.thread.id
? channel.state.messageToImmutable(message)
Expand All @@ -71,7 +73,9 @@ export const channelReducer = (state, action) => {
return {
...state,
thread: message,
threadMessages: channel.state.threads[message.id] || [],
threadMessages: message.id
? channel.state.threads[message.id] || Immutable([])
: Immutable([]),
};
}
case 'startLoadingThread': {
Expand Down
6 changes: 3 additions & 3 deletions src/components/Channel/hooks/useEditMessageHandler.js
Expand Up @@ -4,9 +4,9 @@ import { ChatContext } from '../../../context';

/**
* @typedef {import('stream-chat').Message} Message
* @typedef {import('stream-chat').UpdateMessageAPIResponse} UpdateResponse
* @param {((cid: string, updatedMessage: Message) => Promise<UpdateResponse>) | undefined} doUpdateMessageRequest
* @returns {(updatedMessage: Message) => Promise<UpdateResponse>}
* @typedef {ReturnType<import('stream-chat').StreamChat['updateMessage']>} UpdateMessagePromise
* @param {((cid: string, updatedMessage: Message) => UpdateMessagePromise) | undefined} doUpdateMessageRequest
* @returns {(updatedMessage: Message) => UpdateMessagePromise}
*/
const useEditMessageHandler = (doUpdateMessageRequest) => {
const { channel, client } = useContext(ChatContext);
Expand Down
39 changes: 16 additions & 23 deletions src/components/Channel/types.ts
@@ -1,35 +1,26 @@
import {
Channel,
Message,
MessageResponse,
TypingStartEvent,
Event,
Member,
UserResponse,
ChannelState as ChannelStateFromClient,
} from 'stream-chat';
import { ImmutableArray, ImmutableObject } from 'seamless-immutable';
import { ImmutableArray } from 'seamless-immutable';
import { Reducer } from 'react';

type UserMap<T> = ImmutableObject<{ [user_id: string]: T }>;

export type ChannelState = {
error: Error | null;
loading: boolean;
loadingMore: boolean;
hasMore: boolean;
messages: ImmutableArray<MessageResponse>;
typing: UserMap<ImmutableObject<Event<TypingStartEvent>>>;
members: UserMap<Member>;
watchers: UserMap<UserResponse>;
messages: ChannelStateFromClient['messages'];
typing: ChannelStateFromClient['typing'];
members: ChannelStateFromClient['members'];
watchers: ChannelStateFromClient['watchers'];
watcherCount: number;
read: UserMap<
ImmutableObject<{
last_read: string;
user: UserResponse;
}>
read: ChannelStateFromClient['read'];
thread: ReturnType<ChannelStateFromClient['messageToImmutable']> | null;
threadMessages: ImmutableArray<
ReturnType<ChannelStateFromClient['messageToImmutable']>
>;
thread: ImmutableObject<MessageResponse> | null;
threadMessages: ImmutableArray<MessageResponse>;
threadLoadingMore: boolean;
threadHasMore: boolean;
};
Expand All @@ -44,12 +35,12 @@ export type ChannelStateReducerAction =
| ChannelAction<'copyStateFromChannelOnEvent'>
| {
type: 'setThread';
message: ImmutableObject<MessageResponse>;
message: ReturnType<ChannelStateFromClient['messageToImmutable']>;
}
| {
type: 'loadMoreFinished';
hasMore: boolean;
messages: ImmutableArray<MessageResponse>;
messages: ChannelStateFromClient['messages'];
}
| {
type: 'setLoadingMore';
Expand All @@ -68,15 +59,17 @@ export type ChannelStateReducerAction =
| {
type: 'openThread';
channel: Channel;
message: ImmutableObject<MessageResponse>;
message: ReturnType<ChannelStateFromClient['messageToImmutable']>;
}
| {
type: 'startLoadingThread';
}
| {
type: 'loadMoreThreadFinished';
threadHasMore: boolean;
threadMessages: ImmutableArray<MessageResponse>;
threadMessages: ImmutableArray<
ReturnType<ChannelStateFromClient['messageToImmutable']>
>;
}
| {
type: 'closeThread';
Expand Down

0 comments on commit 3077169

Please sign in to comment.