---
id: custom_components
sidebar_position: 2
title: Custom Components
---

import dateHeader from '../assets/customization/custom-components/date_header.png'
import nameDateHeader from '../assets/customization/custom-components/name_date_header.png'
import newDateHeader from '../assets/customization/custom-components/new_date_header.png'
import noDateHeader from '../assets/customization/custom-components/no_date_header.png'

The core of Stream Chat for React Native is it's ability to be customized to your needs.
Many developers find that simply altering [the theme](./theming.mdx) is enough to achieve their desired results.
But if more modification is needed, or functionality beyond what is provided out of the box is desired, creating a custom component is likely the best course of action.

## When to use a custom component

Understanding what is customizable is helpful in determining when to build a custom component.
If you want to insert your component(s) into another component, you need to create a custom component.
If you want to change the layout of a component significantly, you need to create a custom component.
If you want to change the internal functionality of a component, you need to create a custom component.
A good way to think about it is, with few exceptions, what you see is what you get.
The library can't account for every possible use case out of the box, but don't worry building and using custom components is easy!

### Exceptions

- Paddings, fonts, colors, borders, etc. can all be altered from the [the theme](./theming.mdx) as styles are passed to most components and sub-components.
- Messages can be aligned to one side using the `forceAlignMessages` prop on the `Channel` component.
- Message content can be reordered using the `messageContentOrder` prop on `Channel`; but you may want to adjust the theming to account for changes to border alignments if you alter this order.

Changes to content beyond these tweaks will require custom components.
If your goal is simply to remove a UI feature you can always replace the component with one that returns `null` to achieve this, i.e. `() => null`.

## How to use a custom component

Most custom components are provided to the UI as props on the `Channel` component.
The `OverlayProvider`, `MessageList`, and `ChannelList` have a few exceptions to this rule, but `Channel` is the primary entry point.

Let's say you want to remove the fixed `DateHeader` on the message list.
Create a component that returns `null` and pass it in as out `DateHeader` on `Channel`.

```tsx
const MyEmptyComponent = () => null;

<Channel
  ...
  DateHeader={MyEmptyComponent}
>
```

The new custom component takes the place of the default `DateHeader` within a context and is then provided to and rendered wherever the default `DateHeader` currently is.
The component can be just as easily swapped out for one that renders something different than the original instead of `null`.
Most components rely on context for receiving data, but some use props instead or as well.
In the case of `DateHeader` the component will be rendered with the prop `dateString` which is a `string`.

```tsx
const MyNewComponent = ({ dateString }) => <Text>{`Hello World: ${dateString}`}</Text>;

<Channel
  ...
  DateHeader={MyNewComponent}
>
```

<div style={{ display: 'flex', flexWrap: 'wrap', justifyContent: 'space-around' }}>
<div style={{ position: 'relative' }}>
<div style={{ left: '110px', top: '65px', position: 'absolute', height: '30px', width: '60px', borderColor: '#fa383e', borderWidth: '4px', borderRadius: '15px', borderStyle: 'solid' }}></div>
<img src={dateHeader} alt="DateHeader" width="280" style={{ objectFit: 'contain', paddingBottom: '8px' }} />
</div>
<img src={noDateHeader} alt="NoDateHeader" width="280" style={{ objectFit: 'contain', paddingBottom: '8px' }} />
<div style={{ position: 'relative' }}>
<div style={{ left: '90px', top: '65px', position: 'absolute', height: '20px', width: '100px', borderColor: '#fa383e', borderWidth: '4px', borderRadius: '15px', borderStyle: 'solid' }}></div>
<img src={newDateHeader} alt="NewDateHeader" width="280" style={{ objectFit: 'contain', paddingBottom: '8px' }} />
</div>
</div>

## Using contexts

