# observable-hooks: React hooks that render RxJS Observables as state

> observable-hooks is a TypeScript library that lets React components read from RxJS Observables, push events back into them, and render a component stream with Suspense, while keeping the observable logic outside React's state model. It is deliberately not a Redux replacement, and the README is explicit that you should use it only where you need it.

**crimx/observable-hooks** — ⚛️☯️💪 React hooks for RxJS Observables. Concurrent mode safe.

- Repository: https://github.com/crimx/observable-hooks
- Website: https://observable-hooks.js.org
- Stars: 1,051 · Forks: 43
- Language: TypeScript
- License: MIT
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/crimx-observable-hooks

## The gap is between the Observable model and React's render model

observable-hooks is a set of React hooks for RxJS Observables, written in TypeScript, and the README describes it as concurrent mode compatible. The reasoning starts from what React and RxJS each do well. React added hooks for reusing stateful logic, and an Observable is a powerful way to encapsulate both sync and async logic, with the extra benefit that testing Observables is easier than testing other async implementations. The problem this library addresses is that the two models do not line up: React pulls state during render, while Observables push values over time, and an Observable that a component reads has to be turned into something the renderer can consume. The listed capabilities describe the surface in both directions. Props, state and context can be fed into Observables, and Observables can be turned back into states and props events. On top of that sit conditional rendering with a stream of React components and Render-as-You-Fetch with React Suspense, so a component can be suspended while the data it needs is still arriving.

## What it is not, in the project's own words

The README has a section titled What It Is Not, and it is the most useful paragraph in the document. This library is not for replacing state management tools like Redux, its purpose is to reduce the need of dumping everything into global state. Using it does not mean turning everything observable, which the README says is not encouraged, and it plays well side by side with other hooks, to be used only on the places where it is needed. That is an unusually clear boundary for a library of this kind, and it changes how you evaluate it. The question is not whether Observable programming scales to an application, it is whether a specific component, a typeahead field, a typing indicator, a paginated list, has behaviour that is clearer as a stream than as a set of useState calls. If the answer is yes for one component and no for the rest, this library fits. If the answer is yes for everything, the README is telling you that you are using it wrong. A related claim in the feature list is that the library offers the full power of RxJS with no limitation or compromise, so the operators you already know are the ones you will use.

## Purity is the argument, and the example shows it

One bullet in the feature list stands out from the rest: no `tap` hack needed, and with an Epic-like signature the Observable operation is pure and testable. That is the technical centre of the library. The first-glance example makes the point in a dozen lines: a `useObservableState` hook is given a plain function that takes an event stream and returns a transformed stream, with the second argument being the initial value. The transformation uses `switchMap` over a `timer` with `mapTo(false)` and `startWith(true)`, so the state flips to true on an event and back to false after a second of silence. The comment in the example says the logic is pure and can be tested like an Epic in redux-observable, and that is the real point. The same function can be handed to a marble test without mounting a component, so the behaviour of the component is verified as a stream transformation rather than as a rendering. The README also claims the project will always maintain a 100% coverage, which is a promise about the library's own tests rather than about yours.

## Installing the package and running the first example

The package is published to npm and installed like any other dependency:

```bash
pnpm add observable-hooks
```

The example below is the one the README opens with, and it shows the shape of the API: an event stream in, a state out, with the transformation written as a separate function so it can be tested on its own.

```jsx
import * as React from "react";
import { useObservableState } from "observable-hooks";
import { timer } from "rxjs";
import { switchMap, mapTo, startWith } from "rxjs/operators";

const App = () => {
  const [isTyping, updateIsTyping] = useObservableState(
    transformTypingStatus,
    false
  );

  return (
    <div>
      <input type="text" onKeyDown={updateIsTyping} />
      <p>{isTyping ? "Good you are typing." : "Why stop typing?"}</p>
    </div>
  );
};

// Logic is pure and can be tested like Epic in redux-observable
function transformTypingStatus(event$) {
  return event$.pipe(
    switchMap(() => timer(1000).pipe(mapTo(false), startWith(true)))
  );
}
```

Beyond that, the README sends you to the documentation site at observable-hooks.js.org and to the `docs` directory in the repository, and points at the `examples` folder for runnable code. Four examples are listed, a Pomodoro timer, a typeahead, a Render-as-You-Fetch sample using Suspense, and a React Context example, each of them also available as a CodeSandbox. The README also mentions utilities for common use cases that exist to reduce garbage collection, which is a hint that the library is conscious of allocation pressure in the render path.

