useOutcomesState
The useOutcomesState hook is used for maintain updated states for outcomes.
Outcomes within a condition have a state field that indicates their current state. For instance, OutcomeState.Active signifies the outcome is available for betting, while OutcomeState.Stopped indicates it has temporarily stopped accepting bets. This hook monitors this state and returns an isLocked flag, indicating whether placing a bet on this outcome is currently possible or not.
Usage
Before utilizing useOutcomesState, it is essential to initialize the FeedSocketProvider and ConditionUpdatesProvider:
import {
ChainProvider,
FeedSocketProvider,
ConditionUpdatesProvider,
} from '@azuro-org/sdk'
import { polygonAmoy } from 'viem/chains'
function Providers(props: { children: React.ReactNode }) {
const { children } = props
return (
<ChainProvider initialChainId={polygonAmoy.id}>
<FeedSocketProvider>
<ConditionUpdatesProvider>
{children}
</ConditionUpdatesProvider>
</FeedSocketProvider>
</ChainProvider>
)
}Example of usage: Market outcomes list (Option A).
Pass the full outcomes array to get state and hidden without an extra fetch.
import { useOutcomesState } from '@azuro-org/sdk'
// outcomes: MarketOutcome[] from a market condition
const { data: states, outcomesMap } = useOutcomesState({ outcomes })
// drop the outcomes the feed isn't offering
const visibleOutcomes = outcomes.filter(({ conditionId, outcomeId }) => (
!outcomesMap[`${conditionId}-${outcomeId}`]?.hidden
))Pass every outcome of the condition, hidden ones included, and filter afterwards, as above. The feed sends no updates for a condition that was never subscribed, so an outcome you filter out before subscribing can never be shown again.
useActiveConditions and
useActiveMarkets already do exactly this, at both levels.
Do it by hand only if you need a grid they don’t cover.
Example of usage: Betslip (Option B).
import { useOutcomesState } from '@azuro-org/sdk'
import { OutcomeState } from '@azuro-org/toolkit'
import { useMemo } from 'react'
const items = [{...}]
const { data: states, isFetching: isStatesFetching } = useOutcomesState({
selections: items.map(({ conditionId, outcomeId }) => ({ conditionId, outcomeId })),
})
const isOutcomesInActiveState = useMemo(() => {
return Object.values(states).every(state => state === OutcomeState.Active)
}, [ states ])Props
Two signatures are supported:
Option A — pass full outcome objects (preferred for market outcomes):
{
outcomes: Pick<MarketOutcome, 'conditionId' | 'outcomeId' | 'odds' | 'state' | 'hidden'>[]
}Option B — pass selections only (betslip / ID-only scenarios):
{
selections: Selection[] // { conditionId: string; outcomeId: string }[]
initialStates?: Record<string, OutcomeState> // key is `${conditionId}-${outcomeId}`
}Option A is preferred when rendering market outcomes — it provides initial state and hidden without an extra fetch.
When using Option B, initialStates is optional. If it’s not provided, the hook will automatically fetch the initial states.
enum OutcomeState {
Active = 'Active',
Canceled = 'Canceled',
Stopped = 'Stopped',
Won = 'Won',
Lost = 'Lost'
}OutcomeState.Canceled means voided — not “missing from the feed”. A canceled outcome is one the
protocol voided: bets on it are refunded. When an outcome is absent from the feed, this hook falls back
to OutcomeState.Stopped (it used to fall back to OutcomeState.Canceled). Use
isOutcomeSettled to ask whether an outcome has a final
result — Won, Lost or Canceled — rather than comparing states by hand.
Return Value
{
data: Record<string, OutcomeState> // key is `${conditionId}-${outcomeId}`
outcomesMap: Record<string, OutcomeStateData> // key is `${conditionId}-${outcomeId}`
isFetching: boolean // flag indicates initial states fetching
}An outcomesMap entry is the hook’s own view of an outcome, not the raw socket message: it holds what is
currently known about the outcome after the rules below have been applied. That entry type is exported as
OutcomeStateData, so you can name it in your own code. The wire message is a separate type,
OutcomeUpdateData, and it carries one extra field — conditionState, the state of the condition the
message arrived on, which is what decides how much of the message can be trusted.
import { type OutcomeStateData, type OutcomeUpdateData } from '@azuro-org/sdk'
// what an `outcomesMap` entry holds
type OutcomeStateData = {
odds: number // live odds
turnover: string // empty ('') until a live update arrives — not returned by the state endpoint
/** `undefined` until an update that can be trusted for it, or a state read, has reported it */
state?: OutcomeState
/** `undefined` until the feed has reported it */
hidden?: boolean
}
// the socket message
type OutcomeUpdateData = {
odds: number
turnover: string
state: OutcomeState
hidden: boolean
/** state of the condition this update was carried by - not the outcome's state */
conditionState: ConditionState
}What is taken from an update
odds and turnover are taken from every update. They are real in every message. turnover is only
ever populated by live updates ('' until the first one arrives — the state endpoint doesn’t return it).
state and hidden are taken only from updates whose condition is Active. An update for an
inactive condition reports every one of its outcomes as Stopped, whatever they had actually settled
to — so trusting it would un-settle won, lost and voided outcomes each time their condition is suspended.
Such an update leaves state and hidden alone and schedules a re-read from the state endpoint, which is
authoritative for per-outcome state. The re-read is limited to the messages that can carry news: the move
into an inactive state, and every report of a settled condition.
The consequence to design for: when a condition stops, there is a brief window — one state-endpoint
round trip — in which its outcomes still read their previous state. Condition-level locking is
unaffected and still immediate, so gate the bet on
useConditionState /
useConditionsState as well as on the outcome, and
don’t treat a per-outcome Stopped as instantaneous.
How hidden behaves
hidden says whether the feed is offering this outcome right now, independently of its condition’s own
hidden flag — a condition can be on offer with one of its outcomes withdrawn.
It is latched one way, by design. Once an outcome has been reported visible it stays visible, and
nothing hides it again, so an outcome doesn’t flicker in and out of a market as its condition is suspended
and re-priced. Render the lock from state instead.
hidden is boolean | undefined, and undefined means the feed has not reported it yet — not
“visible” and not “already revealed”. Only an explicit false closes the latch; if undefined counted
as revealed, the latch would close before the feed had said anything. In the selections-only signature
initialStates carries no visibility, so hidden stays undefined only for as long as initialStates
supplies a state for every selection. As soon as one is missing, the hook reads the state endpoint —
which does report per-outcome visibility — and latches what it says.
!outcomesMap[key]?.hidden is the right test for “show it” and treats undefined as visible.
The SDK owns this latch now. If your app keeps its own one-way isHidden latch on top of this hook
to stop outcomes flickering, drop it — re-hiding no longer happens.
When the watched set changes (the feed adds conditions to a running game routinely), outcomes that stayed keep what the socket has already taught the hook — both the latched visibility and the settled states — and only outcomes that left are dropped.