At the core of creating custom components is the ability to access pertinent data from the contexts Stream Chat for React Native provides.
[The hooks](./contexts.mdx#hooks) provided allow you to access this contextual information easily throughout your application.
Keep in mind when developing that these hooks are only usable within the scope of their context providers.
Certain contexts, such at `MessageContext`, are only available within the scope of a single `Message` so you cannot use this hook in for instance the `DateHeader`.

To demonstrate this usage the `DateHeader` can be replaced with a component that makes use of context.
Messages can be pulled from the `PaginatedMessageListContext` and the name of the last sender from the list can be retrieved and displayed in place of the `DateHeader`.

```tsx
const MySenderComponent = () => {
  const { messages } = usePaginatedMessageListContext();
  const latestMessageSender = messages[messages.length - 1]?.user?.name;

  return <Text>{`Last Sender: ${latestMessageSender}`}</Text>;
};

<Channel
  ...
  DateHeader={MySenderComponent}
>
```

<div style={{ display: 'flex', justifyContent: 'space-around' }}>
<div style={{ position: 'relative' }}>
<div style={{ left: '90px', top: '65px', position: 'absolute', height: '20px', width: '100px', borderColor: '#fa383e', borderWidth: '4px', borderRadius: '15px', borderStyle: 'solid' }}></div>
<img src={nameDateHeader} alt="NameDateHeader" width="280" style={{ objectFit: 'contain', paddingBottom: '8px' }} />
</div>
</div>

This allows you as a developer to not only customize the look and feel of every part of the UI, but also decide what information is shown where based on your own design.

:::caution

Custom components within a `Message` should draw from mostly static contexts with the exception of their own `MessageContext`.
`ThemeContext`, `MessagesContext`, and `TranslationContext` all remain static with few exceptions and can be used.

This should be done as memoization of a value from a context surrounding the [`FlatList`](https://reactnative.dev/docs/flatlist) from a component within the `FlatList` does not work properly when the context updates.
Pulling directly from the `PaginatedMessageListContext` within a `Message` for instance would cause every `Message` to re-render to some degree when the message list updates.

:::

## Performance

If a value in a context updates [all components accessing values from that context will re-render](https://reactjs.org/docs/context.html#contextprovider), even if the values they are accessing have not changed.
Many of the out of the box components use memoization with custom `areEqual` checks to ensure components are only being re-rendered when necessary.
Values from context are passed through the memoization as props and the necessity to re-render is then determined.
This is most important inside of messages where the largest performance hit would be noticeable as an update that re-renders every message is computationally expensive.

When creating custom components it is suggested that developers take the same performance considerations to ensure the user experience is not degraded.
Adding a console log to a custom `DateHeader` component that is accessing the `PaginatedMessageListContext` a large number of re-renders can be seen when the channel mounts, and re-renders continue when interacting with messages, such as reacting to one.

```tsx
const MySenderComponent = () => {
  const { messages } = usePaginatedMessageListContext();
  const latestMessageSender = messages[messages.length - 1]?.user?.name;
  console.log('Render.');

  return <Text>{`Last Sender: ${latestMessageSender}`}</Text>;
};
```

To reduce these re-renders the component can be broken apart into a component that consumes context, and a component that is memoized and takes only props.
A custom `areEqual` check can then be used in the memoization to determine if the component should re-render.

```tsx
const MySenderComponentWithContext = ({ latestMessageSender }) => {
  console.log('Rendered');

  return <Text>{`Last Sender: ${latestMessageSender}`}</Text>;
};

const MemoizedMySenderComponent = React.memo(
  MySenderComponentWithContext,
  (prev, next) => prev.latestMessageSender === next.latestMessageSender,
);

const MySenderComponent = () => {
  const { messages } = usePaginatedMessageListContext();
  const latestMessageSender = messages[messages.length - 1]?.user?.name;

  return <MemoizedMySenderComponent latestMessageSender={latestMessageSender} />;
};
```

Following this pattern `MySenderComponentWithContext`, and therefore the UI component `Text`, render only one time, on mount.
The check on `latestMessageSender` will ensure the component re-renders when `latestMessageSender` changes, but otherwise will not.
A user can react to, edit, delete, or add a message, and the component will not re-render until the sender of the latest message changes.

There are many custom equality checks like this in the out of the box components.
When creating a custom component it is always suggested developers **look at the source code of the component being replaced**.
Using the original component as a base for a custom component can help ensure the performance of the custom component matches that of the original.
It also gives insight into what data the out of the box component is using, and from which contexts the data is being drawn.

### Equality

It is import to consider equality when writing `areEqual` checks.
Trying to compare two functions, objects, or arrays during a re-render cycle, unless they are themselves memoized or in another way referentially stable, will fail a shallow comparison.
For this reason in the out of the box components it is often considered *when* the component should re-render, and the equality checks are created based on that logic.

In the out of the box `MessageFooter`, `message` from the `MessageContext` is used, but `message` is an object and not referentially stable.
Checking to see if `prevMessage === nextMessage` will return false whenever the `MessageContext` updates.
Instead the usage of `message` in the component is considered, for the `MessageFooter` data such as `replies` is not import, so a custom equality value is created for the check.

```tsx
const messageEqual =
  prevMessage.deleted_at === nextMessage.deleted_at &&
  prevMessage.reply_count === nextMessage.reply_count &&
  prevMessage.status === nextMessage.status &&
  prevMessage.type === nextMessage.type &&
  prevMessage.text === nextMessage.text;
```

This custom value is then used to determine if the `MessageFooter` should re-render as it takes into consideration if data relevant to the component has updated.
When creating custom `areEqual` checks this approach should be used to ensure the custom memoization is having a meaningful effect on performance and preventing undesired re-renders.
