diff --git a/README.md b/README.md index 654f340..7477079 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# React-RxJS Logo React-RxJS: React bindings for RxJS +# React-RxJS Logo React-RxJS [![Build Status](https://img.shields.io/github/workflow/status/re-rxjs/react-rxjs/CI?style=flat-square)](https://github.com/re-rxjs/react-rxjs/actions) @@ -10,6 +10,10 @@ [![Code of Conduct](https://img.shields.io/badge/code%20of-conduct-ff69b4.svg?style=flat-square)](https://github.com/re-rxjs/react-rxjs/blob/main/CODE_OF_CONDUCT.md) +React-RxJS is a library that offers [React](https://reactjs.org/) bindings for [RxJS](https://rxjs.dev/) + +Please visit the website: https://react-rxjs.org + ## Main features - :cyclone: Truly Reactive @@ -19,249 +23,10 @@ - :microscope: [Tiny and tree-shakeable](https://bundlephobia.com/result?p=react-rxjs) - :muscle: Supports TypeScript -## Table of Contents - -- [Installation](#installation) -- [API](#api) - - Core - - [bind](#bind) - - [Observable overload](#observable-overload) - - [Factory of Observables overload](#factory-of-observables-overload) - - [shareLatest](#sharelatest) - - [SUSPENSE](#suspense) - - [useSubscribe](#usesubscribe) - - [Subscribe](#subscribe) -- [Examples](#examples) - ## Installation npm install @react-rxjs/core -## API - -### bind - -#### Observable overload - -```ts -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 - -```tsx -const [useStory, getStory$] = bind((storyId: number) => - getStoryWithUpdates$(storyId), -) - -const Story: React.FC<{ id: number }> = ({ id }) => { - const story = useStory(id) - - return ( -
-

{story.title}

-

{story.description}

-
- ) -} -``` - -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 - -```ts -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: - -```ts -const shareLatest = (): Observable => - source$.pipe( - multicast(() => new ReplaySubject(1)), - refCount(), - ) -``` - -The enhanced observables returned from `bind` have been enhanced with this operator. - -### SUSPENSE - -```ts -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. - -### useSubscribe - -A React hook that creates a subscription to the provided observable once the -component mounts and it unsubscribes when the component unmounts. - -Arguments: - -- `source$`: Source observable that the hook will subscribe to. - -Important: This hook doesn't trigger any updates. - -### Subscribe - -A React Component that creates a subscription to the provided observable once -the component mounts and it unsubscribes from it when the component unmounts. - -Properties: - -- `source$`: Source observable that the Component will subscribe to. -- `fallback?`: The JSX Element to be rendered before the subscription is created. - It defaults to `null`. - -Important: This Component doesn't trigger any updates. - -## Examples - -- [This is a contrived example](https://codesandbox.io/s/crazy-wood-vn7gg?file=/src/fakeApi.js) based on [this example](https://reactjs.org/docs/concurrent-mode-patterns.html#reviewing-the-changes) from the React docs. - -- A search for Github repos that highlights the most recently updated one: - -```tsx -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() -const onSubmit = (value: string) => searchInput$.next(value) - -const findRepos = (query: string): Promise => - 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

No results were found.

