Building a Sort Algorithm Visualizer with React and RxJS

Steams? Yes, Streams. [http://gratisography.com/]

Working Demo
The Code

About 4 months ago, I set out to do a little experiment. The plan was to build a sort algorithm visualizer, but the main point wasn’t about algorithms themselves. My goal was to:

  1. See how well RxJS works with React: I was looking into redux-observable as a middleware to handle async and side effects. Before trying out the library, I wanted to see how I would approach using RxJS with React myself.
  2. See how “easy-to-reason” RxJS code is: I was under the impression that stream libraries (and functional programming in general) are rather challenging to learn at first, but becomes intuitive once it sinks in.

So the plan was to write a functioning app, then not touch it for an extended period of time, only to return to it later to refactor it. To my delight, using RxJS was painless to work with and was also easy to refactor after 4 months. This post will be a short review of my personal takeaways.

I only experienced about 7 WTFs/minute. Pretty good, pretty good.

Redux is Stuck in My Head

Even after purposefully excluding Redux in my project, I naturally ended up trying to mimic it. I used the CustomEvent web API (aptly name-spaced as “action”) to dispatch events ,

this.goToNextStep = () => {
const action = {
origin: ‘USER’,
request: ‘GO_TO_NEXT_STEP’
}
document.dispatchEvent(new CustomEvent(‘action’, {detail: action}))
}

and create an observable of “action” events, in a React Component <StreamProvider />, with a subscription that will trigger state updates that gets passed down to the child component <SortVisualizer />.

this.sortHistory$ = Observable
.fromEvent(document, 'action').map(e => e.detail)
.mergeMap(...) // deal with actions
this.sortHistory$
.subscribe(x => {
this.setState({
sortState: x[x.length - 1]
})
})

Streams are Cool

Part of the challenge was to not use any comments. Can my code, relying only on good naming practices and organization, be easy to read?

It’s hard to say anything about the high-level organization of the application code, because even after 4+ months of inactivity, it was easy to recall the whats and whys of my design decisions after a few minutes of digging in.

However, it was really cool to experience how easy it was to follow the parts written in RxJS.

Implementing “Undo” is Easy with RxJS

Creating the “go back one step” button was a breeze using RxJS. First, I tracked the current state of sort progress as a prop in<SortVisualizer /> as the following:

const currentSortState = {
bars: [{bar}, ...] // data that we're sorting
nextStep: {targetIndex: 0, type: 'COMPARE'} // describes what the next sorting step is
}

This prop is coming from the observable in <StreamVisualizer /> . RxJS’s .scan() method made it really simple to achieve this:

this.sortHistory$ = this.actions$
// other stuff
.scan((acc, curr) => {
if (curr.request === 'GO_TO_PREV_STEP') { // if want to go back
return acc.length <= 1 ? acc : acc.slice(0, acc.length -1)
// just remove latest step
} else {
// other stuff
}
})

Handling Async Declaratively with RxJS

Another aspect that I was focusing on was how easy it is to handle async operations with RxJS. Again, I loved it. Streams allowed me to write async code in a more declarative manner.

For example, I wanted to have an “autoplay” feature that will be toggled off whenever a user tries to do something else, such as drag’n’dropping a bar. Instead of having to imperatively say “If event A happens, turn off autoplay. If event B happens, turn off autoplay. If event C…” I was able to easily say “autoplay until any other event happens.”

const handleAutoPlay = (action) => {
if (!this.state.playing && action.request === 'TOGGLE_PLAY') {
// to turn on autoplay
return Observable.interval(500) // every 500ms
.startWith(1)
.takeUntil(this.actions$) // until we get some other action
.takeWhile(x => !this.state.sortState.sortCompleted)
// or until sorting is completed
.map(x => {return {request: 'GO_TO_NEXT_STEP'}})
// fire 'GO_TO_NEXT_STEP'
.do(x => {
if (!this.state.playing) {this.setState({playing: true})}
})
.finally(() => {this.setState({playing: false})})
} else {
return Observable.of(action)
}
}
this.sortHistory$ = this.actions$
.mergeMap(handleAutoPlay)
// other stuff

In Conclusion

The codebase can definitely use more work (for example, dispatching events is scattered in both <StreamProvider /> and <SortVisualizer /> , yuk) but this is where I will conclude my experiment for now.

All in all, I am satisfied with the experiment. It is because it gave me confidence that I should continue to teach myself functional programming. It may be painful (reading about monads hurt my brain), but I am sure that it will pay off.

Thank you for reading.

--

--

--

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Vue.js SPA Authentication With Laravel Airlock

OOP JavaScript & Inheritance

Convert a form image to an HTML form using Amazon Textract and NodeJS

To Use Or Not To Use…Action Creators.

How Metamask Detects The Gas Price

Node to Joy: Advantages of Node.js

Angular: create a configurable website title based on build environment

EC2 user data for install Nodejs and CodeDoploy agent

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Jin Yoo

Jin Yoo

More from Medium

Typescript for the rescue

React JS Recap

Few must know javascript questions in tech interviews for experienced professionals

JavaScript, NoSQL and MongoDB