Using RxJS in React Development to Manage Application State
- Transfer
The author of the material, the translation of which we are publishing today, says that here he wants to demonstrate the process of developing a simple React application using RxJS. According to him, he is not an expert in RxJS, as he himself is studying this library and does not refuse the help of knowledgeable people. His goal is to attract the attention of the audience to alternative ways of creating React-applications, to inspire the reader to do independent research. This material cannot be called an introduction to RxJS. One of the many ways to use this library in React development will be shown here.

Recently, my client inspired me to learn how to use RxJS to manage the state of React applications. When I conducted an audit of the application code of this client, he wanted to know my opinion on how to develop the application for him, given that before that he used only the local state of React. The project has reached a level where it was unreasonable to rely solely on React. At first, we talked about using Redux or MobX as state control tools . My client created a prototype for each of these technologies. But he didn’t limit himself to these technologies by creating a prototype of a React application that uses RxJS. From this point on our conversation has become much more interesting.
The application in question was a trading platform for cryptocurrency. It had many widgets whose data was updated in real time. The developers of this application, among others, had to solve the following difficult tasks:
As a result, the main difficulties faced by the developers did not relate to the React library itself, and besides, I could help them in this area. The main problem was to make the internal mechanisms of the system work correctly, such as those that linked cryptocurrency data and interface elements created by means of React. It was in this area that the RxJS capabilities were very helpful, and the prototype they showed me looked very promising.
Suppose that we have an application that, after performing some local actions, makes requests to a third-party API . It allows you to search for articles. Before making a request, we need to get the text that is used to form this request. In particular, we use this text to generate a URL for accessing the API. Here is the code of the React-component that implements this functionality.
This component lacks a state management system. The state for the property is
However, this is not the approach that we will talk about here. Instead, we want to set up an application state management system using RxJS. Let's look at how to do this using Higher-Order Component (HOC).
If you wish, you can implement similar logic in your component
Let us deal with how to manage the state of React-applications using RxJS, applying for this purpose a component of the highest order. Instead, one could implement the render props template . As a result, if you do not want to create components of a higher order for this purpose, you can use the recommended components of a higher order Recompose with
While the higher-order component RxJS does not perform any actions. It only transfers its own state and properties to the input component, which is planned to be expanded with its help. As you can see, the state of React will ultimately be managed by a higher order component. However, this state will be obtained from the observed flow. Before we start implementing HOC and using it with a component
Now let's start using the higher-order component and implementing its logic:
The component itself
After creating an observable object and subscribing to it, the thread for the request should work. However, until now the highest order component itself looks like a black box for us. We implement it:
The higher-order component gets the observed object and the object with triggers (perhaps this object containing functions can be called some more successful term from the RxJS lexicon), represented in the signature of the function.
Triggers are only passed through the HOC input component. That is why the component
The observed object uses the life cycle method
Perform a small component change
Another way to deal with this problem is to set the initial state for
If you now experience this application, the input field should work as expected. The component
Acquisition and state changes occur through observable RxJS objects, despite the fact that the internal storage of the React state is used inside the higher order component. I did not manage to find an obvious solution to the problem of streaming data from a subscription of observed objects directly to the properties of the advanced component (
Create a second stream of values with which, as well as with a property
As you can see, the parameter
Trigger is
Addition to the mechanism implemented by the function
The source object,
Now, in our system, the URL for accessing the API is constructed on the basis of two values from a combined observable object, which includes two other observable objects. In this section, we will use the URL to load data from the API. You may well be able to use the React data loading system , but when using observed RxJS objects, you need to add another observed stream to our application.
Before we work on the next observable object, we establish axios . This is the library that we will use to load data from streams into the program.
Now imagine that we have an array of articles that should display the component
For each article in the list, the use of a spare value is provided due to the fact that the API to which we refer is non-uniform. Now, the most interesting is the implementation of a new observable object, which is responsible for loading data into a React application that will visualize them.
A new observable object is, again, a combination of observable objects
Now we, again, can provide a new observable object to a higher order component. We have at our disposal the last argument of the function
There is no trigger here, since the observable object is indirectly activated by two other observable flows. Each time a value changes in the input field (
However, it is possible that we do not need it to affect the observed object every time the value in the input field changes
The operator
Now the initial values for
B is
Now the initial state can be provided to the higher order component as the third argument. In the future, we can remove the default parameter values for the component
What worries me now is that the initial state is also set in the declaration of the observed objects
The code of the software project that we were involved in here can be found here .
The main goal of this material is to demonstrate an alternative approach to developing React applications using RxJS. We hope he gave you some food for thought. Sometimes there is no need for Redux and MobX, but perhaps in such situations, RxJS will turn out to be just the right one for a particular project.
Dear readers! Do you use RxJS when developing React-applications?


How it all started
Recently, my client inspired me to learn how to use RxJS to manage the state of React applications. When I conducted an audit of the application code of this client, he wanted to know my opinion on how to develop the application for him, given that before that he used only the local state of React. The project has reached a level where it was unreasonable to rely solely on React. At first, we talked about using Redux or MobX as state control tools . My client created a prototype for each of these technologies. But he didn’t limit himself to these technologies by creating a prototype of a React application that uses RxJS. From this point on our conversation has become much more interesting.
The application in question was a trading platform for cryptocurrency. It had many widgets whose data was updated in real time. The developers of this application, among others, had to solve the following difficult tasks:
- Management of multiple (asynchronous) data retrieval requests.
- Updating, in real time, a large number of widgets on the control panel.
- Solving the problem of connectedness of widgets and data, as some widgets needed data not only from some specific sources, but also from other widgets.
As a result, the main difficulties faced by the developers did not relate to the React library itself, and besides, I could help them in this area. The main problem was to make the internal mechanisms of the system work correctly, such as those that linked cryptocurrency data and interface elements created by means of React. It was in this area that the RxJS capabilities were very helpful, and the prototype they showed me looked very promising.
Using RxJS in React
Suppose that we have an application that, after performing some local actions, makes requests to a third-party API . It allows you to search for articles. Before making a request, we need to get the text that is used to form this request. In particular, we use this text to generate a URL for accessing the API. Here is the code of the React-component that implements this functionality.
import React from'react';
const App = ({ query, onChangeQuery }) => (
<div>
<h1>React with RxJS</h1>
<input
type="text"
value={query}
onChange={event => onChangeQuery(event.target.value)}
/>
<p>{`http://hn.algolia.com/api/v1/search?query=${query}`}</p>
</div>
);
export default App;
This component lacks a state management system. The state for the property is
query
not stored anywhere, the function onChangeQuery
also does not update the state. In the usual approach, such a component is equipped with a local state management system. It looks like this:classAppextendsReact.Component{
constructor(props) {
super(props);
this.state = {
query: '',
};
}
onChangeQuery = query => {
this.setState({ query });
};
render() {
return (
<div>
<h1>ReactwithRxJS</h1>
<input
type="text"
value={this.state.query}
onChange={event =>
this.onChangeQuery(event.target.value)
}
/>
<p>{`http://hn.algolia.com/api/v1/search?query=${
this.state.query
}`}</p>
</div>
);
}
}
export defaultApp;
However, this is not the approach that we will talk about here. Instead, we want to set up an application state management system using RxJS. Let's look at how to do this using Higher-Order Component (HOC).
If you wish, you can implement similar logic in your component
App
, but you, most likely, at some point working on the application, decide to arrange such a component in the form of HOC , which is suitable for reuse.React and higher order RxJS components
Let us deal with how to manage the state of React-applications using RxJS, applying for this purpose a component of the highest order. Instead, one could implement the render props template . As a result, if you do not want to create components of a higher order for this purpose, you can use the recommended components of a higher order Recompose with
mapPropsStream()
and componentFromStream()
. In this guide, however, we will do everything on our own.importReact from 'react';
const withObservableStream = (...) => Component => {
returnclassextendsReact.Component{
componentDidMount() {}
componentWillUnmount() {}
render() {
return (
<Component {...this.props} {...this.state} />
);
}
};
};
const App = ({ query, onChangeQuery }) => (
<div>
<h1>ReactwithRxJS</h1>
<input
type="text"
value={query}
onChange={event => onChangeQuery(event.target.value)}
/>
<p>{`http://hn.algolia.com/api/v1/search?query=${query}`}</p>
</div>
);
export default withObservableStream(...)(App);
While the higher-order component RxJS does not perform any actions. It only transfers its own state and properties to the input component, which is planned to be expanded with its help. As you can see, the state of React will ultimately be managed by a higher order component. However, this state will be obtained from the observed flow. Before we start implementing HOC and using it with a component
App
, we need to install RxJS:npm install rxjs --save
Now let's start using the higher-order component and implementing its logic:
import React from'react';
import { BehaviorSubject } from'rxjs';
...
const App = ({ query, onChangeQuery }) => (
<div>
<h1>React with RxJS</h1>
<input
type="text"
value={query}
onChange={event => onChangeQuery(event.target.value)}
/>
<p>{`http://hn.algolia.com/api/v1/search?query=${query}`}</p>
</div>
);
const query$ = new BehaviorSubject({ query: 'react' });
export defaultwithObservableStream(
query$,
{
onChangeQuery: value => query$.next({ query: value }),
}
)(App);
The component itself
App
does not change. We only passed two arguments to a higher order component. We describe them:- Observed object. The argument
query
is an observable object that has an initial value, but, moreover, is outstanding, with time, new values (as this isBehaviorSubject
). Anyone can subscribe to this observable object. This is what RxJSBehaviorSubject
documentation objects say : “One of the options forSubject
an object is an objectBehaviorSubject
that uses the concept of“ current value ”. It stores the last value transmitted to its subscribers, and when a new observer subscribes to it, it immediately receives this “current value” from the objectBehaviorSubject
. Such objects are well suited for the presentation of data, new portions of which appear over time. " - The system of issuing new values for the observed object (trigger). The function
onChangeQuery()
passed through the HOC componentApp
is a normal function that passes the next value to the observed object. This function is transferred in the object, since it may be necessary to transfer to the higher-order component several such functions that perform certain actions with the observed objects.
After creating an observable object and subscribing to it, the thread for the request should work. However, until now the highest order component itself looks like a black box for us. We implement it:
const withObservableStream = (observable, triggers) => Component => {
returnclassextendsReact.Component{
componentDidMount() {
this.subscription = observable.subscribe(newState =>
this.setState({ ...newState }),
);
}
componentWillUnmount() {
this.subscription.unsubscribe();
}
render() {
return (
<Component {...this.props} {...this.state} {...triggers} />
);
}
};
};
The higher-order component gets the observed object and the object with triggers (perhaps this object containing functions can be called some more successful term from the RxJS lexicon), represented in the signature of the function.
Triggers are only passed through the HOC input component. That is why the component
App
directly receives a function onChangeQuery()
that directly works with the observed object, performing the transfer of new values to it. The observed object uses the life cycle method
componentDidMount()
for signing and the method componentDidMount()
for unsubscribing. Cancellation is needed to prevent memory leaks . In the subscription of the observed object, the function only sends all incoming data from the stream to the local storage of the React state using the command this.setState()
.Perform a small component change
App
that will eliminate the problem that arises if a higher order component does not have an initial value for the property query
. If this is not done, then, at the beginning of the work, the property query
will be equal undefined
. Due to this change, this property gets the default value.const App = ({ query = '', onChangeQuery }) => (
<div>
<h1>React with RxJS</h1>
<input
type="text"
value={query}
onChange={event => onChangeQuery(event.target.value)}
/>
<p>{`http://hn.algolia.com/api/v1/search?query=${query}`}</p>
</div>
);
Another way to deal with this problem is to set the initial state for
query
a higher order component:const withObservableStream = (
observable,
triggers,
initialState,
) => Component => {
returnclassextendsReact.Component{
constructor(props) {
super(props);
this.state = {
...initialState,
};
}
componentDidMount() {
this.subscription = observable.subscribe(newState =>
this.setState({ ...newState }),
);
}
componentWillUnmount() {
this.subscription.unsubscribe();
}
render() {
return (
<Component {...this.props} {...this.state} {...triggers} />
);
}
};
};
const App = ({ query, onChangeQuery }) => (
...
);
export default withObservableStream(
query$,
{
onChangeQuery: value => query$.next({ query: value }),
},
{
query: '',
}
)(App);
If you now experience this application, the input field should work as expected. The component
App
receives from HOC, in the form of properties, only the state query
and the function onChangeQuery
for changing the state. Acquisition and state changes occur through observable RxJS objects, despite the fact that the internal storage of the React state is used inside the higher order component. I did not manage to find an obvious solution to the problem of streaming data from a subscription of observed objects directly to the properties of the advanced component (
App
). That is why I had to use the local state of React as an intermediate layer, which, moreover, is convenient in the sense that it causes re-rendering. If you know another way to achieve the same goals - you can share it in the comments.Combining observed objects in React
Create a second stream of values with which, as well as with a property
query
, you can work in a component App
. Later we will use both values, working with them with the help of another observed object.const SUBJECT = {
POPULARITY: 'search',
DATE: 'search_by_date',
};
const App = ({
query = '',
subject,
onChangeQuery,
onSelectSubject,
}) => (
<div>
<h1>React with RxJS</h1>
<input
type="text"
value={query}
onChange={event => onChangeQuery(event.target.value)}
/>
<div>
{Object.values(SUBJECT).map(value => (
<button
key={value}
onClick={() => onSelectSubject(value)}
type="button"
>
{value}
</button>
))}
</div>
<p>{`http://hn.algolia.com/api/v1/${subject}?query=${query}`}</p>
</div>
);
As you can see, the parameter
subject
can be used to refine the query when building the URL used to access the API. Namely, materials can be searched based on their popularity or on the date of publication. Next, create another observable object that can be used to change a parameter subject
. This observable object can be used to organize communication between a component App
and a higher-order component. Otherwise, the properties passed to the component App
will not work.import React from'react';
import { BehaviorSubject, combineLatest } from'rxjs/index';
...
const query$ = new BehaviorSubject({ query: 'react' });
const subject$ = new BehaviorSubject(SUBJECT.POPULARITY);
exportdefault withObservableStream(
combineLatest(subject$, query$, (subject, query) => ({
subject,
query,
})),
{
onChangeQuery: value => query$.next({ query: value }),
onSelectSubject: subject => subject$.next(subject),
},
)(App);
Trigger is
onSelectSubject()
not new. He, by means of the button, can be used to switch between two states subject
. But the observable object transmitted to a higher-order component is something new. It uses the function combineLatest()
from RxJS to combine the most recently issued values from two (or more) observed flows. After the subscription to the observed object is registered, if either of the values ( query
or subject
) is changed, the subscriber will receive both values. Addition to the mechanism implemented by the function
combineLatest()
, is her last argument. Here you can specify the order of returning the values generated by the observed objects. In our case, we need them to be represented as an object. This will allow, as before, to destructurize them in a higher-order component and write them to the local state of React. Since we already have the necessary structure, we can omit the step of wrapping the object of the observed object query
....
const query$ = new BehaviorSubject('react');
const subject$ = new BehaviorSubject(SUBJECT.POPULARITY);
exportdefault withObservableStream(
combineLatest(subject$, query$, (subject, query) => ({
subject,
query,
})),
{
onChangeQuery: value => query$.next(value),
onSelectSubject: subject => subject$.next(subject),
},
)(App);
The source object,
{ query: '', subject: 'search' }
as well as all other objects produced by the combined stream of observed objects, are suitable for destructuring them in a higher-order component and for writing the corresponding values to the local state of React. After updating the state, as before, rendering is performed. When you launch an updated application, you should be able to change both values using the input field and the button. Changed values affect the URL used to access the API. Even if only one of these values changes, the other value maintains its last state, since the function combineLatest()
always combines the latest values from the observed flows.Axios and RxJS in React
Now, in our system, the URL for accessing the API is constructed on the basis of two values from a combined observable object, which includes two other observable objects. In this section, we will use the URL to load data from the API. You may well be able to use the React data loading system , but when using observed RxJS objects, you need to add another observed stream to our application.
Before we work on the next observable object, we establish axios . This is the library that we will use to load data from streams into the program.
npm install axios --save
Now imagine that we have an array of articles that should display the component
App
. Here, as the value of the corresponding parameter by default, we use an empty array, acting in the same way as we did with other parameters....
const App = ({
query = '',
subject,
stories = [],
onChangeQuery,
onSelectSubject,
}) => (
<div>
...
<p>{`http://hn.algolia.com/api/v1/${subject}?query=${query}`}</p>
<ul>
{stories.map(story => (
<likey={story.objectID}>
<ahref={story.url || story.story_url}>
{story.title || story.story_title}
</a>
</li>
))}
</ul>
</div>
);
For each article in the list, the use of a spare value is provided due to the fact that the API to which we refer is non-uniform. Now, the most interesting is the implementation of a new observable object, which is responsible for loading data into a React application that will visualize them.
import React from'react';
import axios from'axios';
import { BehaviorSubject, combineLatest } from'rxjs';
import { flatMap, map } from'rxjs/operators';
...
const query$ = new BehaviorSubject('react');
const subject$ = new BehaviorSubject(SUBJECT.POPULARITY);
const fetch$ = combineLatest(subject$, query$).pipe(
flatMap(([subject, query]) =>
axios(`http://hn.algolia.com/api/v1/${subject}?query=${query}`),
),
map(result => result.data.hits),
);
...
A new observable object is, again, a combination of observable objects
subject
and query
, since, in order to build a URL with which we will access the API for loading data, we need both values. In the pipe()
observable object method , we can use the so-called “RxJS operators” to perform certain actions with values. In this case, we are mapping two values to be placed in the query, which is used by axios to get the result. We here use the operator flatMap()
and not map()
to access the result of the successfully resolved promise and not the most returned promise. As a result, after subscribing to this new observable object, each time a new value arrives in the system, from other observable objects, subject
orquery
, a new request is executed, and the result is in the subscription function. Now we, again, can provide a new observable object to a higher order component. We have at our disposal the last argument of the function
combineLatest()
; this makes it possible to directly map it to a property with a name stories
. In the end, this is how this data is already used in the component App
.export default withObservableStream(
combineLatest(
subject$,
query$,
fetch$,
(subject, query, stories) => ({
subject,
query,
stories,
}),
),
{
onChangeQuery: value => query$.next(value),
onSelectSubject: subject => subject$.next(subject),
},
)(App);
There is no trigger here, since the observable object is indirectly activated by two other observable flows. Each time a value changes in the input field (
query
) or a click is made on the button ( subject
), this affects the observed object fetch
that contains the most recent values from both streams. However, it is possible that we do not need it to affect the observed object every time the value in the input field changes
fetch
. In addition, we would not want to fetch
be affected if the value is represented by an empty string. That is why we can expand the observed object query
using the operatordebounce
, which allows you to eliminate too frequent request changes. Namely, thanks to this mechanism, a new event is accepted only after a specified time after the previous event. In addition, we use an operator here filter
that filters out the events of the stream if the string query
is empty.import React from'react';
import axios from'axios';
import { BehaviorSubject, combineLatest, timer } from'rxjs';
import { flatMap, map, debounce, filter } from'rxjs/operators';
...
const queryForFetch$ = query$.pipe(
debounce(() => timer(1000)),
filter(query => query !== ''),
);
const fetch$ = combineLatest(subject$, queryForFetch$).pipe(
flatMap(([subject, query]) =>
axios(`http://hn.algolia.com/api/v1/${subject}?query=${query}`),
),
map(result => result.data.hits),
);
...
The operator
debounce
does his job during data entry in the field. However, when clicking on a button acting on a value subject
, the request must be executed immediately. Now the initial values for
query
and subject
, which we see when the component App
is displayed for the first time, are not the same as those obtained from the initial values of the observed objects:const query$ = new BehaviorSubject('react');
const subject$ = new BehaviorSubject(SUBJECT.POPULARITY);
B is
subject
written undefined
, B query
is an empty string. This is due to the fact that we have provided these values as default parameters for the restructuring in the signature of the component function App
. The reason for this is that we need to wait for the initial request executed by the observed object fetch
. Since I do not know exactly how to immediately get the values from the observed objects query
and subject
in the higher-order component in order to write them to the local state, I decided to set up the initial state for the higher-order component again.const withObservableStream = (
observable,
triggers,
initialState,
) => Component => {
returnclassextendsReact.Component{
constructor(props) {
super(props);
this.state = {
...initialState,
};
}
componentDidMount() {
this.subscription = observable.subscribe(newState =>
this.setState({ ...newState }),
);
}
componentWillUnmount() {
this.subscription.unsubscribe();
}
render() {
return (
<Component {...this.props} {...this.state} {...triggers} />
);
}
};
};
Now the initial state can be provided to the higher order component as the third argument. In the future, we can remove the default parameter values for the component
App
....
const App = ({
query,
subject,
stories,
onChangeQuery,
onSelectSubject,
}) => (
...
);
exportdefault withObservableStream(
combineLatest(
subject$,
query$,
fetch$,
(subject, query, stories) => ({
subject,
query,
stories,
}),
),
{
onSelectSubject: subject => subject$.next(subject),
onChangeQuery: value => query$.next(value),
},
{
query: 'react',
subject: SUBJECT.POPULARITY,
stories: [],
},
)(App);
What worries me now is that the initial state is also set in the declaration of the observed objects
query$
and subject$
. Such an approach is prone to errors, since the initialization of the observed objects and the initial state of a higher-order component share the same values. I would have liked it more if, instead, the initial values would be extracted from the observed objects in the higher order component to set the initial state. Perhaps someone from the readers of this material will be able to share in the comments advice on how to do this. The code of the software project that we were involved in here can be found here .
Results
The main goal of this material is to demonstrate an alternative approach to developing React applications using RxJS. We hope he gave you some food for thought. Sometimes there is no need for Redux and MobX, but perhaps in such situations, RxJS will turn out to be just the right one for a particular project.
Dear readers! Do you use RxJS when developing React-applications?
