Skip to content

Commit 410c3ef

Browse files
volivaVictor Oliva
authored andcommitted
update getting started and core concepts
1 parent 20e45a5 commit 410c3ef

File tree

5 files changed

+89
-31
lines changed

5 files changed

+89
-31
lines changed

docs/core-concepts.md

Lines changed: 61 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -123,9 +123,57 @@ const [useFirst5SpacedNumbers, first5SpacedNumbers$] = bind(
123123

124124
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-
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.
126+
React-RxJS provides different ways of addressing this: The most simple one is to declare the default value for that hook by using the optional argument in `bind`:
127+
128+
```tsx
129+
import { interval } from "rxjs"
130+
import { take } from "rxjs/operators"
131+
import { bind } from "@react-rxjs/core"
132+
133+
const [useFirst5SpacedNumbers, first5SpacedNumbers$] = bind(
134+
interval(1000).pipe(take(5)),
135+
0 // Default value
136+
)
137+
138+
function NumberDisplay() {
139+
const number = useFirst5SpacedNumbers()
140+
141+
return <div>{number}</div>;
142+
}
143+
144+
function App() {
145+
return <NumberDisplay />
146+
}
147+
```
148+
149+
When a React-RxJS hook has a default value and no one is subscribed to its observable, on the first render it will return that value, and then it will safely subscribe to the source after mounting. If the underlying observable did have a subscription before the component was mounted, it will directly get the current value instead.
150+
151+
If you don't give it a default value, you will need to make sure that observable has a subscription active before the Component that uses that hook is called. React-RxJS has a utility that helps with this: `<Subscribe source$={stream}>{ children }</Subscribe>` will render `{ children }` only after subscribing to its `source$`. `Subscribe` also subscribes to all the observables used by its children (as if it were a React's Context), so in this case we can just omit `source$`
127152

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.
153+
154+
```tsx
155+
import { interval } from "rxjs"
156+
import { take } from "rxjs/operators"
157+
import { bind, Subscribe } from "@react-rxjs/core"
158+
159+
const [useFirst5SpacedNumbers, first5SpacedNumbers$] = bind(
160+
interval(1000).pipe(take(5))
161+
)
162+
163+
function NumberDisplay() {
164+
const number = useFirst5SpacedNumbers()
165+
166+
return <div>{number}</div>;
167+
}
168+
169+
function App() {
170+
return <Subscribe>
171+
<NumberDisplay />
172+
</Subscribe>
173+
}
174+
```
175+
176+
Keep in mind that `<Subscribe>` will hold a subscription to the observables until it gets unmounted, you need to decide where to put these `<Subscribe>` boundaries on your application so that subscriptions get cleaned up properly.
129177

130178
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.
131179

@@ -162,32 +210,29 @@ const [useTodos, todo$] = bind(ajax.getJSON("/todos"))
162210

163211
And of course, this will work: Any component can use `useTodos` to get the list of todos.
164212

165-
However, there are some times where we need to use data coming directly from the user. This is where RxJS `Subject`s come into play.
213+
However, there are some times where we need to use data coming directly from the user. This is where RxJS `Subject`s come into play. In React-RxJS we've abstracted this into *signals*, which separate the producer and the consumer of that subject.
166214

167-
With a `Subject` you can create an entry point for your streams. For example, in a local todos app, you can define your state as:
215+
With a signal you can create an entry point for your streams. For example, in a local todos app, you can define your state as:
168216

169217
```ts
170-
import { Subject } from "rxjs"
171-
import { scan, startWith } from "rxjs/operators"
218+
import { scan } from "rxjs/operators"
172219
import { bind } from "@react-rxjs/core"
220+
import { createSignal } from "@react-rxjs/utils"
173221

174-
const newTodos = new Subject()
175-
const postNewTodo = (todo) => newTodos.next(todo)
222+
const [newTodos$, postNewTodo] = createSignal();
176223

177224
const [useTodoList, todoList$] = bind(
178-
newTodos.pipe(
179-
scan((acc, todo) => [...acc, todo], []),
180-
startWith([]),
225+
newTodos$.pipe(
226+
scan((acc, todo) => [...acc, todo], [])
181227
),
228+
[]
182229
)
183230
```
184231

185232
And now the "TodoForm" component can directly call `postNewTodo` whenever the user creates a todo, and the change will be propagated down to the list.
186233

187234
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.
188235

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.
190-
191236
## Instances
192237

