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.
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).
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.
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 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');
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
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.
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;
};
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:
observable$
: This is an observable object that outputs values over time. We use $ at the end, just to show that this can be observed.initialValue
: This is the initial value before the observed object starts sending new data.Initializing the state:
value
: The current value obtained from the observed object.setValue
: A function to update the state with a new value from the observed object.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.
Now let's look at three practical examples in which RxJS can improve your React applications:
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:
searchSubject$
the instance of Subject
to send values from the input field.debounceTime
waits 500 ms without typing before sending the last value.switchMap
cancels any previous API calls if a new value is emitted.results$
and updates the component when new data arrives.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:
BehaviorSubject
for each form field to save its current value.map
operator contains the validation logic that updates the isFormValid
observable.formValid$
to enable or disable the submit button.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:
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:
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.