- } - - return ( -
    - {repos.map((repo) => ( -
  • - -
  • - ))} -
- ) -} - -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 ( -

- The most recently updated repo is {name} -

- ) -} - -export default function App() { - return ( - <> -
Search Github Repos
- - }> - - - - - ) -} -``` - ## Contributors ✨ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): diff --git a/packages/core/README.md b/packages/core/README.md index d06c368..5d78b6a 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -1,123 +1,7 @@ # @react-rxjs/core +Please visit the website: https://react-rxjs.org + ## Installation npm install @react-rxjs/core - -## API - -### bind - -#### Observable overload - -```ts -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 Observables overload - -```tsx -const [useStory, getStory$] = bind((storyId: number) => - getStoryWithUpdates$(storyId), -) - -const Story: React.FC<{ id: number }> = ({ id }) => { - const story = useStory(id) - - return ( -
-

{story.title}

-

{story.description}

-
- ) -} -``` - -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 - -```ts -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: - -```ts -const shareLatest = (): Observable => - source$.pipe( - multicast(() => new ReplaySubject(1)), - refCount(), - ) -``` - -The enhanced observables returned from `bind` have been enhanced with this operator. - -### SUSPENSE - -```ts -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. - -### useSubscribe - -A React hook that creates a subscription to the provided observable once the -component mounts and it unsubscribes when the component unmounts. - -Arguments: - -- `source$`: Source observable that the hook will subscribe to. - -Important: This hook doesn't trigger any updates. - -### Subscribe - -A React Component that creates a subscription to the provided observable once -the component mounts and it unsubscribes from it when the component unmounts. - -Properties: - -- `source$`: Source observable that the Component will subscribe to. -- `fallback?`: The JSX Element to be rendered before the subscription is created. - It defaults to `null`. - -Important: This Component doesn't trigger any updates. diff --git a/packages/dom/README.md b/packages/dom/README.md index c61bfeb..1bc706e 100644 --- a/packages/dom/README.md +++ b/packages/dom/README.md @@ -1,23 +1,7 @@ # @react-rxjs/dom +Please visit the website: https://react-rxjs.org + ## Installation npm install @react-rxjs/dom - -## API - -### batchUpdates - -A RxJS pipeable operator which observes the source observable on -an asapScheduler and uses `ReactDom.unstable_batchedUpdates` to emit the -values. It's useful for observing streams of events that come from outside -of ReactDom event-handlers. - -IMPORTANT: This operator will be deprecated when React 17 is released -(or whenever React CM is released). The reason being that React Concurrent Mode -automatically batches all synchronous updates. Meaning that with React CM, -observing a stream through the asapScheduler accomplishes the same thing. - -```ts -const marketUpdates$ = defer(() => api.getMarketUpdates()).pipe(batchUpdates()) -``` diff --git a/packages/utils/README.md b/packages/utils/README.md index faf5af9..0a34660 100644 --- a/packages/utils/README.md +++ b/packages/utils/README.md @@ -1,172 +1,7 @@ # @react-rxjs/utils +Please visit the website: https://react-rxjs.org + ## Installation npm install @react-rxjs/utils - -## API - -### split - -A RxJS operator that groups the items emitted by the source based on the -keySelector function, emitting one Observable for each group. - -Arguments: - -- `keySelector`: A function that receives an item and returns the key of that - item's group. -- `streamSelector`: (optional) The function to apply to each group observable - (default = identity). - -`split` will subscribe to each group observable and share the result to every -inner subscriber of that group. This inner observable can be mapped to another -observable through the `streamSelector` argument. - -### collectValues - -A pipeable operator that collects all the GroupedObservables emitted by -the source and emits a Map with the latest values of the inner observables. - -```ts -const votesByKey$ = new Subject<{ key: string }>() -const counters$ = votesByKey$.pipe( - split( - (vote) => vote.key, - (votes$) => - votes$.pipe( - mapTo(1), - scan((count) => count + 1), - takeWhile((count) => count < 3), - ), - ), - collectValues(), -) - -counters$.subscribe((counters) => { - console.log("counters$:") - counters.forEach((value, key) => { - console.log(`${key}: ${value}`) - }) -}) - -votesByKey$.next({ key: "foo" }) -// > counters$: -// > foo: 1 - -votesByKey$.next({ key: "foo" }) -// > counters$: -// > foo: 2 - -votesByKey$.next({ key: "bar" }) -// > counters$: -// > foo: 2 -// > bar: 1 - -votesByKey$.next({ key: "foo" }) -// > counters$: -// > bar: 1 - -votesByKey$.next({ key: "bar" }) -// > counters$: -// > bar: 2 -// -votesByKey$.next({ key: "bar" }) -// > counters$: -``` - -### collect - -A pipeable operator that collects all the GroupedObservables emitted by -the source and emits a Map with the active inner observables. - -Arguments: - -- `filter?`: A function that receives the inner Observable and returns an - Observable of boolean values, which indicates whether the inner observable - should be collected. - -### mergeWithKey - -Emits the values from all the streams of the provided object, in a result -which provides the key of the stream of that emission. - -Arguments: - -- `inputObject`: Object of streams - -```ts -const inc$ = new Subject() -const dec$ = new Subject() -const resetTo$ = new Subject() - -const counter$ = mergeWithKey({ - inc$, - dec$, - resetTo$, -}).pipe( - scan((acc, current) => { - switch (current.type) { - case "inc$": - return acc + 1 - case "dec$": - return acc - 1 - case "resetTo$": - return current.payload - default: - return acc - } - }, 0), - startWith(0), -) -``` - -### selfDependant - -A creation operator that helps at creating observables that have circular -dependencies - -```ts -const [_resetableCounter$, connectResetableCounter] = selfDependant() - -const clicks$ = new Subject() -const inc$ = clicks$.pipe( - withLatestFrom(_resetableCounter$), - map((_, x) => x + 1), - share(), -) - -const delayedZero$ = of(0).pipe(delay(10_000)) -const reset$ = inc$.pipe(switchMapTo(delayedZero$)) - -const resetableCounter$ = merge(inc$, reset$, of(0)).pipe( - connectResetableCounter(), -) -``` - -### suspend - -```ts -const story$ = selectedStoryId$.pipe( - switchMap(id => suspend(getStory$(id)) -) -``` - -A RxJS creation operator that prepends a `SUSPENSE` on the source observable. - -### suspended - -```ts -const story$ = selectedStoryId$.pipe( - switchMap((id) => getStory$(id).pipe(suspended())), -) -``` - -The pipeable version of `suspend` - -### switchMapSuspended - -```ts -const story$ = selectedStoryId$.pipe(switchMapSuspended(getStory$)) -``` - -Like `switchMap` but applying a `startWith(SUSPENSE)` to the inner observable.