The purpose of the tutorial is to introduce the most important APIs of the
@react-rxjs/utils package, and for that we are going to build a simple todo-list
application. Our app will be able to do the following:
- Add todo items
- Edit todo items
- Delete todo items
- Filter todo items
- Display useful stats
Capturing user input
The first thing that we should do is to capture the events triggered by the user. Let's create some Signals for this:
Creating a single stream for all the user events
It would be very convenient to have a merged stream with all those events. However,
if we did a traditional
merge, then it would be very challenging to know the
origin of each event.
@react-rxjs/utils exposes the
operator. Let's use it:
Which is basically the same as doing this (but a lot shorter, of course 😄):
Creating a stream for each todo
Now that we have put all the streams together, let's create a stream for
each todo. And for that, we will be using another operator from
Now we have a function,
todosMap, that returns an Observable of events
associated with a given todo.
partitionByKey transforms the source observable in a way
similar to the
groupBy operator that's exposed from RxJS. However, there are
some important differences:
partitionByKeygives you a function that returns an Observable, rather than an Observable that emits Observables. It also provides an Observable that emits the list of keys, whenever that list changes (
keys$in the code above).
partitionByKeyhas an optional third parameter which allows you to create a complex inner stream that will become the "grouped" stream that is returned.
- This returned stream is enhanced with a
partitionByKeyinternally subscribes to it as soon as it is created to ensure that the consumer always has the latest value.
Collecting the GroupedObservables
We now have a way of getting streams for each todo, and we have a stream
keys$) that represents the list of todos by their ids and emits whenever
one is added or deleted. We should also like a stream that emits whenever
the state of any todo changes, and gives us access to all of them.
combineKeys() suits this purpose. Let's try it:
And with this we are ready to start wiring things up.
Wiring up a basic version
Let's start with the top-level component:
Next, let's implement the
And finally, the
That's it! We have a basic version working.
Cutting React out of the state management game
What we've done so far is pretty neat, but there are a lot of unnecessary renders going on in our application. Editing any of the todos, for example, causes the whole list to re-render. To those with experience in React development, this hardly seems noteworthy—our state is our list of todos, it lives in our TodoList component, so of course it re-renders when that state changes. With React-RxJS, we can do better. Before we proceed with the remaining features, let's relieve React of its state management responsibilities altogether.
Take a look at the stream we've bound to the TodoList component:
This is just an
Observable<Todo>, and it will emit every time any todo
gets updated—triggering a TodoList render. In fact TodoList only needs to know
which todos to display; rendering them according to their properties can be
left up to the child component, TodoItem. Therefore let's bind a list of
which todos exist. Luckily we already have a stream for that, returned above
Simple! Now we edit our TodoList component to pass just the todo id:
and teach TodoItem to get its state from the stream corresponding to that id, rather than from its parent component:
As we already know, we will need to capture the filter selected by the user:
Next, let's create a hook and a stream for the current filter:
Also, let's tell our TodoItems not to render if they've been filtered out:
Time to implement the
We will be showing the following stats:
- Total number of todo items
- Total number of completed items
- Total number of uncompleted items
- Percentage of items completed
Let's create a
useTodosStats for it:
And now let's use this hook in the
The result of this tutorial can be seen in this CodeSandbox: