export type LocalStreamChatGenerics = {
// the only augmentations I (as an integrator) currently care about
attachmentType: { myCustomThing?: string };
userType: { profilePicture?: string };
// the rest which would have to be defined to be accepted by the SDK
channelType: Record<string, unknown>;
commandType: string;
eventType: Record<string, unknown>;
memberType: Record<string, unknown>;
messageType: Record<string, unknown>;
pollOptionType: Record<string, unknown>;
pollType: Record<string, unknown>;
reactionType: Record<string, unknown>;
// list would grow with each update to the `DefaultGenerics` type
};
Upgrade stream-chat to v9
List of Breaking Changes
Considered Major
- removal of
StreamChatGenerics
(DefaultGenerics
andExtendableGenerics
types too) - custom property
name
has been removed from typeChannelResponse
, see note in Replacement - Module Augmentation section - removal of the
jsDelivr
(UMD) bundle
Considered Minor
- type
InviteOptions
has been renamed toUpdateChannelOptions
- type
UpdateChannelOptions
has been renamed toUpdateChannelTypeRequest
- type
ThreadResponseCustomData
has been renamed toCustomThreadType
- type
MarkAllReadOptions
has been deleted in favour of typeMarkChannelsReadOptions
- type
QueryFilter
no longer supports$ne
and$nin
operators - type
ChannelMembership
has been deleted in favour of typeChannelMemberResponse
- function
formatMessage
(utils.ts
) no longer returns__html
property in the formatted message output
Note that $ne
and $nin
operators are inefficient and cause performance issues. You’ll need to refactor your queries removing all $ne
or $nin
operators. Feel free to contact our Customer Support to get help finding an appropriate alternative solution for your integration.
Removal of StreamChatGenerics
The generic was initially introduced to our codebase as a mean of providing our integrators with a way to extend our types with their own type definitions. This was mostly targeted at custom data of certain entities such as a message or a channel. Unfortunately cost of maintaining a codebase with such mechanic in place was quite high in comparison to the amount of benefits it brought to our integrators. It also fell short when it came to ease of use. Integrators would have to forward custom-defined type definitions to multiple places seemingly unrelated to the code at hand.
Main Reasons
There are two main issues with the generic mechanism.
Fragile
Integrators would have to define all of the custom types within the generic type even if they augmented only one or two specific entities which also meant that every time we issued a release with a new feature that extended the DefaultGenerics
, just upgrading to the latest package version wouldn’t suffice, the generic type would have to be manually adjusted as well even if not explicitly needed.
Lack of Ease of Use
Integrators would have to import and apply generics to multitude of places to get proper type support.
import type { LocalStreamChatGenerics } from "./types.ts";
export const CustomAttachment = (
props: AttachmentProps<LocalStreamChatGenerics>,
) => {
const { client } = useChatContext<LocalStreamChatGenerics>();
console.log(client.user?.profilePicture);
const [attachment] = props.attachments;
if (typeof attachment.myCustomThing === "string") {
return <div>...</div>;
}
return <Attachment {...props} />;
};
import { CustomAttachment } from "./custom-attachment.tsx";
import type { LocalStreamChatGenerics } from "./types.ts";
const Root = () => {
const chatClient = useCreateChatClient<LocalStreamChatGenerics>({
apiKey: "<api-key>",
tokenOrProvider: "user.access.token",
userData: { id: "<user-id>" },
});
return (
<Chat client={chatClient}>
<Channel Attachment={CustomAttachment}>...</Channel>
</Chat>
);
};
Replacement - Module Augmentation
The transition to the new mechanism is fairly easy and pretty straightforward. Almost all of your custom declarations can stay as is, we’ll just have to move them to a declaration file. In there we’ll import stream-chat
and define a specific interface to extend.
import "stream-chat";
declare module "stream-chat" {
// your "attachmentType" now becomes
interface CustomAttachmentData {
custom_property?: string;
}
// your "userType" now becomes
interface CustomUserData {
profile_picture?: string;
}
interface CustomCommandData {
"custom-command": unknown;
"other-custom-command": unknown;
}
}
CustomCommandType
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.
ChannelResponse.name
If you wish to continue using ChannelResponse.name
property, you’ll have to add it yourself by extending CustomChannelData
interface since it’s considered to be a custom field and is not part of the Channel
entity by default.
Now as for the rest of your code; none of the members exported by the stream-chat-react
(and stream-chat
) no longer support StreamChatGenerics
which means you’ll have to remove them. Using our previous example your code should now look like this:
export const CustomAttachment = (props: AttachmentProps) => {
const { client } = useChatContext();
console.log(client.user?.profile_picture);
const [attachment] = props.attachments;
if (typeof attachment.custom_property === "string") {
return <div>...</div>;
}
return <Attachment {...props} />;
};
import { CustomAttachment } from "./custom-attachment.tsx";
const Root = () => {
const chatClient = useCreateChatClient({
apiKey: "<api-key>",
tokenOrProvider: "user.access.token",
userData: {
id: "<user-id>",
profile_picture: "https://random.picture/1.jpg",
},
});
return (
<Chat client={chatClient}>
<Channel Attachment={CustomAttachment}>...</Channel>
</Chat>
);
};
And that’s it! One place, one definition, no need to pass anything else around your code. Everything is taken care of internally and all of your custom types are now accounted for.
If you don’t care about specifying your custom types and prefer the previous behavior (casting) you can mark every unspecified key as unknown
:
import "stream-chat";
declare module "stream-chat" {
interface CustomAttachmentData {
[key: string]?: unknown;
}
}
Now this is generally not recommended, but it is a possibility.
Removal of the jsDelivr (UMD) Bundle
The package provided a bundle which was built using an outdated UMD format. If your intention was to use stream-chat
imported directly from the CDN, see esm.sh documentation for a viable alternative.
import { StreamChat } from "https://esm.sh/stream-chat";
Replacement of FormatMessageResponse by LocalMessage type
The new type LocalMessage
is used everywhere a local state message object representation is required. In general, if a message response object obtained from the server is to be stored in the state, then it has to be converted into LocalMessage
. The new type contains key error
to store the information about failed message creation server-side. Type FormatMessageResponse
is an alias to LocalMessage
and will be removed in the future.
With this change, the signature of StreamChat.updateMessage()
method has been changed. The first argument - message
- should be of type LocalMessage | Partial<MessageResponse>
instead of UpdatedMessage
.