Leveraging RxJS in React: Managing State with Observables

Krzysztof Różalski
May 02, 2024
Reactive programming with RxJS presents a powerful paradigm for managing and orchestrating asynchronous operations and event streams in JavaScript applications.

Introduction to RxJS in React applications

Integrating RxJS with React, a popular library for building user interfaces, enables developers to manage state changes efficiently and data flow across components, leading to more reactive, scalable, and maintainable applications. Let’s explore how RxJS can be utilized within React applications, focusing on a practical example involving a shared store.

Why Use RxJS with React?

Despite prevalent assertions within the React community that RxJS is non-idiomatic (which is not strictly true, by the way), it warrants a detailed examination of its potential advantages, particularly in complex scenarios.
Historically, well-regarded libraries such as Redux and Sagas have played significant roles within React ecosystems. Redux, complemented by the redux-observable middleware, highlights RxJS's capability to enhance Redux's functionalities. Conversely, despite its initial popularity, Sagas encountered limitations that RxJS could effectively address.
The necessity for RxJS emerges particularly in scenarios where user experiences are exceptionally dynamic and necessitate a "reactive" approach. In complex applications, numerous interdependent inputs and outputs require sophisticated handling where asynchronous processes are intricately chained, and intermediate states are manipulated extensively—merged, split, and filtered.
In such contexts, the question arises: if not RxJS, then what alternative? The discussion of alternatives, including my favorite, Jotai, will be explored in a subsequent article.
RxJS introduces observables, a core concept that allows multiple parts of an application to subscribe to changes in data, reacting accordingly in a declarative manner. This complements React's reactive and component-based nature.
In fact, it offers more nuanced control over asynchronous data streams and side effects beyond what is achievable with React's built-in state management tools alone. I personally consider it a handy tool for managing cross-component states at a similar level in the React tree. This approach makes state exchange between sibling components particularly efficient.

Why?

Contrary to traditional approaches, RxJS does not involve the parent component in the data exchange process, as in traditional approaches (using Context). Updating the state in one component does not trigger a re-render of the parent or sibling components that are not involved in the exchange.
The integration of RxJS opens up many possibilities that surpass the offerings of standard approaches within React. The library facilitates a more streamlined handling of continuous user inputs, simplifying the implementation of functionalities such as
✅ debouncing,
✅ cancellation of network requests,
✅ control over notifications,
✅ and state updates.
This integration allows for a seamless interplay between these operations within the React framework.
However, its steep learning curve often hinders the adoption of RxJS. Many React developers are not thoroughly acquainted with the library, which limits its broader utilization, and that is the reason why I am writing this article: to popularize RxJS in the React ecosystem.

A practical example: shared points store

Let's delve into a practical example demonstrating how RxJS can enhance state management in React, particularly when dealing with shared state across components.
We'll create a simple points system, leveraging `BehaviorSubject` from RxJS to manage the points store.
I need to mark that this example is based on a concept from a real-world project but has been simplified and stripped of project-specific details. I did so to enhance clarity and focus on the core principles of using RxJS for state management in React.

Defining our points model

