chore(core-utils): move Suspense utils from core to utils

This commit is contained in:
Josep M Sobrepere 2020-08-26 22:06:38 +02:00
parent 047f83d0a2
commit 0d40dca5c3
13 changed files with 76 additions and 110 deletions

102
README.md
View File

@ -28,15 +28,11 @@
- [Observable overload](#observable-overload)
- [Factory of Observables overload](#factory-of-observables-overload)
- [shareLatest](#sharelatest)
- React Suspense Support
- [SUSPENSE](#suspense)
- [suspend](#suspend)
- [suspended](#suspended)
- [switchMapSuspended](#switchmapsuspended)
- [Examples](#examples)
## Installation
npm install @react-rxjs/core
## API
@ -44,34 +40,37 @@
### bind
#### Observable overload
```ts
const [useCounter, sharedCounter$] = bind(
clicks$.pipe(
scan(prev => prev + 1, 0),
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.
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.
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 [useStory, getStory$] = bind((storyId: number) =>
getStoryWithUpdates$(storyId),
)
const Story: React.FC<{id: number}> = ({id}) => {
const story = useStory(id);
const Story: React.FC<{ id: number }> = ({ id }) => {
const story = useStory(id)
return (
<article>
@ -81,25 +80,27 @@ const Story: React.FC<{id: number}> = ({id}) => {
)
}
```
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.
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.
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()
filter((planet) => planet.isActive),
map((planet) => planet.name),
shareLatest(),
)
```
@ -120,10 +121,7 @@ The enhanced observables returned from `bind` have been enhanced with this opera
```ts
const story$ = selectedStoryId$.pipe(
switchMap(id => concat(
SUSPENSE,
getStory$(id)
))
switchMap((id) => concat(SUSPENSE, getStory$(id))),
)
```
@ -131,39 +129,8 @@ This is a special symbol that can be emitted from our observables to let the rea
know that there is a value on its way, and that we want to leverage React Suspense
while we are waiting for that value.
### 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.
## 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:
@ -172,7 +139,8 @@ Like `switchMap` but applying a `startWith(SUSPENSE)` to the inner observable.
import React, { Suspense } from "react"
import { Subject } from "rxjs"
import { startWith, map } from "rxjs/operators"
import { bind, switchMapSuspended } from "@react-rxjs/core"
import { bind } from "@react-rxjs/core"
import { switchMapSuspended } from "@react-rxjs/utils"
import { Header, Search, LoadingResults, Repo } from "./components"
interface Repo {
@ -189,8 +157,8 @@ 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 =>
.then((response) => response.json())
.then((rawData) =>
(rawData.items ?? []).map((repo: any) => ({
id: repo.id,
name: repo.name,
@ -202,10 +170,7 @@ const findRepos = (query: string): Promise<Repo[]> =>
)
const [useRepos, repos$] = bind(
searchInput$.pipe(
switchMapSuspended(findRepos),
startWith(null),
),
searchInput$.pipe(switchMapSuspended(findRepos), startWith(null)),
)
function Repos() {
@ -221,7 +186,7 @@ function Repos() {
return (
<ul>
{repos.map(repo => (
{repos.map((repo) => (
<li key={repo.id}>
<Repo {...repo} />
</li>
@ -232,7 +197,7 @@ function Repos() {
const [useMostRecentlyUpdatedRepo] = bind(
repos$.pipe(
map(repos =>
map((repos) =>
Array.isArray(repos) && repos.length > 0
? repos.reduce((winner, current) =>
current.lastUpdate > winner.lastUpdate ? current : winner,
@ -292,6 +257,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<!-- markdownlint-enable -->
<!-- prettier-ignore-end -->
<!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!

View File

@ -97,31 +97,3 @@ const story$ = selectedStoryId$.pipe(
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.
### 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.

View File

@ -1,9 +1,3 @@
// core
export { bind } from "./bind"
export { shareLatest } from "./operators/shareLatest"
// support for React Suspense
export { shareLatest } from "./shareLatest"
export { SUSPENSE } from "./SUSPENSE"
export { suspend } from "./operators/suspend"
export { suspended } from "./operators/suspended"
export { switchMapSuspended } from "./operators/switchMapSuspended"

View File

@ -1,6 +1,6 @@
import { TestScheduler } from "rxjs/testing"
import { from, merge, defer } from "rxjs"
import { shareLatest } from "../"
import { shareLatest } from "./"
import { withLatestFrom, startWith, map } from "rxjs/operators"
const scheduler = () =>

View File

@ -1,6 +1,6 @@
import { Observable } from "rxjs"
import internalShareLatest from "../internal/share-latest"
import { takeUntilComplete } from "../internal/take-until-complete"
import internalShareLatest from "./internal/share-latest"
import { takeUntilComplete } from "./internal/take-until-complete"
/**
* A RxJS pipeable operator which shares and replays the latest emitted value.

View File

@ -146,3 +146,31 @@ const counter$ = mergeWithKey({
startWith(0),
)
```
### 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.

View File

@ -4,3 +4,6 @@ export { collectValues } from "./collectValues"
export { collect } from "./collect"
export { mergeWithKey } from "./mergeWithKey"
export { split } from "./split"
export { suspend } from "./suspend"
export { suspended } from "./suspended"
export { switchMapSuspended } from "./switchMapSuspended"

View File

@ -1,5 +1,6 @@
import { TestScheduler } from "rxjs/testing"
import { suspend, SUSPENSE } from "../"
import { SUSPENSE } from "@react-rxjs/core"
import { suspend } from "./"
const scheduler = () =>
new TestScheduler((actual, expected) => {

View File

@ -1,6 +1,6 @@
import { ObservableInput, from } from "rxjs"
import { startWith } from "rxjs/operators"
import { SUSPENSE } from "../SUSPENSE"
import { SUSPENSE } from "@react-rxjs/core"
/**
* A RxJS creation operator that prepends a SUSPENSE on the source observable.

View File

@ -1,5 +1,6 @@
import { TestScheduler } from "rxjs/testing"
import { suspended, SUSPENSE } from "../"
import { SUSPENSE } from "@react-rxjs/core"
import { suspended } from "./"
const scheduler = () =>
new TestScheduler((actual, expected) => {

View File

@ -1,5 +1,6 @@
import { TestScheduler } from "rxjs/testing"
import { switchMapSuspended, SUSPENSE } from "../"
import { SUSPENSE } from "@react-rxjs/core"
import { switchMapSuspended } from "./"
const scheduler = () =>
new TestScheduler((actual, expected) => {