Skip to content

Commit 165401c

Browse files
committed
update docs to match latest changes
1 parent c021a58 commit 165401c

File tree

7 files changed

+54
-71
lines changed

7 files changed

+54
-71
lines changed

docs/api/core/bind.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,23 @@ Binds an Observable to React, and returns a hook and shared stream representing
88
```ts
99
function bind<T>(
1010
observable: Observable<T>,
11+
defaultValue?: T,
1112
): [() => Exclude<T, typeof SUSPENSE>, Observable<T>]
1213
```
1314

1415
#### Arguments
1516

1617
- `observable`: The source Observable to be used by the hook.
18+
- `defaultValue`: (Optional) value to return when the source hasn't emitted yet.
1719

1820
#### Returns
1921

2022
`[1, 2]`:
2123

2224
1. A React Hook that yields the latest emitted value of the Observable. If the
23-
Observable doesn't synchronously emit a value upon the first subscription, then
24-
the hook will leverage React Suspense while it's waiting for the first value.
25+
Observable doesn't synchronously emit a value, it will return the
26+
`defaultValue` if provided, otherwise it will leverage React Suspense
27+
while it's waiting for the first value.
2528

2629
2. The shared Observable that the hook uses: It also replays back the latest
2730
value emitted. It can be used for composing other streams that depend on it.
@@ -58,6 +61,7 @@ Binds an Observable factory function to React, and returns a hook and shared str
5861
```ts
5962
function bind<A extends unknown[], O>(
6063
getObservable: (...args: A) => Observable<O>,
64+
defaultValue?: T,
6165
): [(...args: A) => Exclude<O, typeof SUSPENSE>, (...args: A) => Observable<O>]
6266
```
6367

