# Polls

A poll is a question with options that the channel's members vote on. It's created on the server first, and then its id is attached to the sent message. While the user fills in the poll form, the poll composer, `messageComposer.pollComposer`, holds the form and checks each field.

>
> **Info:** **Example**
>
> The user has typed "One more thing" in the channel's message input but hasn't sent it. The text is in the channel composer's text composer, `messageComposer.textComposer`. Then they open the poll form, which sits apart from the message input, for example in a dialog. Opening it calls `PollComposer.initState()`, so the form starts empty:
>
> 1. In the form, they type the question "Lunch?" and the options "Pizza" and "Sushi". Each change goes into the poll composer through `PollComposer.updateFields()`. The text in the message input doesn't change. Once the question and an option are filled in, `pollComposer.canCreatePoll` becomes `true`.
> 2. They press Create in the form, and the UI layer makes two calls. `MessageComposer.createPoll()` creates the poll on the server and stores its id in `messageComposer.pollId`. Then `MessageComposer.send()` sends a message that carries only the poll.
> 3. After the send, `messageComposer.pollId` is empty again. "One more thing" stays in the message input, ready to send as the next message.
>

## What it holds

The poll composer's state is in `pollComposer.state`. It has two fields: `data` (stores the form's state), and `errors`, the problems found in it.

`data` holds the poll's fields:

| Field                          | Starts as        | What it holds                                                                                                                                |
| ------------------------------ | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`                         | `''`             | The question. A poll can't be created without one.                                                                                           |
| `description`                  | `''`             | More about the question.                                                                                                                     |
| `options`                      | one empty option | The options, each `{ id, text }`. The last one is kept empty, for the user to type the next option into.                                     |
| `enforce_unique_vote`          | `true`           | Each member votes for one option only.                                                                                                       |
| `max_votes_allowed`            | `''`             | How many options a member can vote for, as a string from `'2'` to `'10'`. `''` means no limit. Used only while `enforce_unique_vote` is off. |
| `allow_answers`                | `false`          | Members can comment on the poll, one comment each, which is called an answer. Answers don't count as votes.                                  |
| `allow_user_suggested_options` | `false`          | Members can add options.                                                                                                                     |
| `voting_visibility`            | `'public'`       | `'anonymous'` hides who voted for what.                                                                                                      |
| `id`                           | a new id         | The id the poll gets on the server.                                                                                                          |

Each field has a getter that reads its current value, such as `pollComposer.name` or `pollComposer.options`. `pollComposer.canCreatePoll` says whether the form is complete: the question is set, at least one option has text, `max_votes_allowed` is empty or a number from 2 to 10, and `pollComposer.state.errors` holds no [validation error](#validation-errors).

## Filling in the form

Each change passes the form through the **poll middleware**: a pipeline of functions that processes the changed fields and checks them, as [Composer middleware](https://getstream.io/chat/docs/sdk/react/state/writing-messages/composer-middleware/#polls) explains.

What a poll form needs, and the call for each (the last column says which middleware handler a call runs):

| To…                                   | Call                                                                 | What happens                                                                                                                                                                                | Poll middleware     |
| ------------------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------- |
| change fields as the user types       | `PollComposer.updateFields({ data })`                                | Updates the fields in `data` and checks them, as below.                                                                                                                                     | `handleFieldChange` |
| change the text of one option         | `PollComposer.updateFields({ data: { options: { index, text } } })`  | Changes the option at `index`. An option emptied before the last one is removed. A new empty option is added at the end when none is empty, up to 100 options.                              | `handleFieldChange` |
| reorder the options                   | `PollComposer.updateFields({ data: { options } })`                   | Replaces the options with the array, in its order.                                                                                                                                          | `handleFieldChange` |
| check a field once the user leaves it | `PollComposer.handleFieldBlur(field)`, from the field's blur handler | Adds the errors that only make sense once the user is done with the field, such as an empty question, to `pollComposer.state.errors`. A form of your own calls it when a field loses focus. | `handleFieldBlur`   |
| start a new poll                      | `PollComposer.initState()`                                           | Resets the form to its starting values. Sending a poll doesn't reset the form, so call it when a new poll form opens.                                                                       | none                |

```ts
const { pollComposer } = channel.messageComposer;

await pollComposer.updateFields({ data: { name: "Lunch?" } });
await pollComposer.updateFields({
  data: { options: { index: 0, text: "Pizza" } },
});
```

While processing a change, the built-in middleware keeps the form consistent: changing `enforce_unique_vote` empties `max_votes_allowed`, and `max_votes_allowed` is turned into a whole number from 2 to 10, or `''` when it isn't a number.

`PollComposer.updateFields()` also takes `injectedFieldErrors`, errors of your own, for example from a check performed by your own logic. They win over the errors the middleware finds.

For example, to reject questions that your own check doesn't allow:

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

const { pollComposer } = channel.messageComposer;

// Your own check, for example a request to your server.
const isQuestionAllowed = async (name: string) => !name.includes("spam");

const changeQuestion = async (name: string) => {
  const allowed = await isQuestionAllowed(name);

  await pollComposer.updateFields({
    data: { name },
    injectedFieldErrors: {
      name: allowed
        ? undefined
        : {
            // `code` takes only the built-in codes, so a code of your own needs a cast.
            code: "myapp:poll:name:notAllowed" as PollComposerValidationCode,
            message: "This question isn't allowed",
          },
    },
  });
};
```

The error is saved to `pollComposer.state.errors.name`, and `pollComposer.canCreatePoll` stays `false` while it's there. Passing `undefined` for a field clears your error.

## Validation errors