## A pnpm workspace with vuepress documentation and a strict lint setup

The repository is a monorepo that publishes one package. The root package.json is private and named observable-hooks-mono, with a `pnpm-workspace.yaml` and a `packages/` directory, and the build script runs a filtered build for the observable-hooks package and for the example packages. The test script filters down to observable-hooks alone, and the lint script runs eslint over TypeScript, TSX, JavaScript and MJS files and then checks formatting with prettier, with a `lint:fix` counterpart. The documentation site is built with vuepress, and both docs scripts pass `NODE_OPTIONS=--openssl-legacy-provider` to the vuepress invocation, which is the kind of detail that tells you the toolchain is pinned to a specific era. The package manager is pinned as well, with `packageManager` set to pnpm 9.6.0 and a lockfile hash. The dev dependencies are modest: typescript-eslint, eslint, prettier, vuepress and its PWA plugin, cross-env and fs-extra. For contributors, the README gives three commands, `pnpm i`, `pnpm test` and `pnpm lint`.

## How it differs from plain RxJS inside useEffect

The closest alternative is not another library but the pattern most teams reach for first: subscribing to an Observable inside `useEffect` and storing the latest value in `useState`. The difference in approach is where the stream lives. In that pattern the subscription is created and torn down inside the component, the transformation is often written inline or with a `tap` to push values into state, and testing it means rendering the component with a fake emitter. With observable-hooks, the transformation is a plain function that takes a stream and returns a stream, the hook is the only part that touches React, and the function can be exercised with a marble test on its own. The cost is an extra abstraction layer and a dependency, plus the discipline the README insists on, since the benefit only appears if you actually keep the logic out of the component. The other alternative, global state, is a different architecture entirely, and the README is explicit that this library is not trying to replace it. Redux with redux-observable is the reference point the example itself uses when it compares the pure operation to an Epic.

## Conclusion

Adopt observable-hooks if you already use RxJS and have components whose behaviour is naturally a stream, such as search, typing indicators or fetch-and-render flows, and you want that logic testable outside React. Do not adopt it to move your application state into Observables, because the README says the opposite, that the library is not for replacing state management tools like Redux and that turning everything observable is not encouraged. Before you commit, check the peer situation: the package supports RxJS 6 and 7, the workspace pins pnpm 9.6.0, and the docs site is built with vuepress. Verify that the patterns you want, especially conditional rendering of a stream of components and Render-as-You-Fetch with Suspense, are covered in the docs site and the examples directory, since the README points there rather than documenting the API inline. The licence is MIT, there are no GitHub releases, and the last push was on 2026-04-09.

## FAQ

### How do I install observable-hooks?

The README shows `pnpm add observable-hooks`, and the package is published on npm under the name observable-hooks. The library is compatible with RxJS 6 and 7.

### Is observable-hooks a replacement for Redux?

No. The README says the library is not for replacing state management tools like Redux, but to reduce the need of dumping everything into global state, and it says turning everything observable is not encouraged and that you should use it only where it is needed.

### How do I test logic that uses observable-hooks?

The transformation is written as a pure function that takes an event stream and returns a stream, in the style of an Epic in redux-observable, so it can be tested with marble testing without mounting a React component. The README notes that testing Observables is easier than testing other async implementations.

### What examples come with observable-hooks?

The examples directory holds a Pomodoro timer, a typeahead, a Render-as-You-Fetch sample using Suspense and a React Context example, and the README links each of them as a CodeSandbox you can play with.

### Which RxJS version does observable-hooks support?

The feature list states that it is compatible with RxJS 6 and 7, and the README lists the full power of RxJS with no limitation or compromise, so the operators you already use are the ones you will use.

## Sources

- [crimx/observable-hooks on GitHub](https://github.com/crimx/observable-hooks)
- [Issues](https://github.com/crimx/observable-hooks/issues)
- [License: MIT](https://github.com/crimx/observable-hooks/blob/main/LICENSE)
- [Project website](https://observable-hooks.js.org)
- [README](https://github.com/crimx/observable-hooks/blob/main/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/crimx-observable-hooks
