hcengineering/platform · warning

Duplicate emoji reaction is not allowed.

Error message

Duplicate emoji reaction is not allowed.

What it means

When a DocReaction creation transaction arrives, the plugin validates that the user has not already created an identical reaction: same reactionType and same value, or for Emoji reactions the same emoji. If an equivalent existing reaction is found, the duplicate create is rejected with this error.

Source

Thrown at server-plugins/rating/src/index.ts:92

    }
    return await this.provideTx(ctx, tx)
  }

  private async validateNewReaction (ctx: MeasureContext, create: TxCreateDoc<DocReaction>): Promise<void> {
    // Should allow only one reaction per document per user for Emoji, Like, Usefull
    const current = await this.provideFindAll(ctx, rating.class.DocReaction, {
      attachedTo: create.attachedTo,
      attachedToClass: create.objectClass
    })
    if (
      current.some(
        (it) =>
          it.reactionType === create.attributes.reactionType &&
          (it.value === create.attributes.value ||
            (create.attributes.reactionType === ReactionKind.Emoji && it.emoji === create.attributes.emoji))
      )
    ) {
      throw new Error('Duplicate emoji reaction is not allowed.')
    }
  }

  private async validateReactionUpdate (ctx: MeasureContext, upd: TxUpdateDoc<DocReaction>): Promise<void> {
    if (upd.operations.reactionType !== undefined) {
      throw new Error('Modifications of reaction type are not allowed.')
    }
    // Find current reaction tried to be modified
    const current = (
      await this.provideFindAll(ctx, rating.class.DocReaction, {
        _id: upd.objectId,
        _class: upd.objectClass
      })
    ).shift()
    if (current === undefined) {
      throw new Error('Reaction not found.')
    }
    if (upd.operations.value === undefined || upd.operations.value < 0 || upd.operations.value > 10) {

View on GitHub (pinned to 63e28dc964)

Solutions

  1. Catch this error client-side and treat it as success/idempotent (the reaction already exists).
  2. Query existing DocReaction for (user, attachedTo, reactionType) before creating.
  3. Debounce/disable the reaction button until the server confirms.
  4. Make the create idempotent by checking via findAll on the plugin before submitting the tx.

Example fix

// before
await client.createDoc(rating.class.DocReaction, space, reaction)
// after
const existing = await client.findAll(rating.class.DocReaction, {
  attachedTo: docId, createdBy: me, reactionType: reaction.reactionType
})
if (existing.length === 0) {
  await client.createDoc(rating.class.DocReaction, space, reaction)
}
Defensive patterns

Strategy: try-catch

Validate before calling

const existing = await client.findAll(rating.class.DocReaction, {
  attachedTo: docId,
  createdBy: myAccountId,
  reactionType,
})
if (existing.length > 0) return // already reacted; skip create

Try / catch

try {
  await client.createDoc(rating.class.DocReaction, space, reaction)
} catch (err) {
  if (/Duplicate emoji reaction/.test(err.message)) {
    return // idempotent: reaction already recorded
  }
  throw err
}

Prevention

When it happens

Trigger: Submitting TxCreateDoc for rating.class.DocReaction when the same user already has a reaction on the same attachedTo doc with the same reactionType and value/emoji — e.g. double-clicking a like button, retrying a request that actually succeeded, or replayed transactions.

Common situations: UI not debouncing star/like clicks, optimistic-UI retries after network timeouts, offline clients syncing queued reactions twice, event/tx replays.

Related errors


AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29). Data as JSON: /api/errors/0595af80174fb338. Report an issue: GitHub.