import { DefaultChannelType } from "stream-chat-react-native";
declare module "stream-chat" {
interface CustomChannelData extends DefaultChannelType {
"custom-channel-property": string;
}
interface CustomAttachmentData {
id: string;
size: number;
}
interface CustomCommandData {
poke: unknown;
}
}
Typescript
The stream-chat-react-native
as well as the client library stream-chat-js
are written in TypeScript and therefore provide full TypeScript support.
The SDK allows for a variety of customizations including custom fields on messages, channels, users, and more. The goal of the Stream Chat TypeScript implementation is to provide static type safety not just on out of the box Stream Chat implementation, but also on custom data provided to the API & SDK as well.
Module augmentation and interface merging
To make it easier to work with, you can use module augmentation to define the shape of your custom data.
Here you can find a full list of customizable interfaces that are then going to be merged with the ones you’ve defined.
The interfaces concerning the entities which can be extended will always be named in the format of Custom<uppercase-entity-name>Data
.
The definition of these custom interfaces comes in the form of a module declaration file (containing the .d.ts
extension) and might look something like the following example.
Note that commands (CustomCommandType
) are a special case which transforms from string union type (commandType: 'custom-command' | 'other-custom-command'
) to an interface from which only the keys would be used, the value type is not important and unknown
would suffice.
The module declaration above is going to add custom-channel-property
of type string
to our Channel
type and make it be able to understand this new property. It will also make sure to keep the default properties defined in the SDK in the process. It is also going to add the id
and size
properties to the Attachment
type, however it will not do this as an extension to the default one from the SDK. The command
types are also extended with an additional one, called poke
.
You may put the .d.ts
declaration file anywhere in your file structure and the Typescript compiler should be able to consume it.
However, keep in mind that declaration merging does not work as an extension chain - but will rather override whatever the definition was unless explicitly told to extend.
Default SDK properties
The React Native SDK defined some custom properties on top of the already existing ones in stream-chat
in order for it to be able to properly define business logic. However, it also exposes these custom properties in default types that can be used.
You may find a full list of these defaults here, as well as the module declaration used in the SDK itself. If interfaces are defined within your own application, they will override the interfaces in the SDK rather than extend them (and this is done by design).
The default SDK types are always going to be named in the format of Default<uppercase-entity-name>Type
.
Whenever defining your own custom data, you can choose to either use these defaults or simply omit them. In the example above, CustomChannelData
will respect the SDK’s type whereas CustomAttachmentData
will not.
If you do not wish to extend any of the default types but still require strong typing based on the SDK’s types, you may do the following:
import {
DefaultAttachmentType,
DefaultChannelType,
DefaultCommandType,
DefaultEventType,
DefaultMemberType,
DefaultMessageType,
DefaultPollOptionType,
DefaultPollType,
DefaultReactionType,
DefaultThreadType,
DefaultUserType,
} from "stream-chat-react-native";
declare module "stream-chat" {
/* eslint-disable @typescript-eslint/no-empty-object-type */
interface CustomAttachmentData extends DefaultAttachmentType {}
interface CustomChannelData extends DefaultChannelType {}
interface CustomCommandData extends DefaultCommandType {}
interface CustomEventData extends DefaultEventType {}
interface CustomMemberData extends DefaultMemberType {}
interface CustomUserData extends DefaultUserType {}
interface CustomMessageData extends DefaultMessageType {}
interface CustomPollOptionData extends DefaultPollOptionType {}
interface CustomPollData extends DefaultPollType {}
interface CustomReactionData extends DefaultReactionType {}
interface CustomThreadData extends DefaultThreadType {}
/* eslint-enable @typescript-eslint/no-empty-object-type */
}
Even though a lot of these default interfaces are simply empty, by doing this you’ll ensure that in case we do decide to extend them in the future you won’t need to change anything in your codebase.
In that regard, it is important to add all of them here unless you have a very good reason not to.
Provided below is a more complicated and real world example:
import {
DefaultAttachmentType,
DefaultChannelType,
} from "stream-chat-react-native";
declare module "stream-chat" {
interface CustomChannelData extends DefaultChannelType {
color: string;
topic: "gardening" | "cats" | "f1";
}
interface CustomUserData {
nickname: string;
}
interface CustomMessageData {
isSecret: boolean;
}
interface CustomAttachmentData extends DefaultAttachmentType {
lat?: string;
lon?: string;
}
interface CustomReactionData {
// Turn off type checks for custom properties
[key: string]: unknown;
}
}
In this case, your custom data will be properly typed:
let channel: Channel | undefined;
console.log(channel?.data?.topic);
let user: User | undefined;
console.log(user?.nickname);
let message: StreamMessage | undefined;
console.log(message?.isSecret);
let attachment: Attachment | undefined;
console.log(attachment?.lat);
console.log(attachment?.lon);
let reaction: Reaction | undefined;
// CustomReactionData allows any key on Reaction
console.log(reaction?.foo);
The rest of the types that are not defined will be the types used for the entities in stream-chat
.
Disabling type checks
If you do not want type checks for custom properties on a specific entity, you can define the custom interface like this:
interface CustomMessageData {
// Turn off type checks for custom properties
[key: string]: unknown;
}
which will make sure anything goes.