React is a technology (library) that allows you to create interfaces both in the browser and in mobile applications. The component approach and one-way data flow, as a basic concept, have been appreciated in the world of web development and are now used as the main tool for building interfaces in many projects.


As applications become more complex, it can be difficult to handle state and asynchronous data correctly. That's where RxJS (reactive extensions for JavaScript) can help us. RxJS provides powerful tools for working with asynchronous data streams. In this article, we'll look at how to make React more reactive by mixing it with RxJS and share some techniques to get the most out of this combination.

Understanding the “reactivity“ of React

React’s “reactivity” revolves around changes in a component's state. When a component's state updates, React re-renders that component along with its child elements. While this model works well for most scenarios, it can get tricky when you’re dealing with more complex asynchronous tasks, such as:



Sure, React’s hooks like useState and useEffect can help with these situations. But I’d like to show you another way to tackle these challenges using RxJS. Who knows, maybe you’ll find it even better).

Introduction to RxJS: Observables, Subjects and more

RxJS (Reactive Extensions for JavaScript) is a library for reactive programming that uses so-called Observables. They allow you to work with asynchronous code in a simpler way.

Observables

Thus, observable objects are essentially data streams that you can “observe" over time. You can create them from different data sources and then use different operators to combine or modify them as needed.


import { Observable } from 'rxjs';

const observable$ = new Observable(subscriber => {
  subscriber.next('Hello');
  subscriber.next('World');
  subscriber.complete();
});

observable$.subscribe({
  next: value => console.log(value), // 'Hello', then 'World'
  complete: () => console.log('Completed') // 'Completed'
}); 

Subjects

Subjects in RxJS are kind of special because they work as both an Observable and an Observer. This means they can broadcast values to multiple Observers at once. They're often used when you want to represent some shared resource, like a stream of events or data that many parts of your app need to listen to.

import { Subject } from 'rxjs';

const subject$ = new Subject();

subject$.subscribe(value => console.log('Observer 1:', value)); // 'Observer 1: Hello', then 'Observer 1: World'
subject$.subscribe(value => console.log('Observer 2:', value)); // 'Observer 2: Hello', then 'Observer 2: World'

subject$.next('Hello');
subject$.next('World');

BehaviorSubject

A BehaviorSubject is a type of Subject that always holds the current value. It needs an initial value when you create it, and whenever someone subscribes to it, they immediately get the latest value.

import { BehaviorSubject } from 'rxjs';

const behaviorSubject$ = new BehaviorSubject('Initial Value');

behaviorSubject$.subscribe(value => console.log('Observer 1:', value)); //  'Observer 1: Initial Value'

behaviorSubject$.next('Hello'); //  'Observer 1: Hello'

behaviorSubject$.subscribe(value => console.log('Observer 2:', value)); //  'Observer 2: Hello'

behaviorSubject$.next('World'); //  'Observer 1: World', then 'Observer 2: World'


Other Subjects

Why the Angular Team Uses RxJS

The Angular team chose RxJS because it helps them handle reactive programming for things like HTTP requests, forms, and routing. As applications get bigger and more complex, RxJS makes it easier to work with asynchronous processes. It is a great tool for managing the state and side effects of web applications, which is why it is popular in large projects.

Practical Examples

A useObservable hook:

Before moving on to the examples, let's create a useObservable hook. In React, it is recommended to separate some types of behavior into custom hooks, and we will need this hook later for examples.

const useObservable = (observable$, initialValue) => {
  const [value, setValue] = useState(initialValue);

  useEffect(() => {
    const subscription = observable$.subscribe((newValue) => {
      setValue(newValue);
    });
    return () => subscription.unsubscribe();
  }, [observable$]);

  return value;
};

Hook’s explanation:

The useObservable function connects to the RxJS observable inside the React component. It allows you to use real-time data from observable in the component.


Parameters:

Initializing the state:


The useEffect function ensures that we subscribe to the observable when the component is connected, and deletes the subscription when disconnected. This avoids memory leaks. Each time the observed object sends a new value, it updates the state, which causes re-render, so the component always displays the latest data.

Examples

Now let's look at three practical examples in which RxJS can improve your React applications:

1. Debounced search input

We will create a SearchComponent that will extract data from the API while the user enters text. But instead of calling the API for each keystroke, we'll cancel the input to wait a bit before making the call.


