DEV Community

Johann Schuster
Johann Schuster

Posted on

Sharing UI Event State in React With RxJS

This piece is a breakdown of a nifty solution to sharing UI event data between React components, which has been a recurring challenge for me over time. It’s a strategy I recently used in a React/ TypeScript project, but it’s applicable to any front-end JavaScript framework.
This piece is primarily about reactive programming using ReactiveX streams, via the RxJS JavaScript library.

The Problem

Generally, click handlers (or any UI event handlers) in React are somehow bound to the state, either inside the component or in a parent/child.
One of the first components we learn to write from the docs is a stateful class, with a handler that increments a value in the state or something. It works — there’s no doubt — but the combination of conforming to a top-to-bottom data flow and remaining in the virtual DOM sphere without querying the document requires trade-offs. Things to keep in mind:

  • Reconciliation: Is all the diffing really worth it for UI events? This has a high impact on low-CPU devices. If your app needs a smooth, complex UI, you’re at risk of blocking the UI because of it.
  • Stateful UI components: If you need to make a request based on a DOM event in another part of the app (example to follow), maintaining a separation of concerns will be a challenge. Remember we want to keep UI components “dumb” and void of business logic.
  • Prop threading: Not the end of the world, but boy do we hate it when there’s a lot of it.

It’s for the above reasons that using frameworks like React aren’t recommended for TV-based applications.

I recently needed to access innerHTML data from a component that was multiple children deep in the component hierarchy. On clicking the item, I needed to use its value to make a request in the outer most parent. Like this:

Alt text of image

The UI is determined by the shape of a network response that contains a reference to the component types and data contained within it, so it’s important the list content and request itself be as content-agnostic as possible.

The Solution: Rx, BehaviorSubject

For the last year or so, I’ve been working on web-based applications that run on a living room TV’s and gaming consoles. The high user expectations Netfilx, Hulu, and other services set — together with the difficulty of building for TV browsers — makes it an interesting challenge, and one tool we have learned to be extremely effective for our needs is RxJS.

Rx is an implementation of a programming paradigm called reactive programming that’s used across multiple languages — in JavaScript’s case, RxJS. Regardless of your front-end language preference, an event-driven UI can become complex and reach a point where thinking in streams that respond to events as they happen becomes easier to deal with than state changes as events happen.

The idea here is to store a reference to the value of a selected DOM element and access it in other sections of our application (not limited to the UI, though). We want to subscribe to the values that get emitted by this storage mechanism and update them when the user selects a new one. The implementation is simple and consists of the following steps:

  • Create a new BehaviorSubject with a default value (the store that we’ll subscribe to in other components)
  • Create a method to update the subscription
  • Implement the click handler
  • Subscribe to the BehaviorSubject to get the latest emitted value

The code looks like this in order:

// 1: create the BehaviorSubject
export const featuredCategory$ = new BehaviorSubject("").pipe(
  distinctUntilChanged(),
  skip(1)
);
// 2: create a method to update the BehaviorSubject
const setfeaturedCategory = (category: string): void => featuredCategory$.next(index);

We can now use the BehaviorSubject to subscribe and update:

// 3: Implement the click handler
<li onClick={(category): void => setfeaturedCategory(category)}>{category}</li>
// 4: Subscribe to the behaviorSubject to get the latest emitted value

// <=== Anywhere in our app ===>
import { featuredCategory$ } from "component";
featuredCategory$.subscribe((category: string): void => this.setState({ selectedCategory: category }))

As you can see, we now read our state with much more simplicity. Below are some great advantages of using this method.

  • Composition: Because Rx is all about streams, it’s easy to use them in combination with other streams if I need to.

  • Flexibility: RxJS ships with a bunch of methods I can use to manipulate my streams as needed — like if I needed to delay the emission of the emitted value only the first time on page load, for example.

  • Control: If I wanted to stop listening to value changes after a certain condition is met, all I have to do is unsubscribe.

featuredCategory$.unsubscribe();

Pretty cool, eh? We’re only starting to scratch the surface of this powerful tool. I thought about sharing this nifty trick just in case you find yourself in a similar situation. Hope it’s helpful!

Top comments (5)

Collapse
 
mateiadrielrafael profile image
Matei Adriel

Why would you do it that way when you can just use rxjs-hooks

Collapse
 
jschuster profile image
Johann Schuster • Edited

This isn't meant to be a replacement ;) I tried to emphasize that you can use a behaviourSubject to listen to changes anywhere in your app, especially outside of hooks reach. This is only a small example of many UI streams that my app needs to react to and the target devices are unable to handle all the calculations React makes under the hood. Lastly, the post serves as an intro to "reactive programming", not opposing React api's at all :)

Collapse
 
mateiadrielrafael profile image
Matei Adriel

Well, when I was googled rxjs react and found something similar to this I wanted to drop the idea since just having to subscribe/unsubscirbe manually inside lifecycle methods seemed ugly to me, but then I discovered rxjs-hooks and haven't looked back ever since

Thread Thread
 
jschuster profile image
Johann Schuster

Nice 👍

Thread Thread
 
jschuster profile image
Johann Schuster

Nice 👍 will check it out !