First, we define our points model as follows:
[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop
This model captures the number of points, their percentage (with a custom type ensuring values between 0 and 100), an `updatedAt` timestamp, and an `isOptimistic` flag indicating whether the current points value is a temporary optimistic UI update.

Setting up BehaviorSubject

We use `BehaviorSubject` to create an observable store for our points model. `BehaviorSubject` is particularly useful here as it retains the current value and emits it to any new subscribers immediately upon subscription, ensuring components are updated with the latest state.
[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

Updating and retrieving points

We provide functions to update and retrieve points from our store, enabling components to interact with the points state reactively:

`getCurrentPoints` returns the current points value.
`setOptimisticPoints` allows setting a temporary optimistic points value, simulating a change before it's confirmed by a backend.
[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

React hook: useMyPoints

To abstract state management and improve maintainability, we create a custom React hook, `useMyPoints`, that encapsulates the logic for fetching and managing points data.
This hook exposes the current points data and loading state to any React component, eliminating the need for manual propagation through the component tree. `useMyPoints` offers a single source of truth for points data, automatically synchronized with the backend at a configured interval (e.g., 20 seconds).
[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop
Compared to using individual `useState` calls or `useContext` for points data,  `useMyPoints` reduces the number of involved components and, therefore, state management calls, too.
This leads to cleaner and more maintainable code. For instance, this approach can potentially reduce the number of state-management components used by 30% in a simple scenario with a parent and two child components. The actual reduction percentage will depend on your specific application structure, but the benefit is obvious.

Implementing useObservable hook in React with RxJS

To effectively incorporate RxJS observables into React components, the `useObservable` hook plays a pivotal role. This custom hook enables components to subscribe to observable streams and update their state accordingly. Let’s break down how the `useObservable` hook functions, especially in the context of our earlier points system example.

The useObservable hook explained

The `useObservable` hook is designed to subscribe to an RxJS `Observable` and manage the component's state based on the observable's emitted values. Here's a closer look at its implementation:
[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

Key components of useObservable


`observable: Observable<H>`
This is the RxJS `Observable` that the hook will subscribe to. The hook listens for any emissions from this observable.
`options?: UseObservableOptions<I | undefined, H>`
An optional parameter that allows you to specify initial values and callback functions for the observable's lifecycle events (like onError, and onCompleted).
`useState<H | I | undefined>(options?.initialValue)`
Initializes the state with an optional initial value. This is particularly useful for setting a default state before the observable emits its first value.
`useEffect` Hook
It manages the subscription to the observable. Upon mounting, it subscribes to the observable, and on unmounting — or when dependencies change — it cleans up by unsubscribing. This prevents memory leaks and ensures that the component only reacts to current subscriptions.
`subscription.unsubscribe()`
This cleanup function is called when the component unmounts or the observable changes, ensuring that the component no longer listens to previous subscriptions.

How useObservable enhances component reactivity

By leveraging `useObservable`, React components become highly responsive to changes in observable streams. For example, in our points system, the `useMyPoints` hook uses `useObservable` to subscribe to `myPointsSubject`. This means any component using `useMyPoints` will automatically re-render with the latest points data whenever `myPointsSubject` emits a new value.
This pattern decouples the state management from the component logic, leading to cleaner, more maintainable code. Components become mere subscribers to the data stream, reacting to state changes instead of directly managing the state.
This approach aligns well with React's declarative nature, where UIs are designed to reflect the current state, and with RxJS's reactive programming model, where data flows and changes are managed as observable streams.
In summary,`useMyPoints` facilitates a more dynamic and responsive way of handling data in React applications, perfectly complementing the reactive programming paradigm offered by RxJS.

Displaying points with MyPointsButton component

Finally, we demonstrate how to use our `useMyPoints` hook within a React component to display the current points and loading state, enhancing the component with real-time responsiveness.
[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop
In the following examples, we will explore the efficiency of working with elements within an observable stream.

React component: optimized percentage display by 5%

To further demonstrate the power of RxJS with React, let's consider a scenario where we have a leaf component that needs to display updates only when there is a distinct change in the percentage of points, up until a certain threshold.
This is particularly useful in cases where frequent minor updates to the percentage are not significant enough to require a UI update, which can help reduce unnecessary renders and improve performance.
Here’s how we can implement this:
[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

Explanation of distinct until changed implementation

`distinctUntilChanged` Operator
This operator is used here to filter out emissions that have not changed sufficiently, based on a comparison function that only considers changes greater than 5% as significant.

React component: Click-driven emoji display based on points

First, we'll create an observable from the click events and combine it with our points observable. This setup will allow us to react to each click with updated information about the points:
[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

Explanation of Code

`fromEvent`
This function creates an observable that emits events of a specific type from a given target, here listening for clicks on the entire document.
`withLatestFrom`
This operator combines the values from `clicks$` with the latest values emitted from `myPointsSubject`. It only emits when the source observable (`clicks$`) emits.
`map`
Transforms the array emitted by `withLatestFrom` into an emoji based on the `percent`. If `percent` is greater than 50, it emits 🏆, otherwise 🏅.

Conclusion

Integrating RxJS with React through observables and `BehaviorSubject` offers a robust and flexible approach to managing shared state across components. By abstracting state management into reactive data streams, applications can achieve a higher degree of responsiveness and scalability, effectively handling complex state synchronization and updates in real-time.
This example illustrates just one of the many possibilities RxJS opens up in the context of React development, encouraging developers to explore the potential of reactive programming in their applications further.
category

Krzysztof Różalski

Software Architect & Tech Lead at Vazco
Software Architect & Tech Lead with 10+ years of experience building high-performance, scalable web and mobile applications. React, React Native, Node.js, and Meteor - I've been there and done innovative software solutions. I lead and mentor teams of software engineers. Problem-solving is my superpower.

Seeking expertise in React?