Official agent skill

Rxjs Like A Pro

by sanity-io in sanity-io/sanity

How to write idiomatic, efficient RxJS code. An agent skill from sanity-io/sanity.

OfficialMITAuto-check passedDevelopment

Install Rxjs Like A Pro

skills CLI
$ npx skills add sanity-io/sanity --skill rxjs-like-a-pro -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install sanity-io/sanity rxjs-like-a-pro --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/sanity-io/sanity.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.agents/skills/rxjs-like-a-pro .claude/skills/rxjs-like-a-pro && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
rxjs-like-a-pro
GitHub stars
6.4k
Token cost
~3k tokens
SKILL.md length
1,063 words
Files
5 (incl. references)
Skills in repo
34
Repo updated
First seen
Licence
MIT

At a glance

How to write idiomatic, efficient RxJS code. An agent skill from sanity-io/sanity.

  • Works in 3 steps: Don't subscribe at all — let the… → Use operators that complete naturally —… → Use takeUntil with a notifier
  • The user is writing
  • SKILL.md covers Reference files, The #1 Anti-pattern: Premature…, The Massive new Observable()… and Choosing the Right Flattening…, plus 9 more sections
  • Instructions only: no scripts, shell commands, URLs or credentials in SKILL.md

What it does

Rxjs Like A Pro is an agent skill from sanity-io/sanity, published by the product's own GitHub organization. How to write idiomatic, efficient RxJS code. Use this skill whenever the user is writing, refactoring, reviewing, or debugging code that uses RxJS — including any file that imports from 'rxjs' or 'rxjs/operators'. Trigger on mentions of observables, subscriptions, RxJS operators, or reactive streams. Even if the user doesn't say "RxJS" explicitly, activate when you see patterns like .pipe(), .subscribe(), Observable, Subject, BehaviorSubject, switchMap, mergeMap, or similar.

Its SKILL.md is about 3k tokens, which your agent loads only when the skill is triggered. The skill folder holds 5 other files, including reference files (for example `references/custom-operators.md`, `references/inner-observable-chains.md` and `references/loading-state-patterns.md`).

It sits in Development, covering Refactoring. It works with RxJS. The repository describes itself as: Sanity Studio – Rapidly configure content workspaces powered by structured content. The licence is MIT.

When your agent uses it

  • The user is writing
  • Debugging code that uses RxJS — including any file that imports from rxjs
  • Mentions of observables
  • Reactive streams

Example prompts

  • “rxjs/operators”
  • “t say”
  • “/rxjs-like-a-pro”

Workflow steps

3 steps, taken from the first numbered list in SKILL.md.

  1. Don't subscribe at all — let the framework handle subscription lifecycle where possible
  2. Use operators that complete naturally — first(), take(n), takeUntil(destroy$)
  3. Use takeUntil with a notifier

What it can do on your machine

Read from SKILL.md and the folder at commit efa15fb. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    No scripts in the folder and no shell commands in SKILL.md (its code samples are typescript).

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    No URLs in SKILL.md.

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Rxjs Like A Pro loads about 3k tokens when it runs, and up to ~5.9k if it reads all its reference files. Until then it costs about 127 tokens; SKILL.md has 1,063 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~127
When it runs · the whole SKILL.md, loaded when a task matches
~3k
With references · SKILL.md plus every file in references/, read only if the agent opens them
~5.9k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from sanity-io/sanity at commit efa15fb, republished under its MIT licence (© sanity-io). 1,063 words, ~3,005 tokens.