import React from 'react';
import { Subject } from 'rxjs';
import { debounceTime, switchMap } from 'rxjs/operators';
import useObservable from './useObservable';

const searchSubject$ = new Subject();

const fetchResults = (query) => {
  return fetch(`https://api.example.com/search?q=${query}`).then((res) =>
    res.json()
  );
};

const results$ = searchSubject$.pipe(
  debounceTime(500),
  switchMap((query) => fetchResults(query))
);

const SearchComponent = () => {
  const results = useObservable(results$, []);

  const onChange = (e) => {
    searchSubject$.next(e.target.value);
  };

  return (
    <div>
      <input type="text" onChange={onChange} />
      <ul>
        {results.map((item) => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </div>
  );
};

export default SearchComponent;


Explanation:

2. Form handling with RxJS

When you have complex forms, especially with multiple inputs and checks, RxJS can simplify the job by treating the form fields as data streams.


import React from 'react';
import { BehaviorSubject, combineLatest } from 'rxjs';
import { map } from 'rxjs/operators';
import useObservable from './useObservable';

const firstName$ = new BehaviorSubject('');
const lastName$ = new BehaviorSubject('');
const email$ = new BehaviorSubject('');

const isValidEmail = (email) => /\S+@\S+\.\S+/.test(email);

const formValid$ = combineLatest([firstName$, lastName$, email$]).pipe(
  map(([firstName, lastName, email]) => {
    return (
      firstName.trim() !== '' &&
      lastName.trim() !== '' &&
      isValidEmail(email)
    );
  })
);

const FormComponent = () => {
  const isFormValid = useObservable(formValid$, false);

  const handleFirstNameChange = (e) => firstName$.next(e.target.value);
  const handleLastNameChange = (e) => lastName$.next(e.target.value);
  const handleEmailChange = (e) => email$.next(e.target.value);

  const handleSubmit = () => {
    console.log('Form Submitted');
  };

  return (
    <div>
      <input type="text" placeholder="First Name" onChange={handleFirstNameChange} />
      <input type="text" placeholder="Last Name" onChange={handleLastNameChange} />
      <input type="email" placeholder="Email" onChange={handleEmailChange} />
      <button disabled={!isFormValid} onClick={handleSubmit}>
        Submit
      </button>
    </div>
  );
};

export default FormComponent;


Explanation:

3. Real-time data updates with WebSockets

For applications such as chat systems or interactive dashboards that require real-time data, managing website connections and data flows can be difficult. RxJS simplifies this.


import React from 'react';
import { webSocket } from 'rxjs/webSocket';
import { scan } from 'rxjs/operators';
import useObservable from './useObservable';

const socket$ = webSocket('wss://example.com/socket');

const messages$ = socket$.pipe(
  scan((acc, message) => [...acc, message], [])
);

const ChatComponent = () => {
  const messages = useObservable(messages$, []);

  const sendMessage = (msg) => {
    socket$.next({ type: 'message', data: msg });
  };

  return (
    <div>
      <ul>
        {messages.map((msg, idx) => (
          <li key={idx}>{msg.data}</li>
        ))}
      </ul>
      <button onClick={() => sendMessage('Hello World!')}>Send</button>
    </div>
  );
};

export default ChatComponent;


Explanation:

The RxJS and React integration libraries

We’ve gone over how RxJS works with React and covered the basics, but there are also some really helpful npm packages out there that make hooking RxJS into React way easier. These libraries simplify the process and give you tools that are specifically made for React, making the whole integration smoother.


rxjs-hooks:

A set of React hooks that simplify working with RxJS observables in components.


Features:


react-rxjs:

A library that offers a concise way to bind RxJS observables to React components.


Features:

Conclusion

RxJS is a powerful but complex technology, and you might not need it for simpler applications. However, if you’re dealing with complex logic, like managing multiple API calls, handling user events that depend on each other, or watching for changes across different parts of your app, RxJS can be a great addition. It’s worth having a brainstorming session with your team before diving into RxJS to make sure it’s the right fit for your project.


When used in the right scenarios, integrating RxJS with React can greatly improve how you manage asynchronous data flows and state handling. By following best practices—like careful subscription management, custom hooks, and using RxJS operators—you can make your React applications more efficient and scalable. RxJS gives you the power of reactive programming, making your code more flexible and easier to maintain, especially in complex, data-driven environments.