Crossing Over: A Transition from Angular to React

10/24/2018 10:26:42 AM

A bit of background

I'm rewriting a project that I'd originally developed with Knockout and jQuery circa 2011. Web development is a completely different beast these days, a perpetually exhausting pursuit of relevance. Luckily I've been working in AngularJS and Angular 2 3 4 5 6 7 and I've kept my head above water.

I wanted to challenge myself even further this time and step into the ReactJS world. After all, I'd handled the jarring shift from AngularJS to Angular just fine. How difficult could it be?

When a "small" component is a big deal

A core feature of this project is an interactive time-based "Navigator" component. For example, if a timeline has a date range of 1776 to 2018, Navigator would host 243 clickable years ("segments").

For visual reference, a short demo of the original project and Navigator:

At first glance it seems like a pretty simple component, yeah? But then I consider this (partial) list of related requirements:

  • User can drag Navigator and select segments directly or via Previous/First/Next/Last buttons.
  • When a segment is selected, associated data must be fetched from a Web API and rendered.
  • Buttons and segments must be disabled while the API fetch is underway.
  • On window resize, Navigator must adjust and auto-center the active segment (when possible).

Whether I'd chosen Angular or ReactJS the high-level component hierarchy wouldn't been quite similar:

But as we'll see below, that's pretty much where the similarities end.

What I might have done in Angular

In the Angular world, components provide data models and supporting functions for their corresponding views. Services handle the heavy lifting of business logic, AJAX calls, storage operations, and so on. Based on that, and strictly focusing on Navigator initialization, my Angular setup might've looked something like this:

To summarize: Timeline component tells its injected TimelineService to get metadata, the service fetches from a Web API. On completion of the fetch, the Timeline component tells NavigatorService to generate the "segments." Now that this data is ready, child components are initialized and Navigator can render markup based on the segments in NavigatorService.

What I've done so far in ReactJS + Redux

I started with a 36-hour(!) course on Udemy entitled The Complete React Web Developer Course (with Redux). It was a decent primer but it didn't prepare me for async/AJAX, middleware, and other crucial concepts for real-world applications.

The wall I hit immediately: where the heck is my business logic supposed to go? In Angular I'd use services, but how did that translate in ReactJS? Reducers, action generators, thunks, sagas, what the...!? The nomenclature was driving me up a wall.

I finally broke down and asked the ReactJS Facebook Group for help, and I was astounded by how helpful they were. One particularly important recommendation was a ReactJS + Redux Basics playlist on YouTube, where Max's excellent teaching style helped me settle down and grasp the basics.

In the ReactJS + Redux world, everything's all about managing application state (via actions and reducers) and putting it in a central location (the store). Components monitor the slices of state that are relevant to them and re-render whenever that state changes. Once I understood these fundamental concepts I could attempt a ReactJS + Redux approach to initializing the Navigator:

To summarize: the top-level TimelineContainer component fetches from a Web API, uses a module to generate segments, and dispatches an "UPDATE_SEGMENTS" action. A reducer ("segmentsReducer") processes that action and updates the store with the segments, which triggers the re-rendering of associated components. Happy to say I'm pretty close to re-creating the original component in this brave new world.

Still plenty of challenges to deal with, though. For example, the Navigator could host hundreds of elements so I wouldn't want it to re-render with every arbitrary state change. The shouldComponentUpdate() lifecycle method was my escape hatch for this particular scenario, but it felt like I was working against the spirit of the "React Way." I've said that a lot these past few weeks.

The verdict (spoiler: it's too early to have one)

I've been developing software for roughly twenty years, and I can honestly say I've never struggled so much out of the gate like I have with ReactJS + Redux. Its individual pieces aren't technically or conceptually difficult; the challenge is shifting my habits away from all-inclusive frameworks (e.g. Angular) and moving toward libraries and patterns which put state (and unidirectional data flow) above all else.

I'm becoming more comfortable with ReactJS concepts each passing day, though, and I'll even admit I'm starting to like certain pieces of it. With any luck, in a few months I'll revisit this article and have a good laugh.