loading...

React-Redux apps on Rx steroids

mbelsky profile image Max Belsky ・2 min read

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.

website mockup

As you may see this webpage highlight favorite quotes for signed-in users only. So this website also has a sign in popup.

website sign in popup mockup

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:

  1. replace the componentDidUpdate implementation with componentDidMount
  2. 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:

  1. wait for enqueue action
  2. 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.

Posted on by:

Discussion

markdown guide
 

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:

Not React, but looks interesting, imho 🙂
GL