Download SKILL.mdSave it as .claude/skills/rxjs-like-a-pro/SKILL.md (or your agent's skills folder). This skill also uses 4 other files; get the full folder from GitHub.
name
rxjs-like-a-pro
description
How to write idiomatic, efficient RxJS code. Use this skill whenever the user is writing, refactoring, reviewing, or debugging code that uses RxJS — including any file that imports from 'rxjs' or 'rxjs/operators'. Trigger on mentions of observables, subscriptions, RxJS operators, or reactive streams. Even if the user doesn't say "RxJS" explicitly, activate when you see patterns like `.pipe()`, `.subscribe()`, `Observable`, `Subject`, `BehaviorSubject`, `switchMap`, `mergeMap`, or similar.

RxJS Like a Pro

This skill helps you write RxJS code that is idiomatic, composable, and free of common pitfalls. The core philosophy: keep logic in the observable chain. Every time you reach for .subscribe(), ask whether the work could instead be expressed as a transformation inside .pipe().

Reference files

For detailed examples and patterns, read the relevant reference file:

  • references/loading-state-patterns.md — Deriving loading/error state in the chain, the withLoadingState custom operator, and using scan to preserve previous results across loading states. Read when working with async data fetching that needs loading indicators.
  • references/massive-observable.md — How to refactor bloated new Observable() constructors into small focused pieces. Read when you see a new Observable callback longer than ~10 lines.
  • references/inner-observable-chains.md — Building rich inner observable sequences with timing, delays, and animation phases. Read when composing multi-step async sequences or replacing setTimeout patterns.
  • references/custom-operators.md — How to write inline and extracted custom operators with OperatorFunction. Read when extracting reusable stream logic.

The #1 Anti-pattern: Premature Subscribe

The most common RxJS mistake is subscribing too early and then doing imperative work inside the callback — tracking state in variables, calling functions with side effects, or worse, subscribing to another observable inside the callback (the "subscribe-in-subscribe" pattern).

Why this matters: when you subscribe early, you lose the power of the reactive chain. You can no longer compose, retry, cancel, debounce, or share that work. You've escaped from the declarative world into imperative spaghetti, and every new requirement (add a retry, add a timeout, combine with another stream) means more manual state management.

typescript
// ❌ Bad: subscribe-in-subscribe with manual state tracking
let currentData: Data | null = null
let loading = false

input$.subscribe((value) => {
  loading = true
  fetchData(value).subscribe((data) => {
    currentData = data
    loading = false
  })
})

// ✅ Good: everything is in the chain
const data$ = input$.pipe(switchMap((value) => fetchData(value)))

For loading state, derive it inside the chain using startWith — see references/loading-state-patterns.md.

The Massive new Observable() Antipattern

Another common antipattern is stuffing an entire program into a single new Observable(subscriber => { ... }) constructor — setting up listeners, resolving promises, subscribing to other observables, managing retry state, all in one giant callback. This is imperative code wearing an Observable costume.

The new Observable() constructor should be small and focused — a thin bridge from one non-reactive source into the reactive world. For promise-based sources, use defer(() => promise) instead. Retry logic, error handling, combining sources — all of that belongs in the operator chain.

See references/massive-observable.md for a full before/after example.

Choosing the Right Flattening Operator

OperatorBehaviorUse when
switchMapCancels previous inner when new value arrivesUser input, search-as-you-type, route changes — only the latest matters
mergeMapRuns all inner observables concurrentlyIndependent operations where all results are needed (logging, fire-and-forget)
concatMapQueues inner observables, runs in orderOrder matters and nothing should be dropped (sequential writes, queues)
exhaustMapIgnores new values while inner is runningPreventing duplicate submissions (form submit clicks)

Default to switchMap for most UI/request scenarios.

The inner observable doesn't have to be a single request — it can be an entire timeline of events using concat, merge, timer, delay. See references/inner-observable-chains.md for animation and timing examples.

Error Handling

Put catchError on the inner observable when you want the outer stream to keep running. Put it on the outer stream only when you truly want to replace the entire stream on error:

typescript
// ❌ Bad: catchError on outer stream kills it for good
source$.pipe(
  switchMap((value) => fetchData(value)),
  catchError((err) => of(fallback)),
)

// ✅ Good: catchError inside switchMap — outer stream survives
source$.pipe(switchMap((value) => fetchData(value).pipe(catchError((err) => of(fallback)))))

Same principle applies to retry — retry the inner operation, not the entire outer stream:

typescript
source$.pipe(
  switchMap((value) =>
    fetchData(value).pipe(
      retry({count: 3, delay: 1000}),
      catchError((err) => of(fallback)),
    ),
  ),
)

Avoiding Memory Leaks

The fewer manual subscriptions, the fewer chances to leak. In order of preference:

  1. Don't subscribe at all — let the framework handle subscription lifecycle where possible
  2. Use operators that complete naturally — first(), take(n), takeUntil(destroy$)
  3. Use takeUntil with a notifier:
typescript
const destroy$ = new Subject<void>();
someObservable$.pipe(
  takeUntil(destroy$),
).subscribe(value => /* ... */);

// In teardown: destroy$.next(); destroy$.complete();

takeUntil must be the last operator in the pipe. Operators after it (especially flattening operators) can create inner subscriptions that takeUntil doesn't know about, causing leaks.

  1. Compose into a single subscription — if you have multiple independent streams with side effects, merge them into one and subscribe once.

Hot vs Cold

  • Cold observables (new Observable(...), of(), HTTP requests) create a new execution per subscriber
  • Hot observables (Subject, fromEvent) share a single execution

Share cold observables with shareReplay({ bufferSize: 1, refCount: true }). Always use refCount: true — without it, the source subscription stays alive after all subscribers unsubscribe (memory leak).

Show full SKILL.md (406 more words)Show less

Deriving State Reactively

Instead of mutable variables updated from multiple subscriptions, derive state from streams:

typescript
// ❌ Bad: mutable state, inconsistent windows
let items: Item[] = []
let filter = ''
items$.subscribe((i) => {
  items = i
  recompute()
})
filter$.subscribe((f) => {
  filter = f
  recompute()
})

// ✅ Good: always consistent
const filteredItems$ = combineLatest([items$, filter$]).pipe(
  map(([items, filter]) => items.filter((item) => item.name.includes(filter))),
)

combineLatest vs withLatestFrom: combineLatest emits when any input emits (all inputs drive output). withLatestFrom emits only when the source emits (one driver, others are context).

startWith: combineLatest won't emit until every input has emitted at least once. Use startWith to provide initial values and unblock the stream.

Subjects: Use Sparingly

Subject, BehaviorSubject, ReplaySubject are escape hatches for bridging imperative and reactive code. Appropriate for event buses and bridging callbacks. Not appropriate as general-purpose state containers — if you're calling .next() in multiple places to keep a Subject in sync, use a derived stream instead.

Custom Operators

Don't be afraid to write them — they're just functions with the signature (source: Observable<A>) => Observable<B>. Extract repeated .pipe() chains into named operators with OperatorFunction<In, Out>. See references/custom-operators.md for inline and extracted examples.

Side Effects Belong in tap, Not in subscribe

A good rule of thumb: .subscribe() should have no arguments. All side effects — logging, updating the DOM, writing to a database, sending analytics — belong in tap inside the chain. The .subscribe() at the end just activates the stream.

typescript
// ❌ Bad: side effects crammed into subscribe
source$.pipe(switchMap((value) => fetchData(value))).subscribe(
  (data) => {
    updateUI(data)
    logAnalytics('data_loaded', data)
    cache.set(data)
  },
  (err) => showError(err),
)

// ✅ Good: side effects in tap, subscribe just activates
source$
  .pipe(
    switchMap((value) => fetchData(value)),
    tap((data) => updateUI(data)),
    tap((data) => logAnalytics('data_loaded', data)),
    tap((data) => cache.set(data)),
    tap({error: (err) => showError(err)}),
  )
  .subscribe()

Why this matters: when side effects are in the chain, they're composable. You can add, remove, or reorder them. You can put a filter between them. You can share the stream and have different subscribers without duplicating side-effect logic. When everything is stuffed into .subscribe(), you've lost all of that.

tap also accepts an observer object with lifecycle hooks — particularly useful for debugging:

typescript
source$.pipe(
  tap({
    subscribe: () => console.log('subscribed!'),
    next: (value) => console.log('value:', value),
    error: (err) => console.log('error:', err),
    complete: () => console.log('complete'),
    unsubscribe: () => console.log('unsubscribed'),
    finalize: () => console.log('finalized (complete or unsubscribe)'),
  }),
)

The subscribe hook is especially handy for debugging "why isn't my stream emitting?" — it confirms whether anything is actually subscribing.

Avoid Unnecessary Promise Conversion

firstValueFrom/lastValueFrom are appropriate for one-shot interop with promise-based APIs. They're a code smell when used inside subscribe callbacks to avoid learning the reactive approach — that work belongs in the chain with switchMap.

Quick Reference: Common Refactoring Patterns

Anti-patternRefactoring
a$.subscribe(x => b$.subscribe(y => ...))a$.pipe(switchMap(x => b$)) (or mergeMap/concatMap/exhaustMap)
Mutable variable updated in subscribescan() or combineLatest to derive state
setTimeout inside subscribedelay(), timer(), or debounceTime()
if guard in subscribe to skip valuesfilter() before subscribe
try/catch inside subscribecatchError() in the pipe
Manual request cancellation flagsswitchMap (auto-cancels previous)
Multiple subscribes to same cold observableshareReplay({ bufferSize: 1, refCount: true })
.subscribe() just to trigger side effectstap() for side effects, keep the chain going
Massive new Observable() constructorSmall focused constructors + defer() + operator composition
await firstValueFrom() inside subscribeswitchMap — stay in the chain

© sanity-io, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

SKILL.md and 4 other files (references) in .agents/skills/rxjs-like-a-pro of sanity-io/sanity.

  • SKILL.md
  • references/custom-operators.md
  • references/inner-observable-chains.md
  • references/loading-state-patterns.md
  • references/massive-observable.md

Open the folder on GitHubat commit efa15fb

Compare with similar skills

Rxjs Like A Pro next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Rxjs Like A Pro compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Rxjs Like A Pro this skillsanity-io/sanity6.4k—~3kAutomated safety check: PassMIT
Angularkid-sid/claude-spellbook190—~5kAutomated safety check: PassMIT
Angularericrisco/rsc-harness180—~3.4kAutomated safety check: PassMIT
Guidelinesakash-network/node1.1k20 repos~577Automated safety check: PassMIT
Migrate Core Code to Submodulestinyhumansai/openhuman42k—~2.6kAutomated safety check: PassGPL-3.0
Component Refactoringlangflow-ai/langflow155k—~3.5kAutomated safety check: PassMIT

Similar skills

  • Angular

    kid-sid/claude-spellbook

    A skill your agent uses when building or refactoring Angular applications — choosing between signals, RxJS, and NgRx for state, configuring routing with guards and lazy loading, optimizing change…

    190 GitHub stars~5k tokensUpdated 2 mo ago
    DevelopmentAuto-check passed
  • Angular

    ericrisco/rsc-harness

    A skill your agent uses when building, refactoring, or debugging Angular (v20/21+): standalone components, signals, zoneless change detection, @if/@for/@defer control flow, inject() DI…

    180 GitHub stars~3.4k tokensUpdated yesterday
    DevelopmentAuto-check passed
  • Guidelines

    akash-network/node

    Behavioral guidelines to reduce common LLM coding mistakes. An agent skill from akash-network/node.

    1.1k GitHub starsUsed in 20 repos~577 tokens
    DevelopmentAuto-check passed
  • Migrate Core Code to Submodules

    tinyhumansai/openhuman

    Plans and carries out moving non-host-specific code and its tests from the OpenHuman core into vendored tiny submodule libraries, then releases the submodule and re-pins the host.

    42k GitHub stars~2.6k tokensUpdated today
    DevelopmentAuto-check passed
  • Component Refactoring

    langflow-ai/langflow

    Refactor high-complexity React components in Langflow frontend.

    155k GitHub stars~3.5k tokensUpdated today
    DevelopmentAuto-check passed
  • Ponytail Lazy Developer Mode

    DietrichGebert/ponytail

    Makes the agent pick the laziest solution that works: skip unneeded work, reuse what exists, prefer the standard library and platform features, and keep diffs small.

    160k GitHub starsUsed in 1 repo~873 tokens
    DevelopmentAuto-check passed

More from sanity-io/sanity

All 34 skills in this repo
  • Playwright CLI

    sanity-io/sanity

    Official

    Automates browser interactions for web testing, form filling, screenshots, and data extraction.

    6.4k GitHub starsUsed in 18 repos~1.9k tokens
    Auto-check passed
  • Find Skills

    sanity-io/sanity

    Official

    Helps users discover and install agent skills when they ask questions like "how do I do X", "find a skill for X", "is there a skill that can...", or express interest in extending capabilities.

    6.4k GitHub starsUsed in 67 repos~1.2k tokens
    Auto-check passed
  • Official

    React and Next.js performance optimization guidelines from Vercel Engineering.

    6.4k GitHub starsUsed in 129 repos~1.6k tokens
    Auto-check passed
  • Before And After

    sanity-io/sanity

    Official

    Add existing screenshots or screen recordings to a GitHub pull request as a before/after or preview block.

    6.4k GitHub starsUsed in 1 repo~2.1k tokens
    Auto-check passed
  • React Devtools

    sanity-io/sanity

    Official

    React DevTools CLI for AI agents. An agent skill from sanity-io/sanity.

    6.4k GitHub starsUsed in 3 repos~2.1k tokens
    Auto-check passed
  • TDD

    sanity-io/sanity

    Official

    Test-driven development with red-green-refactor loop. An agent skill from sanity-io/sanity.

    6.4k GitHub starsUsed in 20 repos~1k tokens
    Auto-check passed

Works with

Categories

Questions about Rxjs Like A Pro

What does Rxjs Like A Pro do?

How to write idiomatic, efficient RxJS code. An agent skill from sanity-io/sanity. Rxjs Like A Pro is an agent skill from sanity-io/sanity, published by the product's own GitHub organization. How to write idiomatic, efficient RxJS code.

When should I use Rxjs Like A Pro?

Rxjs Like A Pro fits situations like: the user is writing; debugging code that uses RxJS — including any file that imports from rxjs; mentions of observables; reactive streams.

How do I install Rxjs Like A Pro in Claude Code?

Run `npx skills add sanity-io/sanity --skill rxjs-like-a-pro -a claude-code`. Or copy the skill folder (.agents/skills/rxjs-like-a-pro in sanity-io/sanity) into .claude/skills/rxjs-like-a-pro in your project. Claude Code loads it when a task matches its description.

How do I install Rxjs Like A Pro in Codex?

Run `npx skills add sanity-io/sanity --skill rxjs-like-a-pro -a codex`. Or copy the skill folder (.agents/skills/rxjs-like-a-pro in sanity-io/sanity) into .agents/skills/rxjs-like-a-pro in your project. Codex loads it when a task matches its description.

Can I use Rxjs Like A Pro in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add sanity-io/sanity --skill rxjs-like-a-pro -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/rxjs-like-a-pro, .gemini/skills/rxjs-like-a-pro, .github/skills/rxjs-like-a-pro and .opencode/skills/rxjs-like-a-pro in your project.

What does Rxjs Like A Pro need to run?

SKILL.md names no scripts, command-line tools or credentials: Rxjs Like A Pro is instructions for the agent only.

Does Rxjs Like A Pro access the network?

SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.

Is Rxjs Like A Pro safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does Rxjs Like A Pro use?

Rxjs Like A Pro is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Rxjs Like A Pro use?

About 3k tokens (SKILL.md is roughly 12k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full. Its references folder adds about 2.9k tokens, read only when the agent opens those files.

What are the alternatives to Rxjs Like A Pro?

Skills that share tags, products or a category with Rxjs Like A Pro: Angular (kid-sid/claude-spellbook, 190 stars), Angular (ericrisco/rsc-harness, 180 stars), Guidelines (akash-network/node, 1.1k stars) and Migrate Core Code to Submodules (tinyhumansai/openhuman, 42k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Rxjs Like A Pro?

sanity-io (a GitHub organization, an official publisher) maintains it in sanity-io/sanity, which has 6,352 GitHub stars. The repository holds 34 skills in this directory. The repository was last updated on October 9, 2026.

Source: sanity-io/sanity on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.