@@ -66,18 +70,15 @@ function bind<A extends unknown[], O>(
6670
- `getObservable`: Factory of Observables. The arguments of this function
6771
will be the ones used in the hook.
6872

69-
:::note
70-
It's important that the observable returned by `getObservable` shouldn't make the side-effect (or request) on subscription - Instead, it should take the needed value from other existing streams. See [Core Concepts - instances](core-concepts.md#instances) for more info
71-
:::
72-
7373
#### Returns
7474

7575
`[1, 2]`:
7676

7777
1. A React hook with the same arguments as the factory function. This hook
7878
will yield the latest update from the Observable returned from the factory function.
79-
If the Observable doesn't synchronously emit a value upon the first subscription, then
80-
the hook will leverage React Suspense while it's waiting for the first value.
79+
If the Observable doesn't synchronously emit a value, it will return the
80+
`defaultValue` if provided, otherwise it will leverage React Suspense
81+
while it's waiting for the first value.
8182

8283
2. The factory function that returns the shared Observable that the hook uses
8384
for the specific arguments. It can be used for composing other streams that depend on it.

docs/api/core/subscribe.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,26 @@ title: <Subscribe />
66
A React Component that creates a subscription to the provided Observable once
77
the component mounts, and unsubscribes when the component unmounts.
88

9+
It also acts as a Suspense boundary, rendering a fallback element until the
10+
suspended element resolves.
11+
912
```tsx
1013
const Subscribe: React.FC<{
11-
source$: Observable<any>;
12-
fallback?: JSX.Element;
14+
source$: Observable<any>
15+
fallback?: JSX.Element
1316
}>
1417
```
1518
1619
#### Properties
1720
1821
- `source$`: Source Observable that the Component will subscribe to.
19-
- `fallback`: (Optional) The JSX Element to be rendered
20-
before the subscription is created. Default: `null`.
22+
- `fallback`: (Optional) The JSX Element to be rendered before the
23+
subscription is created. Default: `null`.
2124
2225
:::note Important
2326
This Component doesn't trigger any updates.
2427
:::
2528
2629
## See also
27-
* [`useSubscribe()`](useSubscribe)
30+
31+
- [`useSubscribe()`](useSubscribe)

docs/core-concepts.md

Lines changed: 14 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ title: Core Concepts
44

55
## Push vs Pull
66

7-
Historically, React uses a **pull**-based architecture. This means that when React needs to re-render, it will call the render function of every affected component. This will return a new representation of the UI, which React can reconcile with the previous one. Any changes are then propogated to the DOM.
7+
Historically, React uses a **pull**-based architecture. This means that when React needs to re-render, it will call the render function of every affected component. This will return a new representation of the UI, which React can reconcile with the previous one. Any changes are then propagated to the DOM.
88

99
This kind of behavior is called _pull_ because the consumer (in this case, React), is the one that _requests_ the new value.
1010

@@ -121,17 +121,17 @@ const [useFirst5SpacedNumbers, first5SpacedNumbers$] = bind(
121121

122122
`useFirst5SpacedNumbers` is a hook that will return just a number, which is shared for all components that use it.
123123

124-
Something important to note, though, is that the subscription will happen as soon as there's a subscriber and it will be alive until there are no more subscribers. This means that if all of the components that subscribe to this stream unmount for a while, the latest value will be forgotten, and it will restart the stream when there's a new subscription.
124+
Something important to note, though, is that the subscription to the shared observable (in this case, `first5SpacedNumbers$`) must have an active subscription before the hook can execute. We can't rely on React renderer to make the initial subscription for us (the subscription which would trigger the side effect), because we can't rely on when rendering happens, nor if it will be interrupted or cancelled.
125125

126-
For this reason, it's important to understand that these streams only represent state. React can subscribe to any of these at any time to read the state. If the subscription is alive at that moment, then it will receive the latest value, but if it isn't, a new subscription will be made (possibly making a request to the server).
126+
This means that we need manage the subscription logic for the stream, in order to have full control over when to execute the initial subscription.
127127

128-
If you want to keep one of these streams alive for as long as you need, React-RxJS also exposes `useSubscribe(stream)` and `<Subscribe source$={stream}>{ content }</Subscribe>`, which will render `{ content }` only after it subscribed to `stream`.
128+
There are many ways of doing that, but React-RxJS provides a utility to make it easier: `<Subscribe source$={stream}>{ content }</Subscribe>` will render `{ content }` only after subscribing to its `$source`. It also acts as a Suspense boundary, so you can also provide a `fallback` element.
129129

130130
With the mental model of "streams as state", it's also worth noting that the observables returned by `bind` won't complete: If the source of that observable completes, it will keep the last value and replay it back to new subscribers, as a completion on the source means that there won't be more changes to that stream. Remember that if the subscriber count reaches 0, this state will be cleaned up, and the subscription will restart when a new observer subscribes later on.
131131

132132
## Composing streams
133133

134-
You might have noticed that `bind` returns the hook inside a tuple. This is because `bind` also exposes the stream with `shareLatest` applied so it can be easily composed.
134+
As the stream returned by `bind` is shared, it can be easily composed with other streams.
135135

136136
```ts
137137
import { interval } from "rxjs"
@@ -186,14 +186,12 @@ And now the "TodoForm" component can directly call `postNewTodo` whenever the us
186186

187187
Keep in mind that `bind` doesn't do magic. If no one is subscribed to `todoList$` (not even from the hook) then that stream won't be listening for changes on `newTodos` subject, and if a subscription happens late, the subject won't replay the todos created, so they would get lost.
188188

189-
Remember, if you have a case like this (where you are pushing a Subject but no one is subscribed to those changes), make sure you have an active subscription to the stream by using `<Subscribe source$={stream} />` or `useSubscribe(stream)`. This way, `todoList$` will update when a new value is pushed to the subject, and the result will be replayed for every new subscriber that arrives later.
189+
This can be easily prevent if the component that would call `postNewTodo` is also inside the boundary `<Subscribe source$={todoList$}>` - Which in this case it would make sense as it's probably part of the same feature.
190190

191191
## Instances
192192

193193
There are many times where you need components to access a particular instance - Classic example is a list of posts. To help with that, `bind` can also take a factory function that returns an Observable for that instance.
194194

195-
However, when using this overload it's important that this observable shouldn't make the actual request, because it's hard to manage the lifecycle of these observables (when they subscribe, and when they can clean up) - Instead, it should take the value from an existing Observable-state.
196-
197195
For example, if we have a list of posts, we might have an observable that has all of them in a dictionary:
198196

199197
```ts
@@ -221,54 +219,25 @@ import { bind } from "@react-rxjs/core"
221219
const [useTodos, todo$] = bind(ajax.getJSON("/todos"))
222220
```
223221

224-
You might be wondering - how does this _exactly_ work with React? If React is pull-based and it _needs_ a value at the time it's re-rendering, this stream won't have a value until the ajax call is resolved.
222+
You might be wondering - how does this _exactly_ work with React? If React is pull-based and it _needs_ a value at the time it's re-rendering, this stream might not have a value until the ajax call is resolved.
225223

226224
Well, React added a feature called Suspense. With Suspense, we can represent values that are not yet ready, and we can notify React when those values have been loaded.
227225

228226
`react-rxjs` comes with full support for Suspense, and it treats it as a first-class citizen. This means that by default, using a hook from a stream that hasn't emitted any value will result in that hook suspending the component.
229227

230-
Note that for this to work properly, you need to have proper Suspense boundaries throughout your component tree. If you don't want to use Suspense just yet, the solution is simple: Make sure that the stream always has a value. In our example, if we decide that `null` represents missing values, the solution is as simple as:
228+
Note that for this to work properly, you need to have proper Suspense boundaries throughout your component tree. If you don't want to use Suspense just yet, the solution is simple: Make sure that the stream always has a value. This can be done through RxJS' `startWith`, but `bind` can also take an optional parameter for the default value:
231229

232230
```ts
233231
import { ajax } from "rxjs/ajax"
234-
import { startWith } from "rxjs/operators"
235232
import { bind } from "@react-rxjs/core"
236233

237-
const [useTodos, todos$] = bind(ajax.getJSON("/todos").pipe(startWith(null)))
234+
const [useTodos, todos$] = bind(ajax.getJSON("/todos"), null)
238235
```
239236

240237
Now `useTodos` will emit `null` immediately while it's fetching data (so that we can manually handle that), instead of suspending the component, and when the ajax call is resolved, it will emit the result of that call.
241238

242239
When using Suspense, however, there's also another way to suspend a component with `react-rxjs`: by emitting `SUSPENSE`. For example, this can come in handy if you need to refresh the data because some filter has changed.
243240

244-
There's something to keep in mind though: React Suspense works in a serialized way within a Component. Imagine this example:
245-
246-
```tsx
247-
const UserProfile = () => {
248-
const details = useUserDetails()
249-
const posts = useUserPosts()
250-
251-
return (
252-
<div>
253-
<UserDetails details={details} />
254-
<UserPosts posts={posts} />
255-
</div>
256-
)
257-
}
258-
```
259-
260-
In this case, because of the way that Suspense works, these fetches will happen in sequential order. This means that initially `useUserDetails` will subscribe to `userDetails$`, which will start fetching data and suspend the component. When the fetch call resolves, `useUserDetails` will "resume" the component, and `useUserPosts` will run, subscribing and fetching the data, and suspending the component yet again.
261-
262-
This is usually a code smell. If you need to use many react-rxjs hooks in a component, each of which will do some asynchronous work, it's often better to move this logic into a single stream (and hook) by using composition:
263-
264-
```ts
265-
const [useUserDetailsAndPosts] = bind(combineLatest(userDetail$, userPosts$))
266-
```
267-
268-
Now `useUserDetailsAndPosts` will start fetching both resources and suspend the component just once for both of them.
269-
270-
However, in this particular example, there is an even better solution. Note that `UserProfile` is not using `details` or `posts` directly, so we can move the usage of those two hooks down into the components that actually use them, `<UserDetails />` and `<UserPosts />`. This way, React will render both components, and both of them will suspend at the same time, while also subscribing to both streams simultaneously.
271-
272241
## Error boundaries
273242

274243
React 16 added the concept of Error Boundaries: A way to catch errors in the component tree and show a fallback UI so it can be recovered from.
@@ -285,7 +254,7 @@ import { interval } from "rxjs"
285254
import { map, startWith } from "rxjs/operators"
286255
import { ErrorBoundary } from "react-error-boundary"
287256

288-
const [useTimedBomb] = bind(
257+
const [useTimedBomb, timedBomb$] = bind(
289258
interval(1000).pipe(
290259
map((v) => v + 1),
291260
startWith(0),
@@ -319,7 +288,9 @@ function App() {
319288
return (
320289
<div className="App">
321290
<ErrorBoundary FallbackComponent={ErrorFallback}>
322-
<Bomb />
291+
<Subscribe source$={timedBomb$}>
292+
<Bomb />
293+
</Subscribe>
323294
</ErrorBoundary>
324295
</div>
325296
)
@@ -328,6 +299,6 @@ function App() {
328299

329300
In here, `useTimedBomb` will start counting from 0 and emit an `error` in the 3rd second. React-RxJS ensures that this error will be caught in the ErrorBoundary defined for the component that's using this stream, so the fallback UI will be shown.
330301

331-
When a rxjs stream emits an error, the stream gets immediately closed. This way, if our strategy to recover from the error is to try again, when `Bomb` resubscribes to the stream it will create a new subscription and start over again.
302+
When a rxjs stream emits an error, the stream gets immediately closed. This way, if our strategy to recover from the error is to try again, when our `Subscribe` boundary resubscribes to the stream it will create a new subscription and start over again.
332303

333304
In this case, after 3 seconds it will throw an error again, but in a real-world case this might be different, and you might need different recovery strategies depending on each case. `react-error-boundary` helps by providing a declarative way to define these strategies.

docs/examples/CharacterCounter.jsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React from "react"
22
import { Subject } from "rxjs"
33
import { map, startWith } from "rxjs/operators"
4-
import { bind } from "@react-rxjs/core"
4+
import { bind, Subscribe } from "@react-rxjs/core"
55

66
const textSubject = new Subject()
77
const setText = (newText) => textSubject.next(newText)
@@ -23,7 +23,7 @@ function TextInput() {
2323
)
2424
}
2525

26-
const [useCharCount] = bind(text$.pipe(map((text) => text.length)))
26+
const [useCharCount, charCount$] = bind(text$.pipe(map((text) => text.length)))
2727

2828
function CharacterCount() {
2929
const count = useCharCount()
@@ -34,8 +34,10 @@ function CharacterCount() {
3434
export default function CharacterCounter() {
3535
return (
3636
<div>
37-
<TextInput />
38-
<CharacterCount />
37+
<Subscribe source$={charCount$}>
38+
<TextInput />
39+
<CharacterCount />
40+
</Subscribe>
3941
</div>
4042
)
4143
}

docs/getting-started.md

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ title: Getting Started
33
---
44

55
import CharacterCounter from "./examples/CharacterCounter"
6+
import BrowserOnly from '@docusaurus/BrowserOnly';
67

78
## Installation
89

@@ -52,12 +53,12 @@ function TextInput() {
5253

5354
```tsx
5455
import { map } from "rxjs/operators"
55-
import { bind } from "@react-rxjs/core"
56+
import { bind, Subscribe } from "@react-rxjs/core"
5657

5758
// Previously...
5859
// const [useText, text$] = bind(...);
5960

60-
const [useCharCount] = bind(text$.pipe(map((text) => text.length)))
61+
const [useCharCount, charCount$] = bind(text$.pipe(map((text) => text.length)))
6162

6263
function CharacterCount() {
6364
const count = useCharCount()
@@ -66,22 +67,26 @@ function CharacterCount() {
6667
}
6768
```
6869

69-
If we put everything together
70+
Something to note is that a subscription on the underlying observable must be present before the hook is executed. We can use `Subscribe` to help us with it:
7071

7172
```tsx
7273
function CharacterCounter() {
7374
return (
7475
<div>
75-
<TextInput />
76-
<CharacterCount />
76+
<Subscribe source$={charCount$}>
77+
<TextInput />
78+
<CharacterCount />
79+
</Subscribe>
7780
</div>
7881
)
7982
}
8083
```
8184

8285
This is shown as:
8386

84-
<CharacterCounter />
87+
<BrowserOnly>
88+
{() => <CharacterCounter />}
89+
</BrowserOnly>
8590

8691
## Next steps
8792

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"dependencies": {
1212
"@docusaurus/core": "^2.0.0-alpha.63",
1313
"@docusaurus/preset-classic": "^2.0.0-alpha.63",
14-
"@react-rxjs/core": "^0.1.2",
14+
"@react-rxjs/core": "^0.6.1",
1515
"clsx": "^1.1.1",
1616
"react": "^16.8.4",
1717
"react-dom": "^16.8.4",

yarn.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1588,10 +1588,10 @@
15881588
dependencies:
15891589
mkdirp "^1.0.4"
15901590

1591-
"@react-rxjs/core@^0.1.2":
1592-
version "0.1.2"
1593-
resolved "https://registry.yarnpkg.com/@react-rxjs/core/-/core-0.1.2.tgz#5c2fab5500efd7ac359c00845fbdd9b7f0b70ead"
1594-
integrity sha512-Lztpv681KC8eT5hRj+ndVw8mPRxREAx2ZI5QBSBNfvVammERV64ZqBGaeg6mnYJp5Hr0Dv4G6QME2M+kq3J5PA==
1591+
"@react-rxjs/core@^0.6.1":
1592+
version "0.6.1"
1593+
resolved "https://registry.yarnpkg.com/@react-rxjs/core/-/core-0.6.1.tgz#d2d401bdcb72baf7b5ecdf59d4b809bd044d2f0e"
1594+
integrity sha512-E2LXU+udZpaoy528IKVaIzuwcB7IEcBL9caWdnPx25B7SaoBo5BTVnuLNdTVZjjwExCBBeg0NO3Nmj408lMLvw==
15951595

15961596
"@sindresorhus/is@^0.14.0":
15971597
version "0.14.0"

0 commit comments

Comments
 (0)