Feb 11, 2020

Create your own Redux with RxJS

Basic concepts of reactivity and the RxJS library, and how the Redux store can be implemented using RxJS.

Author

Rishabh KarnadRishabh KarnadAssociate Engineering Manager
Create your own Redux with RxJS

Reactivity.

Reactive programming is something you might have heard of, but might not know how to approach yet. In very simple words, it's a programming paradigm like 'Functional programming' or 'Object-oriented programming' which focuses on a different aspect, namely, how to write programs that automatically do something when something else happens.

Event handlers in JS are naturally reactive and demonstrate this well:

const someDiv = document.getElementById('my-div')

function handleClick() {
  doSomeFancyStuff()
}

someDiv.addEventListener('click', handleClick)

They listen for events and automatically trigger a handler (in this case handleClick) when the event happens, which in turn does something (in this case, some fancy stuff).

In fact events are a nice way to model things that are yet to happen or finish (like asynchronous code returning a value).

A Note on Sequences:

An array is the simplest kind of collection of data. It is a sequence of things; which is as simple as a collection can get.

Arrays provide ways to deal with collections easily. You are probably familiar with loops that let you process an array by iterating over its elements.

Also, many languages like JavaScript, provide higher order functions like map and filter, that let you pass a function that will be applied to each element in the array.

Adding 2 to an array of numbers is easy with Array.prototype.map

const arrayOfNumbers = [1, 2, 3, 4]

const add2 = x => x + 2

const arrayOfSlightlyBiggerNumbers = arrayOfNumbers.map(add2)

Array.prototype.map returns a new array and does not mutate the old one. Moreover, the array prototype methods can be chained.

const todoList = [
  { id: 1, text: 'Learn ES6', done: true },
  { id: 2, text: 'Learn RxJS', done: false },
  { id: 3, text: 'Buy groceries', done: false },
]

const isCompleted = todo => todo.done
const getTodoText = todo => todo.text
const startsWithLearn = text => text.startsWith('Learn')

const completedLearningTaskCount = todoList
  .filter(isCompleted)
  .map(getTodoText)
  .filter(startsWithLearn)
  .length

These two facts let us deal with arrays in a clean, declarative and functional way.

Wouldn't it be great if we had a way to deal with events and asynchronous operations in a clean and familiar way, much similar to the one mentioned above?

Well, this is exactly what ReactiveX does. ReactiveX is a language independent specification that describes an API (functions and interfaces) that helps us deal with all sorts of things by treating them as sequences of events.

RxJS is the JavaScript implementation of ReactiveX.

Why ReactiveX matters.

ReactiveX lets us model just about everything as a stream of events.

An event stream is very similar to an array, except it's asynchronous. So each element in the stream arrives as and when it is generated.

A sequence of mouse clicks can easily be visualised as an event stream.

So can an API call, although it is a stream of a single event. And even regular arrays!

In the end, ReactiveX and RxJS provide a uniform way to deal with asynchronous and synchronous data using a huge number of functions called operators.

RxJS Concepts.

RxJS is a huge library, and is slightly intimidating. It can be overwhelming to go through the documentation and figure out where to start.

This article aims to give you the bare minimum information to get quickly started with RxJS, and hopefully to absorb the idea and whole point of reactivity.

So here goes.

RxJS has four things at its core:

  1. Observables
  2. Subscriptions
  3. Observers
  4. Operators

- Observables are streams (or sequences) of events.

- Subscriptions trigger code when an observable generates a new event (addEventListener behaves like a subscription in this way).

- Observers are what subscribe to the observable (usually they take the form of a handler, or a function, just like the click handler handleClick we saw earlier).

- Operators are functions which process each event coming from the observable and generate a new observable stream in the process (a bit like map, filter, reduce and other higher order functions).

And that's it. That's the core of RxJS.

This sandbox demonstrates all four of these things. It creates an observable from the clicks coming from a button.

Some Advanced Concepts.

Apart from the four concepts we saw earlier, you'll need to know a couple more things before implementing you own Redux store implementation:

  1. Subject
  2. BehaviourSubject

A Subject is like an Observable in many ways, but provides a way for multiple observers to listen easily. Think of them as event emitters.

BehaviourSubject is exactly like a Subject, except it stores the last emitted event. So you could consider it to be stateful.

This statefulness is exactly what we need to implement Redux's store.

And finally, the part you've been waiting for

Redux is a popular state management library, whose main philosophy is that an app's state must come from a single source of truth, named the store.

The store in Redux provides three methods:

  1. getState: a way to get the store's state at any given time
  2. dispatch: a way to trigger a change in the store state using actions
  3. subscribe: a way to reactively subscribe to state changes in the store

