Since 2014 almost every week I've heard something about Rx. I've read many Rx docs, articles, and code examples. But I still had no idea why I should move to reactive programming paradigm if I can successfully solve my tasks with element.addEventListener(…)
.
In 2020 I found an awesome way to make complex react-redux components dumb and testable with Rx. And I'd like to share it with you in this article.
This article doesn't include Rx basics. If need you may find it here.
To see the Rx power let's start with an example.
Imagine there is a website that lists famous quotes. This website's users may do the following things:
- read quotes
- mark a quote as favorite
And this website may look like on the image below.
As you may see this webpage highlight favorite quotes for signed-in users only. So this website also has a sign in popup.
So now we imagine a component connected to redux that has quotes list, user data, and favorite quotes ids. And it makes a request to get favorite quotes list when a user is signed in.
class QuotesComponent extends React.Component {
componentDidUpdate(prevProps) {
const {user} = this.props
if (undefiend === prevProps.user && undefined !== user) {
dispatch(loadFavorites(user.id))
}
}
// other methods
}
With current realization, this component knows not only about things to render. This component also contains some logic to make requests at the right moment.
And Rx comes here.
redux-observable is an awesome library that allows you to work with redux actions and state like with a stream. The core primitive to do that is Epic.
To make the QuotesComponent
simpler we will do the following steps:
- replace the
componentDidUpdate
implementation withcomponentDidMount
- create an epic to load favorite quotes ids for a user
With redux-observable we don't need to track the user prop more. So we may just enqueue favorites loading in componentDidMount
:
class QuotesComponent extends React.Component {
componentDidMount() {
dispatch(enqueueLoadFavorites())
}
// other methods
}
Why we don't need to track the user prop? Because we will create an epic that will do that for us.
const enqueueLoadFavoritesEpic = (action$, state$) => action$.pipe(
filter(action => action.type === 'enqueueLoadFavorites'),
switchMap(() => {
const user = selectUser(state$.value)
if (undefined === user) {
// wait for signing in before loading favorites
return state$.pipe(
map(state => selectUser(state)),
filter(user => user !== undefined)
take(1),
switchMap(() => {
return of(loadFavorites(user.id))
}),
)
}
return of(loadFavorites(user.id))
}),
)
const loadFavoritesEpic = () => {
// do some async work, requests, get data and dispatch result action
}
The enqueueLoadFavoritesEpic
doing the following steps:
- wait for enqueue action
- check for signed-in user
- dispatch load action if a user is signed in
- create a store observer that waits for a signed-in user to dispatch load action
Well as you see redux-observable is a great tool to handle pending actions depends on the store. redux-observable makes your components dumb and gets a way to test pending actions with unit tests.
Top comments (1)
Hey, Max! Nice article!
N.B: for local events/state management there are Observable hook wrappers, like observable-hooks (there are a couple more implementations)
Also, check out my experiment with JSX+RxJS:
🐶 Truly reactive! Rx+JSX experiment
Kostia Palchyk ・ Dec 12 '19 ・ 3 min read
Not React, but looks interesting, imho 🙂
GL