Master Cross-Component Communication with React & RxJS

Master Cross-Component Communication with React & RxJS
September 17, 2021
React and RxJS form an amazing combination for cross-component communication, especially in large projects. The downside is that not every frontend developer can quickly grasp Observables, Subjects, and successfully emitting events quickly, simply because there is so much RxJS offers. This is why we wrote this article to show you some easy-to-understand and completely reusable code examples that will help you gain an understanding of this robust library.

Complex web application requirements challenge developers to use other methods of cross-component communication and asynchronous data manipulation. If you’re a React developer, one of the best ways to achieve all of the aforementioned, while having a robust and highly performant web application, is to use React with RxJS. RxJS is based on a programming paradigm known as reactive programming, a concept of writing code based on events. This is why RxJS introduces key terms such as Observables, Observers, and Subjects to achieve efficient and elegant callback-based event handling.

To quickly explain the three key terms mentioned previously:

  • Observable - the core building block of RxJS - represents a stream of data
  • Observer - a collection of callbacks listening to values provided by Observables
  • Subject - an event or value emitter to one or multiple Observers

The best way to show how React with RxJS works is with a practical demo application. Note that it will include built-in React Hooks, a pattern most React developers are already familiar with, and one that tidies up Observable-based event handling with RxJS into a neat and performant package.

We at SABO prefer using RxJS whenever our projects require independent components to listen to any number of commonly shared events, which allows us to provide clean, yet amazing and powerful cross-component communication to our clients. Given the fact that we are not really able to share our client projects, we would like to demonstrate our approach with a simple example. In our alarm clock app, the user will see the countdown, as well as the status on the screen. There are two buttons: Snooze (which delays the alarm for another 5 seconds) and Dismiss (which turns off the countdown and wishes you a nice day).

The rendered React component will look like this:

{% c-block language="js" %}
function App() {
 return (
   <>
     <h3>{"Alarm Clock ⏰"}</h3>
     <StatusIndicator observable$={observable$} />
     <Actions actions$={actions$} />
   </>
 );
}
})
{% c-block-end %}

Although the final product is simple and feasible for a single-file React with RxJS project, we will split it up for the purposes of cross-component communication. Notice the following structure:

Our root component (index.js) imports several custom components from the components/index.js file, the StatusIndicator and the Actions component. In addition, we see several RxJS packages and operators used to manipulate events and values fired by an RxJS Subject. To make handling simpler, all RxJS constants are defined outside of the React functional component.

The first three lines define an RxJS Subject and assign Snooze and Dismiss actions to Observables. Right after that, we define a countdown$ Observable, which triggers an event every n milliseconds (as defined by the interval(n, () => {}) RxJS function), which we are using to decrease the timer by 1 second, and to then share() or multicast the triggered event to all subscribers.

The snoozableAlarm$ is another Observable, which we’re using to subscribe to the previously defined countdown$ Observable. After reaching 0, it will fire the “Wake up!” message. The snoozableAlarm$ Observable also adds snooze$ to the pipe, so that it can handle clicking the Snooze button, therefore resetting the counter back to 5 seconds.

Lastly, the main Observable, which we’re naming observable$, will subscribe to snoozableAlarm$, and listen to it until the dismiss$ Observable triggers (which occurs once we click the Dismiss button). This RxJS Observable is the one we’ll subscribe to in our React component.

{% c-block language="js" %}
const actions$ = new Subject();
const snooze$ = actions$.pipe(filter((action) => action === "snooze"));
const dismiss$ = actions$.pipe(filter((action) => action === "dismiss"));

const countdown$ = interval(1000).pipe(
 // Define a starting value
 startWith(5),
 // Emit the current accumulation state after each update
 scan((time) => time - 1),
 // Emit values as long as each value satisfies the criteria
 takeWhile((time) => time > 0),
 // Multicast
 share()
);

const snoozeableAlarm$ = concat(countdown$, of("Wake up! 🎉")).pipe(
 // Re-subscribe to the snooze$ Observable once it triggers
 repeatWhen(() => snooze$)
);

const observable$ = concat(
 // Emit further values until the dismiss$ Observable emits a value
 snoozeableAlarm$.pipe(takeUntil(dismiss$)),
 of("Have a nice day! ☀️")
);
{% c-block-end %}

By the way, we recommend checking up on the official RxJS API documentation for each operator and function used in this documentation, as you will rely on a majority of them in every project consisting of React with RxJS. Note how all RxJS variables end with a dollar sign ($). This is part of a convention for all Observables, initially defined by Angular, so that it is easier to visually separate them from the rest of the code.

The StatusIndicator component takes in observable$, as seen above in the final render, and its purpose is to Subscribe to the observable, passing the React setState Action, so that the emitted values are processed by the React component’s state.

{% c-block language="js" %}
import { useEffect, useState } from "react";
export const StatusIndicator = ({ observable$ }) => {
 const [alarmIndicatorState, setAlarmIndicatorState] = useState();
 useEffect(() => {
   const sub = observable$.subscribe(setAlarmIndicatorState);
   return () => sub.unsubscribe();
 }, [observable$]);
 return <div className="display">{alarmIndicatorState}</div>;
};
{% c-block-end %}

Finally, the Actions component takes in the actions$ Observable, so that the button clicks can successfully trigger the correct action - Snooze or Dismiss.

{% c-block language="js" %}
export const Actions = ({ actions$ }) => {
return (
   <section className="actions">
     <button className="snooze" onClick={() => actions$.next("snooze")}>
       Snooze
     </button>
     <button className="dismiss" onClick={() => actions$.next("dismiss")}>
       Dismiss
     </button>
   </section>
 );
};
{% c-block-end %}

This example was based on a conference talk about VueRx, an adaptation of RxJS for Vue, and it is publicly available on CodeSandbox.

Observable-based libraries can really prove tough to understand at first, but after following examples, thoroughly reading the documentation, and following best practices from the frontend web developer community (as well as this article), we hope you can now understand (therefore, obviously, master) React with RxJS much better! Happy coding!


Share:
Stefan is a medior React developer and former content manager & technical SEO specialist from Serbia. Enjoys playing squash, having fun with friends, visiting new places, and writing interesting content about topics close to his tech stack. Dank memes enjoyer. "You miss 100% of the shots you don't take - Wayne Gretzky" - Michael Scott.

Article collaborators

SABO Newsletter icon

SABO NEWSLETTER

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

About SABO Mobile IT

We focus on developing specialized software for our customers in the automotive, supplier, medical and high-tech industries in Germany and other European countries. We connect systems, data and users and generate added value for our customers with products that are intuitive to use.
Learn more about sabo