Info:
This is release candidate documentation for Stream Chat React Native SDK v10. For the latest stable version, see the latest version (v9).

How the SDK is built

The SDK has two layers. This page follows one action through both of them: a user opens a channel and sends a message. It ends with the rule that follows from it: which layer to change for what.

The two layers

  • State lives in stream-chat. Channels, threads, channel lists, message lists and message composers are objects, and each keeps its data in stores that anything can subscribe to. You configure these objects through client.config. None of this depends on React.
  • UI lives in stream-chat-react-native, or stream-chat-expo in Expo apps. Its components subscribe to those stores with useStateStore, render what they hold, and call methods on the objects when the user acts. You replace any component through WithComponents.

Where a channel or a thread opens is up to your app's navigation. The SDK calls back when the user picks a channel (onSelect on ChannelList) or opens a thread (onThreadSelect on MessageList and ThreadList), and your screens render Channel and Thread.

One action through the layers

The user has the app from the Quick start open: a channel list screen and a channel screen. They open "Design" and send "Hello". Step through what happens in each layer:

flowchart LR
  subgraph UI["UI: stream-chat-react-native"]
    CL[ChannelList]
    ML[MessageList]
    MC[MessageComposer]
  end
  subgraph State["State: stream-chat"]
    CP[ChannelPaginator]
    MP[channel.messagePaginator]
    CO[channel.messageComposer]
    MO[channel.messageOperations]
  end
  API[(Stream API)]
  CP -- channels --> CL
  MP -- messages --> ML
  CO -- what is typed --> MC
  MC -- "send()" --> CO
  CO --> MO
  MO -- optimistic message --> MP
  MO -- request --> API
  API -- "response, message.new" --> MP
  API -- "message.new" --> CP

ChannelPaginator loads the channels from the Stream API with a channel query, and feeds them to ChannelList: the list subscribes to paginator.state and renders a row for each channel. The paginator is registered on client.channelManager, which keeps it up to date as events arrive.

The UI rendered things and the app's navigation placed them; the state layer loaded the data, sent the message and updated the stores. In a thread, the same steps run on thread.messagePaginator, thread.messageComposer and thread.messageOperations, and the send still goes through the channel's request handler.

The rule

Change behavior in the state layer. Change appearance in the UI layer.

  • Behavior is what the objects do: what they load, what they send and what they allow. Change it through client.config: configuration values, request handlers, and setup functions that add composer middleware. The change applies wherever the object is used: in the SDK's components, in your own components, and in channels and threads alike.
  • Appearance is what a component renders. Replace the component through WithComponents, or change the theme.
  • Placement is where a channel or a thread opens. It belongs to your app's navigation: the screens that render Channel and Thread, and the callbacks that open them.

Two examples:

  • Turning on drafts is behavior: set messageComposer.drafts.enabled in client.config. It reaches every message composer, in channels and threads, including a composer UI of your own, because it configures the composer objects, not a component.
  • Changing the date separator is appearance: pass an InlineDateSeparator override to WithComponents. Every message list below it renders yours.

Where each layer is taught