API Reference

getActiveConflicts()

Returns every currently unresolved handshake conflict, across every store, as one flat array — the read side of the universal conflict register that drives a single developer-controlled resolution UI.

Prerequisites
  • At least one store registered with queueConfig.replayStrategy: 'handshake' — see api/register
  • core-concepts/conflict-resolution for the full handshake and register model
1

Basic usage

flux.getActiveConflicts() returns a snapshot array — every conflict currently frozen in the register, from every store, at the instant you call it.
typescript
1// A one-off snapshot of every currently unresolved conflict,
2// across every store, at the moment you call it.
3const conflicts = flux.getActiveConflicts()
4
5conflicts.forEach((conflict) => {
6 console.log(conflict.storeName, conflict.recordId)
7})
2

UnifiedConflictFrame shape

Each entry carries everything a resolution UI needs to show the user both sides of the disagreement — what they changed offline, and what actually landed on the server in the meantime.
typescript
1interface UnifiedConflictFrame {
2 storeName: string // the queue channel this conflict came from
3 recordId: string // entry.dedupeKey if set, otherwise entry.id
4 localEntry: QueueEntry // the full queued entry — payload, timestamp, endpoint, method
5 serverState: any // the current server row, as returned by revalidateFn
6}
7
8// localEntry.payload — what the user changed while offline
9// serverState — what changed on the server during that same window
10conflict.localEntry.payload
11conflict.serverState
3

A one-off pull, not a live view

getActiveConflicts() doesn't update on its own — call it again to get a fresh read, or use subscribeToConflicts() if you want to be notified as the list changes.
typescript
1// getActiveConflicts() is a pull. subscribeToConflicts() is the push
2// equivalent — call it once, get notified on every change, and stop
3// listening whenever you want via the returned unregister function.
4
5const unsubscribe = flux.subscribeToConflicts((conflicts) => {
6 setConflictPanelData(conflicts)
7})
8
9// Later, e.g. on component unmount
10unsubscribe()
4

When the list changes

The register only ever grows or shrinks in response to a handshake reconnect probe or an explicit resolution — never from a fire_and_forget store, which has no server record to conflict against in the first place.
typescript
1// The full list is recomputed and pushed on every one of these:
2
3// - a new handshake conflict is detected on reconnect -> list grows
4// - resolveUnifiedConflict() is called -> list shrinks
5// - the last conflict is resolved -> called with []
6// - clearUserSession() purges a 'user'-scoped conflict -> list shrinks
7
8// Only stores registered with queueConfig.replayStrategy: 'handshake'
9// ever produce a frame here. fire_and_forget stores never conflict —
10// there's no existing server record for them to conflict against.
Logout clears user-scoped conflicts too

clearUserSession() removes any frame belonging to a 'user'-scoped store and re-fires the update — a conflict panel driven by this array clears itself automatically on logout without extra wiring. See core-concepts/multi-tenant-scoping.

5

useFlux().activeConflicts

In flux-next / flux-react, useFlux() exposes this same array as activeConflicts, kept live internally so you don't need to wire your own subscription for a typical component.
typescript
1import { useFlux } from '@tsworldtech/flux-next'
2
3function ConflictPanel() {
4 const { activeConflicts } = useFlux()
5 // activeConflicts mirrors engine.getActiveConflicts(), kept live
6 // internally via the same subscription mechanism as subscribeToConflicts()
7
8 if (activeConflicts.length === 0) return null
9
10 return (
11 <div>
12 {activeConflicts.map((c) => (
13 <ConflictRow key={c.recordId} conflict={c} />
14 ))}
15 </div>
16 )
17}
6

Resolving what you're shown

This API only reads the register — resolving a conflict is a separate call. A typical panel renders each frame and lets the user choose which side wins.
typescript
1// getActiveConflicts() / subscribeToConflicts() give you what to show —
2// resolveUnifiedConflict() is what a resolution UI actually calls.
3
4function ConflictRow({ conflict }: { conflict: UnifiedConflictFrame }) {
5 return (
6 <div>
7 <p>Yours: {JSON.stringify(conflict.localEntry.payload)}</p>
8 <p>Server: {JSON.stringify(conflict.serverState)}</p>
9 <button onClick={() => flux.resolveUnifiedConflict(conflict.recordId, 'proceed')}>
10 Keep mine
11 </button>
12 <button onClick={() => flux.resolveUnifiedConflict(conflict.recordId, 'drop')}>
13 Keep server's
14 </button>
15 </div>
16 )
17}

For actually resolving a conflict, see api/resolve-unified-conflict. For the full two-pass handshake model that produces these frames, see core-concepts/conflict-resolution.