How to use RxJS with React

How to use RxJS with React

Published:

There are multiple ways to integrate RxJS into your React app. Which one is right for you? In this post, we will explore some integration options.

Cover image

Integrating RxJS with React

As a React developer, you should ask yourself whether RxJS is really necessary for your project. I would recommend using it only if you really have a strong use case for it, like complex event and message handling, or state management. If you are uncertain whether you should really integrate RxJS into your app, you can have a read of Should I Learn RxJS? .

To keep things simple, we will integrate a basic Observable in our examples.

export const count$ = timer(0, 1000);

It should emit an increasing value at every full second.

Let’s now take a look at how to connect React’s class components to RxJS Observables.

Class Components

Even though class components are deprecated in React, it can still be useful to know this integration option, if you have to deal with legacy code.

You can implement componentDidMount() to start listening to an Observable and componentWillUnmount() to unsubscribe again. When new values are emitted, you can change the component’s state, which triggers a re-rendering of the component.

export class CountClass extends React.Component {
  state = { count: 0 };

  #sub: Subscription | null = null;

  componentDidMount(): void {
    this.#sub = count$.subscribe((count) =>
      this.setState({ count }),
    );
  }

  componentWillUnmount(): void {
    this.#sub?.unsubscribe();
  }

  render() {
    return <>{this.state.count}</>;
  }
}

This approach is quite straight-forward, but there is some boilerplate in this approach, and it’s easy to forget to unsubscribe again, which can lead to memory leaks.

So let’s try a functional approach now.

Functional Components with hooks

When writing functional components in React, we can use useEffect() to handle our RxJS subscriptions. We can then update state, created via the useState() hook, to re-render the component.

export function CountFunction() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const sub = count$.subscribe((count) =>
      setCount(count),
    );
    return () => sub.unsubscribe();
  }, []);

  return <>{count}</>;
}

Returning the teardown function is necessary to unsubscribe, as soon as the component gets unmounted.

This is less verbose than the class-based approach above, but we still have optimization potential.

Custom hook

We can get rid of some Overhead by creating our own hook that combines the useState() and useEffect() hook calls.

function useObservable<T>(
  obs$: Observable<T>,
  initialState: T,
) {
  const [state, setState] = useState(initialState);

  useEffect(() => {
    const sub = obs$.subscribe((count) => setState(count));
    return () => sub.unsubscribe();
  }, [obs$]);

  return state;
}

This hook can then be used like this:

export function CountCustomHook() {
  const count = useObservable(count$, 0);

  return <>{count}</>;
}

With this abstraction, we also don’t have to bother about subscription management anymore. So we minimized the risk of memory leaks as well.

Using a library

Of course, we can also use a library for the integration. The observable-hooks library, for example, provides a hook useObservableState() , similar to the one we wrote before. If you want to do more complex stuff, you might consider using a library like this.

Caveats

Cold Observables in Strict mode

In the strict mode of React , components are rendered twice. This will trigger subscriptions twice. If you are consuming a cold Observable, this may lead to unexpected behavior during development. So make sure that your cold Observables don’t trigger side effects that should not be triggered multiple times. Alternatively, you can use hot Observables that may replay the last emitted value.

Missing fine-grained reactivity

Some libraries allow you to update only sub-parts of the (virtual) DOM. This is called “fine-grained reactivity”. In Angular and Solid, there is the Signals approach and in Lit and AngularJS, you can use Directives to achieve a similar effect. In Vue, the Composition API also provides fine-grained reactivity.

React does not have fine-grained reactivity. This can lead to some performance drawbacks in some situations because more VDOM is re-rendered than necessary.

If you hit one of those situations, you can introduce a new component that renders only as much as necessary to reflect changes from the Observable.

Conclusion

With those integration techniques, you can easily integrate RxJS Observables into your React app. It makes sense to write your own custom hook or use a library like observable-hooks to reduce complexity and the danger of running into memory leaks.

Never stop learning, and have a nice Day.