Three years ago at ClearPoint Strategy, we made a bold move: transitioning our application from AngularJS to React. As someone who got his start with Macromedia ColdFusion (more on that later), diving into the React ecosystem was both exhilarating and humbling. Here’s a look back at our journey, the lessons we learned, and some code comparisons that highlight how much the landscape has evolved.
The Angular Factor: Why We Really Moved
It wasn’t just a matter of chasing the latest trends. One of the biggest catalysts behind our migration was a shift in the ecosystem itself—Google, the original creator of AngularJS, pivoted to a completely new framework: Angular. Although Angular shares a name with AngularJS, it’s a completely different beast built from the ground up with modern TypeScript and a reimagined component architecture. Continuing to invest in AngularJS just didn’t make sense when its creator had moved on, and we needed a robust solution that wouldn’t leave us stranded as the ecosystem evolved.
AngularJS vs. React: A Code Comparison
AngularJS Example
In AngularJS, creating a simple “Hello World” component often looked something like this:
Copy
// app.js (AngularJS)
angular.module('myApp', [])
.controller('HelloController', ['$scope', function($scope) {
$scope.greeting = "Hello, world!";
}]);
And in your HTML, you might have:
<!-- index.html (AngularJS) -->
<div ng-app="myApp" ng-controller="HelloController">
{{ greeting }}
</div>
AngularJS bundled much of the magic for you—you simply declared your module, controller, and data binding was taken care of with minimal boilerplate.
React Example
Jumping into React meant rethinking how we structure our UI. Here’s a comparable “Hello World” example in React:
// Hello.jsx (React)
import React, { useState } from 'react';
const Hello = () => {
const [greeting] = useState("Hello, world!");
return <div>{greeting}</div>;
};
export default Hello;
And then in your main entry file:
// index.jsx
import React from 'react';
import ReactDOM from 'react-dom';
import Hello from './Hello';
ReactDOM.render(<Hello />, document.getElementById('root'));
What’s Different?
Explicit Imports and Modules:
In AngularJS, much of what you needed was globally available (or provided by the framework). In React, you must explicitly import React, ReactDOM, and any additional libraries you need. This explicitness provides clarity—but it also means more setup and a steeper learning curve.
Component Structure:
AngularJS’s controllers and directives offered one way to manage UI logic, while React’s component-based architecture forces you to break your UI into small, reusable pieces.
Tooling and Configuration:
AngularJS came with a lot of built-in functionality, whereas React’s unopinionated nature requires you to make choices about routing, state management, and more. That freedom can be liberating, but it also means you must carefully choose and integrate the right tools.
Create-React-App: The Source of All Evil?
If there’s one tool that has sparked as much debate as React itself, it’s create-react-app (CRA). Designed to help developers get started quickly, CRA abstracts away the build configuration, making it seem like magic—until you need to customize it.
Some notable voices in the React community have been quite vocal about its drawbacks:
“Create-react-app gives you an easy start, but its hidden configuration can lead to unexpected pitfalls when you need more control.”
— Dan Abramov“The convenience of CRA comes at a cost. When your project grows, that black-box build process can become a source of frustration rather than a help.”
— Kent C. Dodds
For many, this “magic” is the source of all evil. Developers end up chasing elusive bugs or struggling to optimize performance because the build process—and its hidden complexities—remains opaque. While CRA is fantastic for beginners and small projects, we quickly learned that as our application grew, the convenience turned into an obstacle that required us to eventually eject and face the underlying configuration head on.
Embracing Frameworks, Opinions, and Standard Libraries
One of our early lessons was that frameworks and opinions are important. While React gives you the freedom to choose your tools, having a set of standard libraries (like React Router, Redux, etc.) is critical for building a scalable application. Setting clear guidelines and choosing the right libraries early on saved us countless headaches down the road.
TypeScript Really Is Your Friend
Adopting TypeScript with React was another game changer. It provided type safety, improved the developer experience, and made our code more maintainable. A simple example:
// Hello.tsx (TypeScript + React)
import React, { FC } from 'react';
interface HelloProps {
greeting: string;
}
const Hello: FC<HelloProps> = ({ greeting }) => {
return <div>{greeting}</div>;
};
export default Hello;
This level of type assurance was something we wished we had from the start when working with AngularJS’s looser typing system.
Avoiding Re-render Pitfalls: Angular's Safeguards vs. React's Flexibility
AngularJS comes with an inherent safeguard when it comes to view updates. Its digest cycle ensures that model changes automatically propagate through the UI without much fuss. This built-in mechanism makes it harder to accidentally cause critical re-render mistakes.
In React, however, the rendering process is more explicit. It’s all too easy to introduce subtle bugs that cause unnecessary or even infinite re-renders if you’re not careful with state updates and component structure. For example, consider this pitfall:
// Pitfall: Unintended re-render loop in React
import React, { useState, useEffect } from 'react';
const PitfallComponent = () => {
const [data, setData] = useState(null);
// Without a dependency array, this effect runs after every render
useEffect(() => {
fetchData().then(newData => setData(newData));
});
return <div>{data ? data : 'Loading...'}</div>;
};
In this example, the missing dependency array in the useEffect hook causes the effect to run on every render, potentially leading to a re-render loop. AngularJS’s digest cycle, by contrast, would have managed updates more gracefully, reducing the likelihood of such pitfalls.
Another common mistake was the creation of inline functions inside render methods. Consider this scenario:
const ParentComponent = () => {
const [count, setCount] = useState(0);
// This inline function is recreated on every render
const increment = () => setCount(count + 1);
return (
<div>
<ChildComponent onClick={increment} />
<p>{count}</p>
</div>
);
};
const ChildComponent = React.memo(({ onClick }) => {
console.log("Child rendered");
return <button onClick={onClick}>Increment</button>;
});
Even though ChildComponent is wrapped in React.memo, it still re-renders every time because the increment function is a new instance on each render. These kinds of subtle issues are less prevalent in AngularJS, where the binding system abstracts much of the re-render logic away from the developer.
We learned these lessons the hard way, and they became key drivers in how we structured our React components and state management strategies.
Tools That Saved the Day: Sentry.io
No matter how well you architect your application, bugs and unexpected behavior are inevitable. Integrating a tool like Sentry.io was a lifesaver for us. It allowed us to catch and resolve issues quickly, ensuring that our users had a smooth experience.
// Sentry setup in a React application
import * as Sentry from "@sentry/react";
import { Integrations } from "@sentry/tracing";
Sentry.init({
dsn: "your DSN here",
integrations: [new Integrations.BrowserTracing()],
tracesSampleRate: 1.0,
});
This proactive monitoring became an essential part of our development workflow, catching errors that would have otherwise slipped through the cracks.
React: Easy to Start, Hard to Master
It’s true—React is incredibly easy to get started with. The "Hello World" example is just a few lines of code. But as our application grew, so did the complexity of our components, state management, and performance optimizations. The more we dug into React, the more we realized that truly mastering it takes time and continuous learning.
Final Thoughts
Looking back, our migration from AngularJS to React was filled with learning opportunities. We gained a deeper understanding of how important it is to lean on a strong ecosystem of tools and libraries, appreciate the benefits of strict typing with TypeScript, and recognize that while React’s simplicity is alluring, mastering its intricacies takes time.
Dylan Miyake
Co-founder of ClearPoint Strategy | MIT & Bowdoin Alum (Go U Bears)
Top comments (0)