While the user fills in the form, the poll composer saves field errors in `pollComposer.state.errors`, for your UI to show next to the fields. While any error is there, `pollComposer.canCreatePoll` is `false`.

Subscribe to `pollComposer.state` to show the errors as they change:

```ts
const { pollComposer } = channel.messageComposer;

const unsubscribe = pollComposer.state.subscribeWithSelector(
  ({ errors }) => ({ errors }),
  ({ errors }) => {
    // The question's error, or undefined when the question is fine.
    if (errors.name) {
      console.log(errors.name.code, errors.name.message);
    }

    // One error per option that has a problem, keyed by the option's id.
    Object.entries(errors.options ?? {}).forEach(([optionId, error]) => {
      console.log(optionId, error.code, error.message);
    });
  },
);
```

For example, after the user leaves the question empty and types "Pizza" into two options, `pollComposer.state.errors` is:

```json
{
  "name": {
    "code": "validation:poll:name:required",
    "message": "Question is required"
  },
  "options": {
    "<id of the second Pizza option>": {
      "code": "validation:poll:option:duplicate",
      "message": "Option already exists"
    }
  }
}
```

`code` says what's wrong: translate it. `message` is in English, to show when you have no translation. [Polls](https://getstream.io/chat/docs/sdk/react/features/rich-content/polls/#creating-a-poll) shows how the React SDK displays them.

The table lists every `code`, and where to read it:

| `code`                                        | Read from                   | Found when                                         | Means                                                   |
| --------------------------------------------- | --------------------------- | -------------------------------------------------- | ------------------------------------------------------- |
| `validation:poll:name:required`               | `errors.name`               | the user leaves the question                       | The question is empty.                                  |
| `validation:poll:option:duplicate`            | `errors.options[option.id]` | an option changes, or the user leaves the options  | The option has the same text as an option before it.    |
| `validation:poll:option:empty`                | `errors.options[option.id]` | the user leaves the options                        | An option other than the last one is empty.             |
| `validation:poll:maxVotes:uniqueVoteEnforced` | `errors.max_votes_allowed`  | `max_votes_allowed` changes                        | A vote limit was set while `enforce_unique_vote` is on. |
| `validation:poll:maxVotes:notNumeric`         | `errors.max_votes_allowed`  | `max_votes_allowed` changes                        | The limit isn't a number.                               |
| `validation:poll:maxVotes:outOfRange`         | `errors.max_votes_allowed`  | `max_votes_allowed` changes, or the user leaves it | The limit isn't from 2 to 10.                           |

The built-in processing already turns `max_votes_allowed` into a number from 2 to 10, so the last two appear only with processors of your own.

## Creating and sending the poll

Creating the poll takes two steps:

```ts
const composer = channel.messageComposer;

await composer.createPoll(); // creates the poll on the server and sets composer.pollId
await composer.send(); // sends the message that carries the poll
```

`MessageComposer.createPoll()` does nothing while `pollComposer.canCreatePoll` is `false`. Otherwise it sends the form, without its empty options, to the server and stores the new poll's id in `messageComposer.pollId`. When the request fails, it publishes an `api:poll:create:failed` notification and throws.

While `messageComposer.pollId` is set, the composer has something to send. In a channel composer, `MessageComposer.send()` then sends a message that carries only the poll: no text, attachments, mentions, quoted message or custom data. The text, attachments and quoted message stay in the composer, ready to send as the next message, as [Messages with a poll](https://getstream.io/chat/docs/sdk/react/state/writing-messages/composer-middleware/#messages-with-a-poll) explains. After the send, `messageComposer.pollId` is cleared.

To send the poll together with the text and attachments, remove the poll-only middleware. Its id is exported from `stream-chat` as `POLL_ONLY_COMPOSITION_MIDDLEWARE_ID`:

```ts
import { POLL_ONLY_COMPOSITION_MIDDLEWARE_ID } from "stream-chat";

client.config.setSetupFunction("messageComposer", ({ composer }) => {
  // A poll then goes out with the text and attachments, and the composer clears after the send.
  composer.compositionMiddlewareExecutor.remove(
    POLL_ONLY_COMPOSITION_MIDDLEWARE_ID,
  );
});
```

The setup function runs for every composer, existing and new, so this removes the poll-only middleware from all of them.

[Poll with text and attachments](https://getstream.io/chat/docs/sdk/react/ui-cookbooks/composer/poll-with-text-and-attachments/) builds the UI for it.

A [draft](https://getstream.io/chat/docs/sdk/react/state/writing-messages/message-composer/#drafts) keeps `messageComposer.pollId`, so a poll that was created but not sent comes back with the draft. The form in `pollComposer.state` isn't saved in it.

## When polls are on

Polls are on while `messageComposer.config.polls.enabled` is `true`. That's when both the `polls.enabled` setting in `client.config` and the channel type's `polls` setting are on. The composer doesn't check it when it creates or sends a poll: the UI reads it to decide whether to offer polls.

## Settings

Set them for every composer in `client.config`, under `messageComposer.polls`:

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

The poll composer has no setters. To change a setting for one composer only, patch that composer's configuration, for example `channel.messageComposer.updateConfig({ polls: { enabled: false } })`.

| Setting   | Default | What it does                                                               |
| --------- | ------- | -------------------------------------------------------------------------- |
| `enabled` | `true`  | `false` turns off polls. Combined with the channel type's `polls` setting. |

See [Polls](https://getstream.io/chat/docs/sdk/react/features/rich-content/polls/) for the feature, including voting.

## Used in

- [Polls](https://getstream.io/chat/docs/sdk/react/features/rich-content/polls/)

---

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react/state/writing-messages/polls/](https://getstream.io/chat/docs/sdk/react/state/writing-messages/polls/).