You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
It's important that the observable returned by `getObservable` shouldn'tmaketheside-effect (orrequest) onsubscription-Instead, itshouldtaketheneededvaluefromotherexistingstreams. See [CoreConcepts-instances](core-concepts.md#instances) formoreinfo
Copy file name to clipboardExpand all lines: docs/core-concepts.md
+14-43Lines changed: 14 additions & 43 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -4,7 +4,7 @@ title: Core Concepts
4
4
5
5
## Push vs Pull
6
6
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.
8
8
9
9
This kind of behavior is called _pull_ because the consumer (in this case, React), is the one that _requests_ the new value.
`useFirst5SpacedNumbers` is a hook that will return just a number, which is shared for all components that use it.
123
123
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.
125
125
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.
127
127
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.
129
129
130
130
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.
131
131
132
132
## Composing streams
133
133
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.
135
135
136
136
```ts
137
137
import { interval } from"rxjs"
@@ -186,14 +186,12 @@ And now the "TodoForm" component can directly call `postNewTodo` whenever the us
186
186
187
187
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.
188
188
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.
190
190
191
191
## Instances
192
192
193
193
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.
194
194
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
-
197
195
For example, if we have a list of posts, we might have an observable that has all of them in a dictionary:
198
196
199
197
```ts
@@ -221,54 +219,25 @@ import { bind } from "@react-rxjs/core"
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.
225
223
226
224
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.
227
225
228
226
`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.
229
227
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:
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.
241
238
242
239
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.
243
240
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
-
<UserDetailsdetails={details} />
254
-
<UserPostsposts={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:
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
-
272
241
## Error boundaries
273
242
274
243
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"
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.
330
301
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.
332
303
333
304
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.
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:
0 commit comments