# Custom navigation items

> **Info:** This is release candidate documentation for **Stream Chat React Native SDK v10**. For the latest stable version, see the [latest version (v9)](https://getstream.io/chat/docs/sdk/react-native/).

The navigation section of channel details has three rows: **Pinned Messages**, **Photos & Videos** and **Files**. Each opens its list in a full-screen modal. This UI cookbook adds a row of your own that opens a link, hides a row, and changes the order of the rows. To open the default rows as screens of your navigator instead, see the [Custom navigation UI cookbook](https://getstream.io/chat/docs/sdk/react-native/v10/ui-cookbook/channel-details/custom-navigation/).

## Best practices

- Start from `defaultItems` and add, filter or reorder rows. Rows you leave alone keep opening their modal.
- Give every row you add an `onPress`. Only the three default sections have a modal, so a row of your own without one opens an empty modal.
- Give each row a unique `section`. It's the row's key, and the default rows use `pinned-messages`, `photos-and-videos` and `files`.
- Pass `getNavigationItems` through an override of `ChannelDetailsNavigationSection`. `ChannelDetails` renders the section without props.
- Read what the rows need, such as the channel and the icons, through hooks in the override. The function's `context` holds only `t`.

## Add a row that opens a link

A row is a `ChannelDetailsNavigationItem`: an `Icon` that receives `IconProps`, a `label`, a `section` and an optional `onPress`. This row opens the project wiki of the channel, whose URL the app keeps in the channel's custom data:

```tsx title="src/NavigationSection.tsx"
import { useCallback } from "react";
import { Linking } from "react-native";
import {
  asDynamicKey,
  ChannelDetailsNavigationSection,
  useChannelDetailsContext,
  useComponentsContext,
} from "stream-chat-react-native";
import type {
  ChannelDetailsNavigationSectionProps,
  GetChannelDetailsNavigationItems,
} from "stream-chat-react-native";

declare module "stream-chat" {
  interface CustomChannelData {
    wiki_url?: string;
  }
}

export const NavigationSection = (
  props: ChannelDetailsNavigationSectionProps,
) => {
  const { channel } = useChannelDetailsContext();
  const { icons } = useComponentsContext();
  const wikiUrl = channel.data?.custom?.wiki_url;

  const getNavigationItems = useCallback<GetChannelDetailsNavigationItems>(
    ({ context, defaultItems }) => {
      if (!wikiUrl) return defaultItems;

      return [
        ...defaultItems,
        {
          Icon: icons.Link,
          label: context.t(
            asDynamicKey("myApp.channelDetails.wiki.label"),
            "Project Wiki",
          ),
          onPress: () => void Linking.openURL(wikiUrl),
          section: "project-wiki",
        },
      ];
    },
    [icons.Link, wikiUrl],
  );

  return (
    <ChannelDetailsNavigationSection
      {...props}
      getNavigationItems={getNavigationItems}
    />
  );
};
```

<table>
  <tr>
  <td align='center' width='50%'>

![Channel details with a Project Wiki row after Files on iOS](https://getstream.io/docs-assets/images/b9cb15eaed73.png)

  </td>
  <td align='center' width='50%'>

![Channel details with a Project Wiki row after Files on Android](https://getstream.io/docs-assets/images/fcc8b08683d0.png)

  </td>
  </tr>
</table>

Channels without a wiki URL show the default rows only. The custom field lives under `channel.data.custom`, and the `declare module` block types it; see [Custom data types](https://getstream.io/chat/docs/sdk/react-native/v10/state/state-and-entities/data-conventions/#custom-data-types). `icons` comes from `useComponentsContext()`, so the row uses your icon override for `Link` when you register one, as the default rows do for theirs. Any component that receives `IconProps` works as an `Icon`.

The section builds its rows again when `getNavigationItems` changes, so `useCallback` keeps it stable between renders. `channel.data` is read while rendering here, so the row appears once the section renders with the channel's data.

## Register it

Register the override with `WithComponents` inside `ChannelDetailsContextProvider`, and declare the overrides once:

```tsx title="src/ChannelDetailsScreen.tsx"
import type { Channel as StreamChannel } from "stream-chat";
import {
  ChannelDetails,
  ChannelDetailsContextProvider,
  WithComponents,
} from "stream-chat-react-native";
import type { ComponentOverrides } from "stream-chat-react-native";
import { NavigationSection } from "./NavigationSection";

const overrides: ComponentOverrides = {
  ChannelDetailsNavigationSection: NavigationSection,
};

export const ChannelDetailsScreen = ({
  channel,
  onBack,
}: {
  channel: StreamChannel;
  onBack: () => void;
}) => (
  <ChannelDetailsContextProvider channel={channel}>
    <WithComponents overrides={overrides}>
      <ChannelDetails onBack={onBack} />
    </WithComponents>
  </ChannelDetailsContextProvider>
);
```

`ChannelDetailsScreen` takes the channel and the back handler from the screen that renders it, such as the screen on [Channel UI](https://getstream.io/chat/docs/sdk/react-native/v10/ui/components/channel-ui/#channel-details).

## Hide a row

Filter `defaultItems` by `section`. This one leaves out Pinned Messages:

```ts title="src/navigationItems.ts"
import type { GetChannelDetailsNavigationItems } from "stream-chat-react-native";

export const getNavigationItems: GetChannelDetailsNavigationItems = ({
  defaultItems,
}) => defaultItems.filter(({ section }) => section !== "pinned-messages");
```

A function that needs nothing from the component can be declared at module scope, like this one, and passed to the section in the override. Return an empty list to hide the whole section's rows. The section's card still renders, empty.

## Change the order

The section renders the rows in the order the function returns them. This one puts Files first:

```ts title="src/navigationItems.ts"
import type { GetChannelDetailsNavigationItems } from "stream-chat-react-native";

export const getNavigationItems: GetChannelDetailsNavigationItems = ({
  defaultItems,
}) => [
  ...defaultItems.filter(({ section }) => section === "files"),
  ...defaultItems.filter(({ section }) => section !== "files"),
];
```

## Change a row's label

The modal of a default row shows the row's `label` in its header, so a changed label changes both. Keep the `section`, which decides what the modal shows:

```ts title="src/navigationItems.ts"
import { asDynamicKey } from "stream-chat-react-native";
import type { GetChannelDetailsNavigationItems } from "stream-chat-react-native";

export const getNavigationItems: GetChannelDetailsNavigationItems = ({
  context,
  defaultItems,
}) =>
  defaultItems.map((item) =>
    item.section === "photos-and-videos"
      ? {
          ...item,
          label: context.t(
            asDynamicKey("myApp.channelDetails.media.label"),
            "Media",
          ),
        }
      : item,
  );
```

The default labels are translation keys of the SDK, such as `channelDetails.navigation.photosAndVideos.label`. To change the wording in every language, override the key in your translations instead; see [Localization](https://getstream.io/chat/docs/sdk/react-native/v10/ui/customizing/localization/).

---

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react-native/v10/ui-cookbook/channel-details/custom-navigation-items/](https://getstream.io/chat/docs/sdk/react-native/v10/ui-cookbook/channel-details/custom-navigation-items/).