Let's try to implement the store and this API using RxJS. We'll use BehaviourSubject to do that.

Let's start off with a class to define our store interface.

class Store {
  constructor(reducer, initialState) {
    this.state = initialState
    this.reducer = reducer
  }

  // Gives us the state at that instant
  getState = () => {}

  // Takes an action and causes a change in the store state
  dispatch = (action) => {}

  //Calls the observer whenever the state changes
  subscribe = (observer) => {}
}

Let's first initialise the state. Since we're using BehaviourSubject, we just initialise the state to a new BehaviourSubject with the initial state

constructor(reducer, initialState) {
  this.state = newBehaviourSubject(initialState)
  this.reducer = reducer
}

The state of the BehaviourSubject at any given time can be retrieved easily using the value property. This gives us a natural way to implement getState

getState = () => {
  this.state.value
}

The dispatch is a bit more complex. It takes an action and changes the state according to the reducer. So we can get the next state by calling the reducer with the action. We can then update the state by calling BehaviourSubject.next

dispatch = (action) => {
  const nextState = this.reducer(this.getState(), action)
  this.state.next(nextState)
}

The subscribe method is almost as simple as getState. Since one can easily subscribe to a BehaviourSubject by using its subscribe method, we do just that.

subscribe = (observer) => {
  this.state.subscribe(observer)
}

And that's it!

We have created a class that defines the Redux store.

Here's the full implementation, used in a simple todo list app written in React. Notice how I just replace Redux's createStore with my own, and everything just works.

It's important to note that a lot of error handling and special cases have been ignored here, but it really showcases how powerful RxJS can be.

Conclusion

Reactive programming is an incredibly powerful programming model and can help us deal with something as complex as event handling with ease.

RxJS is a library that helps us write apps in such a way. It's powerful enough to let you implement your own Redux store, or any other state management mechanism with it.

It's also worth noting that the reason I was able to swap in my own store so easily in the todo app is because Redux's own API is simple and well defined. Good API design can go a long way in making maintainable software that may require you to swap out components in favour of others that better suit the changing requirements.

Further reading

Subscribe to Our Newsletter

RELATED ARTICLES

More from the engineering frontline.

Dive deep into our research and insights on design, development, and the impact of various trends to businesses.
The Bug That Doesn't Show Up in Code Review: Why Your Flutter Web App Reloads on Safari
The Bug That Doesn't Show Up in Code Review: Why Your Flutter Web App Reloads on Safari
A real-world look at how oversized images can trigger Safari reloads and iOS crashes in Flutter apps and how smarter image decoding prevents them.
From Prompting to Process: What Changed When Flutter Shipped Agent Skills
From Prompting to Process: What Changed When Flutter Shipped Agent Skills
This blog explores how Flutter Agent Skills improve AI-assisted development by combining official framework workflows with project-specific guidance for more consistent development.
Why Everything Your AI Builds Looks the Same
Why Everything Your AI Builds Looks the Same
This blog explores why AI-generated interfaces often look alike and explains how design systems, product context, and reusable engineering practices help teams build distinctive, scalable
How We Built the Missing Bridge from Code to Figma
Technology

Jul 10, 2026

How We Built the Missing Bridge from Code to Figma
This blog explores how AI-generated React apps get turned into fully editable, designer-ready Figma files by reading React Fiber instead of the DOM.
Building a Resilient Hybrid-Cloud Network with WireGuard HA, Route-Based Failover, and Deep Observability
Technology

Jun 27, 2026

Building a Resilient Hybrid-Cloud Network with WireGuard HA, Route-Based Failover, and Deep Observability
A practical breakdown of building resilient AWS-to-on-premises connectivity with WireGuard HA, active-standby failover, and deep packet-forwarding observability.
We Built a 114-Second AWS-to-Azure Failover. Here’s What We Learned
Technology

Jun 19, 2026

We Built a 114-Second AWS-to-Azure Failover. Here’s What We Learned
A practical guide to building a 114-second multi-cloud disaster recovery failover between AWS and Azure — what we built, what broke, and what we learned.
Cloud-Native and Cloud-Agnostic Are Not Ideologies; They Are Business-Stage Decisions
Technology

Jun 12, 2026

Cloud-Native and Cloud-Agnostic Are Not Ideologies; They Are Business-Stage Decisions
This blog explains how organizations can balance speed, scalability, and operational flexibility as they grow from startup to enterprise scale.

The Right Conversation Can Save You Six Months.

Whether you’re navigating AI adoption, modernizing legacy systems, or scaling a product - we start by listening. No pitch deck. No template. A real conversation.