# Polls

> **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/).

A poll is a question with options that members of a channel vote on. This page covers creating a poll in the composer, how votes reach every client, and the components that render a poll in a message.

## What users get

The user opens the attachment picker and taps the poll button. A sheet asks for the question and the options, which they can drag to reorder. They can also allow more than one vote (with a maximum), make the voting anonymous, let others suggest options, and let them add comments. They tap the send button in the sheet's header, and the poll appears in the channel as a message.

Bob taps the circle next to an option. His vote shows at once, with the number of votes and the avatars of the latest voters next to each option, and a bar under it. Below the options, everyone sees View Results. As the poll's creator, the user also sees End Vote. Everyone sees the actions the user allowed while the poll is open, Suggest an option and Add a comment, and View 2 comments once there are comments. A poll with more than five options shows the first five and a "+3 More Options" button that opens the full list. The buttons other than End Vote open their screens in a modal.

In the channel list, and in a reply that quotes it, a poll message shows the poll's question.

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

![The Create Poll sheet with the question, the options and the poll settings on iOS](https://getstream.io/docs-assets/images/f3d5834949cc.png)

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

![The Create Poll sheet with the question, the options and the poll settings on Android](https://getstream.io/docs-assets/images/b14857bf4cc1.png)

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

## How it works across the layers

- **State.** The composer's `pollComposer` holds the poll being created. Once created, the poll is a `Poll` object kept in `client.polls`, one per poll, with its data in `poll.state`. The message only refers to it, through `message.poll_id`.
- **UI.** The poll button of the attachment picker opens `CreatePoll`. Each message with a poll renders `Poll`, which renders the question, the options and the buttons.
- **Layout.** Not involved.

### Creating a poll

1. Tapping the poll button opens the creation sheet, `CreatePoll` in a `PollModal`. Each field writes to `messageComposer.pollComposer` with `PollComposer.updateFields()`, which validates the fields as the user types: the question is required, options can't be empty or repeat each other, and the maximum number of votes is a number from 2 to 10.
2. The send button in the header is enabled when `pollComposer.canCreatePoll` is `true`.
3. Sending calls `MessageComposer.createPoll()`, which creates the poll on the server through `client.polls.createPoll()` and sets the composer's `pollId`.
4. Then the composer sends a message that carries only the poll. Text the user typed in the composer stays there for their next message.

`MessageComposer.createPoll()` publishes an `api:poll:create:failed` notification when the request fails, and throws, so nothing is sent and the sheet stays open. Closing the sheet resets `pollComposer`. [Message composer](https://getstream.io/chat/docs/sdk/react-native/v10/state/writing-messages/message-composer/#polls) shows the same two steps in code.

### Voting and keeping the poll current

`client.polls` keeps one `Poll` for each poll id. It fills from every loaded message that carries a poll, and from new ones as they arrive. Poll events (`poll.updated`, `poll.closed`, `poll.vote_casted`, `poll.vote_changed`, `poll.vote_removed`) update the matching `Poll`, so every message that shows it re-renders.

Tapping an option toggles Bob's vote: `Poll.castVote(optionId, messageId)`, or `Poll.removeVote(voteId, messageId)` when he has voted for it already. The vote shows in `poll.state` before the server answers, and is undone if the request fails. `Poll.close()` works the same way. When Bob has reached `max_votes_allowed`, `Poll.castVote()` publishes a notification instead of voting. [What's optimistic and what isn't](https://getstream.io/chat/docs/sdk/react-native/v10/state/writing-messages/whats-optimistic-and-what-isnt/) has the details.

`poll.state` holds the poll's data, such as `vote_counts_by_option`, and a few values derived from it: `ownVotesByOptionId` (Bob's votes), `ownAnswer` (his comment) and `maxVotedOptionIds` (the leading options). Read it from there, not from `message.poll`, which is only the copy that came with the message.

### Rendering a poll

`MessageContent` reads the poll with `client.polls.fromState(message.poll_id)` and renders `<Poll message={message} poll={poll} />`. `Poll` provides `PollContext` and renders `PollContent`:

- `PollHeader`: the question, and how many options can be chosen, or "Poll has ended".
- A `PollOption` for each of the first five options, with its `VoteButton`, the vote count, the latest voters and a bar. `ShowAllOptionsButton` opens the full list, `PollAllOptionsContent`, when there are more.
- `PollButtons`: `ViewResultsButton`, `EndVoteButton`, `SuggestOptionButton`, `AddCommentButton` and `ShowAllCommentsButton`. Each one other than `EndVoteButton` opens its screen in a `PollModal`: `PollResultsContent`, `PollAnswersListContent`, or a dialog for a new option or a comment. In the results, an option with more than five votes has a Show All button, which opens `PollOptionFullResultsContent`.

Each button shows only when it applies. Voting needs the `cast-poll-vote` capability and an open poll. End Vote is for the poll's creator while the poll is open. Suggest an option and Add a comment need an open poll that allows them. View Results always shows.

## Turning it on or off

Polls are on by default. Two sides decide:

- **The client:** `messageComposer.polls.enabled`, `true` by default.
- **The server:** the channel type's `polls` setting. Creating a poll also needs the user's `send-poll` capability.

To turn poll creation off in every composer:

```ts
client.config.set({
  messageComposer: { polls: { enabled: false } },
});
```

`hasCreatePoll={false}` on `Channel` hides the poll button for that channel only. Messages that already carry a poll still render it. The attachment picker offers polls only in channels, not in threads. Until the channel's server settings arrive, the button follows the client setting alone. [Configuration](https://getstream.io/chat/docs/sdk/react-native/v10/state/state-and-entities/configuration/#who-decides-what) lists every setting both sides decide.

## Customizing

### Replace part of the poll

The poll's parts are override keys: `PollContent`, which has no default, `PollHeader`, `PollButtons`, `PollAllOptionsContent`, `PollResultsContent`, `PollAnswersListContent`, `PollOptionFullResultsContent` and `CreatePollContent`. A replacement reads the poll from `usePollContext()` and the state with `usePollState()`, or subscribes to `poll.state` itself. This header shows the question and the number of votes:

```tsx
import { StyleSheet, Text, View } from "react-native";
import type { PollState } from "stream-chat";
import { usePollContext, useStateStore } from "stream-chat-react-native";
import type { ComponentOverrides } from "stream-chat-react-native";

const headerSelector = ({ name, vote_count }: PollState) => ({
  name,
  voteCount: vote_count,
});

const PollHeaderWithVotes = () => {
  const { poll } = usePollContext();
  const { name, voteCount } = useStateStore(poll.state, headerSelector);

  return (
    <View style={styles.header}>
      <Text style={styles.name}>{name}</Text>
      <Text>{voteCount} votes</Text>
    </View>
  );
};

// Pass to `WithComponents`, declared once at module scope.
export const overrides: ComponentOverrides = {
  PollHeader: PollHeaderWithVotes,
};

const styles = StyleSheet.create({
  header: { gap: 2 },
  name: { fontWeight: "600" },
});
```

### Change the buttons

`PollButtons` renders the buttons below the options. Each button is exported, and each one other than `EndVoteButton` takes `onPress`, which replaces opening its screen and receives `{ message, poll }`. This replacement keeps View Results and End Vote, and opens the results on a screen of your own:

```tsx
import { StyleSheet, View } from "react-native";
import type { LocalMessage, Poll } from "stream-chat";
import { EndVoteButton, ViewResultsButton } from "stream-chat-react-native";

export const makePollButtons =
  (openResults: (params: { message: LocalMessage; poll: Poll }) => void) =>
  () => (
    <View style={styles.buttons}>
      <ViewResultsButton onPress={openResults} />
      <EndVoteButton />
    </View>
  );

const styles = StyleSheet.create({
  buttons: { gap: 8 },
});
```

Create the component once, for example with your navigation's `navigate` call, and register it as the `PollButtons` override. `ShowAllOptionsButton`, `SuggestOptionButton`, `AddCommentButton` and `ShowAllCommentsButton` take `onPress` the same way. A screen of your own renders the SDK's `PollResults` with the `message` and `poll` it receives, inside the same `Channel` so its contexts are there. The [Custom poll flow UI cookbook](https://getstream.io/chat/docs/sdk/react-native/v10/ui-cookbook/app/custom-poll-flow/) moves every poll screen into a navigation stack.

### Create polls from your own UI

`openPollCreationDialog` on `Channel` replaces the creation sheet. The poll button calls it with `{ sendMessage }`. Your UI fills the composer's `pollComposer`, creates the poll, and sends it. `MessageComposer.createPoll()` throws when the request fails, and returns without creating a poll when the form isn't valid, so send only once `pollId` is set. Otherwise `MessageComposer.send()` sends whatever text is in the composer, without the poll:

```ts
import type { MessageComposer } from "stream-chat";

export const createAndSendPoll = async (
  composer: MessageComposer,
  question: string,
  answers: string[],
) => {
  await composer.pollComposer.updateFields({
    data: {
      name: question,
      options: answers.map((text, index) => ({ id: `option-${index}`, text })),
    },
  });
  if (!composer.pollComposer.canCreatePoll) return false;

  try {
    await composer.createPoll();
  } catch {
    // The composer has published `api:poll:create:failed`.
    return false;
  }
  if (!composer.pollId) return false;

  await composer.send();
  return true;
};
```

Pass it the composer the poll button belongs to: `channel.messageComposer`, or `useMessageComposer()` inside the channel's screen. To keep the SDK's sheet and change only its form, override `CreatePollContent`. `useCreatePollContentContext()` gives it `createAndSendPoll()` and `closePollCreationDialog()`. Of the default form's parts, the SDK exports `CreatePollOptions` and `MultipleVotesSettings`, and the question field and the header aren't exported.

### Validate the fields

The poll composer's state middleware checks the question, the options and the maximum number of votes as the user types and leaves each field. To add a check of your own or change a built-in one, replace that middleware with one built by `createPollComposerStateMiddleware({ processors, validators })`, as [Poll validators and processors](https://getstream.io/chat/docs/sdk/react-native/v10/state/writing-messages/composer-middleware/#poll-validators-and-processors) shows.

### Text and translations

The poll's copy has keys under `poll`, such as `poll.viewResults.label` and `poll.endVote.label`, and the creation sheet's under `poll.createPoll`. Validation errors are keyed by their `POLL_COMPOSER_VALIDATION_CODE`. See [Localization](https://getstream.io/chat/docs/sdk/react-native/v10/ui/customizing/localization/).

## Reference

- [Poll components](https://getstream.io/chat/docs/sdk/react-native/v10/component-reference/poll-components/): `Poll`, its parts, the creation sheet and `PollContext`.
- [Poll hooks](https://getstream.io/chat/docs/sdk/react-native/v10/reference/hooks/poll-hooks/): `usePollContext`, `usePollState` and the other poll hooks.
- [MessageComposer API](https://getstream.io/chat/docs/sdk/react-native/v10/reference/state/messagecomposer-api/): `pollComposer`, `createPoll()` and `send()`.
- [Configuration tree](https://getstream.io/chat/docs/sdk/react-native/v10/reference/state/configuration-tree/): `messageComposer.polls`.
- [Attachments and uploads](https://getstream.io/chat/docs/sdk/react-native/v10/features/rich-content/attachments-and-uploads/): the attachment picker that opens the creation sheet.
- [Custom poll flow](https://getstream.io/chat/docs/sdk/react-native/v10/ui-cookbook/app/custom-poll-flow/): the poll's screens in a navigation stack.

---

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react-native/v10/features/rich-content/polls/](https://getstream.io/chat/docs/sdk/react-native/v10/features/rich-content/polls/).