BricqsBricqs

Headless SDK: streaks

useStreak returns the live streak count, freeze inventory, grace status, and the next milestone. Wire one tick event a day; the SDK renders the rest.

Reading time7 minutes
Last updatedMay 2026

Key takeaways

Quick read
  • useStreak({ streakId }) returns count, freezesAvailable, gracePeriodEndsAt, and nextMilestone.
  • Tick the streak server-side. Sending the event from the client risks losing days when the user is offline.
  • spendFreeze is an imperative call. Use it when the user explicitly opts to use one.
  • Listen with onMilestoneHit for celebration moments at 7, 30, 100 days.
  • Always render the freeze count if available. Hidden inventory undermines the safety net.

Render the streak

One hook, full state

components/StreakWidget.tsx·tsx
"use client";
import { useStreak } from "@bricqs/headless-react";

export function StreakWidget() {
  const {
    count,
    freezesAvailable,
    gracePeriodEndsAt,
    nextMilestone,
    isLoading,
    spendFreeze,
  } = useStreak({ streakId: "daily_practice" });

  if (isLoading) return null;

  return (
    <section className="rounded-xl border p-5">
      <header className="flex items-baseline justify-between mb-2">
        <h3 className="font-bold text-2xl">{count} day streak</h3>
        {freezesAvailable > 0 && (
          <span className="text-sm text-slate-500">
            {freezesAvailable} freeze{freezesAvailable === 1 ? "" : "s"} left
          </span>
        )}
      </header>
      {gracePeriodEndsAt && (
        <p className="text-amber-600 text-sm">
          You missed yesterday. Grace period ends {gracePeriodEndsAt}. Use a freeze?
          <button onClick={() => spendFreeze()} className="ml-2 underline">
            Use freeze
          </button>
        </p>
      )}
      {nextMilestone && (
        <p className="text-sm text-slate-500 mt-3">
          Next milestone: {nextMilestone.days} days · earns {nextMilestone.rewardLabel}
        </p>
      )}
    </section>
  );
}

Tick the streak

One event a day, server-side

Always tick from the server. Client-side ticks lose days when the user is offline.

server-side: when the user does the qualifying action·ts
import { emitToBricqs } from "@/lib/bricqs";

export async function logPractice(userId: string) {
  await savePractice(userId);

  // One tick per day. Idempotent: same key means same day.
  const today = new Date().toISOString().slice(0, 10); // YYYY-MM-DD
  await emitToBricqs(
    userId,
    "streak_tick",
    { streak_id: "daily_practice" },
    `p_${userId}:streak_tick:daily_practice:${today}`
  );
}

Milestones

Celebrate the long runs

tsx
const { onMilestoneHit } = useStreak({ streakId: "daily_practice" });

onMilestoneHit((event) => {
  // event = { days: 7, rewardId: "..." }
  showCelebration(`${event.days}-day streak earned!`);
  fireConfetti();
});

Common mistakes

What breaks streaks

01Mistake

Ticking from the client. Offline users lose days; the streak appears broken.

Fix

Always tick from the server when the qualifying action lands. Use idempotent keys so retries are safe.

02Mistake

Hiding the freeze count. The safety net feels punitive when invisible.

Fix

Always show freezesAvailable. The user only uses the freeze if they know it exists.

03Mistake

Allowing infinite freeze accumulation. The streak loses meaning.

Fix

Cap freezes server-side (configured at the streak definition). The SDK reflects the cap; do not implement client-side caps.

04Mistake

Showing 'You broke your streak!' immediately on miss. Users uninstall in anger.

Fix

Use gracePeriodEndsAt. Surface the freeze option; only show 'broken' after the grace expires.

Developer FAQ

Common questions when integrating gamification with Bricqs.

Ready to ship?

Wire it up with the Bricqs SDK or API

Headless SDK for React UIs, REST API for any backend. Same engine behind both.

1 brief to align the room2 mechanics max in version one
What happens next
01
Pick the mechanic
Choose the smallest working system for the brief.
02
Launch without rebuilds
Configure rules and rewards in one place.