Skip to main content

TypeScript and Generics

As of version 5.0.0, stream-chat-react has been converted to TypeScript. The stream-chat library was converted to TypeScript in version 2.0.0. These upgrades not only improve type safety, but also allow user provided typings to be passed to server requests and custom components through the use of generics.

In many cases, TypeScript uses inference from provided props to establish a component or value's type. It is therefore important that the proper generics be applied to the stream-chat client instance during construction. The documentation on stream-chat TypeScript has detailed examples of how this can be accomplished. The Stream Chat client takes a type with seven customizable fields that currently exist in stream-chat.

const client = new StreamChat<StreamChatGenerics>('YOUR_API_KEY');

StreamChatGenerics can be defined as a type with the seven generics that correspond to the seven customizable fields as follows:

type StreamChatGenerics = {
attachmentType: LocalAttachmentType;
channelType: LocalChannelType;
commandType: LocalCommandType;
eventType: LocalEventType;
messageType: LocalMessageType;
reactionType: LocalReactionType;
userType: LocalUserType;
};
note

LocalAttachmentType, LocalChannelType etc. are type definitions for their respective key as per your application types necessities.

type LocalAttachmentType = {
file_size?: number;
};
type LocalChannelType = Record<string, unknown>;
type LocalCommandType = string;
type LocalEventType = Record<string, unknown>;
type LocalMessageType = Record<string, unknown>;
type LocalReactionType = Record<string, unknown>;
type LocalUserType = {
image?: string;
};

The seven customizable fields these generics extend are provided via stream-chat:

  1. Attachment
  2. ChannelResponse
  3. CommandVariants
  4. Event
  5. MessageBase
  6. Reaction
  7. User

All seven generics contain defaults in the component library that can be extended for custom types for Channel, Messages, etc.

type DefaultAttachmentType = Record<string, unknown>;
type DefaultChannelType = Record<string, unknown> & {
image?: string;
};
type DefaultCommandType = string & {};
type DefaultEventType = Record<string, unknown>;
type DefaultMessageType = Record<string, unknown> & {
isAdminMessage?: boolean;
};
type DefaultReactionType = Record<string, unknown>;
type DefaultUserType = Record<string, unknown> & {
image?: string;
nickName?: string;
};

Additional fields on the defaults (eg: file_size, and image) are added by stream-chat-react within the SDK. When adding to the subset of generics, the preceding and interceding generics must also be set in order for the TypeScript compiler to correctly understand intent.

The below example shows how to extend ChannelType, MessageType, and UserType during client instantiation:

type StreamChatGenerics = {
attachmentType: DefaultAttachmentType;
channelType: DefaultChannelType;
commandType: DefaultCommandType;
eventType: DefaultEventType;
messageType: DefaultMessageType;
reactionType: DefaultReactionType;
userType: DefaultUserType;
};
const client = new StreamChat<StreamChatGenerics>('YOUR_API_KEY');

Through this client instantiation, we have added type support for the following values:

  • channel.image
  • message.isAdminMessage
  • user.nickName
note

DefaultCommandType extends string & {} to maintain intellisense for the included commands. This allows you to extend a string union.

The below snippet shows the current default types in the stream-chat-react component library. Any additional custom types will extend these defaults. Core to understanding this pattern is how generics can be applied to JSX elements.

export type DefaultAttachmentType = UnknownType & {
asset_url?: string;
file_size?: number;
id?: string;
images?: Array<{
image_url?: string;
thumb_url?: string;
}>;
};

export type DefaultChannelType = UnknownType & {
frozen?: boolean;
image?: string;
member_count?: number;
subtitle?: string;
};

type DefaultCommandType = string & {};

type DefaultEventType = Record<string, unknown>;

export type CustomMessageType = 'channel.intro' | 'message.date';

export type DefaultMessageType<
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics
> = UnknownType & {
customType?: CustomMessageType;
date?: string | Date;
errorStatusCode?: number;
event?: Event<StreamChatGenerics>;
unread?: boolean;
};

type DefaultReactionType = Record<string, unknown>;

export type DefaultUserTypeInternal = {
image?: string;
status?: string;
};

export type DefaultUserType<
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics
> = UnknownType &
DefaultUserTypeInternal & {
mutes?: Array<Mute<StreamChatGenerics>>;
};

The exported custom context hooks also require generics be applied to correctly type custom returns. For example, the useChannelStateContext hook needs all generics to get a correctly typed return for channel.

const { channel } = useChannelStateContext<
DefaultAttachmentType,
{ image?: string },
DefaultCommandType,
DefaultEventType,
{ isAdminMessage?: boolean },
DefaultReactionType,
{ nickName?: string }
>();
tip

To see an example of extending the default Attachment type, see the Geolocation Attachment custom code example.

Did you find this page helpful?