193238
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.
@@ -208,6 +253,8 @@ const [usePost, post$] = bind((id: string) =>
208253

209254
And now the component can use `usePost(id)` by passing it's own id, and that component will re-render only when that post changes. The second parameter returned, `post$`, it's actually also a function so that it can be composed in other streams: `post$(id)` returns the observable instance that emits Posts for that specific id.
210255

256+
Lastly, do not use this overload of `bind` as a way of calling the server. React-RxJS' mental model is that the observables are state, using bind to create a hook `useCreateUser(userName, email)` that makes the action of creating a new user won't work as you'd like to. Instead, create a [`signal`](#entry-points) and have an observable depend on that signal to send the request to the server.
257+
211258
## Suspense
212259

213260
In an earlier example:
@@ -225,7 +272,7 @@ Well, React added a feature called Suspense. With Suspense, we can represent val
225272

226273
`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.
227274

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:
275+
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. `bind` also takes an optional parameter for the default value, which guarantees that the hook won't invoke suspense:
229276

230277
```ts
231278
import { ajax } from "rxjs/ajax"

docs/examples/CharacterCounter.jsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import React from "react"
2-
import { Subject } from "rxjs"
3-
import { map, startWith } from "rxjs/operators"
2+
import { map } from "rxjs/operators"
43
import { bind, Subscribe } from "@react-rxjs/core"
4+
import { createSignal } from "@react-rxjs/utils"
55

6-
const textSubject = new Subject()
7-
const setText = (newText) => textSubject.next(newText)
8-
const [useText, text$] = bind(textSubject.pipe(startWith("")))
6+
const [textChange$, setText] = createSignal();
7+
const [useText, text$] = bind(textChange$, "")
98

109
function TextInput() {
1110
const text = useText()
@@ -15,6 +14,7 @@ function TextInput() {
1514
<input
1615
type="text"
1716
value={text}
17+
placeholder="Type something..."
1818
onChange={(e) => setText(e.target.value)}
1919
/>
2020
<br />

docs/getting-started.md

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,13 @@ yarn add rxjs @react-rxjs/core
2424
`@react-rxjs/core` exports a function called `bind` which is used to connect a stream to a hook.
2525

2626
```tsx
27-
import { Subject } from "rxjs"
28-
import { startWith } from "rxjs/operators"
2927
import { bind } from "@react-rxjs/core"
28+
import { createSignal } from "@react-rxjs/utils"
3029

31-
const textSubject = new Subject()
32-
const setText = (newText) => textSubject.next(newText)
33-
const [useText, text$] = bind(textSubject.pipe(startWith("")))
30+
// A signal is an entry point to react-rxjs. It's equivalent to using a subject
31+
const [textChange$, setText] = createSignal();
32+
33+
const [useText, text$] = bind(textChange$, "")
3434

3535
function TextInput() {
3636
const text = useText()
@@ -40,6 +40,7 @@ function TextInput() {
4040
<input
4141
type="text"
4242
value={text}
43+
placeholder="Type something..."
4344
onChange={(e) => setText(e.target.value)}
4445
/>
4546
<br />
@@ -58,7 +59,11 @@ import { bind, Subscribe } from "@react-rxjs/core"
5859
// Previously...
5960
// const [useText, text$] = bind(...);
6061

61-
const [useCharCount, charCount$] = bind(text$.pipe(map((text) => text.length)))
62+
const [useCharCount, charCount$] = bind(
63+
text$.pipe(
64+
map((text) => text.length)
65+
)
66+
)
6267

6368
function CharacterCount() {
6469
const count = useCharCount()
@@ -82,7 +87,7 @@ function CharacterCounter() {
8287
}
8388
```
8489

85-
This is shown as:
90+
The interactive result:
8691

8792
<BrowserOnly>
8893
{() => <CharacterCounter />}

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
"dependencies": {
1212
"@docusaurus/core": "^2.0.0-alpha.63",
1313
"@docusaurus/preset-classic": "^2.0.0-alpha.63",
14-
"@react-rxjs/core": "^0.6.1",
14+
"@react-rxjs/core": "^0.8.0",
15+
"@react-rxjs/utils": "^0.8.2",
1516
"clsx": "^1.1.1",
1617
"react": "^16.8.4",
1718
"react-dom": "^16.8.4",

yarn.lock

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

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==
1591+
"@react-rxjs/core@^0.8.0":
1592+
version "0.8.0"
1593+
resolved "https://registry.yarnpkg.com/@react-rxjs/core/-/core-0.8.0.tgz#b73c0480a457b654fdcd6d6ede36bbd99c691b2e"
1594+
integrity sha512-LvmVEel9MPykAJoI0oZafmui2JOU18bmIf0X0P+oEtVii0LNaj8p/3eL52NfWgsQd6hSrjevSwNbbrScsYuaFg==
1595+
1596+
"@react-rxjs/utils@^0.8.2":
1597+
version "0.8.2"
1598+
resolved "https://registry.yarnpkg.com/@react-rxjs/utils/-/utils-0.8.2.tgz#b662dd9abc03de382bfdcf08e0a2890329e288c7"
1599+
integrity sha512-oFDCzv9cMdzXqNTnOz70LG+Lj8LoyQ41uXosy7kbWL0BvDuGt08oZjlKr19VxXcLb8zWr5MkU5s+eofBEA8zZA==
15951600

15961601
"@sindresorhus/is@^0.14.0":
15971602
version "0.14.0"

0 commit comments

Comments
 (0)