How to replace RxJS observables with React hooks
As we saw in one of our previous articles, Doctolib is trying to help our new joiners to ramp up faster on our stack. One of the hardest things for them is to understand RxJS because it was used for just about anything and everything. As a result, we decided to get rid of it in our codebase.
Quick reminder:
When Doctolib was in its early stages, state management for React apps was still very experimental and there was no consensus about which way to go. Two emerging libraries were becoming fashionable: Redux and RxJS. The decision was made to pick RxJS, but it could have just as well been Redux.
Over time, RxJS usage grew and the library was soon imported into every file and used for just about anything and everything: app state management, tiny component states, forms, buttons, AJAX requests, etc.
In this article, I will show you a simple way to replace RxJS with React Hooks.
If you don’t know much about RxJS, I suggest you check their documentation. But basically we can summarize it with:
Observables provide support for passing messages between publishers and subscribers in your application — source
How to refactor RxJS observables with React hooks
Examples are always better than words, so today we will be working on a simple app that will render some notes which can be filtered with a search.
We will see how to refactor this piece of code that is using some RxJS & Recompact, a fork of Recompose.
Note: this is untested code for the sake of the example.
1. Choose one observable that is preferably not used everywhere
Before you do any refactoring, try to find observables that are not used everywhere. In our example, notes$
and query$
are only used in this file so we will only have to focus on the NotesApp
. This is the best case scenario.
If your observable is used in multiple features/files in your app, you will have to work feature by feature until the observable file is not imported anymore. When this is finally the case, you will be able to delete it. Don’t do them all at once because if a feature is broken, you will have to revert the whole thing! Work feature by feature on different branch and be sure that you don’t regress your app.
Note: adding a $
at the end of the var declaration is a common convention that allows us to easily spot them.
2. Understand what each observable does
Next, try to quickly understand what all the observables in this file do:
query$
: create a Subject instance that will store the querysource$
: will fetch the notes based on the query$notes$
: will return source$ (the notes fetched from the server)
It’s very common that all the observables are using each other in the same file. That can make refactoring quite tricky because you will have to tackle them all at once.
In this example, since each of them are using one another, we will have to clean the whole file.
3. Move everything to the highest component in the hierarchy
Doing this will make the rest of the refactoring easier since we will only have to work on the NotesApp
component.
To do that, we will use the same code that connected the observables to the component before, here it’s connectObs from Recompact. It allows to transform observables to props and give them to a component.
In our example, we will move query$
and notes$
to the highest component NotesApp
. It means that NotesApp
will have the props query
and notes
and it will be able to give them to its children Search
and List
, just like this:
That would look like this in the code:
4. Start replacing each observable with React Hooks, one by one.
React hooks were introduced with the 16.8 release. If you are not familiar with them yet, I highly suggest you read the documentation first. But to briefly summarize:
React Hooks are functions that let us hook into the React state and lifecycle features from function components.
The hooks that we are going to use today are:
- useState: Persist value between renders, trigger re-render.
- useEffect: Side effects that run after render, same as componentDidMount and componentDidUpdate.
— source
Back to our refactoring, what we want to do is to fetch notes from our API, but for that to happen, we first need to have the query
in the state of our functional component. By default, it will be an empty string. We can use the useState hook directly like this :
The useState hook return a pair of values: the current state and a function that updates it.
Now that we have the query
and its setter setQuery
, we will need to fetch the notes. We will write a custom hook for that called useFetchNotes
that will take the query
as argument.
Here you can see that we initialize notes
with an empty array. Then we will use the useEffect hook. In this hook we will fetch our notes using the standard fetch() and it will return a Promise.
When we have the response of the server, we will set the response inside the state notes
using the setter setNotes
and then it will display the notes.
Because useEffect run after each render, it will fetch notes each time the component re-render. To prevent this infinite loop, useEffect takes a second argument that will allow the component to skip this effect if its value is the same as before. If we takes our example, if our component re-render while query
is the same as before, then it will be skipped and not trigger an another http request.
Here is a GIF that will explain the lifeCycle of our component:
And the final result of our code would look like this:
5. Be sure to have good test coverage
While refactoring, it’s easy to miss some intended feature that the observables provide. Having good test coverage allows you to be more confident when refactoring.
Here in Doctolib, we currently have around 12,000 integration + unit tests so we can generally refactor code without worrying it might break the application.
6. Delete the observable file
When you think you’re finished, delete the observable file and see if it breaks anything. If not, congrats you’re done! 🎊
Conclusion
If you are still afraid of embarking upon this task, don’t worry, I was afraid too at the beginning.
Just remember to follow these steps:
- Choose one observable that is not used everywhere.
- See what logic or transformation it is responsible for.
- Move everything to the highest component.
- Replace the observables with React Hooks.
If you follow that, you will see that it’s not such a hard task, you just need to go step by step. So just throw yourself into the lion-pit. And don’t forget to commit your progress as you go!
Happy refactoring!🤓
If you found this tutorial helpful and feel like building amazing functionality with React Hooks, come apply at Doctolib to help us improve healthcare in both France and Germany