React-RxJS Logo React-RxJS: React bindings for RxJS

Build Status codecov version MIT License All Contributors PRs Welcome Code of Conduct

Main features

  • 🌀 Truly Reactive
  • Highly performant and free of memory-leaks
  • 🔀 First class support for React Suspense and ready for Concurrent Mode
  • ✂️ Decentralized and composable, thus enabling optimal code-splitting
  • 🔬 Tiny and tree-shakeable
  • 💪 Supports TypeScript

Table of Contents

Installation

npm install @react-rxjs/core

API

bind

Observable overload

const [useCounter, sharedCounter$] = bind(
  clicks$.pipe(
    scan((prev) => prev + 1, 0),
    startWith(0),
  ),
)

Accepts: An Observable.

Returns [1, 2]

  1. A React Hook that yields the latest emitted value of the observable. If the Observable doesn't synchronously emit a value upon the first subscription, then the hook will leverage React Suspense while it's waiting for the first value.

  2. A sharedLatest version of the observable. It can be used for composing other streams that depend on it. The shared subscription is closed as soon as there are no subscribers to that observable.

Factory of Observables overload

const [useStory, getStory$] = bind((storyId: number) =>
  getStoryWithUpdates$(storyId),
)

const Story: React.FC<{ id: number }> = ({ id }) => {
  const story = useStory(id)

  return (
    <article>
      <h1>{story.title}</h1>
      <p>{story.description}</p>
    </article>
  )
}

Accepts: A factory function that returns an Observable.

Returns [1, 2]

  1. A React Hook function with the same parameters as the factory function. This hook will yield the latest update from the observable returned from the factory function. If the Observable doesn't synchronously emit a value upon the first subscription, then the hook will leverage React Suspense while it's waiting for the first value.

  2. A sharedLatest version of the observable returned by the factory function. It can be used for composing other streams that depend on it. The shared subscription is closed as soon as there are no subscribers to that observable.

shareLatest

const activePlanetName$ = planet$.pipe(
  filter((planet) => planet.isActive),
  map((planet) => planet.name),
  shareLatest(),
)

A RxJS pipeable operator which shares and replays the latest emitted value. It's the equivalent of:

const shareLatest = <T>(): Observable<T> =>
  source$.pipe(
    multicast(() => new ReplaySubject<T>(1)),
    refCount(),
  )

The enhanced observables returned from bind have been enhanced with this operator.

SUSPENSE

const story$ = selectedStoryId$.pipe(
  switchMap((id) => concat(SUSPENSE, getStory$(id))),
)

This is a special symbol that can be emitted from our observables to let the react hook know that there is a value on its way, and that we want to leverage React Suspense while we are waiting for that value.

Examples

import React, { Suspense } from "react"
import { Subject } from "rxjs"
import { startWith, map } from "rxjs/operators"
import { bind } from "@react-rxjs/core"
import { switchMapSuspended } from "@react-rxjs/utils"
import { Header, Search, LoadingResults, Repo } from "./components"

interface Repo {
  id: number
  name: string
  description: string
  author: string
  stars: number
  lastUpdate: number
}

const searchInput$ = new Subject<string>()
const onSubmit = (value: string) => searchInput$.next(value)

const findRepos = (query: string): Promise<Repo[]> =>
  fetch(`https://api.github.com/search/repositories?q=${query}`)
    .then((response) => response.json())
    .then((rawData) =>
      (rawData.items ?? []).map((repo: any) => ({
        id: repo.id,
        name: repo.name,
        description: repo.description,
        author: repo.owner.login,
        stars: repo.stargazers_count,
        lastUpdate: Date.parse(repo.update_at),
      })),
    )

const [useRepos, repos$] = bind(
  searchInput$.pipe(switchMapSuspended(findRepos), startWith(null)),
)

function Repos() {
  const repos = useRepos()

  if (repos === null) {
    return null
  }

  if (repos.length === 0) {
    return <p>No results were found.</p>
  }

  return (
    <ul>
      {repos.map((repo) => (
        <li key={repo.id}>
          <Repo {...repo} />
        </li>
      ))}
    </ul>
  )
}

const [useMostRecentlyUpdatedRepo] = bind(
  repos$.pipe(
    map((repos) =>
      Array.isArray(repos) && repos.length > 0
        ? repos.reduce((winner, current) =>
            current.lastUpdate > winner.lastUpdate ? current : winner,
          )
        : null,
    ),
  ),
)

function MostRecentlyUpdatedRepo() {
  const mostRecent = useMostRecentlyUpdatedRepo()

  if (mostRecent === null) {
    return null
  }

  const { id, name } = mostRecent
  return (
    <p>
      The most recently updated repo is <a href={`#${id}`}>{name}</a>
    </p>
  )
}

export default function App() {
  return (
    <>
      <Header>Search Github Repos</Header>
      <Search onSubmit={onSubmit} />
      <Suspense fallback={<LoadingResults />}>
        <MostRecentlyUpdatedRepo />
        <Repos />
      </Suspense>
    </>
  )
}

Contributors

Thanks goes to these wonderful people (emoji key):


Josep M Sobrepere

💻 🤔 🚧 ⚠️ 👀 📖 🚇

Víctor Oliva

🤔 👀 💻 ⚠️ 📖

Ed

🎨

Pierre Grimaud

📖

Bhavesh Desai

👀 📖 ⚠️

Matt Mischuk

📖

Riko Eksteen

🚇

This project follows the all-contributors specification. Contributions of any kind welcome!

Description
React bindings for RxJS
Readme MIT 6.4 MiB
Languages
TypeScript 99.3%
JavaScript 0.7%