Poll components
Poll renders the poll of a message. It provides PollContext and renders the PollContent override: the question, the options and the actions. The default MessageUI renders it for every message with a poll_id, with the poll from client.polls.fromState(message.poll_id). Polls explains how polls are created, voted on and kept current.
Best practices
- Read live poll data from
poll.state.message.pollis only the copy that came with the message. - Replace parts through the override keys below, and keep
Poll, which provides the context. - Render poll parts inside a message, in a
Channelor aThread.PollActionsandPollOptionSelectorread the message fromuseMessageContext()and the user's capabilities from the channel. - Keep option texts short, so the message stays readable.
- A quoted poll has no poll UI. The quoted message shows a poll icon and, when it has no text, the poll's name.
Poll UI customization
Poll is built from these parts. The ones with an override key are replaced through WithComponents; the others through the props of the part that renders them.
| Part | Override key | Rendered by | Shows |
|---|---|---|---|
PollContent |
PollContent |
Poll |
The whole poll: PollHeader, PollOptionList and PollActions |
PollHeader |
PollHeader |
PollContent |
The question and how many options can be chosen, or "Vote ended" |
PollOptionList |
none | PollContent |
A PollOptionSelector per option, and a button for the full list |
PollOptionSelector |
PollOptionSelector |
PollOptionList |
One option: a checkbox while the user can vote, the text, the votes and a bar |
PollOptionsFullList |
none | PollOptionList |
Every option, in a dialog |
PollActions |
PollActions |
PollContent |
The buttons below the options and the dialogs they open |
PollCreationDialog |
PollCreationDialog |
AttachmentSelector |
The form that creates a poll; see Attachment Selector |
PollContent shows five options, and a button that opens the full list when there are more. Each PollActions button shows only when it applies:
| Button | Shows when | Opens |
|---|---|---|
| End Poll | The poll is open and the user created it | EndPollAlert |
| View Results | The poll has votes | PollResults |
| Suggest an Option | The user can vote (cast-poll-vote, poll open), suggestions are allowed, and the poll has fewer than 100 options |
SuggestPollOptionForm |
| Add a Comment, or Update Your Comment | The poll is open and allows comments | AddCommentPrompt |
| View Comments | The poll has comments and the user has query-poll-votes |
PollAnswerList |
Replace a dialog
PollActions takes the dialogs it opens as props. Wrap it to replace one and keep the others, and register the wrapper as the PollActions override. This one replaces the form that suggests an option:
import { useState } from "react";
import type { FormEvent, PropsWithChildren } from "react";
import {
PollActions,
useModalContext,
usePollContext,
WithComponents,
} from "stream-chat-react";
const SuggestOptionForm = () => {
const { poll } = usePollContext();
const { close } = useModalContext();
const [text, setText] = useState("");
const submit = async (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
try {
await poll.createOption({ text: text.trim() });
close();
} catch {
// The option wasn't added. Show the error in the form here.
}
};
return (
<form className="suggest-option-form" onSubmit={submit}>
<input
aria-label="Option"
onChange={(event) => setText(event.target.value)}
value={text}
/>
<button disabled={!text.trim()} type="submit">
Suggest
</button>
</form>
);
};
const PollActionsWithOwnSuggestion = () => (
<PollActions SuggestPollOptionForm={SuggestOptionForm} />
);
const overrides = { PollActions: PollActionsWithOwnSuggestion };
export const WithOwnPollSuggestions = ({ children }: PropsWithChildren) => (
<WithComponents overrides={overrides}>{children}</WithComponents>
);The dialog's frame stays the SDK's, and useModalContext() closes it. Polls replaces EndPollAlert the same way.
Change the layout
Override PollContent and arrange the parts. PollOptionList takes the full-list dialog as PollOptionsFullList:
import type { PollState } from "stream-chat";
import {
PollActions,
PollHeader,
PollOptionList,
usePollContext,
useStateStore,
} from "stream-chat-react";
const optionsSelector = ({ options }: PollState) => ({ options });
const OptionTexts = () => {
const { poll } = usePollContext();
const { options } = useStateStore(poll.state, optionsSelector);
return (
<ul>
{options.map((option) => (
<li key={option.id}>{option.text}</li>
))}
</ul>
);
};
// Register as the PollContent override.
export const PollContentWithThreeOptions = () => (
<div className="str-chat__poll">
<PollHeader />
<PollOptionList optionsDisplayCount={3} PollOptionsFullList={OptionTexts} />
<PollActions />
</div>
);Props
Poll
| Prop | Type | Default | Description |
|---|---|---|---|
poll |
Poll |
required | The poll, a Poll from stream-chat. Without one, nothing renders. |
PollContent, PollHeader, PollOptionsFullList, PollResults and EndPollAlert take no props; they read the poll from PollContext.
PollOptionList
| Prop | Type | Default | Description |
|---|---|---|---|
optionsDisplayCount |
number |
every option | How many options to show. With more options, a button opens PollOptionsFullList. PollContent passes 5. |
PollOptionsFullList |
React.ComponentType |
PollOptionsFullList |
The dialog content that lists every option. |
PollOptionSelector
| Prop | Type | Default | Description |
|---|---|---|---|
option |
PollOptionResponseData |
required | The option. |
displayAvatarCount |
number |
none | How many avatars of the latest voters to show. They show only when votes are public. PollOptionList passes 3. |
voteCountVerbose |
boolean |
false |
Shows "2 votes" instead of "2". |
Clicking the option, or pressing Enter or Space on it, casts or removes the user's vote.
PollActions
| Prop | Type | Default | Description |
|---|---|---|---|
AddCommentPrompt |
React.ComponentType<AddCommentPromptProps> |
AddCommentPrompt |
Adds or updates the user's comment. |
EndPollAlert |
React.ComponentType |
EndPollAlert |
Confirms ending the poll. |
PollAnswerList |
React.ComponentType<PollAnswerListProps> |
PollAnswerList |
Lists the comments. |
PollResults |
React.ComponentType |
PollResults |
Shows the results by option. |
SuggestPollOptionForm |
React.ComponentType<SuggestPollOptionFormProps> |
SuggestPollOptionPrompt |
Suggests a new option. |
The dialogs' own props:
| Component | Props |
|---|---|
AddCommentPrompt |
messageId: string, the message that carries the poll |
PollAnswerList |
onUpdateOwnAnswerClick: () => void, which switches to AddCommentPrompt |
PollCreationDialog |
close: () => void, which closes the dialog |
PollVote |
vote: PollVoteResponseData, one vote with its author and time |
PollVoteListing |
votes: PollVoteResponseData[] |
Poll API and state
poll.state is a store of PollState: the poll's data from the server, such as name, options, vote_counts_by_option, latest_votes_by_option, is_closed and vote_count, plus four values the client derives:
ownVotesByOptionId: the user's votes, by option id;ownAnswer: the user's comment;maxVotedOptionIds: the options with the most votes;lastActivityAt: when the client last updated the poll from the server, in Unix nanoseconds.
The Poll instance has the methods query, update, partialUpdate, close, delete, createOption, updateOption, deleteOption, castVote, removeVote, addAnswer, removeAnswer, queryAnswers and queryOptionVotes. castVote, removeVote and close update poll.state before the server answers; see Voting and keeping the poll current.
Don't read votes from MessageContext's message.poll. Read poll.state.
PollContext
Poll provides PollContext to its parts. usePollContext() reads it, and throws outside a Poll.
| Value | Type | Description |
|---|---|---|
poll |
Poll |
The Poll instance from client.polls. |
PollProvider is exported too. It takes poll and renders nothing without one.
Examples
Show the user's votes
import type { PollState } from "stream-chat";
import { usePollContext, useStateStore } from "stream-chat-react";
const ownVotesSelector = ({ options, ownVotesByOptionId }: PollState) => ({
options,
ownVotesByOptionId,
});
export const MyVotes = () => {
const { poll } = usePollContext();
const { options, ownVotesByOptionId } = useStateStore(
poll.state,
ownVotesSelector,
);
const chosen = options.filter((option) => ownVotesByOptionId[option.id]);
if (!chosen.length) return <span>You haven't voted yet.</span>;
return (
<span>You voted for {chosen.map(({ text }) => text).join(", ")}.</span>
);
};Render it inside a poll part, for example in your own PollContent.
Related hooks
| Hook | Returns |
|---|---|
usePollContext() |
{ poll }; throws outside a Poll |
usePollAnswerPagination({ paginationParams? }) |
the poll's comments, a page at a time |
usePollOptionVotesPagination({ paginationParams }) |
the votes for one option, a page at a time |
useCanCreatePoll() |
whether the composer's poll form can be sent |
See Poll hooks and useCanCreatePoll.